fix(prompts): normalize agent names for continuation injections
This commit is contained in:
parent
21dc48e159
commit
8381ea076a
@ -855,7 +855,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
|
|||||||
.notifyParentSession(task)
|
.notifyParentSession(task)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(capturedBody?.agent).toBe("sisyphus")
|
expect(capturedBody?.agent).toBe("Sisyphus (Ultraworker)")
|
||||||
expect(capturedBody?.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
expect(capturedBody?.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
|
||||||
|
|
||||||
manager.shutdown()
|
manager.shutdown()
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
resolveInheritedPromptTools,
|
resolveInheritedPromptTools,
|
||||||
createInternalAgentTextPart,
|
createInternalAgentTextPart,
|
||||||
} from "../../shared"
|
} from "../../shared"
|
||||||
|
import { normalizeAgentForPrompt } from "../../shared/agent-display-names"
|
||||||
import { setSessionTools } from "../../shared/session-tools-store"
|
import { setSessionTools } from "../../shared/session-tools-store"
|
||||||
import { SessionCategoryRegistry } from "../../shared/session-category-registry"
|
import { SessionCategoryRegistry } from "../../shared/session-category-registry"
|
||||||
import { ConcurrencyManager } from "./concurrency"
|
import { ConcurrencyManager } from "./concurrency"
|
||||||
@ -1311,10 +1312,11 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolvedTools = resolveInheritedPromptTools(task.parentSessionID, tools)
|
const resolvedTools = resolveInheritedPromptTools(task.parentSessionID, tools)
|
||||||
|
const promptAgent = normalizeAgentForPrompt(agent)
|
||||||
|
|
||||||
log("[background-agent] notifyParentSession context:", {
|
log("[background-agent] notifyParentSession context:", {
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
resolvedAgent: agent,
|
resolvedAgent: promptAgent,
|
||||||
resolvedModel: model,
|
resolvedModel: model,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1323,7 +1325,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|||||||
path: { id: task.parentSessionID },
|
path: { id: task.parentSessionID },
|
||||||
body: {
|
body: {
|
||||||
noReply: !allComplete,
|
noReply: !allComplete,
|
||||||
...(agent !== undefined ? { agent } : {}),
|
...(promptAgent !== undefined ? { agent: promptAgent } : {}),
|
||||||
...(model !== undefined ? { model } : {}),
|
...(model !== undefined ? { model } : {}),
|
||||||
...(resolvedTools ? { tools: resolvedTools } : {}),
|
...(resolvedTools ? { tools: resolvedTools } : {}),
|
||||||
parts: [createInternalAgentTextPart(notification)],
|
parts: [createInternalAgentTextPart(notification)],
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import type { BackgroundManager } from "../../features/background-agent"
|
import type { BackgroundManager } from "../../features/background-agent"
|
||||||
|
import { normalizeAgentForPrompt } from "../../shared/agent-display-names"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared"
|
import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared"
|
||||||
import { HOOK_NAME } from "./hook-name"
|
import { HOOK_NAME } from "./hook-name"
|
||||||
@ -40,6 +41,7 @@ export async function injectBoulderContinuation(input: {
|
|||||||
const prompt =
|
const prompt =
|
||||||
BOULDER_CONTINUATION_PROMPT.replace(/{PLAN_NAME}/g, planName) +
|
BOULDER_CONTINUATION_PROMPT.replace(/{PLAN_NAME}/g, planName) +
|
||||||
`\n\n[Status: ${total - remaining}/${total} completed, ${remaining} remaining]`
|
`\n\n[Status: ${total - remaining}/${total} completed, ${remaining} remaining]`
|
||||||
|
const promptAgent = normalizeAgentForPrompt(agent ?? "atlas") ?? "atlas"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log(`[${HOOK_NAME}] Injecting boulder continuation`, { sessionID, planName, remaining })
|
log(`[${HOOK_NAME}] Injecting boulder continuation`, { sessionID, planName, remaining })
|
||||||
@ -50,7 +52,7 @@ export async function injectBoulderContinuation(input: {
|
|||||||
await ctx.client.session.promptAsync({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
agent: agent ?? "atlas",
|
agent: promptAgent,
|
||||||
...(promptContext.model !== undefined ? { model: promptContext.model } : {}),
|
...(promptContext.model !== undefined ? { model: promptContext.model } : {}),
|
||||||
...(inheritedTools ? { tools: inheritedTools } : {}),
|
...(inheritedTools ? { tools: inheritedTools } : {}),
|
||||||
parts: [createInternalAgentTextPart(prompt)],
|
parts: [createInternalAgentTextPart(prompt)],
|
||||||
|
|||||||
@ -997,7 +997,7 @@ describe("atlas hook", () => {
|
|||||||
// then - should call prompt for sisyphus
|
// then - should call prompt for sisyphus
|
||||||
expect(mockInput._promptMock).toHaveBeenCalled()
|
expect(mockInput._promptMock).toHaveBeenCalled()
|
||||||
const callArgs = mockInput._promptMock.mock.calls[0][0]
|
const callArgs = mockInput._promptMock.mock.calls[0][0]
|
||||||
expect(callArgs.body.agent).toBe("sisyphus")
|
expect(callArgs.body.agent).toBe("Sisyphus (Ultraworker)")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should debounce rapid continuation injections (prevent infinite loop)", async () => {
|
test("should debounce rapid continuation injections (prevent infinite loop)", async () => {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
normalizeSDKResponse,
|
normalizeSDKResponse,
|
||||||
resolveInheritedPromptTools,
|
resolveInheritedPromptTools,
|
||||||
} from "../../shared"
|
} from "../../shared"
|
||||||
|
import { normalizeAgentForPrompt } from "../../shared/agent-display-names"
|
||||||
|
|
||||||
type MessageInfo = {
|
type MessageInfo = {
|
||||||
agent?: string
|
agent?: string
|
||||||
@ -68,11 +69,12 @@ export async function injectContinuationPrompt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inheritedTools = resolveInheritedPromptTools(sourceSessionID, tools)
|
const inheritedTools = resolveInheritedPromptTools(sourceSessionID, tools)
|
||||||
|
const promptAgent = normalizeAgentForPrompt(agent)
|
||||||
|
|
||||||
await ctx.client.session.promptAsync({
|
await ctx.client.session.promptAsync({
|
||||||
path: { id: options.sessionID },
|
path: { id: options.sessionID },
|
||||||
body: {
|
body: {
|
||||||
...(agent !== undefined ? { agent } : {}),
|
...(promptAgent ? { agent: promptAgent } : {}),
|
||||||
...(model !== undefined ? { model } : {}),
|
...(model !== undefined ? { model } : {}),
|
||||||
...(inheritedTools ? { tools: inheritedTools } : {}),
|
...(inheritedTools ? { tools: inheritedTools } : {}),
|
||||||
parts: [createInternalAgentTextPart(options.prompt)],
|
parts: [createInternalAgentTextPart(options.prompt)],
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import type { createOpencodeClient } from "@opencode-ai/sdk"
|
import type { createOpencodeClient } from "@opencode-ai/sdk"
|
||||||
|
import { normalizeAgentForPrompt } from "../../shared/agent-display-names"
|
||||||
import type { MessageData, ResumeConfig } from "./types"
|
import type { MessageData, ResumeConfig } from "./types"
|
||||||
import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared"
|
import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared"
|
||||||
|
|
||||||
@ -25,13 +26,15 @@ export function extractResumeConfig(userMessage: MessageData | undefined, sessio
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function resumeSession(client: Client, config: ResumeConfig): Promise<boolean> {
|
export async function resumeSession(client: Client, config: ResumeConfig): Promise<boolean> {
|
||||||
|
const promptAgent = normalizeAgentForPrompt(config.agent)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const inheritedTools = resolveInheritedPromptTools(config.sessionID, config.tools)
|
const inheritedTools = resolveInheritedPromptTools(config.sessionID, config.tools)
|
||||||
await client.session.promptAsync({
|
await client.session.promptAsync({
|
||||||
path: { id: config.sessionID },
|
path: { id: config.sessionID },
|
||||||
body: {
|
body: {
|
||||||
parts: [createInternalAgentTextPart(RECOVERY_RESUME_TEXT)],
|
parts: [createInternalAgentTextPart(RECOVERY_RESUME_TEXT)],
|
||||||
agent: config.agent,
|
...(promptAgent ? { agent: promptAgent } : {}),
|
||||||
model: config.model,
|
model: config.model,
|
||||||
...(inheritedTools ? { tools: inheritedTools } : {}),
|
...(inheritedTools ? { tools: inheritedTools } : {}),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import type { BackgroundManager } from "../../features/background-agent"
|
import type { BackgroundManager } from "../../features/background-agent"
|
||||||
import { getMainSessionID, getSessionAgent } from "../../features/claude-code-session-state"
|
import { getMainSessionID, getSessionAgent } from "../../features/claude-code-session-state"
|
||||||
|
import { normalizeAgentForPrompt } from "../../shared/agent-display-names"
|
||||||
import { log } from "../../shared/logger"
|
import { log } from "../../shared/logger"
|
||||||
import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared"
|
import { createInternalAgentTextPart, resolveInheritedPromptTools } from "../../shared"
|
||||||
import {
|
import {
|
||||||
@ -80,7 +81,7 @@ async function resolveMainSessionTarget(
|
|||||||
log(`[${HOOK_NAME}] Failed to resolve main session agent`, { sessionID, error: String(error) })
|
log(`[${HOOK_NAME}] Failed to resolve main session agent`, { sessionID, error: String(error) })
|
||||||
}
|
}
|
||||||
|
|
||||||
return { agent, model, tools: resolveInheritedPromptTools(sessionID, tools) }
|
return { agent: normalizeAgentForPrompt(agent), model, tools: resolveInheritedPromptTools(sessionID, tools) }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getThinkingSummary(ctx: BabysitterContext, sessionID: string): Promise<string | null> {
|
async function getThinkingSummary(ctx: BabysitterContext, sessionID: string): Promise<string | null> {
|
||||||
|
|||||||
@ -53,4 +53,32 @@ export function getAgentConfigKey(agentName: string): string {
|
|||||||
if (reversed !== undefined) return reversed
|
if (reversed !== undefined) return reversed
|
||||||
if (AGENT_DISPLAY_NAMES[lower] !== undefined) return lower
|
if (AGENT_DISPLAY_NAMES[lower] !== undefined) return lower
|
||||||
return lower
|
return lower
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize an agent name for prompt APIs.
|
||||||
|
* - Known display names -> canonical display names
|
||||||
|
* - Known config keys (any case) -> canonical display names
|
||||||
|
* - Unknown/custom names -> preserved as-is (trimmed)
|
||||||
|
*/
|
||||||
|
export function normalizeAgentForPrompt(agentName: string | undefined): string | undefined {
|
||||||
|
if (typeof agentName !== "string") {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = agentName.trim()
|
||||||
|
if (!trimmed) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const lower = trimmed.toLowerCase()
|
||||||
|
const reversed = REVERSE_DISPLAY_NAMES[lower]
|
||||||
|
if (reversed !== undefined) {
|
||||||
|
return AGENT_DISPLAY_NAMES[reversed] ?? trimmed
|
||||||
|
}
|
||||||
|
if (AGENT_DISPLAY_NAMES[lower] !== undefined) {
|
||||||
|
return AGENT_DISPLAY_NAMES[lower]
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user