fix: add oh-my-openagent.jsonc config file detection (fixes #2624)
This commit is contained in:
@@ -3736,6 +3736,147 @@
|
|||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"openclaw": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"default": false,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gateways": {
|
||||||
|
"default": {},
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"default": "http",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"http",
|
||||||
|
"command"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"method": {
|
||||||
|
"default": "POST",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"timeout": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"method"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hooks": {
|
||||||
|
"default": {},
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"gateway": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"instruction": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"gateway",
|
||||||
|
"instruction"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replyListener": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"discordBotToken": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"discordChannelId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"discordMention": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"authorizedDiscordUserIds": {
|
||||||
|
"default": [],
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"telegramBotToken": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"telegramChatId": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pollIntervalMs": {
|
||||||
|
"default": 3000,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"rateLimitPerMinute": {
|
||||||
|
"default": 10,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"maxMessageLength": {
|
||||||
|
"default": 500,
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"includePrefix": {
|
||||||
|
"default": true,
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"authorizedDiscordUserIds",
|
||||||
|
"pollIntervalMs",
|
||||||
|
"rateLimitPerMinute",
|
||||||
|
"maxMessageLength",
|
||||||
|
"includePrefix"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enabled",
|
||||||
|
"gateways",
|
||||||
|
"hooks"
|
||||||
|
],
|
||||||
|
"additionalProperties": false
|
||||||
|
},
|
||||||
"babysitting": {
|
"babysitting": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import { readFileSync } from "node:fs"
|
|||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
|
|
||||||
import { OhMyOpenCodeConfigSchema } from "../../../config"
|
import { OhMyOpenCodeConfigSchema } from "../../../config"
|
||||||
import { detectConfigFile, getOpenCodeConfigDir, parseJsonc } from "../../../shared"
|
import { detectPluginConfigFile, getOpenCodeConfigDir, parseJsonc } from "../../../shared"
|
||||||
import { CHECK_IDS, CHECK_NAMES, PACKAGE_NAME } from "../constants"
|
import { CHECK_IDS, CHECK_NAMES, PACKAGE_NAME } from "../constants"
|
||||||
import type { CheckResult, DoctorIssue } from "../types"
|
import type { CheckResult, DoctorIssue } from "../types"
|
||||||
import { loadAvailableModelsFromCache } from "./model-resolution-cache"
|
import { loadAvailableModelsFromCache } from "./model-resolution-cache"
|
||||||
import { getModelResolutionInfoWithOverrides } from "./model-resolution"
|
import { getModelResolutionInfoWithOverrides } from "./model-resolution"
|
||||||
import type { OmoConfig } from "./model-resolution-types"
|
import type { OmoConfig } from "./model-resolution-types"
|
||||||
|
|
||||||
const USER_CONFIG_BASE = join(getOpenCodeConfigDir({ binary: "opencode" }), PACKAGE_NAME)
|
const USER_CONFIG_DIR = getOpenCodeConfigDir({ binary: "opencode" })
|
||||||
const PROJECT_CONFIG_BASE = join(process.cwd(), ".opencode", PACKAGE_NAME)
|
const PROJECT_CONFIG_DIR = join(process.cwd(), ".opencode")
|
||||||
|
|
||||||
interface ConfigValidationResult {
|
interface ConfigValidationResult {
|
||||||
exists: boolean
|
exists: boolean
|
||||||
@@ -21,10 +21,10 @@ interface ConfigValidationResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findConfigPath(): string | null {
|
function findConfigPath(): string | null {
|
||||||
const projectConfig = detectConfigFile(PROJECT_CONFIG_BASE)
|
const projectConfig = detectPluginConfigFile(PROJECT_CONFIG_DIR)
|
||||||
if (projectConfig.format !== "none") return projectConfig.path
|
if (projectConfig.format !== "none") return projectConfig.path
|
||||||
|
|
||||||
const userConfig = detectConfigFile(USER_CONFIG_BASE)
|
const userConfig = detectPluginConfigFile(USER_CONFIG_DIR)
|
||||||
if (userConfig.format !== "none") return userConfig.path
|
if (userConfig.format !== "none") return userConfig.path
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import { readFileSync } from "node:fs"
|
import { readFileSync } from "node:fs"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
import { detectConfigFile, getOpenCodeConfigPaths, parseJsonc } from "../../../shared"
|
import { detectPluginConfigFile, getOpenCodeConfigPaths, parseJsonc } from "../../../shared"
|
||||||
import type { OmoConfig } from "./model-resolution-types"
|
import type { OmoConfig } from "./model-resolution-types"
|
||||||
|
|
||||||
const PACKAGE_NAME = "oh-my-opencode"
|
const USER_CONFIG_DIR = getOpenCodeConfigPaths({ binary: "opencode", version: null }).configDir
|
||||||
const USER_CONFIG_BASE = join(
|
const PROJECT_CONFIG_DIR = join(process.cwd(), ".opencode")
|
||||||
getOpenCodeConfigPaths({ binary: "opencode", version: null }).configDir,
|
|
||||||
PACKAGE_NAME
|
|
||||||
)
|
|
||||||
const PROJECT_CONFIG_BASE = join(process.cwd(), ".opencode", PACKAGE_NAME)
|
|
||||||
|
|
||||||
export function loadOmoConfig(): OmoConfig | null {
|
export function loadOmoConfig(): OmoConfig | null {
|
||||||
const projectDetected = detectConfigFile(PROJECT_CONFIG_BASE)
|
const projectDetected = detectPluginConfigFile(PROJECT_CONFIG_DIR)
|
||||||
if (projectDetected.format !== "none") {
|
if (projectDetected.format !== "none") {
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(projectDetected.path, "utf-8")
|
const content = readFileSync(projectDetected.path, "utf-8")
|
||||||
@@ -21,7 +17,7 @@ export function loadOmoConfig(): OmoConfig | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const userDetected = detectConfigFile(USER_CONFIG_BASE)
|
const userDetected = detectPluginConfigFile(USER_CONFIG_DIR)
|
||||||
if (userDetected.format !== "none") {
|
if (userDetected.format !== "none") {
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(userDetected.path, "utf-8")
|
const content = readFileSync(userDetected.path, "utf-8")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
getOpenCodeConfigDir,
|
getOpenCodeConfigDir,
|
||||||
addConfigLoadError,
|
addConfigLoadError,
|
||||||
parseJsonc,
|
parseJsonc,
|
||||||
detectConfigFile,
|
detectPluginConfigFile,
|
||||||
migrateConfigFile,
|
migrateConfigFile,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
|
|
||||||
@@ -162,20 +162,19 @@ export function loadPluginConfig(
|
|||||||
): OhMyOpenCodeConfig {
|
): OhMyOpenCodeConfig {
|
||||||
// User-level config path - prefer .jsonc over .json
|
// User-level config path - prefer .jsonc over .json
|
||||||
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
||||||
const userBasePath = path.join(configDir, "oh-my-opencode");
|
const userDetected = detectPluginConfigFile(configDir);
|
||||||
const userDetected = detectConfigFile(userBasePath);
|
|
||||||
const userConfigPath =
|
const userConfigPath =
|
||||||
userDetected.format !== "none"
|
userDetected.format !== "none"
|
||||||
? userDetected.path
|
? userDetected.path
|
||||||
: userBasePath + ".json";
|
: path.join(configDir, "oh-my-openagent.json");
|
||||||
|
|
||||||
// Project-level config path - prefer .jsonc over .json
|
// Project-level config path - prefer .jsonc over .json
|
||||||
const projectBasePath = path.join(directory, ".opencode", "oh-my-opencode");
|
const projectBasePath = path.join(directory, ".opencode");
|
||||||
const projectDetected = detectConfigFile(projectBasePath);
|
const projectDetected = detectPluginConfigFile(projectBasePath);
|
||||||
const projectConfigPath =
|
const projectConfigPath =
|
||||||
projectDetected.format !== "none"
|
projectDetected.format !== "none"
|
||||||
? projectDetected.path
|
? projectDetected.path
|
||||||
: projectBasePath + ".json";
|
: path.join(projectBasePath, "oh-my-openagent.json");
|
||||||
|
|
||||||
// Load user config first (base)
|
// Load user config first (base)
|
||||||
let config: OhMyOpenCodeConfig =
|
let config: OhMyOpenCodeConfig =
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { detectConfigFile, parseJsonc, parseJsoncSafe, readJsoncFile } from "./jsonc-parser"
|
import { detectConfigFile, detectPluginConfigFile, parseJsonc, parseJsoncSafe, readJsoncFile } from "./jsonc-parser"
|
||||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
|
|
||||||
@@ -264,3 +264,84 @@ describe("detectConfigFile", () => {
|
|||||||
expect(result.format).toBe("none")
|
expect(result.format).toBe("none")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("detectPluginConfigFile", () => {
|
||||||
|
const testDir = join(__dirname, ".test-detect-plugin")
|
||||||
|
|
||||||
|
test("prefers oh-my-openagent over oh-my-opencode", () => {
|
||||||
|
// given
|
||||||
|
if (!existsSync(testDir)) mkdirSync(testDir, { recursive: true })
|
||||||
|
writeFileSync(join(testDir, "oh-my-openagent.jsonc"), "{}")
|
||||||
|
writeFileSync(join(testDir, "oh-my-opencode.jsonc"), "{}")
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = detectPluginConfigFile(testDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.format).toBe("jsonc")
|
||||||
|
expect(result.path).toBe(join(testDir, "oh-my-openagent.jsonc"))
|
||||||
|
|
||||||
|
rmSync(testDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to oh-my-opencode when oh-my-openagent doesn't exist", () => {
|
||||||
|
// given
|
||||||
|
if (!existsSync(testDir)) mkdirSync(testDir, { recursive: true })
|
||||||
|
writeFileSync(join(testDir, "oh-my-opencode.jsonc"), "{}")
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = detectPluginConfigFile(testDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.format).toBe("jsonc")
|
||||||
|
expect(result.path).toBe(join(testDir, "oh-my-opencode.jsonc"))
|
||||||
|
|
||||||
|
rmSync(testDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("falls back to oh-my-opencode.json when no jsonc exists", () => {
|
||||||
|
// given
|
||||||
|
if (!existsSync(testDir)) mkdirSync(testDir, { recursive: true })
|
||||||
|
writeFileSync(join(testDir, "oh-my-opencode.json"), "{}")
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = detectPluginConfigFile(testDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.format).toBe("json")
|
||||||
|
expect(result.path).toBe(join(testDir, "oh-my-opencode.json"))
|
||||||
|
|
||||||
|
rmSync(testDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("returns none when no config files exist", () => {
|
||||||
|
// given
|
||||||
|
const emptyDir = join(testDir, "empty")
|
||||||
|
if (!existsSync(emptyDir)) mkdirSync(emptyDir, { recursive: true })
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = detectPluginConfigFile(emptyDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.format).toBe("none")
|
||||||
|
expect(result.path).toBe(join(emptyDir, "oh-my-openagent.json"))
|
||||||
|
|
||||||
|
rmSync(testDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
test("prefers oh-my-openagent.json over oh-my-opencode.jsonc", () => {
|
||||||
|
// given
|
||||||
|
if (!existsSync(testDir)) mkdirSync(testDir, { recursive: true })
|
||||||
|
writeFileSync(join(testDir, "oh-my-openagent.json"), "{}")
|
||||||
|
writeFileSync(join(testDir, "oh-my-opencode.jsonc"), "{}")
|
||||||
|
|
||||||
|
// when
|
||||||
|
const result = detectPluginConfigFile(testDir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(result.format).toBe("json")
|
||||||
|
expect(result.path).toBe(join(testDir, "oh-my-openagent.json"))
|
||||||
|
|
||||||
|
rmSync(testDir, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { existsSync, readFileSync } from "node:fs"
|
import { existsSync, readFileSync } from "node:fs"
|
||||||
|
import { join } from "node:path"
|
||||||
import { parse, ParseError, printParseErrorCode } from "jsonc-parser"
|
import { parse, ParseError, printParseErrorCode } from "jsonc-parser"
|
||||||
|
|
||||||
export interface JsoncParseResult<T> {
|
export interface JsoncParseResult<T> {
|
||||||
@@ -64,3 +65,16 @@ export function detectConfigFile(basePath: string): {
|
|||||||
}
|
}
|
||||||
return { format: "none", path: jsonPath }
|
return { format: "none", path: jsonPath }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PLUGIN_CONFIG_NAMES = ["oh-my-openagent", "oh-my-opencode"] as const
|
||||||
|
|
||||||
|
export function detectPluginConfigFile(dir: string): {
|
||||||
|
format: "json" | "jsonc" | "none"
|
||||||
|
path: string
|
||||||
|
} {
|
||||||
|
for (const name of PLUGIN_CONFIG_NAMES) {
|
||||||
|
const result = detectConfigFile(join(dir, name))
|
||||||
|
if (result.format !== "none") return result
|
||||||
|
}
|
||||||
|
return { format: "none", path: join(dir, PLUGIN_CONFIG_NAMES[0] + ".json") }
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { join } from "path"
|
|||||||
import { BUILTIN_SERVERS } from "./constants"
|
import { BUILTIN_SERVERS } from "./constants"
|
||||||
import type { ResolvedServer } from "./types"
|
import type { ResolvedServer } from "./types"
|
||||||
import { getOpenCodeConfigDir } from "../../shared"
|
import { getOpenCodeConfigDir } from "../../shared"
|
||||||
import { parseJsonc, detectConfigFile } from "../../shared/jsonc-parser"
|
import { parseJsonc, detectConfigFile, detectPluginConfigFile } from "../../shared/jsonc-parser"
|
||||||
|
|
||||||
interface LspEntry {
|
interface LspEntry {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
@@ -38,8 +38,8 @@ export function getConfigPaths(): { project: string; user: string; opencode: str
|
|||||||
const cwd = process.cwd()
|
const cwd = process.cwd()
|
||||||
const configDir = getOpenCodeConfigDir({ binary: "opencode" })
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" })
|
||||||
return {
|
return {
|
||||||
project: detectConfigFile(join(cwd, ".opencode", "oh-my-opencode")).path,
|
project: detectPluginConfigFile(join(cwd, ".opencode")).path,
|
||||||
user: detectConfigFile(join(configDir, "oh-my-opencode")).path,
|
user: detectPluginConfigFile(configDir).path,
|
||||||
opencode: detectConfigFile(join(configDir, "opencode")).path,
|
opencode: detectConfigFile(join(configDir, "opencode")).path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user