Ver Fonte

chore: generate

opencode-agent[bot] há 3 meses atrás
pai
commit
756488d534

+ 4 - 1
packages/opencode/src/session/run-state.ts

@@ -107,7 +107,10 @@ export const layer = Layer.effect(
   }),
 )
 
-export const defaultLayer = layer.pipe(Layer.provide(BackgroundJob.defaultLayer), Layer.provide(SessionStatus.defaultLayer))
+export const defaultLayer = layer.pipe(
+  Layer.provide(BackgroundJob.defaultLayer),
+  Layer.provide(SessionStatus.defaultLayer),
+)
 
 const cancelBackgroundJobs = Effect.fn("SessionRunState.cancelBackgroundJobs")(function* (
   background: BackgroundJob.Interface,

+ 39 - 24
packages/opencode/src/tool/task.ts

@@ -71,7 +71,12 @@ function backgroundOutput(sessionID: SessionID) {
   ].join("\n")
 }
 
-function backgroundMessage(input: { sessionID: SessionID; description: string; state: "completed" | "error"; text: string }) {
+function backgroundMessage(input: {
+  sessionID: SessionID
+  description: string
+  state: "completed" | "error"
+  text: string
+}) {
   const tag = input.state === "completed" ? "task_result" : "task_error"
   const title =
     input.state === "completed"
@@ -106,7 +111,9 @@ export const TaskTool = Tool.define(
       const cfg = yield* config.get()
       const runInBackground = params.background === true
       if (runInBackground && !flags.experimentalBackgroundSubagents) {
-        return yield* Effect.fail(new Error("Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true"))
+        return yield* Effect.fail(
+          new Error("Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true"),
+        )
       }
 
       if (!ctx.extra?.bypassAgentCheck) {
@@ -196,27 +203,30 @@ export const TaskTool = Tool.define(
         return result.parts.findLast((item) => item.type === "text")?.text ?? ""
       })
 
-      const resumeWhenIdle: (input: { userID: MessageID; state: "completed" | "error" }) => Effect.Effect<void> = 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,
+      const resumeWhenIdle: (input: { userID: MessageID; state: "completed" | "error" }) => Effect.Effect<void> =
+        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 }))
         })
