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:
4
.github/workflows/sisyphus-agent.yml
vendored
4
.github/workflows/sisyphus-agent.yml
vendored
@@ -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
|
||||
|
||||
---
|
||||
|
||||
8
bun.lock
8
bun.lock
@@ -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=="],
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user