fix(chat): use ~/.codex/skills/ for Codex expert symlinks and keep expert button always enabled
Change Codex skill storage to use only ~/.codex/skills/ instead of ~/.agents/skills/, and never disable the expert skills button so users can always access the empty-state hint. Also fix stale expert list in conversation window after linking in the separate settings window by re-fetching on window focus. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1213,11 +1213,8 @@ pub(crate) fn skill_storage_spec(agent_type: AgentType) -> Option<SkillStorageSp
|
|||||||
}),
|
}),
|
||||||
AgentType::Codex => Some(SkillStorageSpec {
|
AgentType::Codex => Some(SkillStorageSpec {
|
||||||
kind: SkillStorageKind::SkillDirectoryOrMarkdownFile,
|
kind: SkillStorageKind::SkillDirectoryOrMarkdownFile,
|
||||||
global_dirs: vec![
|
global_dirs: vec![codex_home_dir().join("skills")],
|
||||||
home_dir_or_default().join(".agents").join("skills"),
|
project_rel_dirs: vec![".codex/skills"],
|
||||||
codex_home_dir().join("skills"),
|
|
||||||
],
|
|
||||||
project_rel_dirs: vec![".agents/skills", ".codex/skills"],
|
|
||||||
}),
|
}),
|
||||||
AgentType::OpenCode => Some(SkillStorageSpec {
|
AgentType::OpenCode => Some(SkillStorageSpec {
|
||||||
kind: SkillStorageKind::SkillDirectoryOnly,
|
kind: SkillStorageKind::SkillDirectoryOnly,
|
||||||
|
|||||||
@@ -851,9 +851,8 @@ pub async fn experts_unlink_from_agent(
|
|||||||
|
|
||||||
let _guard = mutation_lock().lock().await;
|
let _guard = mutation_lock().lock().await;
|
||||||
|
|
||||||
// Scan ALL global dirs for this agent to handle shared-dir agents
|
// Scan ALL global dirs for this agent to handle shared-dir agents.
|
||||||
// (Codex and Cline both point at `~/.agents/skills/`). Remove the
|
// Remove the link wherever it is found.
|
||||||
// link wherever it is found.
|
|
||||||
let dirs = scoped_skill_dirs(agent_type, AgentSkillScope::Global, None)
|
let dirs = scoped_skill_dirs(agent_type, AgentSkillScope::Global, None)
|
||||||
.map_err(|_| ExpertsError::UnsupportedAgent(agent_type))?;
|
.map_err(|_| ExpertsError::UnsupportedAgent(agent_type))?;
|
||||||
|
|
||||||
|
|||||||
@@ -1757,7 +1757,7 @@ export function MessageInput({
|
|||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled || availableExperts.length === 0}
|
disabled={disabled}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-6 w-6 shrink-0 bg-transparent"
|
className="h-6 w-6 shrink-0 bg-transparent"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
|
|
||||||
import { expertsListForAgent } from "@/lib/api"
|
import { expertsListForAgent } from "@/lib/api"
|
||||||
import type { AgentType, ExpertListItem } from "@/lib/types"
|
import type { AgentType, ExpertListItem } from "@/lib/types"
|
||||||
@@ -10,6 +10,25 @@ const inflightMap = new Map<AgentType, Promise<ExpertListItem[]>>()
|
|||||||
|
|
||||||
const EMPTY: ExpertListItem[] = []
|
const EMPTY: ExpertListItem[] = []
|
||||||
|
|
||||||
|
function fetchForAgent(agentType: AgentType): Promise<ExpertListItem[]> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
export function useAgentExperts(agentType: AgentType | null): ExpertListItem[] {
|
export function useAgentExperts(agentType: AgentType | null): ExpertListItem[] {
|
||||||
const cached = useMemo(
|
const cached = useMemo(
|
||||||
() => (agentType ? (agentCache.get(agentType) ?? null) : null),
|
() => (agentType ? (agentCache.get(agentType) ?? null) : null),
|
||||||
@@ -22,35 +41,34 @@ export function useAgentExperts(agentType: AgentType | null): ExpertListItem[] {
|
|||||||
experts: ExpertListItem[]
|
experts: ExpertListItem[]
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
const doFetch = useCallback(() => {
|
||||||
if (!agentType || agentCache.has(agentType)) return
|
if (!agentType || agentCache.has(agentType)) return
|
||||||
|
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
let promise = inflightMap.get(agentType)
|
fetchForAgent(agentType).then((list) => {
|
||||||
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 })
|
if (!cancelled) setFetched({ agentType, experts: list })
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [agentType])
|
}, [agentType])
|
||||||
|
|
||||||
|
// Initial fetch
|
||||||
|
useEffect(() => doFetch(), [doFetch])
|
||||||
|
|
||||||
|
// Re-fetch when window regains focus (covers cross-window cache
|
||||||
|
// invalidation — e.g. settings window links/unlinks experts while the
|
||||||
|
// conversation window stays mounted).
|
||||||
|
useEffect(() => {
|
||||||
|
const onFocus = () => {
|
||||||
|
if (!agentType) return
|
||||||
|
agentCache.delete(agentType)
|
||||||
|
inflightMap.delete(agentType)
|
||||||
|
doFetch()
|
||||||
|
}
|
||||||
|
window.addEventListener("focus", onFocus)
|
||||||
|
return () => window.removeEventListener("focus", onFocus)
|
||||||
|
}, [agentType, doFetch])
|
||||||
|
|
||||||
if (!agentType) return EMPTY
|
if (!agentType) return EMPTY
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
if (fetched && fetched.agentType === agentType) return fetched.experts
|
if (fetched && fetched.agentType === agentType) return fetched.experts
|
||||||
|
|||||||
Reference in New Issue
Block a user