-        yield* ops.loop({ sessionID: ctx.sessionID }).pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true }))
-      })
 
       const continueIfIdle = Effect.fn("TaskTool.continueIfIdle")(function* (input: {
         userID: MessageID
@@ -225,7 +235,10 @@ export const TaskTool = Tool.define(
         yield* resumeWhenIdle(input).pipe(Effect.ignore, Effect.forkIn(scope, { startImmediately: true }))
       })
 
-      const inject = Effect.fn("TaskTool.injectBackgroundResult")(function* (state: "completed" | "error", text: string) {
+      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,
@@ -249,7 +262,9 @@ export const TaskTool = Tool.define(
 
       const existing = yield* background.get(nextSession.id)
       if (existing?.status === "running") {
-        return yield* Effect.fail(new Error(`Task ${nextSession.id} is already running. Use task_status to check progress.`))
+        return yield* Effect.fail(
+          new Error(`Task ${nextSession.id} is already running. Use task_status to check progress.`),
+        )
       }
 
       if (runInBackground) {

+ 28 - 16
packages/opencode/src/tool/task_status.ts

@@ -14,7 +14,9 @@ const POLL_MS = 300
 
 const Parameters = Schema.Struct({
   task_id: SessionID.annotate({ description: "The task_id returned by the task tool" }),
-  wait: Schema.optional(Schema.Boolean).annotate({ description: "When true, wait until the task reaches a terminal state or timeout" }),
+  wait: Schema.optional(Schema.Boolean).annotate({
+    description: "When true, wait until the task reaches a terminal state or timeout",
+  }),
   timeout_ms: Schema.optional(PositiveInt).annotate({
     description: "Maximum milliseconds to wait when wait=true (default: 60000)",
   }),
@@ -39,7 +41,8 @@ function inspectMessage(message: MessageV2.WithParts): InspectResult | undefined
   if (message.info.role !== "assistant") return
   const text = message.parts.findLast((part) => part.type === "text")?.text ?? ""
   if (message.info.error) return { state: "error", text: text || errorText(message.info.error) }
-  if (message.info.finish && !["tool-calls", "unknown"].includes(message.info.finish)) return { state: "completed", text }
+  if (message.info.finish && !["tool-calls", "unknown"].includes(message.info.finish))
+    return { state: "completed", text }
   return { state: "running", text: text || "Task is still running." }
 }
 
@@ -51,7 +54,9 @@ export const TaskStatusTool = Tool.define(
     const status = yield* SessionStatus.Service
     const flags = yield* RuntimeFlags.Service
 
-    const inspect: (taskID: SessionID) => Effect.Effect<InspectResult> = Effect.fn("TaskStatusTool.inspect")(function* (taskID: SessionID) {
+    const inspect: (taskID: SessionID) => Effect.Effect<InspectResult> = Effect.fn("TaskStatusTool.inspect")(function* (
+      taskID: SessionID,
+    ) {
       const job = yield* jobs.get(taskID)
       if (job) {
         return {
@@ -75,26 +80,32 @@ export const TaskStatusTool = Tool.define(
         }
       }
 
-      const latestAssistant = yield* sessions.findMessage(taskID, (item) => item.info.role === "assistant").pipe(Effect.orDie)
+      const latestAssistant = yield* sessions
+        .findMessage(taskID, (item) => item.info.role === "assistant")
+        .pipe(Effect.orDie)
       if (Option.isSome(latestAssistant)) {
         const latest = inspectMessage(latestAssistant.value)
         if (!latest) return { state: "error", text: "Task is not running in this process." }
-        if (latest.state === "running") return { state: "error", text: "Task is not running in this process and has no final output." }
+        if (latest.state === "running")
+          return { state: "error", text: "Task is not running in this process and has no final output." }
         return latest
       }
       return { state: "error", text: "Task is not running in this process and has not produced output." }
     })
 
-    const waitForTerminal: (taskID: SessionID, timeout: number) => Effect.Effect<{ result: InspectResult; timedOut: boolean }> = Effect.fn(
-      "TaskStatusTool.waitForTerminal",
-    )(function* (taskID: SessionID, timeout: number) {
-      const result = yield* inspect(taskID)
-      if (result.state !== "running") return { result, timedOut: false }
-      if (timeout <= 0) return { result, timedOut: true }
-      const sleep = Math.min(POLL_MS, timeout)
-      yield* Effect.sleep(`${sleep} millis`)
-      return yield* waitForTerminal(taskID, timeout - sleep)
-    })
+    const waitForTerminal: (
+      taskID: SessionID,
+      timeout: number,
+    ) => Effect.Effect<{ result: InspectResult; timedOut: boolean }> = Effect.fn("TaskStatusTool.waitForTerminal")(
+      function* (taskID: SessionID, timeout: number) {
+        const result = yield* inspect(taskID)
+        if (result.state !== "running") return { result, timedOut: false }
+        if (timeout <= 0) return { result, timedOut: true }
+        const sleep = Math.min(POLL_MS, timeout)
+        yield* Effect.sleep(`${sleep} millis`)
+        return yield* waitForTerminal(taskID, timeout - sleep)
+      },
+    )
 
     const run = Effect.fn("TaskStatusTool.execute")(function* (
       params: Schema.Schema.Type<typeof Parameters>,
@@ -161,7 +172,8 @@ export const TaskStatusTool = Tool.define(
     return {
       description: DESCRIPTION,
       parameters: Parameters,
-      execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie),
+      execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context) =>
+        run(params, ctx).pipe(Effect.orDie),
     }
   }),
 )

+ 5 - 3
packages/opencode/test/tool/registry.test.ts

@@ -111,9 +111,11 @@ describe("tool.registry", () => {
       const agent = yield* Agent.Service
       const build = yield* agent.get("build")
       if (!build) throw new Error("build agent not found")
-      const task = (
-        yield* registry.tools({ providerID: ProviderID.opencode, modelID: ModelID.make("test"), agent: build })
-      ).find((tool) => tool.id === "task")
+      const task = (yield* registry.tools({
+        providerID: ProviderID.opencode,
+        modelID: ModelID.make("test"),
+        agent: build,
+      })).find((tool) => tool.id === "task")
 
       expect(task?.jsonSchema).toBeDefined()
       expect((task?.jsonSchema?.properties as Record<string, unknown> | undefined)?.background).toBeUndefined()

+ 4 - 1
packages/ui/src/components/message-part.tsx

@@ -1738,7 +1738,10 @@ ToolRegistry.register({
     const title = createMemo(() => agent().name ?? i18n.t("ui.tool.agent.default"))
     const tone = createMemo(() => agent().color)
     const subtitle = createMemo(() => {
-      const value = typeof props.input.description === "string" && props.input.description ? props.input.description : childSessionId()
+      const value =
+        typeof props.input.description === "string" && props.input.description
+          ? props.input.description
+          : childSessionId()
       if (!value) return value
       if (props.metadata.background === true) return `${value} (background)`
       return value