fix(migration): remove auto model-to-category conversion (#764)

* chore(deps): upgrade @opencode-ai/plugin and sdk to 1.1.19

* docs(prometheus): add Question tool usage reminder

* fix(migration): remove auto model-to-category conversion

- Remove migrateAgentConfigToCategory call from migrateConfigFile
- User's explicit model/category settings are now preserved as-is
- No more unwanted deletion of agent configs (e.g., multimodal-looker)
- Add BUILTIN_AGENT_NAMES constant for future reference
- Update tests to reflect new behavior

* ci(sisyphus): add mandatory 'new branch + PR' todos for implementation tasks

---------

Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
This commit is contained in:
justsisyphus
2026-01-14 11:57:08 +09:00
committed by GitHub
parent 000a61c961
commit 22619d137e
6 changed files with 55 additions and 116 deletions

View File

@@ -430,6 +430,10 @@ jobs:
2. **CREATE TODOS IMMEDIATELY**: Right after reading, create your todo list using todo tools.
- First todo: "Summarize issue/PR context and requirements"
- Break down ALL work into atomic, verifiable steps
- **GIT WORKFLOW (MANDATORY for implementation tasks)**: ALWAYS include these final todos:
- "Create new branch from origin/BRANCH_PLACEHOLDER (NEVER push directly to BRANCH_PLACEHOLDER)"
- "Commit changes"
- "Create PR to BRANCH_PLACEHOLDER branch"
- Plan everything BEFORE starting any work
---

View File

@@ -11,8 +11,8 @@
"@code-yeongyu/comment-checker": "^0.6.1",
"@modelcontextprotocol/sdk": "^1.25.1",
"@openauthjs/openauth": "^0.4.3",
"@opencode-ai/plugin": "^1.1.1",
"@opencode-ai/sdk": "^1.1.1",
"@opencode-ai/plugin": "^1.1.19",
"@opencode-ai/sdk": "^1.1.19",
"commander": "^14.0.2",
"hono": "^4.10.4",
"js-yaml": "^4.1.1",
@@ -85,9 +85,9 @@
"@openauthjs/openauth": ["@openauthjs/openauth@0.4.3", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-RlnjqvHzqcbFVymEwhlUEuac4utA5h4nhSK/i2szZuQmxTIqbGUxZ+nM+avM+VV4Ing+/ZaNLKILoXS3yrkOOw=="],
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.1.1", "", { "dependencies": { "@opencode-ai/sdk": "1.1.1", "zod": "4.1.8" } }, "sha512-OZGvpDal8YsSo6dnatHfwviSToGZ6mJJyEKZGxUyWDuGCP7VhcoPkoM16ktl7TCVHkDK+TdwY9tKzkzFqQNc5w=="],
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.1.19", "", { "dependencies": { "@opencode-ai/sdk": "1.1.19", "zod": "4.1.8" } }, "sha512-Q6qBEjHb/dJMEw4BUqQxEswTMxCCHUpFMMb6jR8HTTs8X/28XRkKt5pHNPA82GU65IlSoPRph+zd8LReBDN53Q=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.1", "", {}, "sha512-PfXujMrHGeMnpS8Gd2BXSY+zZajlztcAvcokf06NtAhd0Mbo/hCLXgW0NBCQ+3FX3e/G2PNwz2DqMdtzyIZaCQ=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.19", "", {}, "sha512-XhZhFuvlLCqDpvNtUEjOsi/wvFj3YCXb1dySp+OONQRMuHlorNYnNa7P2A2ntKuhRdGT1Xt5na0nFzlUyNw+4A=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],

View File

@@ -52,8 +52,8 @@
"@code-yeongyu/comment-checker": "^0.6.1",
"@modelcontextprotocol/sdk": "^1.25.1",
"@openauthjs/openauth": "^0.4.3",
"@opencode-ai/plugin": "^1.1.1",
"@opencode-ai/sdk": "^1.1.1",
"@opencode-ai/plugin": "^1.1.19",
"@opencode-ai/sdk": "^1.1.19",
"commander": "^14.0.2",
"hono": "^4.10.4",
"js-yaml": "^4.1.1",

View File

@@ -479,6 +479,7 @@ sisyphus_task(agent="librarian", prompt="Find open source implementations of [fe
- Maintain conversational tone
- Use gathered evidence to inform suggestions
- Ask questions that help user articulate needs
- **Use the \`Question\` tool when presenting multiple options** (structured UI for selection)
- Confirm understanding before proceeding
- **Update draft file after EVERY meaningful exchange** (see Rule 6)

View File

@@ -457,13 +457,13 @@ describe("migrateConfigFile with backup", () => {
})
})
test("creates backup file with timestamp when migration needed", () => {
// #given: Config file path and config needing migration
test("creates backup file with timestamp when legacy migration needed", () => {
// #given: Config file path with legacy agent names needing migration
const testConfigPath = "/tmp/test-config-migration.json"
const testConfigContent = globalThis.JSON.stringify({ agents: { oracle: { model: "openai/gpt-5.2" } } }, null, 2)
const testConfigContent = globalThis.JSON.stringify({ agents: { omo: { model: "test" } } }, null, 2)
const rawConfig: Record<string, unknown> = {
agents: {
oracle: { model: "openai/gpt-5.2" },
omo: { model: "test" },
},
}
@@ -492,70 +492,54 @@ describe("migrateConfigFile with backup", () => {
expect(backupContent).toBe(testConfigContent)
})
test("deletes agent config when all fields match category defaults", () => {
// #given: Config with agent matching category defaults
const testConfigPath = "/tmp/test-config-delete.json"
test("preserves model setting without auto-conversion to category", () => {
// #given: Config with model setting (should NOT be converted to category)
const testConfigPath = "/tmp/test-config-preserve-model.json"
const rawConfig: Record<string, unknown> = {
agents: {
oracle: {
model: "openai/gpt-5.2",
temperature: 0.1,
},
"multimodal-looker": { model: "anthropic/claude-haiku-4-5" },
oracle: { model: "openai/gpt-5.2" },
"my-custom-agent": { model: "google/gemini-3-pro-preview" },
},
}
fs.writeFileSync(testConfigPath, globalThis.JSON.stringify({ agents: { oracle: { model: "openai/gpt-5.2" } } }, null, 2))
fs.writeFileSync(testConfigPath, globalThis.JSON.stringify(rawConfig, null, 2))
cleanupPaths.push(testConfigPath)
// #when: Migrate config file
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
// #then: Agent should be deleted (matches strategic category defaults)
expect(needsWrite).toBe(true)
// #then: No migration needed - model settings should be preserved as-is
expect(needsWrite).toBe(false)
const migratedConfig = JSON.parse(fs.readFileSync(testConfigPath, "utf-8"))
expect(migratedConfig.agents).toEqual({})
const dir = path.dirname(testConfigPath)
const basename = path.basename(testConfigPath)
const files = fs.readdirSync(dir)
const backupFiles = files.filter((f) => f.startsWith(`${basename}.bak.`))
backupFiles.forEach((f) => cleanupPaths.push(path.join(dir, f)))
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
expect(agents["multimodal-looker"].model).toBe("anthropic/claude-haiku-4-5")
expect(agents.oracle.model).toBe("openai/gpt-5.2")
expect(agents["my-custom-agent"].model).toBe("google/gemini-3-pro-preview")
})
test("keeps agent config with category when fields differ from defaults", () => {
// #given: Config with agent having custom temperature override
const testConfigPath = "/tmp/test-config-keep.json"
test("preserves category setting when explicitly set", () => {
// #given: Config with explicit category setting
const testConfigPath = "/tmp/test-config-preserve-category.json"
const rawConfig: Record<string, unknown> = {
agents: {
oracle: {
model: "openai/gpt-5.2",
temperature: 0.5,
},
"multimodal-looker": { category: "quick" },
oracle: { category: "ultrabrain" },
},
}
fs.writeFileSync(testConfigPath, globalThis.JSON.stringify({ agents: { oracle: { model: "openai/gpt-5.2" } } }, null, 2))
fs.writeFileSync(testConfigPath, globalThis.JSON.stringify(rawConfig, null, 2))
cleanupPaths.push(testConfigPath)
// #when: Migrate config file
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
// #then: Agent should be kept with category and custom override
expect(needsWrite).toBe(true)
// #then: No migration needed - category settings should be preserved as-is
expect(needsWrite).toBe(false)
const migratedConfig = JSON.parse(fs.readFileSync(testConfigPath, "utf-8"))
const agents = migratedConfig.agents as Record<string, unknown>
expect(agents.oracle).toBeDefined()
expect((agents.oracle as Record<string, unknown>).category).toBe("ultrabrain")
expect((agents.oracle as Record<string, unknown>).temperature).toBe(0.5)
expect((agents.oracle as Record<string, unknown>).model).toBeUndefined()
const dir = path.dirname(testConfigPath)
const basename = path.basename(testConfigPath)
const files = fs.readdirSync(dir)
const backupFiles = files.filter((f) => f.startsWith(`${basename}.bak.`))
backupFiles.forEach((f) => cleanupPaths.push(path.join(dir, f)))
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
expect(agents["multimodal-looker"].category).toBe("quick")
expect(agents.oracle.category).toBe("ultrabrain")
})
test("does not write when no migration needed", () => {
@@ -583,56 +567,5 @@ describe("migrateConfigFile with backup", () => {
expect(backupFiles.length).toBe(0)
})
test("handles multiple agent migrations correctly", () => {
// #given: Config with multiple agents needing migration
const testConfigPath = "/tmp/test-config-multi-agent.json"
const rawConfig: Record<string, unknown> = {
agents: {
oracle: { model: "openai/gpt-5.2" },
librarian: { model: "anthropic/claude-sonnet-4-5" },
frontend: {
model: "google/gemini-3-pro-preview",
temperature: 0.9,
},
},
}
fs.writeFileSync(
testConfigPath,
globalThis.JSON.stringify(
{
agents: {
oracle: { model: "openai/gpt-5.2" },
librarian: { model: "anthropic/claude-sonnet-4-5" },
frontend: { model: "google/gemini-3-pro-preview" },
},
},
null,
2,
),
)
cleanupPaths.push(testConfigPath)
// #when: Migrate config file
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
// #then: Should migrate correctly
expect(needsWrite).toBe(true)
const migratedConfig = JSON.parse(fs.readFileSync(testConfigPath, "utf-8"))
const agents = migratedConfig.agents as Record<string, unknown>
expect(agents.oracle).toBeUndefined()
expect(agents.librarian).toBeUndefined()
expect(agents.frontend).toBeDefined()
expect((agents.frontend as Record<string, unknown>).category).toBe("visual-engineering")
expect((agents.frontend as Record<string, unknown>).temperature).toBe(0.9)
const dir = path.dirname(testConfigPath)
const basename = path.basename(testConfigPath)
const files = fs.readdirSync(dir)
const backupFiles = files.filter((f) => f.startsWith(`${basename}.bak.`))
backupFiles.forEach((f) => cleanupPaths.push(path.join(dir, f)))
})
})

View File

@@ -22,6 +22,21 @@ export const AGENT_NAME_MAP: Record<string, string> = {
"multimodal-looker": "multimodal-looker",
}
export const BUILTIN_AGENT_NAMES = new Set([
"Sisyphus",
"oracle",
"librarian",
"explore",
"frontend-ui-ux-engineer",
"document-writer",
"multimodal-looker",
"Metis (Plan Consultant)",
"Momus (Plan Reviewer)",
"Prometheus (Planner)",
"orchestrator-sisyphus",
"build",
])
// Migration map: old hook names → new hook names (for backward compatibility)
export const HOOK_NAME_MAP: Record<string, string> = {
// Legacy names (backward compatibility)
@@ -117,21 +132,7 @@ export function migrateConfigFile(configPath: string, rawConfig: Record<string,
}
}
if (rawConfig.agents && typeof rawConfig.agents === "object") {
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
for (const [name, config] of Object.entries(agents)) {
const { migrated, changed } = migrateAgentConfigToCategory(config)
if (changed) {
const category = migrated.category as string
if (shouldDeleteAgentConfig(migrated, category)) {
delete agents[name]
} else {
agents[name] = migrated
}
needsWrite = true
}
}
}
if (rawConfig.omo_agent) {
rawConfig.sisyphus_agent = rawConfig.omo_agent