cc-haha/desktop/src/stores/skillStore.ts
程序员阿江(Relakkes) de736c49f7 Unify plugin capabilities with the desktop management views
Desktop plugin details now route into the shared Skills, Agents, and MCP management surfaces instead of maintaining separate read-only drilldowns. This also extends the desktop/server skill aggregation so plugin-provided skills appear in the shared list, groups MCP entries by source, and preserves detail-view back navigation based on where the user entered the page.

The implementation keeps plugin detail as the high-level capability hub while pushing real inspection into the existing management pages. Disabled plugins no longer expose false navigation paths into shared views, and the agent-browser regression script was expanded to exercise the new end-to-end flows.

Constraint: Shared Agents data only includes enabled plugin agents, so disabled plugins cannot deep-link into agent detail
Rejected: Keep duplicating full Skills/Agents/MCP detail inside Plugin detail | creates divergent UI flows and stale data paths
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If a detail view can be opened from multiple entry points, keep the return target in store state rather than hardcoding a single back destination
Tested: desktop vitest for plugins/skills/agents/mcp; desktop tsc --noEmit; desktop vite build; server skills API test; agent-browser web regression on plugin->skill/mcp and plugin->agent back navigation
Not-tested: packaged desktop app regression after rebuilding the Tauri bundle
2026-04-22 12:31:57 +08:00

65 lines
1.6 KiB
TypeScript

import { create } from 'zustand'
import { skillsApi } from '../api/skills'
import type { SkillMeta, SkillDetail } from '../types/skill'
export type SkillDetailReturnTab = 'skills' | 'plugins'
type SkillStore = {
skills: SkillMeta[]
selectedSkill: SkillDetail | null
selectedSkillReturnTab: SkillDetailReturnTab
isLoading: boolean
isDetailLoading: boolean
error: string | null
fetchSkills: (cwd?: string) => Promise<void>
fetchSkillDetail: (
source: string,
name: string,
cwd?: string,
returnTab?: SkillDetailReturnTab,
) => Promise<void>
clearSelection: () => void
}
export const useSkillStore = create<SkillStore>((set) => ({
skills: [],
selectedSkill: null,
selectedSkillReturnTab: 'skills',
isLoading: false,
isDetailLoading: false,
error: null,
fetchSkills: async (cwd) => {
set({ isLoading: true, error: null })
try {
const { skills } = await skillsApi.list(cwd)
set({ skills, isLoading: false })
} catch (err) {
set({
error: err instanceof Error ? err.message : String(err),
isLoading: false,
})
}
},
fetchSkillDetail: async (source, name, cwd, returnTab = 'skills') => {
set({ isDetailLoading: true, error: null })
try {
const { detail } = await skillsApi.detail(source, name, cwd)
set({
selectedSkill: detail,
selectedSkillReturnTab: returnTab,
isDetailLoading: false,
})
} catch (err) {
set({
error: err instanceof Error ? err.message : String(err),
isDetailLoading: false,
})
}
},
clearSelection: () => set({ selectedSkill: null, selectedSkillReturnTab: 'skills' }),
}))