Skip to content

fix(control-ui): persist Set Default agent through config save#91457

Merged
vincentkoc merged 2 commits into
openclaw:mainfrom
ZengWen-DT:fix/agents-set-default-persist
Jun 29, 2026
Merged

fix(control-ui): persist Set Default agent through config save#91457
vincentkoc merged 2 commits into
openclaw:mainfrom
ZengWen-DT:fix/agents-set-default-persist

Conversation

@ZengWen-DT

@ZengWen-DT ZengWen-DT commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

The Agents page Set Default button did not persist the chosen default agent: clicking it only staged a config form draft and never wrote it back, so the selection was silently discarded on refresh. This PR routes the one-click action through the canonical config-save path so the default actually sticks.

This addresses the default-persistence half of #57067 (labeled impact:data-loss). The issue's other half — replacing the agent dropdown with a visible list to reduce navigation friction — is a separate UI change and will follow in its own PR, so this PR intentionally does not Closes the issue.

What did NOT change: the dropdown selector, the Agents page layout, the canonical default representation (agents.list[].default), the config save/reload RPCs, or any other handler. No new config keys, no new source of truth, no agents.defaultId reintroduced.

AI-assisted (Claude). I authored and reviewed the code; the proof below is from a real browser run I executed locally.

Root Cause

onSetDefault in ui/src/ui/app-render.ts called only:

stageDefaultAgentConfigEntry(state, agentId);

stageDefaultAgentConfigEntry (ui/src/ui/controllers/config.ts) mutates the in-memory config form draft (setting agents.list[].default on the target, clearing it elsewhere) but never sends config.set. Every sibling Agents-page mutation that must persist goes through saveAgentsConfig (e.g. onConfigSave at app-render.ts:3098), which runs saveConfig (config.set) → loadAgents → restores selection. The Set Default handler was the only one that staged without saving, so on the next config.get/refresh the staged draft was dropped.

The fix belongs at the controller boundary, not inline in the renderer: a new setDefaultAgent helper composes the existing canonical pieces (stage the canonical flag, then persist via the shared save path). The renderer handler becomes thin wiring.

export async function setDefaultAgent(state, agentId) {
  if (stageDefaultAgentConfigEntry(state, agentId)) {
    await saveAgentsConfig(state);
  }
}

Verification (supplemental automated checks)

  • pnpm test ui/src/ui/controllers/agents.test.ts — 15 passed (incl. 2 new setDefaultAgent cases).
  • pnpm test ui/src/ui/controllers/config.test.ts ui/src/ui/views/agents.test.ts — 51 passed.
  • A new Playwright + stub-gateway regression in ui/src/ui/e2e/agents-set-default-persistence.e2e.test.ts.
  • pnpm tsgo:core / pnpm tsgo:test:ui, oxlint, oxfmt --check — all clean.

Regression Test Plan

  • Unit (controllers/agents.test.ts): setDefaultAgent stages the canonical agents.list[].default flag and persists it through config.set with the expected raw; and it issues no request when the target agent is absent from the config list.
  • Browser regression (e2e/agents-set-default-persistence.e2e.test.ts): loads the Agents page with two agents, selects the non-default one, clicks Set Default, and asserts a config.set request is emitted carrying kimi's default: true. This guard fails on the pre-fix code (no config.set is ever emitted).

Real behavior proof

  • Behavior addressed: Clicking Set Default on the Agents page now persists the chosen agent as default through the real Gateway config write path instead of only staging an unsaved config draft.
  • Real environment tested: Real browser Control UI connected to two real local Gateway processes on loopback, not a stub Gateway. The before environment used current upstream main at 9d9389b; the after environment used this PR branch at 5d064f3. Both used isolated proof state dirs, gateway.auth.mode: "none" for local loopback screenshot access only, and OPENCLAW_SKIP_CHANNELS=1 so chat channels did not start.
  • Exact steps or command run after this patch:
# before/current-main environment
OPENCLAW_STATE_DIR=/tmp/openclaw-pr91457-before pnpm openclaw config set agents.list '[{"id":"main"},{"id":"openclaw_test_agent"}]' --strict-json --replace
OPENCLAW_STATE_DIR=/tmp/openclaw-pr91457-before OPENCLAW_SKIP_CHANNELS=1 pnpm openclaw gateway --port 18889
# Browser: open http://127.0.0.1:18889/agents, select openclaw_test_agent, click Set Default, refresh.

