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:
parent
f49d92852a
commit
7d2983fafb
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { OhMyOpenCodeConfigSchema } from "./schema"
|
import { AgentOverrideConfigSchema, BuiltinCategoryNameSchema, OhMyOpenCodeConfigSchema } from "./schema"
|
||||||
|
|
||||||
describe("disabled_mcps schema", () => {
|
describe("disabled_mcps schema", () => {
|
||||||
test("should accept built-in MCP names", () => {
|
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",
|
"frontend-ui-ux-engineer",
|
||||||
"document-writer",
|
"document-writer",
|
||||||
"multimodal-looker",
|
"multimodal-looker",
|
||||||
|
"Metis (Plan Consultant)",
|
||||||
])
|
])
|
||||||
|
|
||||||
export const BuiltinSkillNameSchema = z.enum([
|
export const BuiltinSkillNameSchema = z.enum([
|
||||||
"playwright",
|
"playwright",
|
||||||
|
"frontend-ui-ux",
|
||||||
])
|
])
|
||||||
|
|
||||||
export const OverridableAgentNameSchema = z.enum([
|
export const OverridableAgentNameSchema = z.enum([
|
||||||
@ -35,7 +37,8 @@ export const OverridableAgentNameSchema = z.enum([
|
|||||||
"plan",
|
"plan",
|
||||||
"Sisyphus",
|
"Sisyphus",
|
||||||
"OpenCode-Builder",
|
"OpenCode-Builder",
|
||||||
"Planner-Sisyphus",
|
"Prometheus (Planner)",
|
||||||
|
"Metis (Plan Consultant)",
|
||||||
"oracle",
|
"oracle",
|
||||||
"librarian",
|
"librarian",
|
||||||
"explore",
|
"explore",
|
||||||
@ -75,14 +78,23 @@ export const HookNameSchema = z.enum([
|
|||||||
"claude-code-hooks",
|
"claude-code-hooks",
|
||||||
"auto-slash-command",
|
"auto-slash-command",
|
||||||
"edit-error-recovery",
|
"edit-error-recovery",
|
||||||
|
"prometheus-md-only",
|
||||||
|
"start-work",
|
||||||
|
"sisyphus-orchestrator",
|
||||||
])
|
])
|
||||||
|
|
||||||
export const BuiltinCommandNameSchema = z.enum([
|
export const BuiltinCommandNameSchema = z.enum([
|
||||||
"init-deep",
|
"init-deep",
|
||||||
|
"start-work",
|
||||||
])
|
])
|
||||||
|
|
||||||
export const AgentOverrideConfigSchema = z.object({
|
export const AgentOverrideConfigSchema = z.object({
|
||||||
|
/** @deprecated Use `category` instead. Model is inherited from category defaults. */
|
||||||
model: z.string().optional(),
|
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(),
|
temperature: z.number().min(0).max(2).optional(),
|
||||||
top_p: z.number().min(0).max(1).optional(),
|
top_p: z.number().min(0).max(1).optional(),
|
||||||
prompt: z.string().optional(),
|
prompt: z.string().optional(),
|
||||||
@ -103,7 +115,8 @@ export const AgentOverridesSchema = z.object({
|
|||||||
plan: AgentOverrideConfigSchema.optional(),
|
plan: AgentOverrideConfigSchema.optional(),
|
||||||
Sisyphus: AgentOverrideConfigSchema.optional(),
|
Sisyphus: AgentOverrideConfigSchema.optional(),
|
||||||
"OpenCode-Builder": AgentOverrideConfigSchema.optional(),
|
"OpenCode-Builder": AgentOverrideConfigSchema.optional(),
|
||||||
"Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
|
"Prometheus (Planner)": AgentOverrideConfigSchema.optional(),
|
||||||
|
"Metis (Plan Consultant)": AgentOverrideConfigSchema.optional(),
|
||||||
oracle: AgentOverrideConfigSchema.optional(),
|
oracle: AgentOverrideConfigSchema.optional(),
|
||||||
librarian: AgentOverrideConfigSchema.optional(),
|
librarian: AgentOverrideConfigSchema.optional(),
|
||||||
explore: AgentOverrideConfigSchema.optional(),
|
explore: AgentOverrideConfigSchema.optional(),
|
||||||
@ -129,6 +142,33 @@ export const SisyphusAgentConfigSchema = z.object({
|
|||||||
replace_plan: z.boolean().optional(),
|
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({
|
export const CommentCheckerConfigSchema = z.object({
|
||||||
/** Custom prompt to replace the default warning message. Use {{comments}} placeholder for detected comments XML. */
|
/** Custom prompt to replace the default warning message. Use {{comments}} placeholder for detected comments XML. */
|
||||||
custom_prompt: z.string().optional(),
|
custom_prompt: z.string().optional(),
|
||||||
@ -251,6 +291,7 @@ export const OhMyOpenCodeConfigSchema = z.object({
|
|||||||
disabled_hooks: z.array(HookNameSchema).optional(),
|
disabled_hooks: z.array(HookNameSchema).optional(),
|
||||||
disabled_commands: z.array(BuiltinCommandNameSchema).optional(),
|
disabled_commands: z.array(BuiltinCommandNameSchema).optional(),
|
||||||
agents: AgentOverridesSchema.optional(),
|
agents: AgentOverridesSchema.optional(),
|
||||||
|
categories: CategoriesConfigSchema.optional(),
|
||||||
claude_code: ClaudeCodeConfigSchema.optional(),
|
claude_code: ClaudeCodeConfigSchema.optional(),
|
||||||
google_auth: z.boolean().optional(),
|
google_auth: z.boolean().optional(),
|
||||||
sisyphus_agent: SisyphusAgentConfigSchema.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 SkillDefinition = z.infer<typeof SkillDefinitionSchema>
|
||||||
export type RalphLoopConfig = z.infer<typeof RalphLoopConfigSchema>
|
export type RalphLoopConfig = z.infer<typeof RalphLoopConfigSchema>
|
||||||
export type NotificationConfig = z.infer<typeof NotificationConfigSchema>
|
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"
|
export { AnyMcpNameSchema, type AnyMcpName, McpNameSchema, type McpName } from "../mcp/types"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user