Merge remote-tracking branch 'origin/dev' into fix/delegate-task-depth-guard-rebased

This commit is contained in:
YeonGyu-Kim
2026-04-09 11:06:31 +09:00
159 changed files with 5005 additions and 473 deletions
@@ -204,6 +204,50 @@ describeFn("executeBackgroundTask output/session metadata compatibility", () =>
])
})
testFn("strips leading zwsp from agent name before launching background task", async () => {
//#given - display-sorted agent names should be normalized before manager launch
const launchCalls: unknown[] = []
const manager = {
launch: async (input: unknown) => {
launchCalls.push(input)
return {
id: "bg_clean_agent",
sessionID: "ses_clean_agent",
description: "Clean agent",
agent: "sisyphus-junior",
status: "running",
}
},
getTask: () => ({ sessionID: "ses_clean_agent" }),
}
//#when
await executeBackgroundTask(
{
description: "Clean agent",
prompt: "check",
run_in_background: true,
load_skills: [],
},
{
sessionID: "ses_parent",
callID: "call_clean_agent",
metadata: async () => {},
abort: new AbortController().signal,
},
{ manager },
{ sessionID: "ses_parent", messageID: "msg_clean_agent" },
"\u200Bsisyphus-junior",
undefined,
undefined,
undefined,
)
//#then
expectFn(launchCalls).toHaveLength(1)
expectFn((launchCalls[0] as { agent: string }).agent).toBe("sisyphus-junior")
})
testFn("keeps launched background task alive when parent aborts before session id resolves", async () => {
//#given - parallel tool execution can abort the parent call after launch succeeds
const metadataCalls: any[] = []
+5 -3
View File
@@ -10,6 +10,7 @@ import { getSessionTools } from "../../shared/session-tools-store"
import { SessionCategoryRegistry } from "../../shared/session-category-registry"
import { QUESTION_DENIED_SESSION_PERMISSION } from "../../shared/question-denied-session-permission"
import { setSessionFallbackChain } from "../../hooks/model-fallback/hook"
import { stripAgentListSortPrefix } from "../../shared/agent-display-names"
function continueSessionSetup(args: {
taskID: string
@@ -62,11 +63,12 @@ export async function executeBackgroundTask(
try {
const tddEnabled = executorCtx.sisyphusAgentConfig?.tdd
const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse, tddEnabled)
const normalizedAgent = stripAgentListSortPrefix(agentToUse)
const effectivePrompt = buildTaskPrompt(args.prompt, normalizedAgent, tddEnabled)
const task = await manager.launch({
description: args.description,
prompt: effectivePrompt,
agent: agentToUse,
agent: normalizedAgent,
parentSessionID: parentContext.sessionID,
parentMessageID: parentContext.messageID,
parentModel: parentContext.model,
@@ -156,7 +158,7 @@ Do NOT call background_output now. Wait for <system-reminder> notification first
return formatDetailedError(error, {
operation: "Launch background task",
args,
agent: agentToUse,
agent: stripAgentListSortPrefix(agentToUse),
category: args.category,
})
}
+2 -4
View File
@@ -325,7 +325,7 @@ export const PLAN_AGENT_NAMES = ["plan"]
export function isPlanAgent(agentName: string | undefined): boolean {
if (!agentName) return false
const lowerName = agentName.toLowerCase().trim()
return PLAN_AGENT_NAMES.some(name => lowerName === name || lowerName.includes(name))
return PLAN_AGENT_NAMES.some(name => lowerName === name)
}
/**
@@ -342,7 +342,5 @@ export function isPlanFamily(category: string | undefined): boolean
export function isPlanFamily(category: string | undefined): boolean {
if (!category) return false
const lowerCategory = category.toLowerCase().trim()
return PLAN_FAMILY_NAMES.some(
(name) => lowerCategory === name || lowerCategory.includes(name)
)
return PLAN_FAMILY_NAMES.some((name) => lowerCategory === name)
}
+3 -2
View File
@@ -89,9 +89,10 @@ Create the work plan directly - that's your job as the planning agent.`,
const callableAgents = agents.filter((agent) => isTaskCallableAgentMode(agent.mode))
const resolvedDisplayName = getAgentDisplayName(agentToUse)
const resolvedDisplayName = getAgentDisplayName(agentToUse).replace(/^\u200B+/, "")
const normalizedAgentToUse = agentToUse.replace(/^\u200B+/, "")
const matchedAgent = callableAgents.find(
(agent) => agent.name.toLowerCase() === agentToUse.toLowerCase()
(agent) => agent.name.toLowerCase() === normalizedAgentToUse.toLowerCase()
|| agent.name.toLowerCase() === resolvedDisplayName.toLowerCase()
)
if (!matchedAgent) {
@@ -277,15 +277,15 @@ bunDescribe("sendSyncPrompt", () => {
bunExpect(promptArgs.body.options).toEqual({
reasoningEffort: "high",
thinking: { type: "disabled" },
maxTokens: 4096,
})
bunExpect(promptArgs.body.maxOutputTokens).toBe(4096)
bunExpect(getSessionPromptParams("test-session")).toEqual({
temperature: 0.4,
topP: 0.7,
maxOutputTokens: 4096,
options: {
reasoningEffort: "high",
thinking: { type: "disabled" },
maxTokens: 4096,
},
})
})
@@ -30,12 +30,12 @@ function buildPromptGenerationParams(model: DelegatedModelConfig | undefined): R
const promptOptions: Record<string, unknown> = {
...(model.reasoningEffort ? { reasoningEffort: model.reasoningEffort } : {}),
...(model.thinking ? { thinking: model.thinking } : {}),
...(model.maxTokens !== undefined ? { maxTokens: model.maxTokens } : {}),
}
return {
...(model.temperature !== undefined ? { temperature: model.temperature } : {}),
...(model.top_p !== undefined ? { topP: model.top_p } : {}),
...(model.maxTokens !== undefined ? { maxOutputTokens: model.maxTokens } : {}),
...(Object.keys(promptOptions).length > 0 ? { options: promptOptions } : {}),
}
}
@@ -80,7 +80,7 @@ export async function sendSyncPrompt(
const promptArgs = {
path: { id: input.sessionID },
body: {
agent: input.agentToUse,
agent: input.agentToUse.replace(/^\u200B+/, ""),
system: input.systemContent,
tools,
parts: [createInternalAgentTextPart(effectivePrompt)],
+2 -2
View File
@@ -180,8 +180,8 @@ describe("sisyphus-task", () => {
//#given / #when
const result = isPlanAgent("planner")
//#then - "planner" contains "plan" so it matches via includes
expect(result).toBe(true)
//#then - "planner" is NOT an exact match for "plan" (T37 exact match fix)
expect(result).toBe(false)
})
test("returns true for case-insensitive match 'PLAN'", () => {