# after/PR environment
OPENCLAW_STATE_DIR=/tmp/openclaw-pr91457-after pnpm openclaw config set agents.list '[{"id":"main"},{"id":"openclaw_test_agent"}]' --strict-json --replace
OPENCLAW_STATE_DIR=/tmp/openclaw-pr91457-after OPENCLAW_SKIP_CHANNELS=1 pnpm openclaw gateway --port 18899
# Browser: open http://127.0.0.1:18899/agents, select openclaw_test_agent, click Set Default, refresh.
  • Evidence before fix: On upstream main, the browser showed openclaw_test_agent selected while main still appeared as (default), and the page displayed You have unsaved config changes. After the same UI action and refresh, reading the real Gateway config still showed no persisted default flag:
[
  {
    "id": "main"
  },
  {
    "id": "openclaw_test_agent"
  }
]
  • Evidence after fix: On this PR branch, the real Gateway log recorded the Set Default click going through config.set, followed by reload/readback:
14:29:28 [reload] config change detected; evaluating reload (agents.list, meta.lastTouchedAt)
14:29:28 [reload] config hot reload applied (agents.list)
14:29:29 [ws] ⇄ res ✓ config.set 1212ms conn=14e512cc…11d3 id=3174cef8…4cee
14:29:29 [ws] ⇄ res ✓ config.get 203ms conn=14e512cc…11d3 id=924d3e76…a0f5

Reading the real Gateway config after the browser refresh showed the chosen agent persisted as canonical agents.list[].default:

[
  {
    "id": "main"
  },
  {
    "id": "openclaw_test_agent",
    "default": true
  }
]
  • Observed result after fix: After clicking Set Default and refreshing the Agents page, the dropdown showed openclaw_test_agent (default) and the Default button was disabled for that selected agent. The persisted config also survived the UI refresh because the real Gateway wrote openclaw_test_agent.default: true to the active config.
  • Screenshot evidence: Local screenshots were captured and checked: the before screenshot shows openclaw_test_agent selected while main (default) remains the default and the UI reports unsaved config changes; The after-fix screenshot shows openclaw_test_agent (default) still selected after refresh, with the Default button disabled for that selected agent.
image image
  • What was not tested: Remote/non-loopback auth and channel startup were intentionally out of scope for this Control UI persistence proof; channels were skipped. The dropdown-to-list UX change remains out of scope for this PR.

Merge risk

  • Surface: Control UI config persistence (config-adjacent). Touches one handler + one new controller helper; no protocol change, no config schema change, no new config/env surface.
  • Compatibility: none affected — uses the existing canonical agents.list[].default representation and the existing config.set path. No migration, no new key, no removed fallback.
  • Self-claimed labels: impact:data-loss (this fixes the reported loss), Control UI. No product-decision or security implications for this half.

Refs #57067

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: S triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 8, 2026
@clawsweeper

clawsweeper Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 23, 2026, 6:48 PM ET / 22:48 UTC.

Summary
The PR routes the Agents page Set Default action through a new Control UI helper that stages agents.list[].default and saves clean drafts via config.set, with unit and mocked-browser regression coverage.

PR surface: Source +15, Tests +181. Total +196 across 4 files.

Reproducibility: yes. Current main wires Set Default to a draft-only helper without config.set, and the PR body supplies before/after Gateway readback plus screenshots showing the default is dropped before and persisted after.

Review metrics: 1 noteworthy metric.

  • Conditional config write path: 1 added. Set Default changes from draft-only to the saved-config path on clean drafts, which maintainers should notice even without a schema or key change.

Stored data model
Persistent data-model change detected: serialized state: ui/src/ui/controllers/agents.test.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: partial_overlap
Canonical: #57067
Summary: This PR addresses the default-persistence half of the canonical Agents page issue; the visible-list half and dedicated Gateway RPC proposal remain separate work.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • [P2] No repair lane is needed because the PR is focused, proof-backed, and has no concrete review finding.

