diff --git a/docs-site/src/content/docs/guides/codex-app-models.md b/docs-site/src/content/docs/guides/codex-app-models.md index e6549004e5..0ca894ffb8 100644 --- a/docs-site/src/content/docs/guides/codex-app-models.md +++ b/docs-site/src/content/docs/guides/codex-app-models.md @@ -166,7 +166,7 @@ actually advertised, and assigns low catalog priorities in the selected order. W are active, bare native selections expand into selector-qualified groups. Other models remain callable by exact id. -The active roster is separate from the Dashboard's **Sub-agent delegation** selection. It +The configured roster is separate from the Dashboard's **Sub-agent delegation** selection. It controls which overrides Codex offers first; it does not select a model or trigger delegation by itself. diff --git a/docs-site/src/content/docs/guides/model-ordering.md b/docs-site/src/content/docs/guides/model-ordering.md index 05b291b3a2..113a4e0cda 100644 --- a/docs-site/src/content/docs/guides/model-ordering.md +++ b/docs-site/src/content/docs/guides/model-ordering.md @@ -109,7 +109,7 @@ have expanded into selector-qualified groups. ## Changing the order The supported way to customize leading model order is to reorder `subagentModels`. In the dashboard, -open **Subagents** → **Agent Command Center** and reorder the **Active Roster** by dragging, with the +open **Subagents** → **Agent Command Center** and reorder the **Configured Roster** by dragging, with the arrow buttons, or with Alt + /. The searchable **Agent Library** may contain far more than five catalog models; entries remain addressable by exact id when their route is available, while the five-slot limit applies only to the overrides advertised first to `spawn_agent`. diff --git a/docs-site/src/content/docs/guides/sub-agent-surface.md b/docs-site/src/content/docs/guides/sub-agent-surface.md index 111f804116..bfd405e80a 100644 --- a/docs-site/src/content/docs/guides/sub-agent-surface.md +++ b/docs-site/src/content/docs/guides/sub-agent-surface.md @@ -156,8 +156,11 @@ switching an active conversation in place. - **Dashboard** → first stat cell: choose **v1**, **base**, or **v2**. - **Models** → **Current behavior** → **Collaboration**: choose **Reliable v1**, **Codex native** (base/default semantics), or **Concurrent v2**. - **Subagents** → **Agent Command Center**: - - **Active Roster** chooses and orders the five model overrides advertised first to `spawn_agent`. - Drag rows, use the arrow buttons, or press Alt + /. + - **Configured Roster** chooses and orders the five model overrides advertised first to `spawn_agent`. + Drag rows, use the arrow buttons, or press Alt + /. The card + shows your configured quick picks: a row's status line (for example **V1 under Codex defaults** or + **Routed · V2 compatible**) indicates which collaboration surfaces can select it, while rows + without a status line are available on both. - **Agent Library** searches the current model catalog and filters factual capabilities such as reasoning, long context, vision, and tool support. Entries remain addressable by exact id when their route is available, including models outside the five-slot roster. diff --git a/gui/src/components/subagents-workspace/SubagentsWorkspace.tsx b/gui/src/components/subagents-workspace/SubagentsWorkspace.tsx index 3041377718..69ae3732e7 100644 --- a/gui/src/components/subagents-workspace/SubagentsWorkspace.tsx +++ b/gui/src/components/subagents-workspace/SubagentsWorkspace.tsx @@ -10,11 +10,12 @@ import { IconSearch, IconX, } from "../../icons"; -import { useI18n, useT, type Locale } from "../../i18n/shared"; +import { useI18n, useT, type Locale, type TFn } from "../../i18n/shared"; import { modelLabel } from "../../model-display"; import { formatNamespacedModelId, providerIconSrc } from "../../provider-icons"; import SubagentDelegationSection from "./SubagentDelegationSection"; import type { DelegationPatch, DelegationModelOption } from "../../pages/use-subagent-delegation"; +import type { RosterReachability } from "../../pages/subagent-roster-reachability"; export const FEATURED_MAX = 5; export const LONG_CONTEXT_MIN = 200_000; @@ -46,6 +47,9 @@ export interface SubagentsWorkspaceProps { available: string[]; models: AgentModelRow[]; chosen: string[]; + protocol?: "v1" | "default" | "v2" | null; + rosterReachability?: ReadonlyMap; + onUseConcurrentV2?: () => void; busy?: boolean; rosterDirty?: boolean; onToggle: (model: string) => void; @@ -135,10 +139,22 @@ function ModelChips({ model }: { model: AgentModelRow }) { ); } +function surfaceLabel(surface: RosterReachability, t: TFn): string { + switch (surface) { + case "both": return t("sub.roster.surface.both"); + case "v1": return t("sub.roster.surface.v1"); + case "v2": return t("sub.roster.surface.v2"); + case "neither": return t("sub.roster.surface.none"); + } +} + export default function SubagentsWorkspace({ available, models, chosen, + protocol, + rosterReachability, + onUseConcurrentV2, busy = false, rosterDirty = false, onToggle, @@ -213,14 +229,37 @@ export default function SubagentsWorkspace({ { id: "tools", label: t("sub.filter.tools") }, ]; + // Per-selector collaboration-surface exceptions among the chosen rows. + const rosterStatus = useMemo(() => { + const v1: string[] = []; + const v2: string[] = []; + if (rosterReachability) { + for (const selector of chosen) { + const state = rosterReachability.get(selector); + if (state === "v1") v1.push(selector); + else if (state === "v2") v2.push(selector); + } + } + return { v1, v2 }; + }, [chosen, rosterReachability]); + + // One notice, only when the legacy "default" protocol splits the roster + // across surfaces. The gate is inherently map-driven: rosterStatus only + // collects entries from a non-empty rosterReachability map, so an empty or + // missing map can never produce a notice. "neither" rows are deliberately + // not listed by name. Names stay in roster order. + const showSplitNotice = protocol === "default" && (rosterStatus.v1.length > 0 || rosterStatus.v2.length > 0); + const v1Names = rosterStatus.v1.map(selector => formatNamespacedModelId(selector, t)).join(", "); + const v2Names = rosterStatus.v2.map(selector => formatNamespacedModelId(selector, t)).join(", "); + return (
{announcement}
-
+
-

{t("sub.featured")}

+

{t("sub.featured")}

{t("sub.rosterCount", { n: chosen.length, max: FEATURED_MAX })}

+ {t("sub.roster.useConcurrentV2Hint")} +
+ )} + + {rosterReachability && rosterReachability.size > 0 && ( +
+ {t("sub.roster.diagnostics")} +

{t("sub.roster.diagnosticsHint")}

+
    + {chosen.map(selector => { + const surface = rosterReachability.get(selector); + if (!surface) return null; + return ( +
  • + {formatNamespacedModelId(selector, t)} + {surfaceLabel(surface, t)} +
  • + ); + })} +
+
+ )} +