From 23bca2b4d53f8ea5247056acc45d768569b97a79 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 8 Feb 2026 17:54:59 +0900 Subject: [PATCH] feat(tools/background-task): resolve background_output task_id title --- .../modules/background-output.ts | 51 ++++++++++++++++- src/tools/background-task/tools.test.ts | 57 +++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src/tools/background-task/modules/background-output.ts b/src/tools/background-task/modules/background-output.ts index 87bdd9ed2..d86c4be63 100644 --- a/src/tools/background-task/modules/background-output.ts +++ b/src/tools/background-task/modules/background-output.ts @@ -4,6 +4,37 @@ import type { BackgroundOutputArgs } from "../types" import { BACKGROUND_OUTPUT_DESCRIPTION } from "../constants" import { formatTaskStatus, formatTaskResult, formatFullSession } from "./formatters" import { delay } from "./utils" +import { storeToolMetadata } from "../../../features/tool-metadata-store" +import type { BackgroundTask } from "../../../features/background-agent" +import type { ToolContextWithMetadata } from "./utils" + +const SISYPHUS_JUNIOR_AGENT = "sisyphus-junior" + +type ToolContextWithCallId = ToolContextWithMetadata & { + callID?: string + callId?: string + call_id?: string +} + +function resolveToolCallID(ctx: ToolContextWithCallId): string | undefined { + if (typeof ctx.callID === "string" && ctx.callID.trim() !== "") { + return ctx.callID + } + if (typeof ctx.callId === "string" && ctx.callId.trim() !== "") { + return ctx.callId + } + if (typeof ctx.call_id === "string" && ctx.call_id.trim() !== "") { + return ctx.call_id + } + return undefined +} + +function formatResolvedTitle(task: BackgroundTask): string { + const label = task.agent === SISYPHUS_JUNIOR_AGENT && task.category + ? task.category + : task.agent + return `${label} - ${task.description}` +} export function createBackgroundOutput(manager: BackgroundOutputManager, client: BackgroundOutputClient): ToolDefinition { return tool({ @@ -19,13 +50,31 @@ export function createBackgroundOutput(manager: BackgroundOutputManager, client: include_tool_results: tool.schema.boolean().optional().describe("Include tool results in full_session output (default: false)"), thinking_max_chars: tool.schema.number().optional().describe("Max characters for thinking content (default: 2000)"), }, - async execute(args: BackgroundOutputArgs) { + async execute(args: BackgroundOutputArgs, toolContext) { try { + const ctx = toolContext as ToolContextWithCallId const task = manager.getTask(args.task_id) if (!task) { return `Task not found: ${args.task_id}` } + const resolvedTitle = formatResolvedTitle(task) + const meta = { + title: resolvedTitle, + metadata: { + task_id: task.id, + agent: task.agent, + category: task.category, + description: task.description, + sessionId: task.sessionID ?? "pending", + } as Record, + } + await ctx.metadata?.(meta) + const callID = resolveToolCallID(ctx) + if (callID) { + storeToolMetadata(ctx.sessionID, callID, meta) + } + if (args.full_session === true) { return await formatFullSession(task, client, { includeThinking: args.include_thinking === true, diff --git a/src/tools/background-task/tools.test.ts b/src/tools/background-task/tools.test.ts index 6c3a20993..dbb8be906 100644 --- a/src/tools/background-task/tools.test.ts +++ b/src/tools/background-task/tools.test.ts @@ -1,7 +1,11 @@ +/// + +import { describe, test, expect } from "bun:test" import { createBackgroundCancel, createBackgroundOutput } from "./tools" import type { BackgroundManager, BackgroundTask } from "../../features/background-agent" import type { ToolContext } from "@opencode-ai/plugin/tool" import type { BackgroundCancelClient, BackgroundOutputManager, BackgroundOutputClient } from "./tools" +import { consumeToolMetadata, clearPendingStore } from "../../features/tool-metadata-store" const projectDir = "/Users/yeongyu/local-workspaces/oh-my-opencode" @@ -49,6 +53,59 @@ function createTask(overrides: Partial = {}): BackgroundTask { } describe("background_output full_session", () => { + test("resolves task_id into title metadata", async () => { + // #given + clearPendingStore() + + const task = createTask({ + id: "task-1", + agent: "explore", + description: "Find how task output is rendered", + status: "running", + }) + const manager = createMockManager(task) + const client = createMockClient({}) + const tool = createBackgroundOutput(manager, client) + const ctxWithCallId = { + ...mockContext, + callID: "call-1", + } as unknown as ToolContext + + // #when + await tool.execute({ task_id: "task-1" }, ctxWithCallId) + + // #then + const restored = consumeToolMetadata("test-session", "call-1") + expect(restored?.title).toBe("explore - Find how task output is rendered") + }) + + test("shows category instead of agent for sisyphus-junior", async () => { + // #given + clearPendingStore() + + const task = createTask({ + id: "task-1", + agent: "sisyphus-junior", + category: "quick", + description: "Fix flaky test", + status: "running", + }) + const manager = createMockManager(task) + const client = createMockClient({}) + const tool = createBackgroundOutput(manager, client) + const ctxWithCallId = { + ...mockContext, + callID: "call-1", + } as unknown as ToolContext + + // #when + await tool.execute({ task_id: "task-1" }, ctxWithCallId) + + // #then + const restored = consumeToolMetadata("test-session", "call-1") + expect(restored?.title).toBe("quick - Fix flaky test") + }) + test("includes thinking and tool results when enabled", async () => { // #given const task = createTask()