5e4102566c
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
173 lines
6.2 KiB
TypeScript
173 lines
6.2 KiB
TypeScript
import type { DelegateTaskArgs, ToolContextWithMetadata, DelegatedModelConfig } from "./types"
|
|
import type { ExecutorContext, ParentContext } from "./executor-types"
|
|
import type { FallbackEntry } from "../../shared/model-requirements"
|
|
import { getTimingConfig } from "./timing"
|
|
import { buildTaskPrompt } from "./prompt-builder"
|
|
import { publishToolMetadata } from "../../features/tool-metadata-store"
|
|
import { formatDetailedError } from "./error-formatting"
|
|
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 { stripAgentListSortPrefix } from "../../shared/agent-display-names"
|
|
import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract"
|
|
import { resolveMetadataModel } from "./resolve-metadata-model"
|
|
|
|
function continueSessionSetup(args: {
|
|
taskID: string
|
|
manager: ExecutorContext["manager"]
|
|
timing: ReturnType<typeof getTimingConfig>
|
|
fallbackChain?: FallbackEntry[]
|
|
category?: string
|
|
modelFallbackControllerAccessor?: ExecutorContext["modelFallbackControllerAccessor"]
|
|
}): void {
|
|
if (!args.fallbackChain && !args.category) {
|
|
return
|
|
}
|
|
|
|
void (async () => {
|
|
const waitStart = Date.now()
|
|
while (Date.now() - waitStart < args.timing.WAIT_FOR_SESSION_TIMEOUT_MS) {
|
|
await new Promise(resolve => setTimeout(resolve, args.timing.WAIT_FOR_SESSION_INTERVAL_MS))
|
|
const updated = args.manager.getTask(args.taskID)
|
|
if (!updated) {
|
|
return
|
|
}
|
|
if (updated.status === "error" || updated.status === "cancelled" || updated.status === "interrupt") {
|
|
return
|
|
}
|
|
|
|
const sessionId = updated.sessionID
|
|
if (!sessionId) {
|
|
continue
|
|
}
|
|
|
|
args.modelFallbackControllerAccessor?.setSessionFallbackChain(sessionId, args.fallbackChain)
|
|
if (args.category) {
|
|
SessionCategoryRegistry.register(sessionId, args.category)
|
|
}
|
|
return
|
|
}
|
|
})()
|
|
}
|
|
|
|
export async function executeBackgroundTask(
|
|
args: DelegateTaskArgs,
|
|
ctx: ToolContextWithMetadata,
|
|
executorCtx: ExecutorContext,
|
|
parentContext: ParentContext,
|
|
agentToUse: string,
|
|
categoryModel: DelegatedModelConfig | undefined,
|
|
systemContent: string | undefined,
|
|
fallbackChain?: FallbackEntry[],
|
|
): Promise<string> {
|
|
const { manager } = executorCtx
|
|
|
|
try {
|
|
const tddEnabled = executorCtx.sisyphusAgentConfig?.tdd
|
|
const normalizedAgent = stripAgentListSortPrefix(agentToUse)
|
|
const effectivePrompt = buildTaskPrompt(args.prompt, normalizedAgent, tddEnabled)
|
|
const task = await manager.launch({
|
|
description: args.description,
|
|
prompt: effectivePrompt,
|
|
agent: normalizedAgent,
|
|
parentSessionID: parentContext.sessionID,
|
|
parentMessageID: parentContext.messageID,
|
|
parentModel: parentContext.model,
|
|
parentAgent: parentContext.agent,
|
|
parentTools: getSessionTools(parentContext.sessionID),
|
|
model: categoryModel,
|
|
fallbackChain,
|
|
skills: args.load_skills.length > 0 ? args.load_skills : undefined,
|
|
skillContent: systemContent,
|
|
category: args.category,
|
|
sessionPermission: QUESTION_DENIED_SESSION_PERMISSION,
|
|
})
|
|
|
|
// OpenCode TUI's `Task` tool UI calculates toolcalls by looking up
|
|
// `props.metadata.sessionId` and then counting tool parts in that session.
|
|
// BackgroundManager.launch() returns immediately (pending) before the session exists,
|
|
// so we must wait briefly for the session to be created to set metadata correctly.
|
|
const timing = getTimingConfig()
|
|
const waitStart = Date.now()
|
|
let sessionId = task.sessionID
|
|
while (!sessionId && Date.now() - waitStart < timing.WAIT_FOR_SESSION_TIMEOUT_MS) {
|
|
const updated = manager.getTask(task.id)
|
|
if (updated?.status === "error" || updated?.status === "cancelled" || updated?.status === "interrupt") {
|
|
return `Task failed to start (status: ${updated.status}).\n\nTask ID: ${task.id}`
|
|
}
|
|
sessionId = updated?.sessionID
|
|
if (sessionId) {
|
|
break
|
|
}
|
|
if (ctx.abort?.aborted) {
|
|
continueSessionSetup({
|
|
taskID: task.id,
|
|
manager,
|
|
timing,
|
|
fallbackChain,
|
|
category: args.category,
|
|
modelFallbackControllerAccessor: executorCtx.modelFallbackControllerAccessor,
|
|
})
|
|
break
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, timing.WAIT_FOR_SESSION_INTERVAL_MS))
|
|
}
|
|
|
|
if (sessionId) {
|
|
executorCtx.modelFallbackControllerAccessor?.setSessionFallbackChain(sessionId, fallbackChain)
|
|
}
|
|
if (args.category && sessionId) {
|
|
SessionCategoryRegistry.register(sessionId, args.category)
|
|
}
|
|
|
|
const resolvedModel = resolveMetadataModel(categoryModel, parentContext.model)
|
|
const metadata = {
|
|
prompt: args.prompt,
|
|
agent: task.agent,
|
|
category: args.category,
|
|
load_skills: args.load_skills,
|
|
description: args.description,
|
|
run_in_background: args.run_in_background,
|
|
command: args.command,
|
|
...(sessionId ? { taskId: sessionId } : {}),
|
|
backgroundTaskId: task.id,
|
|
...(sessionId ? { sessionId } : {}),
|
|
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
}
|
|
|
|
const unstableMeta = {
|
|
title: args.description,
|
|
metadata,
|
|
}
|
|
await publishToolMetadata(ctx, unstableMeta)
|
|
|
|
const taskMetadataBlock = sessionId
|
|
? `\n\n${buildTaskMetadataBlock({
|
|
sessionId,
|
|
taskId: sessionId,
|
|
backgroundTaskId: task.id,
|
|
agent: task.agent,
|
|
category: args.category,
|
|
})}`
|
|
: ""
|
|
|
|
return `Background task launched.
|
|
|
|
Background Task ID: ${task.id}
|
|
Description: ${task.description}
|
|
Agent: ${task.agent}${args.category ? ` (category: ${args.category})` : ""}
|
|
Status: ${task.status}
|
|
|
|
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.
|
|
|
|
Do NOT call background_output now. Wait for <system-reminder> notification first.${taskMetadataBlock}`
|
|
} catch (error) {
|
|
return formatDetailedError(error, {
|
|
operation: "Launch background task",
|
|
args,
|
|
agent: stripAgentListSortPrefix(agentToUse),
|
|
category: args.category,
|
|
})
|
|
}
|
|
}
|