feat: Add configuration management UI for /agents page
- Add 6-tab horizontal bar: Files, Tools, MCPs, Plugins, Hooks, Skills - Backend: permission parser, config/known-tools/skills/plugins/mcp-json endpoints - Backend: POST endpoints for permissions, hooks, and MCP config - Store: tool entries with 3-state toggle, MCP servers, hooks CRUD, skills/plugins fetch - ToolsManager: search, grouped cards (base/MCP), ask/allow/deny cycle, parameterized rules - McpManager: server cards with enable/disable, add/edit/delete modal - PluginsManager: read-only global plugin cards from ~/.claude/plugins/ - HooksManager: accordion by event type, inline edit with matcher/command/timeout - SkillsManager: two-column layout with SKILL.md preview and references
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// ── Existing types ──
|
||||
|
||||
type FileCategory = 'config' | 'instructions' | 'plugins' | 'history' | 'debug' | 'cache' | 'sessions' | 'backups' | 'other'
|
||||
|
||||
interface AgentFile {
|
||||
@@ -31,11 +33,80 @@ interface OpenFile {
|
||||
export interface CategoryInfo {
|
||||
key: FileCategory
|
||||
label: string
|
||||
icon: string // SVG path
|
||||
icon: string
|
||||
color: string
|
||||
files: AgentFile[]
|
||||
}
|
||||
|
||||
// ── New types for config management ──
|
||||
|
||||
type PermissionStatus = 'allow' | 'deny' | 'ask'
|
||||
type AgentTab = 'files' | 'tools' | 'mcps' | 'plugins' | 'hooks' | 'skills'
|
||||
type HookEventType = 'UserPromptSubmit' | 'PreToolUse' | 'PostToolUse' | 'SessionStart' | 'Stop' | 'Notification' | 'PermissionRequest'
|
||||
|
||||
interface ParsedPermission {
|
||||
raw: string
|
||||
tool: string
|
||||
params: string | null
|
||||
category: 'base' | 'mcp'
|
||||
server?: string
|
||||
host?: string
|
||||
}
|
||||
|
||||
interface ToolEntry {
|
||||
name: string
|
||||
fullKey: string
|
||||
category: 'base' | 'mcp'
|
||||
server?: string
|
||||
host?: string
|
||||
status: PermissionStatus
|
||||
rules: string[]
|
||||
}
|
||||
|
||||
interface McpServerEntry {
|
||||
name: string
|
||||
type: string
|
||||
command?: string
|
||||
args?: string[]
|
||||
url?: string
|
||||
env?: Record<string, string>
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
interface HookCommand {
|
||||
type: string
|
||||
command: string
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
interface HookEntry {
|
||||
matcher: string | null
|
||||
hooks: HookCommand[]
|
||||
}
|
||||
|
||||
interface SkillEntry {
|
||||
name: string
|
||||
description: string
|
||||
path: string
|
||||
skillMdContent: string
|
||||
references: { name: string; path: string }[]
|
||||
}
|
||||
|
||||
interface PluginEntry {
|
||||
name: string
|
||||
description: string
|
||||
author: string
|
||||
mcpConfig: any
|
||||
installed: boolean
|
||||
}
|
||||
|
||||
// ── Constants ──
|
||||
|
||||
const HOOK_EVENT_TYPES: HookEventType[] = [
|
||||
'UserPromptSubmit', 'PreToolUse', 'PostToolUse',
|
||||
'SessionStart', 'Stop', 'Notification', 'PermissionRequest'
|
||||
]
|
||||
|
||||
const CATEGORY_META: Record<FileCategory, { label: string; icon: string; color: string; order: number }> = {
|
||||
config: { label: 'Configuration', icon: 'M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z|M12 12m-3 0a3 3 0 1 0 6 0a3 3 0 1 0-6 0', color: '#6366f1', order: 0 },
|
||||
instructions: { label: 'Instructions', icon: 'M4 19.5A2.5 2.5 0 0 1 6.5 17H20|M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z', color: '#3b82f6', order: 1 },
|
||||
@@ -48,8 +119,8 @@ const CATEGORY_META: Record<FileCategory, { label: string; icon: string; color:
|
||||
other: { label: 'Other files', icon: 'M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z|M14 2v6h6', color: '#78716c', order: 8 }
|
||||
}
|
||||
|
||||
export { CATEGORY_META }
|
||||
export type { FileCategory, AgentFile, Agent }
|
||||
export { CATEGORY_META, HOOK_EVENT_TYPES }
|
||||
export type { FileCategory, AgentFile, Agent, AgentTab, HookEventType, ToolEntry, McpServerEntry, HookEntry, HookCommand, SkillEntry, PluginEntry, ParsedPermission, PermissionStatus }
|
||||
|
||||
function formatSize(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
@@ -60,6 +131,7 @@ function formatSize(bytes: number): string {
|
||||
export { formatSize }
|
||||
|
||||
export const useAgentsStore = defineStore('agents', () => {
|
||||
// ── Existing state ──
|
||||
const agents = ref<Agent[]>([])
|
||||
const selectedAgentId = ref<string | null>(null)
|
||||
const openFile = ref<OpenFile | null>(null)
|
||||
@@ -68,6 +140,39 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
const error = ref<string | null>(null)
|
||||
const collapsedCategories = ref<Set<string>>(new Set())
|
||||
|
||||
// ── New state: tab ──
|
||||
const activeTab = ref<AgentTab>('files')
|
||||
|
||||
// ── New state: tools ──
|
||||
const toolEntries = ref<ToolEntry[]>([])
|
||||
const toolsFilter = ref('')
|
||||
const toolsLoading = ref(false)
|
||||
|
||||
// ── New state: MCP servers ──
|
||||
const mcpServers = ref<McpServerEntry[]>([])
|
||||
const mcpsLoading = ref(false)
|
||||
const enableAllProjectMcpServers = ref(false)
|
||||
const enabledMcpjsonServers = ref<string[]>([])
|
||||
|
||||
// ── New state: hooks ──
|
||||
const hooksConfig = ref<Record<string, HookEntry[]>>({})
|
||||
const hooksLoading = ref(false)
|
||||
const expandedHookTypes = ref<Set<string>>(new Set())
|
||||
|
||||
// ── New state: skills ──
|
||||
const skills = ref<SkillEntry[]>([])
|
||||
const skillsLoading = ref(false)
|
||||
const selectedSkill = ref<SkillEntry | null>(null)
|
||||
|
||||
// ── New state: plugins ──
|
||||
const plugins = ref<PluginEntry[]>([])
|
||||
const pluginsLoading = ref(false)
|
||||
|
||||
// ── New state: config metadata ──
|
||||
const configFile = ref('')
|
||||
const configDirty = ref(false)
|
||||
|
||||
// ── Existing computed ──
|
||||
const isDirty = computed(() => {
|
||||
if (!openFile.value) return false
|
||||
return openFile.value.content !== openFile.value.originalContent
|
||||
@@ -100,6 +205,35 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
.sort((a, b) => CATEGORY_META[a.key].order - CATEGORY_META[b.key].order)
|
||||
})
|
||||
|
||||
// ── New computed ──
|
||||
const filteredTools = computed(() => {
|
||||
if (!toolsFilter.value) return toolEntries.value
|
||||
const q = toolsFilter.value.toLowerCase()
|
||||
return toolEntries.value.filter(t =>
|
||||
t.name.toLowerCase().includes(q) ||
|
||||
t.fullKey.toLowerCase().includes(q) ||
|
||||
(t.server && t.server.toLowerCase().includes(q))
|
||||
)
|
||||
})
|
||||
|
||||
const toolsByCategory = computed(() => {
|
||||
const base: ToolEntry[] = []
|
||||
const mcpGroups: Record<string, ToolEntry[]> = {}
|
||||
|
||||
for (const tool of filteredTools.value) {
|
||||
if (tool.category === 'base') {
|
||||
base.push(tool)
|
||||
} else {
|
||||
const key = tool.server || 'unknown'
|
||||
if (!mcpGroups[key]) mcpGroups[key] = []
|
||||
mcpGroups[key].push(tool)
|
||||
}
|
||||
}
|
||||
|
||||
return { base, mcpGroups }
|
||||
})
|
||||
|
||||
// ── Existing actions ──
|
||||
async function fetchAgents() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
@@ -120,6 +254,10 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
function selectAgent(id: string) {
|
||||
selectedAgentId.value = id
|
||||
openFile.value = null
|
||||
// Reload config for new agent when on config tabs
|
||||
if (activeTab.value !== 'files') {
|
||||
loadTabData()
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFile(agentId: string, file: AgentFile) {
|
||||
@@ -183,7 +321,408 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// ── New actions: tab data loading ──
|
||||
|
||||
async function loadTabData() {
|
||||
const tab = activeTab.value
|
||||
if (tab === 'tools') await fetchConfig()
|
||||
else if (tab === 'mcps') await fetchMcpJson()
|
||||
else if (tab === 'hooks') await fetchConfig()
|
||||
else if (tab === 'skills') await fetchSkills()
|
||||
else if (tab === 'plugins') await fetchPlugins()
|
||||
}
|
||||
|
||||
async function fetchConfig() {
|
||||
toolsLoading.value = true
|
||||
hooksLoading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const agentId = selectedAgentId.value || 'main'
|
||||
const [configRes, knownRes] = await Promise.all([
|
||||
fetch(`/api/agents/config?agentId=${encodeURIComponent(agentId)}`),
|
||||
fetch('/api/agents/known-tools')
|
||||
])
|
||||
|
||||
if (!configRes.ok) throw new Error('Failed to fetch config')
|
||||
if (!knownRes.ok) throw new Error('Failed to fetch known tools')
|
||||
|
||||
const config = await configRes.json()
|
||||
const known = await knownRes.json()
|
||||
|
||||
configFile.value = config.configFile
|
||||
enableAllProjectMcpServers.value = config.enableAllProjectMcpServers
|
||||
enabledMcpjsonServers.value = config.enabledMcpjsonServers
|
||||
|
||||
// Build tool entries from known tools + permissions
|
||||
buildToolEntries(config, known)
|
||||
|
||||
// Build hooks config
|
||||
hooksConfig.value = config.hooks || {}
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
toolsLoading.value = false
|
||||
hooksLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function buildToolEntries(config: any, known: any) {
|
||||
const entries: ToolEntry[] = []
|
||||
const allowMap = new Map<string, ParsedPermission>()
|
||||
const denyMap = new Map<string, ParsedPermission>()
|
||||
|
||||
// Index permissions by tool name
|
||||
for (const p of config.permissions.allow) {
|
||||
allowMap.set(p.raw, p)
|
||||
}
|
||||
for (const p of config.permissions.deny) {
|
||||
denyMap.set(p.raw, p)
|
||||
}
|
||||
|
||||
// Base tools
|
||||
for (const toolName of known.baseTools) {
|
||||
// Find all allow/deny entries for this base tool
|
||||
const allowRules: string[] = []
|
||||
const denyRules: string[] = []
|
||||
let hasSimpleAllow = false
|
||||
let hasSimpleDeny = false
|
||||
|
||||
for (const [raw, p] of allowMap) {
|
||||
if (p.tool === toolName) {
|
||||
if (p.params) allowRules.push(p.params)
|
||||
else hasSimpleAllow = true
|
||||
}
|
||||
}
|
||||
for (const [raw, p] of denyMap) {
|
||||
if (p.tool === toolName) {
|
||||
if (p.params) denyRules.push(p.params)
|
||||
else hasSimpleDeny = true
|
||||
}
|
||||
}
|
||||
|
||||
let status: PermissionStatus = 'ask'
|
||||
if (hasSimpleAllow || allowRules.length > 0) status = 'allow'
|
||||
if (hasSimpleDeny || denyRules.length > 0) status = 'deny'
|
||||
// If both exist, allow takes precedence (list first)
|
||||
if (hasSimpleAllow) status = 'allow'
|
||||
|
||||
entries.push({
|
||||
name: toolName,
|
||||
fullKey: toolName,
|
||||
category: 'base',
|
||||
status,
|
||||
rules: allowRules.length ? allowRules : denyRules
|
||||
})
|
||||
}
|
||||
|
||||
// MCP tools from known discovery
|
||||
for (const [server, hosts] of Object.entries(known.mcpTools as Record<string, Record<string, string[]>>)) {
|
||||
for (const [host, tools] of Object.entries(hosts)) {
|
||||
for (const toolName of tools) {
|
||||
const fullKey = `mcp__${server.replace(/-/g, '_')}__${host}-${toolName}`
|
||||
const isAllowed = allowMap.has(fullKey)
|
||||
const isDenied = denyMap.has(fullKey)
|
||||
|
||||
entries.push({
|
||||
name: toolName,
|
||||
fullKey,
|
||||
category: 'mcp',
|
||||
server: server.replace(/_/g, '-'),
|
||||
host,
|
||||
status: isAllowed ? 'allow' : isDenied ? 'deny' : 'ask',
|
||||
rules: []
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also add any permission entries that weren't in known tools
|
||||
const allKnownKeys = new Set(entries.map(e => e.fullKey))
|
||||
for (const [raw, p] of allowMap) {
|
||||
if (!allKnownKeys.has(raw) && !p.params) {
|
||||
entries.push({
|
||||
name: p.tool,
|
||||
fullKey: raw,
|
||||
category: p.category,
|
||||
server: p.server,
|
||||
host: p.host,
|
||||
status: 'allow',
|
||||
rules: []
|
||||
})
|
||||
}
|
||||
}
|
||||
for (const [raw, p] of denyMap) {
|
||||
if (!allKnownKeys.has(raw) && !p.params) {
|
||||
entries.push({
|
||||
name: p.tool,
|
||||
fullKey: raw,
|
||||
category: p.category,
|
||||
server: p.server,
|
||||
host: p.host,
|
||||
status: 'deny',
|
||||
rules: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
toolEntries.value = entries
|
||||
configDirty.value = false
|
||||
}
|
||||
|
||||
// ── Tool actions ──
|
||||
|
||||
function setToolStatus(toolName: string, status: PermissionStatus) {
|
||||
const tool = toolEntries.value.find(t => t.fullKey === toolName || t.name === toolName)
|
||||
if (tool) {
|
||||
tool.status = status
|
||||
if (status === 'ask') tool.rules = []
|
||||
configDirty.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function cycleToolStatus(toolName: string) {
|
||||
const tool = toolEntries.value.find(t => t.fullKey === toolName || t.name === toolName)
|
||||
if (!tool) return
|
||||
const cycle: PermissionStatus[] = ['ask', 'allow', 'deny']
|
||||
const idx = cycle.indexOf(tool.status)
|
||||
tool.status = cycle[(idx + 1) % 3]
|
||||
if (tool.status === 'ask') tool.rules = []
|
||||
configDirty.value = true
|
||||
}
|
||||
|
||||
function addToolRule(toolName: string, rule: string) {
|
||||
const tool = toolEntries.value.find(t => t.fullKey === toolName || t.name === toolName)
|
||||
if (tool && !tool.rules.includes(rule)) {
|
||||
tool.rules.push(rule)
|
||||
configDirty.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function removeToolRule(toolName: string, rule: string) {
|
||||
const tool = toolEntries.value.find(t => t.fullKey === toolName || t.name === toolName)
|
||||
if (tool) {
|
||||
tool.rules = tool.rules.filter(r => r !== rule)
|
||||
configDirty.value = true
|
||||
}
|
||||
}
|
||||
|
||||
async function savePermissions() {
|
||||
saving.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const allow: string[] = []
|
||||
const deny: string[] = []
|
||||
|
||||
for (const tool of toolEntries.value) {
|
||||
if (tool.status === 'allow') {
|
||||
if (tool.rules.length > 0) {
|
||||
for (const rule of tool.rules) {
|
||||
allow.push(`${tool.name}(${rule})`)
|
||||
}
|
||||
} else {
|
||||
allow.push(tool.category === 'mcp' ? tool.fullKey : tool.name)
|
||||
}
|
||||
} else if (tool.status === 'deny') {
|
||||
if (tool.rules.length > 0) {
|
||||
for (const rule of tool.rules) {
|
||||
deny.push(`${tool.name}(${rule})`)
|
||||
}
|
||||
} else {
|
||||
deny.push(tool.category === 'mcp' ? tool.fullKey : tool.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const res = await fetch('/api/agents/config/permissions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
agentId: selectedAgentId.value || 'main',
|
||||
permissions: { allow, deny }
|
||||
})
|
||||
})
|
||||
|
||||
if (!res.ok) throw new Error('Failed to save permissions')
|
||||
configDirty.value = false
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── MCP actions ──
|
||||
|
||||
async function fetchMcpJson() {
|
||||
mcpsLoading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const [mcpRes, configRes] = await Promise.all([
|
||||
fetch('/api/agents/mcp-json'),
|
||||
fetch(`/api/agents/config?agentId=${encodeURIComponent(selectedAgentId.value || 'main')}`)
|
||||
])
|
||||
if (!mcpRes.ok) throw new Error('Failed to fetch MCP config')
|
||||
|
||||
const mcpData = await mcpRes.json()
|
||||
const configData = configRes.ok ? await configRes.json() : {}
|
||||
|
||||
enableAllProjectMcpServers.value = configData.enableAllProjectMcpServers ?? false
|
||||
enabledMcpjsonServers.value = configData.enabledMcpjsonServers || []
|
||||
|
||||
const servers: McpServerEntry[] = []
|
||||
const mcpServersObj = mcpData.mcpServers || {}
|
||||
for (const [name, config] of Object.entries(mcpServersObj as Record<string, any>)) {
|
||||
servers.push({
|
||||
name,
|
||||
type: config.type || 'stdio',
|
||||
command: config.command,
|
||||
args: config.args,
|
||||
url: config.url,
|
||||
env: config.env,
|
||||
enabled: enableAllProjectMcpServers.value || enabledMcpjsonServers.value.includes(name)
|
||||
})
|
||||
}
|
||||
mcpServers.value = servers
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
mcpsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveMcpServers() {
|
||||
saving.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const mcpServersObj: Record<string, any> = {}
|
||||
for (const s of mcpServers.value) {
|
||||
const entry: any = { type: s.type }
|
||||
if (s.command) entry.command = s.command
|
||||
if (s.args?.length) entry.args = s.args
|
||||
if (s.url) entry.url = s.url
|
||||
if (s.env && Object.keys(s.env).length) entry.env = s.env
|
||||
mcpServersObj[s.name] = entry
|
||||
}
|
||||
|
||||
const res = await fetch('/api/agents/config/mcp', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mcpServers: mcpServersObj })
|
||||
})
|
||||
if (!res.ok) throw new Error('Failed to save MCP config')
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function addMcpServer(server: McpServerEntry) {
|
||||
mcpServers.value.push(server)
|
||||
}
|
||||
|
||||
function removeMcpServer(name: string) {
|
||||
mcpServers.value = mcpServers.value.filter(s => s.name !== name)
|
||||
}
|
||||
|
||||
function toggleMcpServer(name: string) {
|
||||
const server = mcpServers.value.find(s => s.name === name)
|
||||
if (server) server.enabled = !server.enabled
|
||||
}
|
||||
|
||||
// ── Hooks actions ──
|
||||
|
||||
async function saveHooks() {
|
||||
saving.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const res = await fetch('/api/agents/config/hooks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
agentId: selectedAgentId.value || 'main',
|
||||
hooks: hooksConfig.value
|
||||
})
|
||||
})
|
||||
if (!res.ok) throw new Error('Failed to save hooks')
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function addHook(eventType: string) {
|
||||
if (!hooksConfig.value[eventType]) {
|
||||
hooksConfig.value[eventType] = []
|
||||
}
|
||||
hooksConfig.value[eventType].push({
|
||||
matcher: null,
|
||||
hooks: [{ type: 'command', command: '', timeout: 5000 }]
|
||||
})
|
||||
}
|
||||
|
||||
function removeHook(eventType: string, index: number) {
|
||||
if (hooksConfig.value[eventType]) {
|
||||
hooksConfig.value[eventType].splice(index, 1)
|
||||
if (hooksConfig.value[eventType].length === 0) {
|
||||
delete hooksConfig.value[eventType]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateHook(eventType: string, index: number, entry: HookEntry) {
|
||||
if (hooksConfig.value[eventType]?.[index]) {
|
||||
hooksConfig.value[eventType][index] = entry
|
||||
}
|
||||
}
|
||||
|
||||
function toggleHookType(eventType: string) {
|
||||
if (expandedHookTypes.value.has(eventType)) {
|
||||
expandedHookTypes.value.delete(eventType)
|
||||
} else {
|
||||
expandedHookTypes.value.add(eventType)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Skills actions ──
|
||||
|
||||
async function fetchSkills() {
|
||||
skillsLoading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const agentId = selectedAgentId.value || 'main'
|
||||
const res = await fetch(`/api/agents/skills?agentId=${encodeURIComponent(agentId)}`)
|
||||
if (!res.ok) throw new Error('Failed to fetch skills')
|
||||
skills.value = await res.json()
|
||||
if (skills.value.length && !selectedSkill.value) {
|
||||
selectedSkill.value = skills.value[0]
|
||||
}
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
skillsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Plugins actions ──
|
||||
|
||||
async function fetchPlugins() {
|
||||
pluginsLoading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const res = await fetch('/api/agents/plugins')
|
||||
if (!res.ok) throw new Error('Failed to fetch plugins')
|
||||
plugins.value = await res.json()
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
pluginsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// Existing
|
||||
agents,
|
||||
selectedAgentId,
|
||||
selectedAgent,
|
||||
@@ -199,6 +738,59 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
loadFile,
|
||||
saveFile,
|
||||
revertFile,
|
||||
toggleCategory
|
||||
toggleCategory,
|
||||
|
||||
// New: tab
|
||||
activeTab,
|
||||
|
||||
// New: tools
|
||||
toolEntries,
|
||||
toolsFilter,
|
||||
toolsLoading,
|
||||
filteredTools,
|
||||
toolsByCategory,
|
||||
setToolStatus,
|
||||
cycleToolStatus,
|
||||
addToolRule,
|
||||
removeToolRule,
|
||||
savePermissions,
|
||||
|
||||
// New: MCP
|
||||
mcpServers,
|
||||
mcpsLoading,
|
||||
enableAllProjectMcpServers,
|
||||
enabledMcpjsonServers,
|
||||
fetchMcpJson,
|
||||
saveMcpServers,
|
||||
addMcpServer,
|
||||
removeMcpServer,
|
||||
toggleMcpServer,
|
||||
|
||||
// New: hooks
|
||||
hooksConfig,
|
||||
hooksLoading,
|
||||
expandedHookTypes,
|
||||
saveHooks,
|
||||
addHook,
|
||||
removeHook,
|
||||
updateHook,
|
||||
toggleHookType,
|
||||
|
||||
// New: skills
|
||||
skills,
|
||||
skillsLoading,
|
||||
selectedSkill,
|
||||
fetchSkills,
|
||||
|
||||
// New: plugins
|
||||
plugins,
|
||||
pluginsLoading,
|
||||
fetchPlugins,
|
||||
|
||||
// New: config metadata
|
||||
configFile,
|
||||
configDirty,
|
||||
fetchConfig,
|
||||
loadTabData
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user