refactor(core): replace background job service (#34559)

This commit is contained in:
Kit Langton
2026-06-29 23:53:35 -04:00
committed by GitHub
parent 390740cbfc
commit 0f2bbcc4d5
24 changed files with 593 additions and 673 deletions
+3 -3
View File
@@ -48,7 +48,7 @@ import { EventV2Bridge } from "@/event-v2-bridge"
import { Agent } from "../agent/agent"
import { Skill } from "../skill"
import { Permission } from "@/permission"
import { BackgroundJob } from "@/background/job"
import { Job } from "@/job"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
@@ -325,7 +325,7 @@ export const defaultLayer = Layer.suspend(() =>
Layer.provide(Skill.defaultLayer),
Layer.provide(Agent.defaultLayer),
Layer.provide(Session.defaultLayer),
Layer.provide(BackgroundJob.defaultLayer),
Layer.provide(Job.defaultLayer),
Layer.provide(Provider.defaultLayer),
Layer.provide(LSP.defaultLayer),
Layer.provide(Instruction.defaultLayer),
@@ -426,7 +426,7 @@ export const node = LayerNode.make({
Agent.node,
Skill.node,
Session.node,
BackgroundJob.node,
Job.node,
Provider.node,
LSP.node,
Instruction.node,
+26 -27
View File
@@ -2,7 +2,7 @@ import * as Tool from "./tool"
import DESCRIPTION from "./task.txt"
import { ToolJsonSchema } from "./json-schema"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { BackgroundJob } from "@/background/job"
import { Job } from "@/job"
import { Session } from "@/session/session"
import { SessionID, MessageID } from "../session/schema"
import { MessageV2 } from "../session/message-v2"
@@ -33,11 +33,11 @@ const BACKGROUND_STARTED = [
"DO NOT sleep, poll for progress, ask the task for status, or duplicate this task's work — avoid working with the same files or topics it is using.",
"Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.",
].join("\n")
const BACKGROUND_UPDATED = [
"Additional context sent to the running background task.",
const BACKGROUND_ALREADY_RUNNING = [
"The task is already working in the background.",
"The task is still working in the background. You will be notified automatically when it finishes.",
"DO NOT sleep, poll for progress, ask the task for status, or duplicate this task's work — avoid working with the same files or topics it is using.",
"Work on non-overlapping tasks, or briefly tell the user what you sent and end your response.",
"Work on non-overlapping tasks, or briefly tell the user it is still running and end your response.",
].join("\n")
const BaseParameterFields = {
@@ -82,7 +82,7 @@ export const TaskTool = Tool.define(
id,
Effect.gen(function* () {
const agent = yield* Agent.Service
const background = yield* BackgroundJob.Service
const jobs = yield* Job.Service
const config = yield* Config.Service
const sessions = yield* Session.Service
const scope = yield* Scope.Scope
@@ -229,7 +229,7 @@ export const TaskTool = Tool.define(
})
const notify = Effect.fn("TaskTool.notifyBackgroundResult")(function* (jobID: string) {
yield* background.wait({ id: jobID }).pipe(
yield* jobs.wait({ id: jobID }).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 ?? "")
@@ -239,7 +239,8 @@ export const TaskTool = Tool.define(
)
})
if (yield* background.extend({ id: nextSession.id, run: runTask() })) {
const existing = yield* jobs.get(nextSession.id)
if (existing?.status === "running") {
return {
title: params.description,
metadata: {
@@ -250,24 +251,17 @@ export const TaskTool = Tool.define(
output: renderOutput({
sessionID: nextSession.id,
state: "running",
summary: "Background task updated",
text: BACKGROUND_UPDATED,
summary: "Background task already running",
text: BACKGROUND_ALREADY_RUNNING,
}),
}
}
const info = yield* background.start({
const info = yield* jobs.start({
id: nextSession.id,
type: id,
title: params.description,
metadata,
onPromote: Effect.all([
ctx.metadata({
title: params.description,
metadata: { ...metadata, background: true, jobId: nextSession.id },
}),
notify(nextSession.id),
]),
run: runTask().pipe(Effect.onInterrupt(() => ops.cancel(nextSession.id))),
})
@@ -289,6 +283,7 @@ export const TaskTool = Tool.define(
}
if (runInBackground) {
yield* jobs.background(info.id)
yield* notify(info.id)
return backgroundResult()
}
@@ -306,23 +301,27 @@ export const TaskTool = Tool.define(
}),
() =>
Effect.gen(function* () {
const result = yield* Effect.raceFirst(
background.wait({ id: nextSession.id }).pipe(Effect.map((waited) => waited.info)),
background.waitForPromotion(nextSession.id),
)
if (result?.metadata?.background === true) return backgroundResult()
if (result?.status === "error") return yield* Effect.fail(new Error(result.error ?? "Task failed"))
if (result?.status === "cancelled") return yield* Effect.fail(new Error("Task cancelled"))
const result = yield* jobs.block({ id: nextSession.id, sessionID: ctx.sessionID })
if (result?.type === "backgrounded") {
yield* ctx.metadata({
title: params.description,
metadata: { ...metadata, background: true, jobId: nextSession.id },
})
yield* notify(nextSession.id)
return backgroundResult()
}
if (result?.info.status === "error")
return yield* Effect.fail(new Error(result.info.error ?? "Task failed"))
if (result?.info.status === "cancelled") return yield* Effect.fail(new Error("Task cancelled"))
return {
title: params.description,
metadata,
output: renderOutput({ sessionID: nextSession.id, state: "completed", text: result?.output ?? "" }),
output: renderOutput({ sessionID: nextSession.id, state: "completed", text: result?.info.output ?? "" }),
}
}),
(_, exit) =>
Effect.gen(function* () {
if (Exit.hasInterrupts(exit))
yield* Effect.all([cancel, background.cancel(nextSession.id)], { discard: true })
if (Exit.hasInterrupts(exit)) yield* Effect.all([cancel, jobs.cancel(nextSession.id)], { discard: true })
}).pipe(
Effect.ensuring(
Effect.sync(() => {