Security
Cleared: The diff is limited to Control UI TypeScript wiring and tests, with no dependency, workflow, lockfile, credential, package, or new code-execution surface changes.

Review details

Best possible solution:

Land this focused Control UI persistence fix after normal maintainer gates, while keeping the visible-list UX and dedicated RPC direction in their separate items.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main wires Set Default to a draft-only helper without config.set, and the PR body supplies before/after Gateway readback plus screenshots showing the default is dropped before and persisted after.

Is this the best way to solve the issue?

Yes. Composing the existing default-staging helper with saveAgentsConfig at the Control UI controller boundary is the narrowest current fix; the separate agents.setDefault RPC proposal is adjacent API work, not a merged replacement.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against b08d901dd2fc.

Label changes

Label justifications:

  • P2: This fixes a bounded Control UI persistence bug with limited blast radius but real saved-config impact.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (logs): The PR body provides after-fix real Gateway config.set logs/readback and screenshots showing the selected agent remains default after refresh.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides after-fix real Gateway config.set logs/readback and screenshots showing the selected agent remains default after refresh.
Evidence reviewed

PR surface:

Source +15, Tests +181. Total +196 across 4 files.

View PR surface stats
Area Files Added Removed Net
Source 2 18 3 +15
Tests 2 182 1 +181
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 4 200 4 +196

What I checked:

Likely related people:

  • vincentkoc: Current-line blame covers the Set Default wiring and helper area, and recent Agents view work touched nearby selection behavior. (role: recent area contributor; confidence: high; commits: 8e6624cb6cb8, 3cb460873dc7; files: ui/src/ui/app-render.ts, ui/src/ui/controllers/config.ts, ui/src/ui/controllers/agents.ts)
  • luyao618: Commit af548bb07dde moved Set Default persistence to the canonical agents.list[].default flag on the same helper path. (role: introduced current default-agent config behavior; confidence: high; commits: af548bb07dde; files: ui/src/ui/app-render.ts, ui/src/ui/controllers/config.ts, ui/src/ui/controllers/config.test.ts)
  • steipete: Commit c0a7c302f335 added saveAgentsConfig, the save/reload helper this PR now composes with. (role: adjacent config-save contributor; confidence: medium; commits: c0a7c302f335; files: ui/src/ui/controllers/agents.ts, ui/src/ui/app-render.ts, ui/src/ui/controllers/agents.test.ts)
  • joshavant: Recent merged work tightened Agents controller guard and error handling in the same Control UI controller module. (role: recent adjacent controller contributor; confidence: medium; commits: d5284a0d4086; files: ui/src/ui/controllers/agents.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 8, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels Jun 8, 2026
@clawsweeper clawsweeper Bot added the P2 Normal backlog priority with limited blast radius. label Jun 8, 2026
@openclaw-barnacle openclaw-barnacle Bot added triage: blank-template Candidate: PR template appears mostly untouched. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed proof: supplied External PR includes structured after-fix real behavior proof. labels Jun 13, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 13, 2026
@ZengWen-DT

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels Jun 13, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 13, 2026
@openclaw-barnacle openclaw-barnacle Bot added size: M and removed size: S proof: sufficient ClawSweeper judged the real behavior proof convincing. labels Jun 13, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. labels Jun 13, 2026
@clawsweeper clawsweeper Bot added status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. labels Jun 13, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label Jun 14, 2026
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Jun 14, 2026
Zeng-wen and others added 2 commits June 23, 2026 20:51
The Agents page Set Default button only staged a config form draft via
stageDefaultAgentConfigEntry and never called config.set, so the chosen
default was silently dropped on refresh (impact:data-loss). Route the
one-click action through a new setDefaultAgent controller helper that
stages the canonical agents.list[].default flag and then persists via the
shared saveAgentsConfig path.

AI-assisted (Claude).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ZengWen-DT
ZengWen-DT force-pushed the fix/agents-set-default-persist branch from 595d54a to f188d20 Compare June 23, 2026 12:51
@vincentkoc vincentkoc self-assigned this Jun 29, 2026
@vincentkoc
vincentkoc merged commit 32d117c into openclaw:main Jun 29, 2026
68 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. triage: blank-template Candidate: PR template appears mostly untouched.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants