feat(tools/background-task): resolve background_output task_id title

This commit is contained in:
YeonGyu-Kim
2026-02-08 17:54:59 +09:00
parent 83a05630cd
commit 23bca2b4d5
2 changed files with 107 additions and 1 deletions

View File

@@ -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<string, unknown>,
}
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,

View File

@@ -1,7 +1,11 @@
/// <reference types="bun-types" />
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> = {}): 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()