feat(dashboard): enrich profiles dashboard and de-dupe channel env vars (#37872)

* feat(desktop): enrich profiles dashboard and de-dupe channel env vars

Add active-profile switching, role descriptions (manual + auto-generate
via the auxiliary LLM), per-profile model selection, and gateway-running
/ distribution badges to the GUI Profiles page. New profile creation
gains clone-all, optional description and model assignment.

Hide messaging-platform credentials (channel_managed) from the Keys/Env
page since the Channels page is the canonical surface for them, and
relabel the trimmed "messaging" category as "Gateway".

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(desktop): address review feedback on profiles/env changes

- ProfilesPage: scope the action-menu outside-click handler to the menu's
  own container via a ref so opening one card's menu no longer leaves
  others open.
- EnvPage: route the "Gateway" label and hint through i18n
  (t.common.gateway / gatewayHint) instead of hard-coded English, with an
  English fallback for untranslated locales.
- web_server: only report description_auto=true when auto-generation
  actually succeeded.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(desktop): address second-round review on profiles

- ProfilesPage: treat describe-auto success by null-checking the
  description and trust the response's description_auto flag instead of
  assuming true; disable the model-editor Save button unless the selected
  choice resolves to a real /api/model/options entry (avoids silent
  no-op saves).
- tests: cover the new profile endpoints (active get/set + 404,
  description round-trip + 404, model round-trip + 400 validation, and
  describe-auto success/failure contracts).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(desktop): more profiles review fixes (toggles, races, tests)

- ProfilesPage: use the canonical `active` returned by setActiveProfile;
  make the SOUL/description/model action-menu items toggle their editor
  closed when already open; guard description save/auto-describe against
  stale responses via an activeDescRequest ref so a late reply can't
  clobber a different open editor.
- tests: assert /api/env channel_managed classification matches
  _channel_managed_env_keys().

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Austin Pickett
2026-06-03 10:37:36 -04:00
committed by GitHub
co-authored by Cursor
parent 214b7e070f
commit 7fb8a6b5c5
7 changed files with 1494 additions and 176 deletions
+67 -3
View File
@@ -361,15 +361,58 @@ export const api = {
deleteCronJob: (id: string, profile = "default") =>
fetchJSON<{ ok: boolean }>(`/api/cron/jobs/${encodeURIComponent(id)}?profile=${encodeURIComponent(profile)}`, { method: "DELETE" }),
// Profiles (minimal)
// Profiles
getProfiles: () =>
fetchJSON<{ profiles: ProfileInfo[] }>("/api/profiles"),
createProfile: (body: { name: string; clone_from_default: boolean }) =>
fetchJSON<{ ok: boolean; name: string; path: string }>("/api/profiles", {
getActiveProfile: () =>
fetchJSON<ActiveProfileInfo>("/api/profiles/active"),
setActiveProfile: (name: string) =>
fetchJSON<{ ok: boolean; active: string }>("/api/profiles/active", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
}),
createProfile: (body: {
name: string;
clone_from_default: boolean;
clone_all?: boolean;
no_skills?: boolean;
description?: string;
provider?: string;
model?: string;
}) =>
fetchJSON<{ ok: boolean; name: string; path: string; model_set?: boolean }>("/api/profiles", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}),
updateProfileDescription: (name: string, description: string) =>
fetchJSON<{ ok: boolean; description: string; description_auto: boolean }>(
`/api/profiles/${encodeURIComponent(name)}/description`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ description }),
},
),
describeProfileAuto: (name: string, overwrite = true) =>
fetchJSON<ProfileDescribeAutoResult>(
`/api/profiles/${encodeURIComponent(name)}/describe-auto`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ overwrite }),
},
),
setProfileModel: (name: string, provider: string, model: string) =>
fetchJSON<{ ok: boolean; provider: string; model: string }>(
`/api/profiles/${encodeURIComponent(name)}/model`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ provider, model }),
},
),
renameProfile: (name: string, newName: string) =>
fetchJSON<{ ok: boolean; name: string; path: string }>(
`/api/profiles/${encodeURIComponent(name)}`,
@@ -1170,6 +1213,8 @@ export interface EnvVarInfo {
is_password: boolean;
tools: string[];
advanced: boolean;
/** True when this var is a messaging-platform credential owned by the Channels page. */
channel_managed?: boolean;
}
export interface SessionMessage {
@@ -1250,6 +1295,18 @@ export interface AnalyticsResponse {
};
}
export interface ActiveProfileInfo {
active: string;
current: string;
}
export interface ProfileDescribeAutoResult {
ok: boolean;
reason: string;
description: string | null;
description_auto: boolean;
}
export interface ProfileInfo {
name: string;
path: string;
@@ -1258,6 +1315,13 @@ export interface ProfileInfo {
provider: string | null;
has_env: boolean;
skill_count: number;
gateway_running: boolean;
description: string;
description_auto: boolean;
distribution_name: string | null;
distribution_version: string | null;
distribution_source: string | null;
has_alias: boolean;
}
export interface ModelsAnalyticsModelEntry {