fix(start-work): keep native command agents on config keys
This commit is contained in:
@@ -7,7 +7,6 @@ import { tmpdir } from "node:os"
|
|||||||
import { randomUUID } from "node:crypto"
|
import { randomUUID } from "node:crypto"
|
||||||
import { createStartWorkHook } from "./index"
|
import { createStartWorkHook } from "./index"
|
||||||
import { createAtlasHook } from "../atlas"
|
import { createAtlasHook } from "../atlas"
|
||||||
import { getAgentDisplayName, getAgentListDisplayName } from "../../shared/agent-display-names"
|
|
||||||
import {
|
import {
|
||||||
writeBoulderState,
|
writeBoulderState,
|
||||||
clearBoulderState,
|
clearBoulderState,
|
||||||
@@ -467,7 +466,7 @@ You are starting a Sisyphus work session.
|
|||||||
updateSpy.mockRestore()
|
updateSpy.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should stamp the outgoing message with Atlas list key so follow-up events keep the handoff", async () => {
|
test("should stamp the outgoing message with Atlas config key so OpenCode can resolve the agent", async () => {
|
||||||
// given
|
// given
|
||||||
const hook = createStartWorkHook(createMockPluginInput())
|
const hook = createStartWorkHook(createMockPluginInput())
|
||||||
const output = {
|
const output = {
|
||||||
@@ -481,8 +480,8 @@ You are starting a Sisyphus work session.
|
|||||||
output
|
output
|
||||||
)
|
)
|
||||||
|
|
||||||
// then
|
// then - config key, not display name (matches no-sisyphus-gpt / boulder-continuation-injector convention)
|
||||||
expect(output.message.agent).toBe(getAgentDisplayName("atlas"))
|
expect(output.message.agent).toBe("atlas")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should switch to Atlas even when current session is Sisyphus (regression: #3155)", async () => {
|
test("should switch to Atlas even when current session is Sisyphus (regression: #3155)", async () => {
|
||||||
@@ -502,7 +501,7 @@ You are starting a Sisyphus work session.
|
|||||||
)
|
)
|
||||||
|
|
||||||
// atlas is registered in beforeEach, so it must be selected
|
// atlas is registered in beforeEach, so it must be selected
|
||||||
expect(output.message.agent).toBe(getAgentDisplayName("atlas"))
|
expect(output.message.agent).toBe("atlas")
|
||||||
expect(sessionState.getSessionAgent("ses-sisyphus-to-atlas")).toBe("atlas")
|
expect(sessionState.getSessionAgent("ses-sisyphus-to-atlas")).toBe("atlas")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -525,7 +524,7 @@ You are starting a Sisyphus work session.
|
|||||||
)
|
)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.message.agent).toBe("Sisyphus - Ultraworker")
|
expect(output.message.agent).toBe("sisyphus")
|
||||||
expect(sessionState.getSessionAgent("ses-prometheus-to-sisyphus")).toBe("sisyphus")
|
expect(sessionState.getSessionAgent("ses-prometheus-to-sisyphus")).toBe("sisyphus")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -553,7 +552,7 @@ You are starting a Sisyphus work session.
|
|||||||
)
|
)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.message.agent).toBe("Sisyphus - Ultraworker")
|
expect(output.message.agent).toBe("sisyphus")
|
||||||
expect(sessionState.getSessionAgent("ses-prometheus-to-worker")).toBe("sisyphus")
|
expect(sessionState.getSessionAgent("ses-prometheus-to-worker")).toBe("sisyphus")
|
||||||
expect(readBoulderState(testDir)?.agent).toBe("sisyphus")
|
expect(readBoulderState(testDir)?.agent).toBe("sisyphus")
|
||||||
})
|
})
|
||||||
@@ -588,7 +587,7 @@ You are starting a Sisyphus work session.
|
|||||||
)
|
)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.message.agent).toBe("Sisyphus - Ultraworker")
|
expect(output.message.agent).toBe("sisyphus")
|
||||||
expect(readBoulderState(testDir)?.agent).toBe("sisyphus")
|
expect(readBoulderState(testDir)?.agent).toBe("sisyphus")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -623,7 +622,7 @@ You are starting a Sisyphus work session.
|
|||||||
await atlasHook.handler({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
|
await atlasHook.handler({ event: { type: "session.idle", properties: { sessionID: "session-123" } } })
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.message.agent).toBe(getAgentDisplayName("atlas"))
|
expect(output.message.agent).toBe("atlas")
|
||||||
expect(readBoulderState(testDir)?.session_ids).toContain("session-123")
|
expect(readBoulderState(testDir)?.session_ids).toContain("session-123")
|
||||||
expect(readBoulderState(testDir)?.agent).toBe("atlas")
|
expect(readBoulderState(testDir)?.agent).toBe("atlas")
|
||||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||||
@@ -713,7 +712,7 @@ You are starting a Sisyphus work session.
|
|||||||
await firePendingTimers()
|
await firePendingTimers()
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.message.agent).toBe(getAgentDisplayName("atlas"))
|
expect(output.message.agent).toBe("atlas")
|
||||||
expect(readBoulderState(testDir)?.session_ids).toContain("session-123")
|
expect(readBoulderState(testDir)?.session_ids).toContain("session-123")
|
||||||
expect(readBoulderState(testDir)?.agent).toBe("atlas")
|
expect(readBoulderState(testDir)?.agent).toBe("atlas")
|
||||||
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
expect(promptAsyncMock).toHaveBeenCalledTimes(1)
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ import {
|
|||||||
clearBoulderState,
|
clearBoulderState,
|
||||||
} from "../../features/boulder-state"
|
} from "../../features/boulder-state"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
import {
|
|
||||||
getAgentDisplayName,
|
|
||||||
getAgentListDisplayName,
|
|
||||||
stripAgentListSortPrefix,
|
|
||||||
} from "../../shared/agent-display-names"
|
|
||||||
import {
|
import {
|
||||||
isAgentRegistered,
|
isAgentRegistered,
|
||||||
updateSessionAgent,
|
updateSessionAgent,
|
||||||
@@ -86,12 +81,9 @@ export function createStartWorkHook(ctx: PluginInput) {
|
|||||||
const activeAgent = isAgentRegistered("atlas")
|
const activeAgent = isAgentRegistered("atlas")
|
||||||
? "atlas"
|
? "atlas"
|
||||||
: "sisyphus"
|
: "sisyphus"
|
||||||
const activeAgentDisplayName = activeAgent === "atlas"
|
|
||||||
? getAgentListDisplayName(activeAgent)
|
|
||||||
: getAgentDisplayName(activeAgent)
|
|
||||||
updateSessionAgent(input.sessionID, activeAgent)
|
updateSessionAgent(input.sessionID, activeAgent)
|
||||||
if (output.message) {
|
if (output.message) {
|
||||||
output.message["agent"] = stripAgentListSortPrefix(activeAgentDisplayName)
|
output.message["agent"] = activeAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingState = readBoulderState(ctx.directory)
|
const existingState = readBoulderState(ctx.directory)
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ describe("applyCommandConfig", () => {
|
|||||||
expect(commandConfig["agents-global-skill"]?.description).toContain("Agents global skill");
|
expect(commandConfig["agents-global-skill"]?.description).toContain("Agents global skill");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("remaps Atlas command agents to the list display name used by runtime agent lookup", async () => {
|
test("normalizes Atlas command agents to the config key OpenCode expects for native routing", async () => {
|
||||||
// given
|
// given
|
||||||
loadBuiltinCommandsSpy.mockReturnValue({
|
loadBuiltinCommandsSpy.mockReturnValue({
|
||||||
"start-work": {
|
"start-work": {
|
||||||
@@ -119,6 +119,31 @@ describe("applyCommandConfig", () => {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
const commandConfig = config.command as Record<string, { agent?: string }>;
|
const commandConfig = config.command as Record<string, { agent?: string }>;
|
||||||
expect(commandConfig["start-work"]?.agent).toBe(getAgentDisplayName("atlas"));
|
expect(commandConfig["start-work"]?.agent).toBe("atlas");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("normalizes legacy display-name command agents back to config keys", async () => {
|
||||||
|
// given
|
||||||
|
loadBuiltinCommandsSpy.mockReturnValue({
|
||||||
|
"start-work": {
|
||||||
|
name: "start-work",
|
||||||
|
description: "(builtin) Start work",
|
||||||
|
template: "template",
|
||||||
|
agent: getAgentDisplayName("atlas"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const config: Record<string, unknown> = { command: {} };
|
||||||
|
|
||||||
|
// when
|
||||||
|
await applyCommandConfig({
|
||||||
|
config,
|
||||||
|
pluginConfig: createPluginConfig(),
|
||||||
|
ctx: { directory: "/tmp" },
|
||||||
|
pluginComponents: createPluginComponents(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
const commandConfig = config.command as Record<string, { agent?: string }>;
|
||||||
|
expect(commandConfig["start-work"]?.agent).toBe("atlas");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { OhMyOpenCodeConfig } from "../config";
|
import type { OhMyOpenCodeConfig } from "../config";
|
||||||
import { getAgentDisplayName } from "../shared/agent-display-names";
|
import { getAgentConfigKey } from "../shared/agent-display-names";
|
||||||
import {
|
import {
|
||||||
loadUserCommands,
|
loadUserCommands,
|
||||||
loadProjectCommands,
|
loadProjectCommands,
|
||||||
@@ -96,7 +96,7 @@ export async function applyCommandConfig(params: {
|
|||||||
function remapCommandAgentFields(commands: Record<string, Record<string, unknown>>): void {
|
function remapCommandAgentFields(commands: Record<string, Record<string, unknown>>): void {
|
||||||
for (const cmd of Object.values(commands)) {
|
for (const cmd of Object.values(commands)) {
|
||||||
if (cmd?.agent && typeof cmd.agent === "string") {
|
if (cmd?.agent && typeof cmd.agent === "string") {
|
||||||
cmd.agent = getAgentDisplayName(cmd.agent);
|
cmd.agent = getAgentConfigKey(cmd.agent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ describe("createPluginInterface - command.execute.before", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.message.agent).toBe("Atlas - Plan Executor")
|
expect(output.message.agent).toBe("atlas")
|
||||||
expect(getSessionAgent("ses-command-atlas")).toBe("atlas")
|
expect(getSessionAgent("ses-command-atlas")).toBe("atlas")
|
||||||
expect(readBoulderState(testDir)?.agent).toBe("atlas")
|
expect(readBoulderState(testDir)?.agent).toBe("atlas")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ describe("createChatMessageHandler - /start-work integration", () => {
|
|||||||
await handler(input, output)
|
await handler(input, output)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.message["agent"]).toBe("Sisyphus - Ultraworker")
|
expect(output.message["agent"]).toBe("sisyphus")
|
||||||
expect(output.parts[0].text).toContain("<auto-slash-command>")
|
expect(output.parts[0].text).toContain("<auto-slash-command>")
|
||||||
expect(output.parts[0].text).toContain("Auto-Selected Plan")
|
expect(output.parts[0].text).toContain("Auto-Selected Plan")
|
||||||
expect(output.parts[0].text).toContain("boulder.json has been created")
|
expect(output.parts[0].text).toContain("boulder.json has been created")
|
||||||
@@ -116,7 +116,7 @@ describe("createChatMessageHandler - /start-work integration", () => {
|
|||||||
await handler(input, output)
|
await handler(input, output)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(output.message["agent"]).toBe("Sisyphus - Ultraworker")
|
expect(output.message["agent"]).toBe("sisyphus")
|
||||||
expect(output.parts[0].text).toContain("<auto-slash-command>")
|
expect(output.parts[0].text).toContain("<auto-slash-command>")
|
||||||
expect(output.parts[0].text).toContain("Auto-Selected Plan")
|
expect(output.parts[0].text).toContain("Auto-Selected Plan")
|
||||||
expect(output.parts[0].text).toContain("my-feature-plan")
|
expect(output.parts[0].text).toContain("my-feature-plan")
|
||||||
|
|||||||
Reference in New Issue
Block a user