fix: task id passed to background job for continuation (#30485)

This commit is contained in:
Aiden Cline
2026-06-02 22:10:25 -05:00
committed by GitHub
parent 6003217eaa
commit 70cd4bf0ce
4 changed files with 292 additions and 50 deletions

View File

@@ -10,7 +10,7 @@ import { Agent } from "../agent/agent"
import { deriveSubagentSessionPermission } from "../agent/subagent-permissions"
import type { SessionPrompt } from "../session/prompt"
import { Config } from "@/config/config"
import { Cause, Effect, Exit, Schema, Scope } from "effect"
import { Effect, Exit, Schema, Scope } from "effect"
import { EffectBridge } from "@/effect/bridge"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { Database } from "@opencode-ai/core/database/database"
@@ -69,6 +69,17 @@ function backgroundOutput(sessionID: SessionID) {
].join("\n")
}
function backgroundUpdateOutput(sessionID: SessionID) {
return [
`<task id="${sessionID}" state="running">`,
"<summary>Background task updated</summary>",
"<task_result>",
"Additional context sent to the background task.",
"</task_result>",
"</task>",
].join("\n")
}
function backgroundMessage(input: {
sessionID: SessionID
description: string
@@ -90,11 +101,6 @@ function backgroundMessage(input: {
].join("\n")
}
function errorText(error: unknown) {
if (error instanceof Error) return error.message
return String(error)
}
export const TaskTool = Tool.define(
id,
Effect.gen(function* () {
@@ -231,9 +237,16 @@ export const TaskTool = Tool.define(
.pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true }))
})
const existing = yield* background.get(nextSession.id)
if (existing?.status === "running") {
return yield* Effect.fail(new Error(`Task ${nextSession.id} is already running.`))
if (yield* background.extend({ id: nextSession.id, run: runTask() })) {
return {
title: params.description,
metadata: {
...metadata,
background: true,
jobId: nextSession.id,
},
output: backgroundUpdateOutput(nextSession.id),
}
}
if (runInBackground) {
@@ -242,16 +255,18 @@ export const TaskTool = Tool.define(
type: id,
title: params.description,
metadata,
run: runTask().pipe(
Effect.tap((text) => inject("completed", text).pipe(Effect.ignore)),
Effect.catchCause((cause) =>
(Cause.hasInterruptsOnly(cause)
? Effect.void
: inject("error", errorText(Cause.squash(cause))).pipe(Effect.ignore)
).pipe(Effect.andThen(Effect.failCause(cause))),
),
),
run: runTask(),
})
yield* background
.wait({ id: info.id })
.pipe(
Effect.flatMap((result) => {
if (result.info?.status === "completed") return inject("completed", result.info.output ?? "")
if (result.info?.status === "error") return inject("error", result.info.error ?? "")
return Effect.void
}),
Effect.forkIn(scope, { startImmediately: true }),
)
return {
title: params.description,