Remove dead confirm-strategy setting from chat UI.

Plan-mode confirm gate is gone; drop the settings control and coerce API values to strict.
This commit is contained in:
oliver 2026-08-11 02:42:32 +08:00
parent c901655848
commit 4277ad6b14
4 changed files with 14 additions and 68 deletions

View file

@ -738,9 +738,9 @@ def _normalize_execution_mode(payload: dict[str, Any] | None) -> str:
def _normalize_confirm_strategy(payload: dict[str, Any] | None) -> str: def _normalize_confirm_strategy(payload: dict[str, Any] | None) -> str:
body = payload or {} """Plan-mode confirm gate removed; keep a fixed wire default for older clients."""
raw = str(body.get("confirm_strategy") or "").strip().lower() del payload
return raw if raw in {"auto", "strict", "off"} else "strict" return "strict"
def _normalize_plan_agent_version(payload: dict[str, Any] | None) -> str: def _normalize_plan_agent_version(payload: dict[str, Any] | None) -> str:
@ -755,7 +755,7 @@ def _resolve_user_menu_chat_settings(
tenant_id: str, tenant_id: str,
user_id: str, user_id: str,
) -> tuple[str, str, str, str]: ) -> tuple[str, str, str, str]:
"""User-wide settings (⋯ menu): mode + confirm — all sessions share these keys.""" """User-wide settings (⋯ menu): mode + specialist — all sessions share these keys."""
user_mode_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="interaction_mode") user_mode_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="interaction_mode")
user_specialist_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="specialist") user_specialist_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="specialist")
user_confirm_strategy_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="confirm_strategy") user_confirm_strategy_key = _chat_user_mode_setting_key(tenant_id=tenant_id, user_id=user_id, field="confirm_strategy")
@ -850,7 +850,7 @@ def _resolve_mode_settings(
user_id: str, user_id: str,
session_id: str, session_id: str,
) -> tuple[str, str, str, str, str, str]: ) -> tuple[str, str, str, str, str, str]:
"""User-wide mode/specialist + session memory/exec + user confirm/plan_agent for gateway + REST send.""" """User-wide mode/specialist + session memory/exec (+ legacy confirm/plan_agent defaults) for gateway + REST send."""
u_im, u_sp, u_cs, u_pav = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id) u_im, u_sp, u_cs, u_pav = _resolve_user_menu_chat_settings(store=store, tenant_id=tenant_id, user_id=user_id)
s_mm, s_em = _resolve_session_dialog_chat_settings( s_mm, s_em = _resolve_session_dialog_chat_settings(
store=store, tenant_id=tenant_id, user_id=user_id, session_id=str(session_id) store=store, tenant_id=tenant_id, user_id=user_id, session_id=str(session_id)

View file

@ -107,10 +107,6 @@ const I18N = {
"chat.execModeAgent": "Agent", "chat.execModeAgent": "Agent",
"chat.execModePlan": "Plan", "chat.execModePlan": "Plan",
"chat.execModeApplied": "执行态:{mode}", "chat.execModeApplied": "执行态:{mode}",
"chat.confirmStrategyLabel": "确认策略",
"chat.confirmStrategyStrict": "Strict(需切换 Agent)",
"chat.confirmStrategyAuto": "Auto(自动确认执行)",
"chat.confirmStrategyOff": "Off(不拦截确认)",
"chat.specialistGeneralist": "通用", "chat.specialistGeneralist": "通用",
"chat.specialistOps": "运维", "chat.specialistOps": "运维",
"chat.specialistImage": "图像", "chat.specialistImage": "图像",
@ -301,10 +297,6 @@ const I18N = {
"chat.execModeAgent": "Agent", "chat.execModeAgent": "Agent",
"chat.execModePlan": "Plan", "chat.execModePlan": "Plan",
"chat.execModeApplied": "Execution: {mode}", "chat.execModeApplied": "Execution: {mode}",
"chat.confirmStrategyLabel": "Confirm Strategy",
"chat.confirmStrategyStrict": "Strict (switch to Agent first)",
"chat.confirmStrategyAuto": "Auto (confirm executes directly)",
"chat.confirmStrategyOff": "Off (no confirm-mode gate)",
"chat.specialistGeneralist": "Generalist", "chat.specialistGeneralist": "Generalist",
"chat.specialistOps": "Ops", "chat.specialistOps": "Ops",
"chat.specialistImage": "Image", "chat.specialistImage": "Image",
@ -406,14 +398,11 @@ const CHAT_SPECIALIST_PREF_KEY = "ops_chat_specialist_pref";
const CHAT_INTERACTION_MODE_KEY = "ops_chat_interaction_mode"; const CHAT_INTERACTION_MODE_KEY = "ops_chat_interaction_mode";
const CHAT_MEMORY_MODE_KEY = "ops_chat_memory_mode"; const CHAT_MEMORY_MODE_KEY = "ops_chat_memory_mode";
const CHAT_EXECUTION_MODE_KEY = "ops_chat_execution_mode"; const CHAT_EXECUTION_MODE_KEY = "ops_chat_execution_mode";
const CHAT_CONFIRM_STRATEGY_KEY = "ops_chat_confirm_strategy";
const CHAT_USER_MENU_MODE_KEY = "ops_chat_user_menu_mode"; const CHAT_USER_MENU_MODE_KEY = "ops_chat_user_menu_mode";
const CHAT_REASONING_TOGGLE_KEY = "ops_chat_reasoning_toggle"; const CHAT_REASONING_TOGGLE_KEY = "ops_chat_reasoning_toggle";
const EXECUTION_MODE_AGENT = "agent"; const EXECUTION_MODE_AGENT = "agent";
const EXECUTION_MODE_PLAN = "plan"; const EXECUTION_MODE_PLAN = "plan";
const CONFIRM_STRATEGY_STRICT = "strict"; const CONFIRM_STRATEGY_STRICT = "strict";
const CONFIRM_STRATEGY_AUTO = "auto";
const CONFIRM_STRATEGY_OFF = "off";
const PLAN_AGENT_V1 = "v1"; const PLAN_AGENT_V1 = "v1";
/** Default on: reasoning/tool fold matches streamed behavior; new browsers have no localStorage yet. */ /** Default on: reasoning/tool fold matches streamed behavior; new browsers have no localStorage yet. */
const ADMIN_CHAT_SHOW_TOOL_OUTPUT_DEFAULT = true; const ADMIN_CHAT_SHOW_TOOL_OUTPUT_DEFAULT = true;
@ -3064,27 +3053,6 @@ function syncAuthUserLabel() {
} }
}); });
items.push(modeSel); items.push(modeSel);
items.push(el("div", { class: "muted", style: "padding:2px 10px 2px;font-size:12px;", text: t("chat.confirmStrategyLabel") }));
const csSel = el("select", { class: "input", style: "width:100%;margin:4px 8px 8px;max-width:calc(100% - 16px);" });
try {
const rows = Array.isArray(bridge.getConfirmStrategyOptions && bridge.getConfirmStrategyOptions())
? bridge.getConfirmStrategyOptions()
: [];
rows.forEach((r) => csSel.appendChild(el("option", { value: String(r.value || ""), text: String(r.label || r.value || "") })));
csSel.value = String((bridge.getConfirmStrategyValue && bridge.getConfirmStrategyValue()) || "");
} catch (_) {}
csSel.addEventListener("change", async () => {
const v = csSel.value;
try {
csSel.disabled = true;
if (bridge.setConfirmStrategyValue) await bridge.setConfirmStrategyValue(v);
} catch (_) {
// errors are surfaced by saveUserGlobalModePreference()
} finally {
csSel.disabled = false;
}
});
items.push(csSel);
const reasonWrap = el("label", { class: "switch-wrap", style: "margin:2px 8px 8px;" }, [ const reasonWrap = el("label", { class: "switch-wrap", style: "margin:2px 8px 8px;" }, [
el("input", { type: "checkbox", class: "switch-input" }), el("input", { type: "checkbox", class: "switch-input" }),
el("span", { class: "switch-slider" }), el("span", { class: "switch-slider" }),
@ -3351,16 +3319,9 @@ async function renderChatUi() {
const raw = String(v || "").trim().toLowerCase(); const raw = String(v || "").trim().toLowerCase();
return raw === EXECUTION_MODE_PLAN ? EXECUTION_MODE_PLAN : EXECUTION_MODE_AGENT; return raw === EXECUTION_MODE_PLAN ? EXECUTION_MODE_PLAN : EXECUTION_MODE_AGENT;
}; };
const normalizeConfirmStrategy = (v) => {
const raw = String(v || "").trim().toLowerCase();
if (raw === CONFIRM_STRATEGY_AUTO) return CONFIRM_STRATEGY_AUTO;
if (raw === CONFIRM_STRATEGY_OFF) return CONFIRM_STRATEGY_OFF;
return CONFIRM_STRATEGY_STRICT;
};
const executionModeLabel = (v) => const executionModeLabel = (v) =>
normalizeExecutionMode(v) === EXECUTION_MODE_PLAN ? t("chat.execModePlan") : t("chat.execModeAgent"); normalizeExecutionMode(v) === EXECUTION_MODE_PLAN ? t("chat.execModePlan") : t("chat.execModeAgent");
let currentExecutionMode = normalizeExecutionMode(localStorage.getItem(CHAT_EXECUTION_MODE_KEY) || EXECUTION_MODE_AGENT); let currentExecutionMode = normalizeExecutionMode(localStorage.getItem(CHAT_EXECUTION_MODE_KEY) || EXECUTION_MODE_AGENT);
let currentConfirmStrategy = normalizeConfirmStrategy(localStorage.getItem(CHAT_CONFIRM_STRATEGY_KEY) || CONFIRM_STRATEGY_STRICT);
const execSelect = el("select", { const execSelect = el("select", {
class: "input", class: "input",
style: "min-width:96px;max-width:140px;padding:6px 8px;", style: "min-width:96px;max-width:140px;padding:6px 8px;",
@ -3534,7 +3495,6 @@ async function renderChatUi() {
const s = String((resp && resp.specialist) || "").toLowerCase(); const s = String((resp && resp.specialist) || "").toLowerCase();
const mm = String((resp && resp.memory_mode) || "").toLowerCase(); const mm = String((resp && resp.memory_mode) || "").toLowerCase();
const em = String((resp && resp.execution_mode) || "").toLowerCase(); const em = String((resp && resp.execution_mode) || "").toLowerCase();
const cs = String((resp && resp.confirm_strategy) || "").toLowerCase();
const gm = resp && resp.global_menu && typeof resp.global_menu === "object" ? resp.global_menu : null; const gm = resp && resp.global_menu && typeof resp.global_menu === "object" ? resp.global_menu : null;
if (gm) { if (gm) {
const gIm = String(gm.interaction_mode || "").toLowerCase(); const gIm = String(gm.interaction_mode || "").toLowerCase();
@ -3548,8 +3508,6 @@ async function renderChatUi() {
if (ur && ur.ok) { if (ur && ur.ok) {
const gum = String((ur.interaction_mode || "").toLowerCase()); const gum = String((ur.interaction_mode || "").toLowerCase());
const gus = String((ur.specialist || "").toLowerCase()); const gus = String((ur.specialist || "").toLowerCase());
currentConfirmStrategy = normalizeConfirmStrategy(ur.confirm_strategy || currentConfirmStrategy);
localStorage.setItem(CHAT_CONFIRM_STRATEGY_KEY, currentConfirmStrategy);
if (isSelectableSpecialist(gus)) globalMenuModeValue = gus; if (isSelectableSpecialist(gus)) globalMenuModeValue = gus;
else if (isSelectableSpecialist(gum)) globalMenuModeValue = gum; else if (isSelectableSpecialist(gum)) globalMenuModeValue = gum;
else globalMenuModeValue = MAIN_MODE_VALUE; else globalMenuModeValue = MAIN_MODE_VALUE;
@ -3564,8 +3522,6 @@ async function renderChatUi() {
syncHiddenModeSelectFromGlobal(); syncHiddenModeSelectFromGlobal();
if (["default", "store_only"].includes(mm)) localStorage.setItem(CHAT_MEMORY_MODE_KEY, mm); if (["default", "store_only"].includes(mm)) localStorage.setItem(CHAT_MEMORY_MODE_KEY, mm);
setExecutionMode(em, { persistLocal: true, saveSession: false }); setExecutionMode(em, { persistLocal: true, saveSession: false });
currentConfirmStrategy = normalizeConfirmStrategy(cs || currentConfirmStrategy);
localStorage.setItem(CHAT_CONFIRM_STRATEGY_KEY, currentConfirmStrategy);
persistModeSelection(); persistModeSelection();
const mml = String(localStorage.getItem(CHAT_MEMORY_MODE_KEY) || "default").toLowerCase(); const mml = String(localStorage.getItem(CHAT_MEMORY_MODE_KEY) || "default").toLowerCase();
localStorage.setItem(CHAT_MEMORY_MODE_KEY, mml === "store_only" ? "store_only" : "default"); localStorage.setItem(CHAT_MEMORY_MODE_KEY, mml === "store_only" ? "store_only" : "default");
@ -3583,7 +3539,7 @@ async function renderChatUi() {
const resp = await apiPost("/admin/api/chat/user-mode", { const resp = await apiPost("/admin/api/chat/user-mode", {
interaction_mode: "expert", interaction_mode: "expert",
specialist, specialist,
confirm_strategy: String(currentConfirmStrategy || CONFIRM_STRATEGY_STRICT), confirm_strategy: CONFIRM_STRATEGY_STRICT,
plan_agent_version: PLAN_AGENT_V1, plan_agent_version: PLAN_AGENT_V1,
}); });
localStorage.setItem(CHAT_USER_MENU_MODE_KEY, specialist); localStorage.setItem(CHAT_USER_MENU_MODE_KEY, specialist);
@ -3915,18 +3871,6 @@ async function renderChatUi() {
refreshExecUi(); refreshExecUi();
publishUserMenuPrefsBridge(); publishUserMenuPrefsBridge();
}, },
getConfirmStrategyOptions: () => [
{ value: CONFIRM_STRATEGY_STRICT, label: t("chat.confirmStrategyStrict") },
{ value: CONFIRM_STRATEGY_AUTO, label: t("chat.confirmStrategyAuto") },
{ value: CONFIRM_STRATEGY_OFF, label: t("chat.confirmStrategyOff") },
],
getConfirmStrategyValue: () => String(currentConfirmStrategy || CONFIRM_STRATEGY_STRICT),
setConfirmStrategyValue: async (v) => {
currentConfirmStrategy = normalizeConfirmStrategy(v);
localStorage.setItem(CHAT_CONFIRM_STRATEGY_KEY, currentConfirmStrategy);
await saveUserGlobalModePreference();
publishUserMenuPrefsBridge();
},
getReasoningVisible: () => !!showToolOutput, getReasoningVisible: () => !!showToolOutput,
setReasoningVisible: async (v) => { setReasoningVisible: async (v) => {
setReasoningVisible(!!v, { showStatus: true }); setReasoningVisible(!!v, { showStatus: true });

View file

@ -242,7 +242,7 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
# Session POST ignores interaction/specialist; user menu still has generalist. # Session POST ignores interaction/specialist; user menu still has generalist.
self.assertEqual(str(body1.get("specialist") or ""), "generalist") self.assertEqual(str(body1.get("specialist") or ""), "generalist")
self.assertEqual(str(body1.get("memory_mode") or ""), "store_only") self.assertEqual(str(body1.get("memory_mode") or ""), "store_only")
self.assertEqual(str(body1.get("confirm_strategy") or ""), "auto") self.assertEqual(str(body1.get("confirm_strategy") or ""), "strict")
resp2 = self.client.get( resp2 = self.client.get(
f"/admin/api/chat/sessions/{self.session_id}/mode", f"/admin/api/chat/sessions/{self.session_id}/mode",
@ -254,7 +254,7 @@ class AdminChatStreamAsyncTaskTests(unittest.TestCase):
self.assertEqual(str(body2.get("interaction_mode") or ""), "expert") self.assertEqual(str(body2.get("interaction_mode") or ""), "expert")
self.assertEqual(str(body2.get("specialist") or ""), "generalist") self.assertEqual(str(body2.get("specialist") or ""), "generalist")
self.assertEqual(str(body2.get("memory_mode") or ""), "store_only") self.assertEqual(str(body2.get("memory_mode") or ""), "store_only")
self.assertEqual(str(body2.get("confirm_strategy") or ""), "auto") self.assertEqual(str(body2.get("confirm_strategy") or ""), "strict")
def test_messages_use_session_mode_when_payload_omits_mode(self) -> None: def test_messages_use_session_mode_when_payload_omits_mode(self) -> None:
token = self._login() token = self._login()

View file

@ -44,8 +44,8 @@ def test_mode_session_and_user_prefs_merge_for_gateway() -> None:
assert specialist == "generalist" assert specialist == "generalist"
assert memory_mode == "default" assert memory_mode == "default"
assert execution_mode == "plan" assert execution_mode == "plan"
assert confirm_strategy == "auto" assert confirm_strategy == "strict"
assert plan_agent_version == "v2" assert plan_agent_version == "v1"
def test_mode_session_defaults_when_session_keys_missing() -> None: def test_mode_session_defaults_when_session_keys_missing() -> None:
@ -72,11 +72,13 @@ def test_invalid_execution_mode_on_session_dialog_falls_back_to_agent() -> None:
assert interaction_mode == "expert" assert interaction_mode == "expert"
def test_invalid_confirm_strategy_on_user_menu_falls_back_to_strict() -> None: def test_legacy_confirm_and_plan_agent_prefs_coerce_to_defaults() -> None:
store = _DummyStore() store = _DummyStore()
store.set_setting("chat.user.mode.t1.u1.confirm_strategy", "invalid") store.set_setting("chat.user.mode.t1.u1.confirm_strategy", "auto")
store.set_setting("chat.user.mode.t1.u1.plan_agent_version", "v2")
interaction_mode, specialist, memory_mode, execution_mode, confirm_strategy, plan_agent_version = ( interaction_mode, specialist, memory_mode, execution_mode, confirm_strategy, plan_agent_version = (
_resolve_mode_settings(store=store, tenant_id="t1", user_id="u1", session_id="s1") _resolve_mode_settings(store=store, tenant_id="t1", user_id="u1", session_id="s1")
) )
assert confirm_strategy == "strict" assert confirm_strategy == "strict"
assert plan_agent_version == "v1"
assert specialist == "generalist" assert specialist == "generalist"