137c2459f7
Tool return values contained CTAs ("Use background_output to check") that
caused models to immediately poll instead of waiting for <system-reminder>
notifications. Unified all 5 tool return messages with explicit "Do NOT call
background_output now" guard, added pre-notification warning to BackgroundOutput
tool description, and strengthened Background Result Collection sections across
all 3 Sisyphus prompt variants (default, gpt-5-4, main) with BLOCKING
anti-pattern enforcement.
🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
127 lines
4.4 KiB
TypeScript
127 lines
4.4 KiB
TypeScript
import { tool, type PluginInput, type ToolDefinition } from "@opencode-ai/plugin"
|
|
import type { BackgroundManager } from "../../features/background-agent"
|
|
import type { BackgroundTaskArgs } from "./types"
|
|
import { BACKGROUND_TASK_DESCRIPTION } from "./constants"
|
|
import { resolveMessageContext } from "../../features/hook-message-injector"
|
|
import { getSessionAgent } from "../../features/claude-code-session-state"
|
|
import { storeToolMetadata } from "../../features/tool-metadata-store"
|
|
import { log } from "../../shared/logger"
|
|
import { delay } from "./delay"
|
|
import { getMessageDir } from "./message-dir"
|
|
|
|
type ToolContextWithMetadata = {
|
|
sessionID: string
|
|
messageID: string
|
|
agent: string
|
|
abort: AbortSignal
|
|
metadata?: (input: { title?: string; metadata?: Record<string, unknown> }) => void
|
|
callID?: string
|
|
}
|
|
|
|
export function createBackgroundTask(
|
|
manager: BackgroundManager,
|
|
client: PluginInput["client"]
|
|
): ToolDefinition {
|
|
return tool({
|
|
description: BACKGROUND_TASK_DESCRIPTION,
|
|
args: {
|
|
description: tool.schema.string().describe("Short task description (shown in status)"),
|
|
prompt: tool.schema.string().describe("Full detailed prompt for the agent"),
|
|
agent: tool.schema.string().describe("Agent type to use (any registered agent)"),
|
|
},
|
|
async execute(args: BackgroundTaskArgs, toolContext) {
|
|
const ctx = toolContext as ToolContextWithMetadata
|
|
|
|
if (!args.agent || args.agent.trim() === "") {
|
|
return `[ERROR] Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)`
|
|
}
|
|
|
|
try {
|
|
const messageDir = getMessageDir(ctx.sessionID)
|
|
const { prevMessage, firstMessageAgent } = await resolveMessageContext(
|
|
ctx.sessionID,
|
|
client,
|
|
messageDir
|
|
)
|
|
|
|
const sessionAgent = getSessionAgent(ctx.sessionID)
|
|
const parentAgent = ctx.agent ?? sessionAgent ?? firstMessageAgent ?? prevMessage?.agent
|
|
|
|
log("[background_task] parentAgent resolution", {
|
|
sessionID: ctx.sessionID,
|
|
ctxAgent: ctx.agent,
|
|
sessionAgent,
|
|
firstMessageAgent,
|
|
prevMessageAgent: prevMessage?.agent,
|
|
resolvedParentAgent: parentAgent,
|
|
})
|
|
|
|
const parentModel =
|
|
prevMessage?.model?.providerID && prevMessage?.model?.modelID
|
|
? {
|
|
providerID: prevMessage.model.providerID,
|
|
modelID: prevMessage.model.modelID,
|
|
...(prevMessage.model.variant ? { variant: prevMessage.model.variant } : {}),
|
|
}
|
|
: undefined
|
|
|
|
const task = await manager.launch({
|
|
description: args.description,
|
|
prompt: args.prompt,
|
|
agent: args.agent.trim(),
|
|
parentSessionID: ctx.sessionID,
|
|
parentMessageID: ctx.messageID,
|
|
parentModel,
|
|
parentAgent,
|
|
})
|
|
|
|
const WAIT_FOR_SESSION_INTERVAL_MS = 50
|
|
const WAIT_FOR_SESSION_TIMEOUT_MS = 30000
|
|
const waitStart = Date.now()
|
|
let sessionId = task.sessionID
|
|
while (!sessionId && Date.now() - waitStart < WAIT_FOR_SESSION_TIMEOUT_MS) {
|
|
const updated = manager.getTask(task.id)
|
|
if (updated?.status === "error" || updated?.status === "cancelled" || updated?.status === "interrupt") {
|
|
return `Task ${`entered error state`}\.\n\nTask ID: ${task.id}`
|
|
}
|
|
sessionId = updated?.sessionID
|
|
if (sessionId) {
|
|
break
|
|
}
|
|
if (ctx.abort?.aborted) {
|
|
break
|
|
}
|
|
await delay(WAIT_FOR_SESSION_INTERVAL_MS)
|
|
}
|
|
|
|
const bgMeta = {
|
|
title: args.description,
|
|
metadata: {
|
|
...(sessionId ? { sessionId } : {}),
|
|
},
|
|
}
|
|
ctx.metadata?.(bgMeta)
|
|
|
|
if (ctx.callID) {
|
|
storeToolMetadata(ctx.sessionID, ctx.callID, bgMeta)
|
|
}
|
|
|
|
return `Background task launched successfully.
|
|
|
|
Task ID: ${task.id}
|
|
Session ID: ${sessionId ?? "(not yet assigned)"}
|
|
Description: ${task.description}
|
|
Agent: ${task.agent}
|
|
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.`
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error)
|
|
return `[ERROR] Failed to launch background task: ${message}`
|
|
}
|
|
},
|
|
})
|
|
}
|