fix(chat): query expert skills via symlinks and use $ prefix for Codex
Expert skills in the chat session were derived by intersecting built-in experts with ACP availableCommands, which caused Codex experts to never appear since Codex does not advertise skills through ACP. - Add `experts_list_for_agent` backend API that checks symlink status across all global skill dirs for the given agent type - Replace availableCommands-based expert filtering with symlink-based query, making the settings page the single source of truth - Use `$` prefix for Codex expert skills while keeping `/` for slash commands and other agents' experts - Disable the expert button when no experts are linked for the agent - Invalidate per-agent expert cache after link/unlink in settings Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
68
src/hooks/use-agent-experts.ts
Normal file
68
src/hooks/use-agent-experts.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
|
||||
import { expertsListForAgent } from "@/lib/api"
|
||||
import type { AgentType, ExpertListItem } from "@/lib/types"
|
||||
|
||||
const agentCache = new Map<AgentType, ExpertListItem[]>()
|
||||
const inflightMap = new Map<AgentType, Promise<ExpertListItem[]>>()
|
||||
|
||||
const EMPTY: ExpertListItem[] = []
|
||||
|
||||
export function useAgentExperts(agentType: AgentType | null): ExpertListItem[] {
|
||||
const cached = useMemo(
|
||||
() => (agentType ? (agentCache.get(agentType) ?? null) : null),
|
||||
[agentType]
|
||||
)
|
||||
// Track which agent type the fetched result belongs to so stale data
|
||||
// from a previous agent is never returned after a switch.
|
||||
const [fetched, setFetched] = useState<{
|
||||
agentType: AgentType
|
||||
experts: ExpertListItem[]
|
||||
} | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!agentType || agentCache.has(agentType)) return
|
||||
|
||||
let cancelled = false
|
||||
let promise = inflightMap.get(agentType)
|
||||
if (!promise) {
|
||||
promise = expertsListForAgent(agentType)
|
||||
.then((list) => {
|
||||
agentCache.set(agentType, list)
|
||||
inflightMap.delete(agentType)
|
||||
return list
|
||||
})
|
||||
.catch((err) => {
|
||||
inflightMap.delete(agentType)
|
||||
console.warn("[useAgentExperts] failed:", err)
|
||||
return EMPTY
|
||||
})
|
||||
inflightMap.set(agentType, promise)
|
||||
}
|
||||
|
||||
promise.then((list) => {
|
||||
if (!cancelled) setFetched({ agentType, experts: list })
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [agentType])
|
||||
|
||||
if (!agentType) return EMPTY
|
||||
if (cached) return cached
|
||||
if (fetched && fetched.agentType === agentType) return fetched.experts
|
||||
return EMPTY
|
||||
}
|
||||
|
||||
export function invalidateAgentExpertsCache(agentType?: AgentType) {
|
||||
if (agentType) {
|
||||
agentCache.delete(agentType)
|
||||
inflightMap.delete(agentType)
|
||||
} else {
|
||||
agentCache.clear()
|
||||
inflightMap.clear()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user