1
0
Эх сурвалжийг харах

fix(opencode): keep concurrent task resumes foreground

Aiden Cline 3 долоо хоног өмнө
parent
commit
e3d6b7c93f

+ 8 - 6
packages/opencode/src/tool/task.ts

@@ -253,7 +253,9 @@ export const TaskTool = Tool.define(
         )
       })
 
-      if (yield* background.extend({ id: nextSession.id, run: runTask() })) {
+      const extended = yield* background.extend({ id: nextSession.id, run: runTask() })
+      if (extended && runInBackground) {
+        yield* background.promote(nextSession.id)
         return {
           title: params.description,
           metadata: {
@@ -320,11 +322,11 @@ 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()
+            const wait = background.wait({ id: nextSession.id }).pipe(Effect.map((waited) => waited.info))
+            const result = yield* (extended
+              ? wait
+              : Effect.raceFirst(wait, background.waitForPromotion(nextSession.id)))
+            if (!extended && 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"))
             return {

+ 64 - 0
packages/opencode/test/tool/task.test.ts

@@ -255,6 +255,69 @@ describe("tool.task", () => {
     }),
   )
 
+  it.instance("concurrent foreground resumes remain foreground", () =>
+    Effect.gen(function* () {
+      const sessions = yield* Session.Service
+      const { chat, assistant } = yield* seed()
+      const child = yield* sessions.create({ parentID: chat.id, title: "Existing child" })
+      const tool = yield* TaskTool
+      const def = yield* tool.init()
+      const firstStarted = yield* Deferred.make<void>()
+      const firstDone = yield* Deferred.make<void>()
+      const secondStarted = yield* Deferred.make<void>()
+      const secondDone = yield* Deferred.make<void>()
+      let prompts = 0
+      const promptOps: TaskPromptOps = {
+        ...stubOps(),
+        prompt: (input) => {
+          prompts++
+          if (prompts === 1)
+            return Deferred.succeed(firstStarted, undefined).pipe(
+              Effect.andThen(Deferred.await(firstDone)),
+              Effect.as(reply(input, "first done")),
+            )
+          return Deferred.succeed(secondStarted, undefined).pipe(
+            Effect.andThen(Deferred.await(secondDone)),
+            Effect.as(reply(input, "second done")),
+          )
+        },
+      }
+      const context = {
+        sessionID: chat.id,
+        messageID: assistant.id,
+        agent: "build",
+        abort: new AbortController().signal,
+        extra: { promptOps },
+        messages: [],
+        metadata: () => Effect.void,
+        ask: () => Effect.void,
+      }
+      const execute = (prompt: string) =>
+        def.execute(
+          {
+            description: prompt,
+            prompt,
+            subagent_type: "general",
+            task_id: child.id,
+          },
+          context,
+        )
+
+      const first = yield* execute("first follow-up").pipe(Effect.forkChild)
+      yield* Deferred.await(firstStarted)
+      const second = yield* execute("second follow-up").pipe(Effect.forkChild)
+
+      yield* Deferred.succeed(firstDone, undefined)
+      yield* Deferred.await(secondStarted)
+      yield* Deferred.succeed(secondDone, undefined)
+
+      const results = yield* Effect.all([Fiber.join(first), Fiber.join(second)])
+      expect(prompts).toBe(2)
+      expect(results.every((result) => result.metadata.background !== true)).toBe(true)
+      expect(results.every((result) => result.output.includes('state="completed"'))).toBe(true)
+    }),
+  )
+
   it.instance("execute asks by default and skips checks when bypassed", () =>
     Effect.gen(function* () {
       const { chat, assistant } = yield* seed()
@@ -721,6 +784,7 @@ describe("tool.task", () => {
           prompt: "also inspect cancellation",
           subagent_type: "general",
           task_id: started.metadata.sessionId,
+          background: true,
         },
         context,
       )