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.")
|
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", () => {
|
test("defaults mode to subagent when not specified", () => {
|
||||||
const filePath = join(tempDir, "no-mode.md")
|
const filePath = join(tempDir, "no-mode.md")
|
||||||
const content = `---
|
const content = `---
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export function parseMarkdownAgentFile(filePath: string, scope: AgentScope): Loa
|
|||||||
const content = readFileSync(filePath, "utf-8")
|
const content = readFileSync(filePath, "utf-8")
|
||||||
const { data, body } = parseFrontmatter<AgentFrontmatter>(content)
|
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 name = data.name || agentName
|
||||||
const originalDescription = data.description || ""
|
const originalDescription = data.description || ""
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ describe("readOpencodeConfigAgents", () => {
|
|||||||
const opencodeDir = path.join(tempDir, ".opencode")
|
const opencodeDir = path.join(tempDir, ".opencode")
|
||||||
fs.mkdirSync(opencodeDir, { recursive: true })
|
fs.mkdirSync(opencodeDir, { recursive: true })
|
||||||
|
|
||||||
const agentDefFile = path.join(tempDir, "agents.json")
|
const agentDefFile = path.join(opencodeDir, "agents.json")
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
agentDefFile,
|
agentDefFile,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -146,7 +146,7 @@ describe("readOpencodeConfigAgents", () => {
|
|||||||
|
|
||||||
if (Object.keys(result).length > 0) {
|
if (Object.keys(result).length > 0) {
|
||||||
expect(result).toHaveProperty("definition-agent")
|
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 })
|
fs.rmSync(tempDir, { recursive: true })
|
||||||
@@ -157,7 +157,7 @@ describe("readOpencodeConfigAgents", () => {
|
|||||||
const opencodeDir = path.join(tempDir, ".opencode")
|
const opencodeDir = path.join(tempDir, ".opencode")
|
||||||
fs.mkdirSync(opencodeDir, { recursive: true })
|
fs.mkdirSync(opencodeDir, { recursive: true })
|
||||||
|
|
||||||
const agentDefFile = path.join(tempDir, "agents.json")
|
const agentDefFile = path.join(opencodeDir, "agents.json")
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
agentDefFile,
|
agentDefFile,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -288,7 +288,7 @@ describe("readOpencodeConfigAgents", () => {
|
|||||||
const opencodeDir = path.join(tempDir, ".opencode")
|
const opencodeDir = path.join(tempDir, ".opencode")
|
||||||
fs.mkdirSync(opencodeDir, { recursive: true })
|
fs.mkdirSync(opencodeDir, { recursive: true })
|
||||||
|
|
||||||
const agentDef1 = path.join(tempDir, "agents1.json")
|
const agentDef1 = path.join(opencodeDir, "agents1.json")
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
agentDef1,
|
agentDef1,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -298,7 +298,7 @@ describe("readOpencodeConfigAgents", () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const agentDef2 = path.join(tempDir, "agents2.json")
|
const agentDef2 = path.join(opencodeDir, "agents2.json")
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
agentDef2,
|
agentDef2,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as fs from "node:fs"
|
import * as fs from "node:fs"
|
||||||
import * as os from "node:os"
|
|
||||||
import * as path from "node:path"
|
import * as path from "node:path"
|
||||||
|
|
||||||
|
import { getOpenCodeConfigDir } from "../../shared/opencode-config-dir"
|
||||||
import { parseJsoncSafe } from "../../shared/jsonc-parser"
|
import { parseJsoncSafe } from "../../shared/jsonc-parser"
|
||||||
import { loadAgentDefinitions } from "./agent-definitions-loader"
|
import { loadAgentDefinitions } from "./agent-definitions-loader"
|
||||||
import { mapClaudeModelToOpenCode } from "./claude-model-mapper"
|
import { mapClaudeModelToOpenCode } from "./claude-model-mapper"
|
||||||
@@ -13,27 +13,15 @@ interface OpencodeConfigWithAgents {
|
|||||||
agent_definitions?: string | string[]
|
agent_definitions?: string | string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWindowsAppdataDir(): string | null {
|
|
||||||
return process.env.APPDATA || null
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConfigPaths(directory: string): string[] {
|
function getConfigPaths(directory: string): string[] {
|
||||||
const crossPlatformDir = path.join(os.homedir(), ".config")
|
const globalConfigDir = getOpenCodeConfigDir({ binary: "opencode" })
|
||||||
const paths = [
|
const paths = [
|
||||||
path.join(directory, ".opencode", "opencode.json"),
|
path.join(directory, ".opencode", "opencode.json"),
|
||||||
path.join(directory, ".opencode", "opencode.jsonc"),
|
path.join(directory, ".opencode", "opencode.jsonc"),
|
||||||
path.join(crossPlatformDir, "opencode", "opencode.json"),
|
path.join(globalConfigDir, "opencode.json"),
|
||||||
path.join(crossPlatformDir, "opencode", "opencode.jsonc"),
|
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
|
return paths
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +96,7 @@ export function readOpencodeConfigAgents(directory: string): Record<string, Clau
|
|||||||
|
|
||||||
if (agentsToLoad && typeof agentsToLoad === "object") {
|
if (agentsToLoad && typeof agentsToLoad === "object") {
|
||||||
for (const [agentName, agentData] of Object.entries(agentsToLoad)) {
|
for (const [agentName, agentData] of Object.entries(agentsToLoad)) {
|
||||||
|
if (agentName in result) continue
|
||||||
const converted = convertInlineAgent(agentData)
|
const converted = convertInlineAgent(agentData)
|
||||||
if (converted) {
|
if (converted) {
|
||||||
result[agentName] = converted
|
result[agentName] = converted
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ export function loadPluginConfig(
|
|||||||
if (projectConfig?.agent_definitions) {
|
if (projectConfig?.agent_definitions) {
|
||||||
projectConfig.agent_definitions = resolveAgentDefinitionPaths(
|
projectConfig.agent_definitions = resolveAgentDefinitionPaths(
|
||||||
projectConfig.agent_definitions,
|
projectConfig.agent_definitions,
|
||||||
directory,
|
projectBasePath,
|
||||||
directory
|
directory
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user