fix(agents): address cubic review findings on agent loader
- Case-insensitive .md extension stripping for agent name extraction - Resolve project agent_definitions paths relative to config dir (.opencode/) - Use getOpenCodeConfigDir() to respect OPENCODE_CONFIG_DIR/XDG_CONFIG_HOME - First-write-wins semantics for both inline and definition-file agents so project-level agents always take precedence over global-level
This commit is contained in:
committed by
YeonGyu-Kim
parent
3d0fb22cda
commit
42445f5130
@@ -82,6 +82,22 @@ Prompt.`
|
||||
expect(result?.config.prompt).toBe("Prompt.")
|
||||
})
|
||||
|
||||
test("strips .MD extension case-insensitively for agent name", () => {
|
||||
const filePath = join(tempDir, "UpperCase.MD")
|
||||
const content = `---
|
||||
description: Mixed case extension
|
||||
---
|
||||
|
||||
Prompt content.`
|
||||
|
||||
writeFileSync(filePath, content, "utf-8")
|
||||
|
||||
const result = parseMarkdownAgentFile(filePath, "definition-file")
|
||||
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.name).toBe("UpperCase")
|
||||
})
|
||||
|
||||
test("defaults mode to subagent when not specified", () => {
|
||||
const filePath = join(tempDir, "no-mode.md")
|
||||
const content = `---
|
||||
|
||||
@@ -28,7 +28,8 @@ export function parseMarkdownAgentFile(filePath: string, scope: AgentScope): Loa
|
||||
const content = readFileSync(filePath, "utf-8")
|
||||
const { data, body } = parseFrontmatter<AgentFrontmatter>(content)
|
||||
|
||||
const agentName = basename(filePath, ".md")
|
||||
const fileName = basename(filePath)
|
||||
const agentName = fileName.replace(/\.md$/i, "")
|
||||
const name = data.name || agentName
|
||||
const originalDescription = data.description || ""
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ describe("readOpencodeConfigAgents", () => {
|
||||
const opencodeDir = path.join(tempDir, ".opencode")
|
||||
fs.mkdirSync(opencodeDir, { recursive: true })
|
||||
|
||||
const agentDefFile = path.join(tempDir, "agents.json")
|
||||
const agentDefFile = path.join(opencodeDir, "agents.json")
|
||||
fs.writeFileSync(
|
||||
agentDefFile,
|
||||
JSON.stringify({
|
||||
@@ -146,7 +146,7 @@ describe("readOpencodeConfigAgents", () => {
|
||||
|
||||
if (Object.keys(result).length > 0) {
|
||||
expect(result).toHaveProperty("definition-agent")
|
||||
expect(result["definition-agent"].description).toContain("definition-file")
|
||||
expect(result["definition-agent"].description).toContain("From definition file")
|
||||
}
|
||||
|
||||
fs.rmSync(tempDir, { recursive: true })
|
||||
@@ -157,7 +157,7 @@ describe("readOpencodeConfigAgents", () => {
|
||||
const opencodeDir = path.join(tempDir, ".opencode")
|
||||
fs.mkdirSync(opencodeDir, { recursive: true })
|
||||
|
||||
const agentDefFile = path.join(tempDir, "agents.json")
|
||||
const agentDefFile = path.join(opencodeDir, "agents.json")
|
||||
fs.writeFileSync(
|
||||
agentDefFile,
|
||||
JSON.stringify({
|
||||
@@ -288,7 +288,7 @@ describe("readOpencodeConfigAgents", () => {
|
||||
const opencodeDir = path.join(tempDir, ".opencode")
|
||||
fs.mkdirSync(opencodeDir, { recursive: true })
|
||||
|
||||
const agentDef1 = path.join(tempDir, "agents1.json")
|
||||
const agentDef1 = path.join(opencodeDir, "agents1.json")
|
||||
fs.writeFileSync(
|
||||
agentDef1,
|
||||
JSON.stringify({
|
||||
@@ -298,7 +298,7 @@ describe("readOpencodeConfigAgents", () => {
|
||||
})
|
||||
)
|
||||
|
||||
const agentDef2 = path.join(tempDir, "agents2.json")
|
||||
const agentDef2 = path.join(opencodeDir, "agents2.json")
|
||||
fs.writeFileSync(
|
||||
agentDef2,
|
||||
JSON.stringify({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from "node:fs"
|
||||
import * as os from "node:os"
|
||||
import * as path from "node:path"
|
||||
|
||||
import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir"
|
||||
import { parseJsoncSafe } from "../../shared/jsonc-parser"
|
||||
import { loadAgentDefinitions } from "./agent-definitions-loader"
|
||||
import { mapClaudeModelToOpenCode } from "./claude-model-mapper"
|
||||
@@ -13,27 +13,15 @@ interface OpencodeConfigWithAgents {
|
||||
agent_definitions?: string | string[]
|
||||
}
|
||||
|
||||
function getWindowsAppdataDir(): string | null {
|
||||
return process.env.APPDATA || null
|
||||
}
|
||||
|
||||
function getConfigPaths(directory: string): string[] {
|
||||
const crossPlatformDir = path.join(os.homedir(), ".config")
|
||||
const globalConfigDir = getOpenCodeConfigDir({ binary: "opencode" })
|
||||
const paths = [
|
||||
path.join(directory, ".opencode", "opencode.json"),
|
||||
path.join(directory, ".opencode", "opencode.jsonc"),
|
||||
path.join(crossPlatformDir, "opencode", "opencode.json"),
|
||||
path.join(crossPlatformDir, "opencode", "opencode.jsonc"),
|
||||
path.join(globalConfigDir, "opencode.json"),
|
||||
path.join(globalConfigDir, "opencode.jsonc"),
|
||||
]
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const appdataDir = getWindowsAppdataDir()
|
||||
if (appdataDir) {
|
||||
paths.push(path.join(appdataDir, "opencode", "opencode.json"))
|
||||
paths.push(path.join(appdataDir, "opencode", "opencode.jsonc"))
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
@@ -108,6 +96,7 @@ export function readOpencodeConfigAgents(directory: string): Record<string, Clau
|
||||
|
||||
if (agentsToLoad && typeof agentsToLoad === "object") {
|
||||
for (const [agentName, agentData] of Object.entries(agentsToLoad)) {
|
||||
if (agentName in result) continue
|
||||
const converted = convertInlineAgent(agentData)
|
||||
if (converted) {
|
||||
result[agentName] = converted
|
||||
|
||||
@@ -278,7 +278,7 @@ export function loadPluginConfig(
|
||||
if (projectConfig?.agent_definitions) {
|
||||
projectConfig.agent_definitions = resolveAgentDefinitionPaths(
|
||||
projectConfig.agent_definitions,
|
||||
directory,
|
||||
projectBasePath,
|
||||
directory
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user