diff --git a/src/agents/types.ts b/src/agents/types.ts index 4169895cb..f22683a27 100644 --- a/src/agents/types.ts +++ b/src/agents/types.ts @@ -64,7 +64,7 @@ export type BuiltinAgentName = | "multimodal-looker" | "Metis (Plan Consultant)" | "Momus (Plan Reviewer)" - | "atlas" + | "Atlas" export type OverridableAgentName = | "build" diff --git a/src/agents/utils.ts b/src/agents/utils.ts index bb691b59e..6bc3c5d86 100644 --- a/src/agents/utils.ts +++ b/src/agents/utils.ts @@ -25,9 +25,9 @@ const agentSources: Record = { "multimodal-looker": createMultimodalLookerAgent, "Metis (Plan Consultant)": createMetisAgent, "Momus (Plan Reviewer)": createMomusAgent, - // Note: atlas is handled specially in createBuiltinAgents() + // Note: Atlas is handled specially in createBuiltinAgents() // because it needs OrchestratorContext, not just a model string - atlas: createAtlasAgent as unknown as AgentFactory, + Atlas: createAtlasAgent as unknown as AgentFactory, } /** @@ -166,7 +166,7 @@ export function createBuiltinAgents( const agentName = name as BuiltinAgentName if (agentName === "Sisyphus") continue - if (agentName === "atlas") continue + if (agentName === "Atlas") continue if (disabledAgents.includes(agentName)) continue const override = agentOverrides[agentName] @@ -219,8 +219,8 @@ export function createBuiltinAgents( result["Sisyphus"] = sisyphusConfig } - if (!disabledAgents.includes("atlas")) { - const orchestratorOverride = agentOverrides["atlas"] + if (!disabledAgents.includes("Atlas")) { + const orchestratorOverride = agentOverrides["Atlas"] const orchestratorModel = orchestratorOverride?.model ?? systemDefaultModel let orchestratorConfig = createAtlasAgent({ model: orchestratorModel, @@ -233,7 +233,7 @@ export function createBuiltinAgents( orchestratorConfig = mergeAgentConfig(orchestratorConfig, orchestratorOverride) } - result["atlas"] = orchestratorConfig + result["Atlas"] = orchestratorConfig } return result diff --git a/src/config/schema.ts b/src/config/schema.ts index 5f684b572..2fdde902c 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -24,7 +24,7 @@ export const BuiltinAgentNameSchema = z.enum([ "multimodal-looker", "Metis (Plan Consultant)", "Momus (Plan Reviewer)", - "atlas", + "Atlas", ]) export const BuiltinSkillNameSchema = z.enum([ @@ -46,7 +46,7 @@ export const OverridableAgentNameSchema = z.enum([ "librarian", "explore", "multimodal-looker", - "atlas", + "Atlas", ]) export const AgentNameSchema = BuiltinAgentNameSchema @@ -127,7 +127,7 @@ export const AgentOverridesSchema = z.object({ librarian: AgentOverrideConfigSchema.optional(), explore: AgentOverrideConfigSchema.optional(), "multimodal-looker": AgentOverrideConfigSchema.optional(), - atlas: AgentOverrideConfigSchema.optional(), + Atlas: AgentOverrideConfigSchema.optional(), }) export const ClaudeCodeConfigSchema = z.object({ diff --git a/src/features/builtin-commands/commands.ts b/src/features/builtin-commands/commands.ts index d4e10903b..053c3eeda 100644 --- a/src/features/builtin-commands/commands.ts +++ b/src/features/builtin-commands/commands.ts @@ -55,7 +55,7 @@ ${REFACTOR_TEMPLATE} }, "start-work": { description: "(builtin) Start Sisyphus work session from Prometheus plan", - agent: "atlas", + agent: "Atlas", template: ` ${START_WORK_TEMPLATE} diff --git a/src/hooks/atlas/index.test.ts b/src/hooks/atlas/index.test.ts index 19f6db8bd..4a90f9aeb 100644 --- a/src/hooks/atlas/index.test.ts +++ b/src/hooks/atlas/index.test.ts @@ -85,8 +85,8 @@ describe("atlas hook", () => { expect(output.output).toBe("Original output") }) - test("should not transform when caller is not atlas", async () => { - // #given - boulder state exists but caller agent in message storage is not atlas + test("should not transform when caller is not Atlas", async () => { + // #given - boulder state exists but caller agent in message storage is not Atlas const sessionID = "session-non-orchestrator-test" setupMessageStorage(sessionID, "other-agent") @@ -120,10 +120,10 @@ describe("atlas hook", () => { cleanupMessageStorage(sessionID) }) - test("should append standalone verification when no boulder state but caller is atlas", async () => { - // #given - no boulder state, but caller is atlas + test("should append standalone verification when no boulder state but caller is Atlas", async () => { + // #given - no boulder state, but caller is Atlas const sessionID = "session-no-boulder-test" - setupMessageStorage(sessionID, "atlas") + setupMessageStorage(sessionID, "Atlas") const hook = createAtlasHook(createMockPluginInput()) const output = { @@ -146,10 +146,10 @@ describe("atlas hook", () => { cleanupMessageStorage(sessionID) }) - test("should transform output when caller is atlas with boulder state", async () => { - // #given - atlas caller with boulder state + test("should transform output when caller is Atlas with boulder state", async () => { + // #given - Atlas caller with boulder state const sessionID = "session-transform-test" - setupMessageStorage(sessionID, "atlas") + setupMessageStorage(sessionID, "Atlas") const planPath = join(TEST_DIR, "test-plan.md") writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [x] Task 2") @@ -186,9 +186,9 @@ describe("atlas hook", () => { }) test("should still transform when plan is complete (shows progress)", async () => { - // #given - boulder state with complete plan, atlas caller + // #given - boulder state with complete plan, Atlas caller const sessionID = "session-complete-plan-test" - setupMessageStorage(sessionID, "atlas") + setupMessageStorage(sessionID, "Atlas") const planPath = join(TEST_DIR, "complete-plan.md") writeFileSync(planPath, "# Plan\n- [x] Task 1\n- [x] Task 2") @@ -223,9 +223,9 @@ describe("atlas hook", () => { }) test("should append session ID to boulder state if not present", async () => { - // #given - boulder state without session-append-test, atlas caller + // #given - boulder state without session-append-test, Atlas caller const sessionID = "session-append-test" - setupMessageStorage(sessionID, "atlas") + setupMessageStorage(sessionID, "Atlas") const planPath = join(TEST_DIR, "test-plan.md") writeFileSync(planPath, "# Plan\n- [ ] Task 1") @@ -259,9 +259,9 @@ describe("atlas hook", () => { }) test("should not duplicate existing session ID", async () => { - // #given - boulder state already has session-dup-test, atlas caller + // #given - boulder state already has session-dup-test, Atlas caller const sessionID = "session-dup-test" - setupMessageStorage(sessionID, "atlas") + setupMessageStorage(sessionID, "Atlas") const planPath = join(TEST_DIR, "test-plan.md") writeFileSync(planPath, "# Plan\n- [ ] Task 1") @@ -296,9 +296,9 @@ describe("atlas hook", () => { }) test("should include boulder.json path and notepad path in transformed output", async () => { - // #given - boulder state, atlas caller + // #given - boulder state, Atlas caller const sessionID = "session-path-test" - setupMessageStorage(sessionID, "atlas") + setupMessageStorage(sessionID, "Atlas") const planPath = join(TEST_DIR, "my-feature.md") writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2\n- [x] Task 3") @@ -333,9 +333,9 @@ describe("atlas hook", () => { }) test("should include resume and checkbox instructions in reminder", async () => { - // #given - boulder state, atlas caller + // #given - boulder state, Atlas caller const sessionID = "session-resume-test" - setupMessageStorage(sessionID, "atlas") + setupMessageStorage(sessionID, "Atlas") const planPath = join(TEST_DIR, "test-plan.md") writeFileSync(planPath, "# Plan\n- [ ] Task 1") @@ -373,7 +373,7 @@ describe("atlas hook", () => { const ORCHESTRATOR_SESSION = "orchestrator-write-test" beforeEach(() => { - setupMessageStorage(ORCHESTRATOR_SESSION, "atlas") + setupMessageStorage(ORCHESTRATOR_SESSION, "Atlas") }) afterEach(() => { @@ -601,7 +601,7 @@ describe("atlas hook", () => { getMainSessionID: () => MAIN_SESSION_ID, subagentSessions: new Set(), })) - setupMessageStorage(MAIN_SESSION_ID, "atlas") + setupMessageStorage(MAIN_SESSION_ID, "Atlas") }) afterEach(() => { @@ -830,8 +830,8 @@ describe("atlas hook", () => { expect(callArgs.body.parts[0].text).toContain("2 remaining") }) - test("should not inject when last agent is not atlas", async () => { - // #given - boulder state with incomplete plan, but last agent is NOT atlas + test("should not inject when last agent is not Atlas", async () => { + // #given - boulder state with incomplete plan, but last agent is NOT Atlas const planPath = join(TEST_DIR, "test-plan.md") writeFileSync(planPath, "# Plan\n- [ ] Task 1\n- [ ] Task 2") @@ -843,7 +843,7 @@ describe("atlas hook", () => { } writeBoulderState(TEST_DIR, state) - // #given - last agent is NOT atlas + // #given - last agent is NOT Atlas cleanupMessageStorage(MAIN_SESSION_ID) setupMessageStorage(MAIN_SESSION_ID, "Sisyphus") @@ -858,7 +858,7 @@ describe("atlas hook", () => { }, }) - // #then - should NOT call prompt because agent is not atlas + // #then - should NOT call prompt because agent is not Atlas expect(mockInput._promptMock).not.toHaveBeenCalled() }) diff --git a/src/hooks/atlas/index.ts b/src/hooks/atlas/index.ts index 6a2f9638f..f35d70c21 100644 --- a/src/hooks/atlas/index.ts +++ b/src/hooks/atlas/index.ts @@ -111,7 +111,7 @@ const ORCHESTRATOR_DELEGATION_REQUIRED = ` **STOP. YOU ARE VIOLATING ORCHESTRATOR PROTOCOL.** -You (atlas) are attempting to directly modify a file outside \`.sisyphus/\`. +You (Atlas) are attempting to directly modify a file outside \`.sisyphus/\`. **Path attempted:** $FILE_PATH @@ -397,7 +397,7 @@ function isCallerOrchestrator(sessionID?: string): boolean { const messageDir = getMessageDir(sessionID) if (!messageDir) return false const nearest = findNearestMessageWithFields(messageDir) - return nearest?.agent === "atlas" + return nearest?.agent === "Atlas" } interface SessionState { @@ -496,7 +496,7 @@ export function createAtlasHook( await ctx.client.session.prompt({ path: { id: sessionID }, body: { - agent: "atlas", + agent: "Atlas", ...(model !== undefined ? { model } : {}), parts: [{ type: "text", text: prompt }], }, @@ -569,7 +569,7 @@ export function createAtlasHook( } if (!isCallerOrchestrator(sessionID)) { - log(`[${HOOK_NAME}] Skipped: last agent is not atlas`, { sessionID }) + log(`[${HOOK_NAME}] Skipped: last agent is not Atlas`, { sessionID }) return } diff --git a/src/hooks/start-work/index.test.ts b/src/hooks/start-work/index.test.ts index c13b5193f..df4a20631 100644 --- a/src/hooks/start-work/index.test.ts +++ b/src/hooks/start-work/index.test.ts @@ -379,7 +379,7 @@ describe("start-work hook", () => { }) describe("session agent management", () => { - test("should update session agent to atlas when start-work command is triggered", async () => { + test("should update session agent to Atlas when start-work command is triggered", async () => { // #given const updateSpy = spyOn(sessionState, "updateSessionAgent") @@ -395,7 +395,7 @@ describe("start-work hook", () => { ) // #then - expect(updateSpy).toHaveBeenCalledWith("ses-prometheus-to-sisyphus", "atlas") + expect(updateSpy).toHaveBeenCalledWith("ses-prometheus-to-sisyphus", "Atlas") updateSpy.mockRestore() }) }) diff --git a/src/hooks/start-work/index.ts b/src/hooks/start-work/index.ts index 4f3d528b2..dc03d3c71 100644 --- a/src/hooks/start-work/index.ts +++ b/src/hooks/start-work/index.ts @@ -71,7 +71,7 @@ export function createStartWorkHook(ctx: PluginInput) { sessionID: input.sessionID, }) - updateSessionAgent(input.sessionID, "atlas") + updateSessionAgent(input.sessionID, "Atlas") const existingState = readBoulderState(ctx.directory) const sessionId = input.sessionID diff --git a/src/plugin-handlers/config-handler.ts b/src/plugin-handlers/config-handler.ts index 948df8c39..3ba18add2 100644 --- a/src/plugin-handlers/config-handler.ts +++ b/src/plugin-handlers/config-handler.ts @@ -160,7 +160,7 @@ export function createConfigHandler(deps: ConfigHandlerDeps) { explore?: { tools?: Record }; librarian?: { tools?: Record }; "multimodal-looker"?: { tools?: Record }; - atlas?: { tools?: Record }; + Atlas?: { tools?: Record }; Sisyphus?: { tools?: Record }; }; const configAgent = config.agent as AgentConfig | undefined; @@ -319,8 +319,8 @@ export function createConfigHandler(deps: ConfigHandlerDeps) { const agent = agentResult["multimodal-looker"] as AgentWithPermission; agent.permission = { ...agent.permission, task: "deny", look_at: "deny" }; } - if (agentResult["atlas"]) { - const agent = agentResult["atlas"] as AgentWithPermission; + if (agentResult["Atlas"]) { + const agent = agentResult["Atlas"] as AgentWithPermission; agent.permission = { ...agent.permission, task: "deny", call_omo_agent: "deny", delegate_task: "allow" }; } if (agentResult.Sisyphus) { diff --git a/src/shared/migration.test.ts b/src/shared/migration.test.ts index 0bcbbde86..84e563ae4 100644 --- a/src/shared/migration.test.ts +++ b/src/shared/migration.test.ts @@ -64,7 +64,7 @@ describe("migrateAgentNames", () => { // #then: Case-insensitive lookup should migrate correctly expect(migrated["Sisyphus"]).toEqual({ model: "test" }) expect(migrated["Prometheus (Planner)"]).toEqual({ prompt: "test" }) - expect(migrated["atlas"]).toEqual({ model: "openai/gpt-5.2" }) + expect(migrated["Atlas"]).toEqual({ model: "openai/gpt-5.2" }) }) test("passes through unknown agent names unchanged", () => { @@ -81,7 +81,7 @@ describe("migrateAgentNames", () => { expect(migrated["custom-agent"]).toEqual({ model: "custom/model" }) }) - test("migrates orchestrator-sisyphus to atlas", () => { + test("migrates orchestrator-sisyphus to Atlas", () => { // #given: Config with legacy orchestrator-sisyphus agent name const agents = { "orchestrator-sisyphus": { model: "anthropic/claude-opus-4-5" }, @@ -90,11 +90,26 @@ describe("migrateAgentNames", () => { // #when: Migrate agent names const { migrated, changed } = migrateAgentNames(agents) - // #then: orchestrator-sisyphus should be migrated to atlas + // #then: orchestrator-sisyphus should be migrated to Atlas expect(changed).toBe(true) - expect(migrated["atlas"]).toEqual({ model: "anthropic/claude-opus-4-5" }) + expect(migrated["Atlas"]).toEqual({ model: "anthropic/claude-opus-4-5" }) expect(migrated["orchestrator-sisyphus"]).toBeUndefined() }) + + test("migrates lowercase atlas to Atlas", () => { + // #given: Config with lowercase atlas agent name + const agents = { + atlas: { model: "anthropic/claude-opus-4-5" }, + } + + // #when: Migrate agent names + const { migrated, changed } = migrateAgentNames(agents) + + // #then: lowercase atlas should be migrated to Atlas + expect(changed).toBe(true) + expect(migrated["Atlas"]).toEqual({ model: "anthropic/claude-opus-4-5" }) + expect(migrated["atlas"]).toBeUndefined() + }) }) describe("migrateHookNames", () => { diff --git a/src/shared/migration.ts b/src/shared/migration.ts index 4f407b9da..3f9b005c8 100644 --- a/src/shared/migration.ts +++ b/src/shared/migration.ts @@ -18,7 +18,8 @@ export const AGENT_NAME_MAP: Record = { librarian: "librarian", explore: "explore", "multimodal-looker": "multimodal-looker", - "orchestrator-sisyphus": "atlas", + "orchestrator-sisyphus": "Atlas", + atlas: "Atlas", } export const BUILTIN_AGENT_NAMES = new Set([ @@ -30,7 +31,7 @@ export const BUILTIN_AGENT_NAMES = new Set([ "Metis (Plan Consultant)", "Momus (Plan Reviewer)", "Prometheus (Planner)", - "atlas", + "Atlas", "build", ])