fix(ui): scope primary agent selector to selectable agents (#528)

## Summary

- rescope the UI portion of #385 to the remaining main-session selector
issue from #384
- keep main-session primary selectors limited to selectable primary
agents
- preserve child/subagent behavior by keeping the current hidden agent
available for steering
- add focused tests for selector eligibility behavior

This intentionally excludes the OMO/config-directory merge and plugin
retry scope from #385, since that part was determined to belong outside
CodeNomad. Credit to @jollyxenon for the original #385 investigation and
UI direction.

## Validation

- ode --test packages/ui/src/types/session.test.ts 
- pm run typecheck --workspace @codenomad/ui ⚠️ fails on existing
SDK/type drift outside this diff

Fixes #384
Based on #385
This commit is contained in:
Pascal André 2026-06-07 18:19:21 +02:00 committed by GitHub
parent df7c507aa9
commit 8bcf365fc3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 64 additions and 7 deletions

View file

@ -2,7 +2,7 @@ import { Select } from "@kobalte/core/select"
import { Show, createEffect, createMemo } from "solid-js"
import { agents, fetchAgents, sessions } from "../stores/sessions"
import { ChevronDown } from "lucide-solid"
import { isSelectablePrimaryAgent, type Agent } from "../types/session"
import { getSelectableAgentsForSession, type Agent } from "../types/session"
import { useI18n } from "../lib/i18n"
import { getLogger } from "../lib/logger"
const log = getLogger("session")
@ -29,12 +29,7 @@ export default function AgentSelector(props: AgentSelectorProps) {
})
const availableAgents = createMemo(() => {
const allAgents = instanceAgents()
if (isChildSession()) {
return allAgents.filter((agent) => !agent.hidden)
}
return allAgents.filter(isSelectablePrimaryAgent)
return getSelectableAgentsForSession(instanceAgents(), props.currentAgent, isChildSession())
})
createEffect(() => {

View file

@ -0,0 +1,45 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"
import { getSelectableAgentsForSession, isSelectablePrimaryAgent, type Agent } from "./session.ts"
const visiblePrimary: Agent = { name: "plan", description: "", mode: "primary" }
const visibleSubagent: Agent = { name: "review", description: "", mode: "subagent" }
const hiddenPrimary: Agent = { name: "build", description: "", mode: "primary", hidden: true }
const hiddenSubagent: Agent = { name: "debug", description: "", mode: "subagent", hidden: true }
describe("agent selectability", () => {
it("matches primary-session selector rules", () => {
assert.equal(isSelectablePrimaryAgent(visiblePrimary), true)
assert.equal(isSelectablePrimaryAgent(visibleSubagent), false)
assert.equal(isSelectablePrimaryAgent(hiddenPrimary), false)
assert.equal(isSelectablePrimaryAgent(hiddenSubagent), false)
})
it("excludes hidden and subagent entries from main-session selectors", () => {
const agents = [hiddenPrimary, visibleSubagent, visiblePrimary]
assert.deepEqual(
getSelectableAgentsForSession(agents, "build", false).map((agent) => agent.name),
["plan"],
)
})
it("preserves a child session's current hidden agent for steering", () => {
const agents = [hiddenPrimary, visibleSubagent, visiblePrimary]
assert.deepEqual(
getSelectableAgentsForSession(agents, "build", true).map((agent) => agent.name),
["review", "plan", "build"],
)
})
it("does not add unrelated hidden agents to child-session selectors", () => {
const agents = [hiddenPrimary, visibleSubagent, visiblePrimary]
assert.deepEqual(
getSelectableAgentsForSession(agents, "review", true).map((agent) => agent.name),
["review", "plan"],
)
})
})

View file

@ -120,6 +120,23 @@ export function isSelectablePrimaryAgent(agent: Agent): boolean {
return !agent.hidden && agent.mode !== "subagent"
}
export function getSelectableAgentsForSession(
agentList: Agent[],
currentAgentName: string,
isChildSession: boolean,
): Agent[] {
if (!isChildSession) {
return agentList.filter(isSelectablePrimaryAgent)
}
const visibleAgents = agentList.filter((agent) => !agent.hidden)
const currentHiddenAgent = agentList.find((agent) => agent.hidden && agent.name === currentAgentName)
return currentHiddenAgent && !visibleAgents.some((agent) => agent.name === currentHiddenAgent.name)
? [...visibleAgents, currentHiddenAgent]
: visibleAgents
}
// Our client-specific Provider interface (simplified version of SDK Provider)
export interface Provider {
id: string