Skip to content

Provider and Profile pages are not manageable: no Profile delete at all, no in-use guard on Provider delete #68

Description

@yujiezhang-ops

Taking CC-Switch's config surface as the reference point, here is what these two pages can and cannot do today.

Profile: no delete exists anywhere

ProfilesPage offers create (startSetup, line 67) and edit (setEditor, line 260). There is no delete, and this is not a missing button — the capability is absent from the bottom of the stack up:

  • internal/profile/store.go exposes Root, PointerPath, ProfilesPath, ProfilePath, SecretPath, List, LoadActive, LoadActiveContext
  • internal/profile/write.go exposes Save, WriteActive, writeStored, writeSecret
  • there is no Delete method in the package
  • so no UseCases method, no ProfileService binding (services.go:385-395 has only ListProfiles and SaveProfile), no api.deleteProfile

A Profile created by mistake is permanent. Since Profile ID is disabled once set (ProfilesPage.tsx:178), it cannot be renamed either — a typo in an ID is unfixable through the app.

Implementing this is a four-layer change, not a frontend task. Per the write-order rules in CLAUDE.md it also has to remove the secret alongside the profile, and LoadActive needs a defined answer when the deleted Profile is the active one.

Provider: delete has no in-use guard

UseCases.DeleteProvider (internal/app/provider.go:202-212) takes writeMu and calls providers.Delete with no check of who is using it:

u.writeMu.Lock()
defer u.writeMu.Unlock()
return u.providers.Delete(ctx, providerID)

ProvidersPage already computes exactly the fact needed — users, the Agents whose agent.provider matches — and renders it as chips (lines 181-183, 214). It is displayed and then ignored by the confirm, which is a bare window.confirm with just the name (line 89). Deleting a Provider with three Agents bound to it succeeds silently and leaves those Agents pointing at an id that no longer resolves.

The symmetric case is handled correctly: SaveProvider reapplies to every affected Agent and reports reapplied / failures, and the page surfaces both (lines 63-77). Delete should be at least as informative.

Smaller gaps on the same theme

  • Deletion is gated on meta.custom (line 200), so a built-in Provider can be edited but not removed. With only two built-ins (ppio, novita) a user who wants neither is stuck with both in the list. Hiding rather than deleting would fit — the lock file is the source of truth for what ships.
  • The Provider list has no ordering control. byProviderCreatedAt fixes the order; there is no way to promote the one actually in use to the top.
  • AgentProfilePage and ProfilesPage create Profiles by different routes. AgentProfilePage.openCreate (line 180) opens an inline form and calls api.saveProfile; ProfilesPage.startSetup dispatches START_SETUP into the five-step wizard. Two paths, two mental models, one object type. Related to [P2] 「更新」按钮用前端硬编码的 npm 清单判断,已漏掉 OpenClaw,且未安装也照样显示 #61.
  • No duplicate/clone. The common real operation is "same Provider and model, different name," which today means retyping every field.

Suggested split

Worth separating, since they are different sizes and different risk:

  1. Profile delete (Go through UI, four layers, needs the active-pointer decision) — the actual blocker
  2. Provider delete in-use guard (name the bound Agents in the confirm, or refuse) — small, and prevents a broken state
  3. Clone, ordering, built-in hiding — convenience, decide together after 1 and 2

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions