(raw: string, fallbackName: string)
| 150 | } |
| 151 | |
| 152 | function parseAgent(raw: string, fallbackName: string): { name: string; role: RoleConfig } | null { |
| 153 | const m = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\r?\n?([\s\S]*)$/); |
| 154 | let fm: any = {}; |
| 155 | let body = raw; |
| 156 | if (m) { |
| 157 | try { fm = (yaml.load(m[1] ?? '') as any) ?? {}; } catch { fm = {}; } |
| 158 | body = m[2] ?? ''; |
| 159 | } |
| 160 | const name = String(fm.name ?? fallbackName).trim(); |
| 161 | if (!/^[a-zA-Z][\w-]*$/.test(name)) return null; |
| 162 | if (!body.trim()) return null; |
| 163 | const role: RoleConfig = { |
| 164 | systemPrompt: body.trim(), |
| 165 | allowedTools: mapTools(fm.tools), |
| 166 | description: typeof fm.description === 'string' ? fm.description.split('\n')[0]!.slice(0, 200) : undefined, |
| 167 | origin: 'plugin', |
| 168 | // model intentionally omitted: agent files often request cloud aliases (opus/ |
| 169 | // sonnet/inherit). Local-first QodeX inherits roles.subagent → parent instead, |
| 170 | // so imported agents run on the user's own model without needing cloud keys. |
| 171 | }; |
| 172 | return { name, role }; |
| 173 | } |
| 174 | |
| 175 | /** Load Claude Code agents (plugins + standalone .claude/agents) as QodeX roles. */ |
| 176 | export async function loadClaudeCodeAgents(cwd: string): Promise<Record<string, RoleConfig>> { |
no test coverage detected