diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 2fc93c4825..d860c50484 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -128,7 +128,6 @@ export const layer = Layer.effect( cancel: (sessionID: SessionID) => cancel(sessionID), resolvePromptParts: (template: string) => resolvePromptParts(template), prompt: (input: PromptInput) => prompt(input).pipe(Effect.catch(Effect.die)), - loop: (input: LoopInput) => loop(input), } satisfies TaskPromptOps }) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index a09cbcbeb2..4f5d156d5c 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -2,17 +2,14 @@ import * as Tool from "./tool" import DESCRIPTION from "./task.txt" import { ToolJsonSchema } from "./json-schema" import { BackgroundJob } from "@/background/job" -import { Bus } from "@/bus" import { Session } from "@/session/session" import { SessionID, MessageID } from "../session/schema" import { MessageV2 } from "../session/message-v2" import { Agent } from "../agent/agent" import { deriveSubagentSessionPermission } from "../agent/subagent-permissions" import type { SessionPrompt } from "../session/prompt" -import { SessionStatus } from "@/session/status" import { Config } from "@/config/config" -import { TuiEvent } from "@/cli/cmd/tui/event" -import { Cause, Effect, Exit, Option, Schema, Scope } from "effect" +import { Cause, Effect, Exit, Schema, Scope } from "effect" import { EffectBridge } from "@/effect/bridge" import { RuntimeFlags } from "@/effect/runtime-flags" @@ -20,7 +17,6 @@ export interface TaskPromptOps { cancel(sessionID: SessionID): Effect.Effect resolvePromptParts(template: string): Effect.Effect prompt(input: SessionPrompt.PromptInput): Effect.Effect - loop(input: SessionPrompt.LoopInput): Effect.Effect } const id = "task" @@ -108,11 +104,9 @@ export const TaskTool = Tool.define( Effect.gen(function* () { const agent = yield* Agent.Service const background = yield* BackgroundJob.Service - const bus = yield* Bus.Service const config = yield* Config.Service const sessions = yield* Session.Service const scope = yield* Scope.Scope - const status = yield* SessionStatus.Service const flags = yield* RuntimeFlags.Service const run = Effect.fn("TaskTool.execute")(function* ( @@ -214,61 +208,29 @@ export const TaskTool = Tool.define( return result.parts.findLast((item) => item.type === "text")?.text ?? "" }) - const resumeWhenIdle: (input: { userID: MessageID; state: "completed" | "error" }) => Effect.Effect = - Effect.fn("TaskTool.resumeWhenIdle")(function* (input: { userID: MessageID; state: "completed" | "error" }) { - const latest = yield* sessions - .findMessage(ctx.sessionID, (item) => item.info.role === "user") - .pipe(Effect.orDie) - if (Option.isNone(latest)) return - if (latest.value.info.id !== input.userID) return - if ((yield* status.get(ctx.sessionID)).type !== "idle") { - yield* Effect.sleep("300 millis") - return yield* resumeWhenIdle(input) - } - yield* bus.publish(TuiEvent.ToastShow, { - title: input.state === "completed" ? "Background task complete" : "Background task failed", - message: - input.state === "completed" - ? `Background task "${params.description}" finished. Resuming the main thread.` - : `Background task "${params.description}" failed. Resuming the main thread.`, - variant: input.state === "completed" ? "success" : "error", - duration: 5000, - }) - yield* ops - .loop({ sessionID: ctx.sessionID }) - .pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true })) - }) - - const continueIfIdle = Effect.fn("TaskTool.continueIfIdle")(function* (input: { - userID: MessageID - state: "completed" | "error" - }) { - yield* resumeWhenIdle(input).pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true })) - }) - const inject = Effect.fn("TaskTool.injectBackgroundResult")(function* ( state: "completed" | "error", text: string, ) { const currentParent = yield* sessions.get(ctx.sessionID) - const message = yield* ops.prompt({ - sessionID: ctx.sessionID, - noReply: true, - agent: currentParent.agent ?? ctx.agent, - parts: [ - { - type: "text", - synthetic: true, - text: backgroundMessage({ - sessionID: nextSession.id, - description: params.description, - state, - text, - }), - }, - ], - }) - yield* continueIfIdle({ userID: message.info.id, state }) + yield* ops + .prompt({ + sessionID: ctx.sessionID, + agent: currentParent.agent ?? ctx.agent, + parts: [ + { + type: "text", + synthetic: true, + text: backgroundMessage({ + sessionID: nextSession.id, + description: params.description, + state, + text, + }), + }, + ], + }) + .pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true })) }) const existing = yield* background.get(nextSession.id) diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index 2b7d001572..08787e08aa 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -92,7 +92,6 @@ function stubOps(opts?: { onPrompt?: (input: SessionPrompt.PromptInput) => void; opts?.onPrompt?.(input) return reply(input, opts?.text ?? "done") }), - loop: (input) => Effect.succeed(reply({ sessionID: input.sessionID, parts: [] }, opts?.text ?? "done")), } } @@ -307,7 +306,6 @@ describe("tool.task", () => { ready.resolve(input) return cancelled.promise }).pipe(Effect.as(reply(input, "cancelled"))), - loop: (input) => Effect.succeed(reply({ sessionID: input.sessionID, parts: [] }, "done")), } const fiber = yield* def @@ -549,10 +547,9 @@ describe("tool.task", () => { }), ) - background.instance("background task completion does not wait for the parent resume loop", () => + background.instance("background task completion does not wait for the parent async prompt", () => Effect.gen(function* () { const jobs = yield* BackgroundJob.Service - const sessions = yield* Session.Service const { chat, assistant } = yield* seed() const tool = yield* TaskTool const def = yield* tool.init() @@ -573,27 +570,7 @@ describe("tool.task", () => { promptOps: { ...stubOps({ text: "background done" }), prompt: (input) => - input.noReply - ? Effect.gen(function* () { - const user = yield* sessions.updateMessage({ - id: input.messageID ?? MessageID.ascending(), - role: "user", - sessionID: input.sessionID, - agent: input.agent ?? "build", - model: input.model ?? ref, - time: { created: Date.now() }, - }) - const parts = input.parts.map((part) => ({ - ...part, - id: part.id ?? PartID.ascending(), - messageID: user.id, - sessionID: input.sessionID, - })) - yield* Effect.forEach(parts, (part) => sessions.updatePart(part), { discard: true }) - return { info: user, parts } - }) - : Effect.succeed(reply(input, "background done")), - loop: () => Effect.never, + input.sessionID === chat.id ? Effect.never : Effect.succeed(reply(input, "background done")), } satisfies TaskPromptOps, }, messages: [],