From 756488d534f04ff20b7f6418ddbf8e845e794a41 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" Date: Thu, 14 May 2026 16:42:18 +0000 Subject: [PATCH] chore: generate --- packages/opencode/src/session/run-state.ts | 5 +- packages/opencode/src/tool/task.ts | 63 ++++++++++++-------- packages/opencode/src/tool/task_status.ts | 44 +++++++++----- packages/opencode/test/tool/registry.test.ts | 8 ++- packages/ui/src/components/message-part.tsx | 5 +- 5 files changed, 80 insertions(+), 45 deletions(-) diff --git a/packages/opencode/src/session/run-state.ts b/packages/opencode/src/session/run-state.ts index c998f6f669..8f0051dfba 100644 --- a/packages/opencode/src/session/run-state.ts +++ b/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, diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index e4a440d81a..f87f46207b 100644 --- a/packages/opencode/src/tool/task.ts +++ b/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 = 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 = + 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) { diff --git a/packages/opencode/src/tool/task_status.ts b/packages/opencode/src/tool/task_status.ts index 4d46018414..b458b4fc45 100644 --- a/packages/opencode/src/tool/task_status.ts +++ b/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 = Effect.fn("TaskStatusTool.inspect")(function* (taskID: SessionID) { + const inspect: (taskID: SessionID) => Effect.Effect = 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, @@ -161,7 +172,8 @@ export const TaskStatusTool = Tool.define( return { description: DESCRIPTION, parameters: Parameters, - execute: (params: Schema.Schema.Type, ctx: Tool.Context) => run(params, ctx).pipe(Effect.orDie), + execute: (params: Schema.Schema.Type, ctx: Tool.Context) => + run(params, ctx).pipe(Effect.orDie), } }), ) diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 1709644e60..3239855ca5 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/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 | undefined)?.background).toBeUndefined() diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index d347af6460..94da6cc669 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/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