feat(config): add categories, new agents and hooks to schema
Update Zod schema with: - CategoryConfigSchema for task delegation categories - CategoriesConfigSchema for user category overrides - New agents: Metis (Plan Consultant) - New hooks: prometheus-md-only, start-work, sisyphus-orchestrator - New commands: start-work - Agent category and skills fields Includes schema test coverage. 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { OhMyOpenCodeConfigSchema } from "./schema"
|
||||
import { AgentOverrideConfigSchema, BuiltinCategoryNameSchema, OhMyOpenCodeConfigSchema } from "./schema"
|
||||
|
||||
describe("disabled_mcps schema", () => {
|
||||
test("should accept built-in MCP names", () => {
|
||||
@@ -134,3 +134,184 @@ describe("disabled_mcps schema", () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("AgentOverrideConfigSchema", () => {
|
||||
describe("category field", () => {
|
||||
test("accepts category as optional string", () => {
|
||||
// #given
|
||||
const config = { category: "visual-engineering" }
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.category).toBe("visual-engineering")
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts config without category", () => {
|
||||
// #given
|
||||
const config = { temperature: 0.5 }
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
test("rejects non-string category", () => {
|
||||
// #given
|
||||
const config = { category: 123 }
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("skills field", () => {
|
||||
test("accepts skills as optional string array", () => {
|
||||
// #given
|
||||
const config = { skills: ["frontend-ui-ux", "code-reviewer"] }
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.skills).toEqual(["frontend-ui-ux", "code-reviewer"])
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts empty skills array", () => {
|
||||
// #given
|
||||
const config = { skills: [] }
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.skills).toEqual([])
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts config without skills", () => {
|
||||
// #given
|
||||
const config = { temperature: 0.5 }
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
test("rejects non-array skills", () => {
|
||||
// #given
|
||||
const config = { skills: "frontend-ui-ux" }
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("backward compatibility", () => {
|
||||
test("still accepts model field (deprecated)", () => {
|
||||
// #given
|
||||
const config = { model: "openai/gpt-5.2" }
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.model).toBe("openai/gpt-5.2")
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts both model and category (deprecated usage)", () => {
|
||||
// #given - category should take precedence at runtime, but both should validate
|
||||
const config = {
|
||||
model: "openai/gpt-5.2",
|
||||
category: "high-iq"
|
||||
}
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.model).toBe("openai/gpt-5.2")
|
||||
expect(result.data.category).toBe("high-iq")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("combined fields", () => {
|
||||
test("accepts category with skills", () => {
|
||||
// #given
|
||||
const config = {
|
||||
category: "visual-engineering",
|
||||
skills: ["frontend-ui-ux"]
|
||||
}
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.category).toBe("visual-engineering")
|
||||
expect(result.data.skills).toEqual(["frontend-ui-ux"])
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts category with skills and other fields", () => {
|
||||
// #given
|
||||
const config = {
|
||||
category: "high-iq",
|
||||
skills: ["code-reviewer"],
|
||||
temperature: 0.3,
|
||||
prompt_append: "Extra instructions"
|
||||
}
|
||||
|
||||
// #when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
|
||||
// #then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.category).toBe("high-iq")
|
||||
expect(result.data.skills).toEqual(["code-reviewer"])
|
||||
expect(result.data.temperature).toBe(0.3)
|
||||
expect(result.data.prompt_append).toBe("Extra instructions")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("BuiltinCategoryNameSchema", () => {
|
||||
test("accepts all builtin category names", () => {
|
||||
// #given
|
||||
const categories = ["visual-engineering", "high-iq", "artistry", "quick", "most-capable", "writing", "general"]
|
||||
|
||||
// #when / #then
|
||||
for (const cat of categories) {
|
||||
const result = BuiltinCategoryNameSchema.safeParse(cat)
|
||||
expect(result.success).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,10 +24,12 @@ export const BuiltinAgentNameSchema = z.enum([
|
||||
"frontend-ui-ux-engineer",
|
||||
"document-writer",
|
||||
"multimodal-looker",
|
||||
"Metis (Plan Consultant)",
|
||||
])
|
||||
|
||||
export const BuiltinSkillNameSchema = z.enum([
|
||||
"playwright",
|
||||
"frontend-ui-ux",
|
||||
])
|
||||
|
||||
export const OverridableAgentNameSchema = z.enum([
|
||||
@@ -35,7 +37,8 @@ export const OverridableAgentNameSchema = z.enum([
|
||||
"plan",
|
||||
"Sisyphus",
|
||||
"OpenCode-Builder",
|
||||
"Planner-Sisyphus",
|
||||
"Prometheus (Planner)",
|
||||
"Metis (Plan Consultant)",
|
||||
"oracle",
|
||||
"librarian",
|
||||
"explore",
|
||||
@@ -75,14 +78,23 @@ export const HookNameSchema = z.enum([
|
||||
"claude-code-hooks",
|
||||
"auto-slash-command",
|
||||
"edit-error-recovery",
|
||||
"prometheus-md-only",
|
||||
"start-work",
|
||||
"sisyphus-orchestrator",
|
||||
])
|
||||
|
||||
export const BuiltinCommandNameSchema = z.enum([
|
||||
"init-deep",
|
||||
"start-work",
|
||||
])
|
||||
|
||||
export const AgentOverrideConfigSchema = z.object({
|
||||
/** @deprecated Use `category` instead. Model is inherited from category defaults. */
|
||||
model: z.string().optional(),
|
||||
/** Category name to inherit model and other settings from CategoryConfig */
|
||||
category: z.string().optional(),
|
||||
/** Skill names to inject into agent prompt */
|
||||
skills: z.array(z.string()).optional(),
|
||||
temperature: z.number().min(0).max(2).optional(),
|
||||
top_p: z.number().min(0).max(1).optional(),
|
||||
prompt: z.string().optional(),
|
||||
@@ -103,7 +115,8 @@ export const AgentOverridesSchema = z.object({
|
||||
plan: AgentOverrideConfigSchema.optional(),
|
||||
Sisyphus: AgentOverrideConfigSchema.optional(),
|
||||
"OpenCode-Builder": AgentOverrideConfigSchema.optional(),
|
||||
"Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
|
||||
"Prometheus (Planner)": AgentOverrideConfigSchema.optional(),
|
||||
"Metis (Plan Consultant)": AgentOverrideConfigSchema.optional(),
|
||||
oracle: AgentOverrideConfigSchema.optional(),
|
||||
librarian: AgentOverrideConfigSchema.optional(),
|
||||
explore: AgentOverrideConfigSchema.optional(),
|
||||
@@ -129,6 +142,33 @@ export const SisyphusAgentConfigSchema = z.object({
|
||||
replace_plan: z.boolean().optional(),
|
||||
})
|
||||
|
||||
export const CategoryConfigSchema = z.object({
|
||||
model: z.string(),
|
||||
temperature: z.number().min(0).max(2).optional(),
|
||||
top_p: z.number().min(0).max(1).optional(),
|
||||
maxTokens: z.number().optional(),
|
||||
thinking: z.object({
|
||||
type: z.enum(["enabled", "disabled"]),
|
||||
budgetTokens: z.number().optional(),
|
||||
}).optional(),
|
||||
reasoningEffort: z.enum(["low", "medium", "high"]).optional(),
|
||||
textVerbosity: z.enum(["low", "medium", "high"]).optional(),
|
||||
tools: z.record(z.string(), z.boolean()).optional(),
|
||||
prompt_append: z.string().optional(),
|
||||
})
|
||||
|
||||
export const BuiltinCategoryNameSchema = z.enum([
|
||||
"visual-engineering",
|
||||
"high-iq",
|
||||
"artistry",
|
||||
"quick",
|
||||
"most-capable",
|
||||
"writing",
|
||||
"general",
|
||||
])
|
||||
|
||||
export const CategoriesConfigSchema = z.record(z.string(), CategoryConfigSchema)
|
||||
|
||||
export const CommentCheckerConfigSchema = z.object({
|
||||
/** Custom prompt to replace the default warning message. Use {{comments}} placeholder for detected comments XML. */
|
||||
custom_prompt: z.string().optional(),
|
||||
@@ -251,6 +291,7 @@ export const OhMyOpenCodeConfigSchema = z.object({
|
||||
disabled_hooks: z.array(HookNameSchema).optional(),
|
||||
disabled_commands: z.array(BuiltinCommandNameSchema).optional(),
|
||||
agents: AgentOverridesSchema.optional(),
|
||||
categories: CategoriesConfigSchema.optional(),
|
||||
claude_code: ClaudeCodeConfigSchema.optional(),
|
||||
google_auth: z.boolean().optional(),
|
||||
sisyphus_agent: SisyphusAgentConfigSchema.optional(),
|
||||
@@ -279,5 +320,8 @@ export type SkillsConfig = z.infer<typeof SkillsConfigSchema>
|
||||
export type SkillDefinition = z.infer<typeof SkillDefinitionSchema>
|
||||
export type RalphLoopConfig = z.infer<typeof RalphLoopConfigSchema>
|
||||
export type NotificationConfig = z.infer<typeof NotificationConfigSchema>
|
||||
export type CategoryConfig = z.infer<typeof CategoryConfigSchema>
|
||||
export type CategoriesConfig = z.infer<typeof CategoriesConfigSchema>
|
||||
export type BuiltinCategoryName = z.infer<typeof BuiltinCategoryNameSchema>
|
||||
|
||||
export { AnyMcpNameSchema, type AnyMcpName, McpNameSchema, type McpName } from "../mcp/types"
|
||||
|
||||
Reference in New Issue
Block a user