fix(ui): persist default agent via agents list flag

Fix the Control UI Set Default action to persist agents.list[].default instead of the unsupported agents.defaultId config key.\n\nCloses #65565.\n\nThanks @luyao618.
This commit is contained in:
Yao
2026-04-29 18:03:12 +08:00
committed by GitHub
parent d33c3f7da6
commit af548bb07d
4 changed files with 80 additions and 4 deletions

View File

@@ -88,6 +88,7 @@ Docs: https://docs.openclaw.ai
- CLI/gateway: reuse cached paired-device auth during `gateway probe` and report post-connect diagnostic failures as degraded reachability, so healthy local gateways are no longer marked unreachable after loopback auth or read timeouts. Fixes #48360. Thanks @RacecarGuy.
- Channels/Discord: give Discord Gateway WebSocket handshakes a 30s timeout so stalled TLS/network transitions emit an error and Carbon can continue its reconnect loop instead of leaving the bot silent until restart. Refs #50046. Thanks @codexGW.
- Channels/Telegram: suppress standalone failed edit/write warning payloads when a user-facing assistant error reply already covers the turn, while keeping unresolved mutating failures visible behind success-looking or suppressed-error replies. Fixes #39631; refs #73750; carries forward #39636 and #39717; leaves #39406 for configurable delivery policy. Thanks @Bartok9 and @Bortlesboat.
- Control UI/agents: persist the Set Default action through `agents.list[].default` instead of writing the unsupported `agents.defaultId` field, so saved default-agent changes survive config validation. Fixes #65565; carries forward #72585. Thanks @luyao618.
- NVIDIA/NIM: persist the `NVIDIA_API_KEY` provider marker and mark bundled NVIDIA Chat Completions models as string-content compatible, so NIM models load from `models.json` and OpenAI-compatible subagent calls send plain text content. Fixes #73013 and #50107; refs #73014. Thanks @bautrey, @iot2edge, @ifearghal, and @futhgar.
- Channels/Discord: let text-only configs drop the `GuildVoiceStates` gateway intent and expose a bounded `/gateway/bot` metadata timeout with rate-limited fallback logs, reducing idle CPU and warning floods. Fixes #73709 and #73585. Thanks @sanchezm86 and @trac3r00.
- Agents/sessions: mark same-turn `sessions_send` and A2A reply prompts with an inter-session `isUser=false` envelope before they reach the model, so foreign session output no longer lands as bare active user text. Fixes #73702; refs #73698, #73609, #73595, and #73622. Thanks @alvelda.

View File

@@ -40,6 +40,7 @@ import {
resetConfigPendingChanges,
runUpdate,
saveConfig,
stageDefaultAgentConfigEntry,
stageConfigPreset,
updateConfigFormValue,
removeConfigFormValue,
@@ -2148,10 +2149,7 @@ export function renderApp(state: AppViewState) {
updateConfigFormValue(state, basePath, { primary, fallbacks: normalized });
},
onSetDefault: (agentId) => {
if (!configValue) {
return;
}
updateConfigFormValue(state, ["agents", "defaultId"], agentId);
stageDefaultAgentConfigEntry(state, agentId);
},
}),
)

View File

@@ -8,6 +8,7 @@ import {
resetConfigPendingChanges,
runUpdate,
saveConfig,
stageDefaultAgentConfigEntry,
stageConfigPreset,
updateConfigFormValue,
type ConfigState,
@@ -477,6 +478,50 @@ describe("agent config helpers", () => {
},
});
});
it("sets default via agents.list[].default instead of agents.defaultId", () => {
const state = createState();
state.configSnapshot = {
config: {
agents: {
list: [{ id: "alpha", default: true }, { id: "beta" }],
},
},
valid: true,
issues: [],
raw: "{\n}\n",
};
const updated = stageDefaultAgentConfigEntry(state, "beta");
expect(updated).toBe(true);
expect(state.configFormDirty).toBe(true);
expect(state.configForm).toEqual({
agents: {
list: [{ id: "alpha" }, { id: "beta", default: true }],
},
});
});
it("does not stage agents.defaultId when the target agent is absent", () => {
const state = createState();
state.configSnapshot = {
config: {
agents: {
list: [{ id: "alpha", default: true }],
},
},
valid: true,
issues: [],
raw: "{\n}\n",
};
const updated = stageDefaultAgentConfigEntry(state, "beta");
expect(updated).toBe(false);
expect(state.configFormDirty).toBe(false);
expect(state.configForm).toBeNull();
});
});
describe("applyConfig", () => {

View File

@@ -359,6 +359,38 @@ export function ensureAgentConfigEntry(state: ConfigState, agentId: string): num
return nextIndex;
}
export function stageDefaultAgentConfigEntry(state: ConfigState, agentId: string): boolean {
const normalizedAgentId = agentId.trim();
if (!normalizedAgentId) {
return false;
}
const source =
state.configForm ?? (state.configSnapshot?.config as Record<string, unknown> | null);
const targetIndex = findAgentConfigEntryIndex(source, normalizedAgentId);
if (targetIndex < 0) {
return false;
}
mutateConfigForm(state, (draft) => {
const list = (draft as { agents?: { list?: unknown[] } } | null)?.agents?.list;
if (!Array.isArray(list)) {
return;
}
for (let i = 0; i < list.length; i++) {
const entry = list[i];
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
continue;
}
const record = entry as Record<string, unknown>;
if (i === targetIndex) {
record.default = true;
} else {
delete record.default;
}
}
});
return true;
}
export async function openConfigFile(state: ConfigState): Promise<void> {
if (!state.client || !state.connected) {
return;