From 24e029066e1589470faec754dc3b90479c602372 Mon Sep 17 00:00:00 2001 From: Charles Crete Date: Sun, 12 Jul 2026 00:30:32 -0400 Subject: [PATCH] feat(agent): add hiddenFromCycle config option Adds a new config field hiddenFromCycle (hidden_from_cycle in markdown frontmatter) that hides an agent from tab/shift+tab rotation while keeping it visible and selectable via /agents. Previously the only visibility control was the hidden flag which hid agents from both the rotation and dialog. The field flows through all config forms: V1 markdown frontmatter, V2 JSON config, runtime agent schema, generated SDK types, and TUI agent context. --- packages/core/src/config/agent.ts | 1 + .../core/src/plugin/skill/customize-opencode.md | 7 +++++-- packages/core/src/v1/config/agent.ts | 4 ++++ packages/opencode/src/agent/agent.ts | 2 ++ packages/sdk/js/src/v2/gen/types.gen.ts | 2 ++ packages/tui/src/context/local.tsx | 17 +++++++++++++---- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/core/src/config/agent.ts b/packages/core/src/config/agent.ts index 63df995f859f..a3e9219ed80e 100644 --- a/packages/core/src/config/agent.ts +++ b/packages/core/src/config/agent.ts @@ -18,6 +18,7 @@ export class Info extends Schema.Class("ConfigV2.Agent")({ description: Schema.String.pipe(Schema.optional), mode: Schema.Literals(["subagent", "primary", "all"]).pipe(Schema.optional), hidden: Schema.Boolean.pipe(Schema.optional), + hiddenFromCycle: Schema.Boolean.pipe(Schema.optional), color: Color.pipe(Schema.optional), steps: PositiveInt.pipe(Schema.optional), disabled: Schema.Boolean.pipe(Schema.optional), diff --git a/packages/core/src/plugin/skill/customize-opencode.md b/packages/core/src/plugin/skill/customize-opencode.md index 6932dbfd54cc..e18b3353c0c9 100644 --- a/packages/core/src/plugin/skill/customize-opencode.md +++ b/packages/core/src/plugin/skill/customize-opencode.md @@ -266,8 +266,11 @@ frontmatter. `mode` is one of `"primary"`, `"subagent"`, `"all"`. Allowed top-level frontmatter fields: `name, model, variant, description, mode, -hidden, color, steps, options, permission, disable, temperature, top_p`. Any -unknown field is silently routed into `options`. +hidden, hidden_from_cycle, color, steps, options, permission, disable, temperature, +top_p`. Any unknown field is silently routed into `options`. + +`hidden_from_cycle` hides the agent from tab/shift+tab rotation but keeps it +visible in `/agents` for manual selection. To disable a built-in agent: `agent: { build: { disable: true } }`, or in a file, `disable: true` in frontmatter. diff --git a/packages/core/src/v1/config/agent.ts b/packages/core/src/v1/config/agent.ts index b220bd7ef87d..7a5e734c60d6 100644 --- a/packages/core/src/v1/config/agent.ts +++ b/packages/core/src/v1/config/agent.ts @@ -27,6 +27,9 @@ const AgentSchema = Schema.StructWithRest( hidden: Schema.optional(Schema.Boolean).annotate({ description: "Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)", }), + hidden_from_cycle: Schema.optional(Schema.Boolean).annotate({ + description: "Hide from tab/shift+tab rotation but still show in /agents (default: false)", + }), options: Schema.optional(Schema.Record(Schema.String, Schema.Any)), color: Schema.optional(Color).annotate({ description: "Hex color code (e.g., #FF5733) or theme color (e.g., primary)", @@ -50,6 +53,7 @@ const KNOWN_KEYS = new Set([ "top_p", "mode", "hidden", + "hidden_from_cycle", "color", "steps", "maxSteps", diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 536a642fe49f..900409db9d48 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -38,6 +38,7 @@ export const Info = Schema.Struct({ mode: Schema.Literals(["subagent", "primary", "all"]), native: Schema.optional(Schema.Boolean), hidden: Schema.optional(Schema.Boolean), + hiddenFromCycle: Schema.optional(Schema.Boolean), topP: Schema.optional(Schema.Finite), temperature: Schema.optional(Schema.Finite), color: Schema.optional(Schema.String), @@ -287,6 +288,7 @@ const layer = Layer.effect( item.mode = value.mode ?? item.mode item.color = value.color ?? item.color item.hidden = value.hidden ?? item.hidden + item.hiddenFromCycle = value.hidden_from_cycle ?? value.hiddenFromCycle ?? item.hiddenFromCycle item.name = value.name ?? item.name item.steps = value.steps ?? item.steps item.options = mergeDeep(item.options, value.options ?? {}) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 5e067f3afb23..30010da7fd81 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1696,6 +1696,7 @@ export type AgentConfig = { description?: string mode?: "subagent" | "primary" | "all" hidden?: boolean + hidden_from_cycle?: boolean options?: { [key: string]: unknown } @@ -2348,6 +2349,7 @@ export type Agent = { mode: "subagent" | "primary" | "all" native?: boolean hidden?: boolean + hiddenFromCycle?: boolean topP?: number temperature?: number color?: string diff --git a/packages/tui/src/context/local.tsx b/packages/tui/src/context/local.tsx index a05d315166ed..39b0097ed27b 100644 --- a/packages/tui/src/context/local.tsx +++ b/packages/tui/src/context/local.tsx @@ -75,7 +75,12 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ } function createAgent() { - const agents = createMemo(() => sync.data.agent.filter((agent) => agent.mode !== "subagent" && !agent.hidden)) + const agents = createMemo(() => + sync.data.agent.filter((agent) => agent.mode !== "subagent" && !agent.hidden && !agent.hiddenFromCycle), + ) + const allAgents = createMemo(() => + sync.data.agent.filter((agent) => agent.mode !== "subagent" && !agent.hidden), + ) const visibleAgents = createMemo(() => sync.data.agent.filter((agent) => !agent.hidden)) const [agentStore, setAgentStore] = createStore({ current: undefined as string | undefined, @@ -91,13 +96,17 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({ ]) return { list() { - return agents() + return allAgents() }, current() { - return agents().find((x) => x.name === agentStore.current) ?? agents().at(0) + const fromRotation = agents().find((x) => x.name === agentStore.current) + if (fromRotation) return fromRotation + const fromAll = allAgents().find((x) => x.name === agentStore.current) + if (fromAll) return fromAll + return agents().at(0) }, set(name: string) { - if (!agents().some((x) => x.name === name)) + if (!allAgents().some((x) => x.name === name)) return toast.show({ variant: "warning", message: `Agent not found: ${name}`,