From 0f3a2a7b6798fb01f5ec716170befcd570e24e83 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 8 Apr 2026 13:59:58 -0400 Subject: [PATCH] refactor(effect): simplify task tool registry bridge --- packages/opencode/src/session/prompt.ts | 5 +- packages/opencode/src/tool/registry.ts | 90 ++++++++++++------------- packages/opencode/src/tool/task.ts | 10 +-- packages/opencode/src/tool/tool.ts | 19 +++--- 4 files changed, 62 insertions(+), 62 deletions(-) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 079f51d600..c297339992 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -47,6 +47,7 @@ import { Process } from "@/util/process" import { Cause, Effect, Exit, Layer, Option, Scope, ServiceMap } from "effect" import { InstanceState } from "@/effect/instance-state" import { makeRuntime } from "@/effect/run-service" +import { TaskTool } from "@/tool/task" // @ts-ignore globalThis.AI_SDK_LOG_WARNINGS = false @@ -558,7 +559,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the }) { const { task, model, lastUser, sessionID, session, msgs } = input const ctx = yield* InstanceState.context - const taskTool = yield* registry.fromID("task") + const taskTool = yield* registry.fromID(TaskTool.id) const taskModel = task.model ? yield* getModel(task.model.providerID, task.model.modelID, sessionID) : model const assistantMessage: MessageV2.Assistant = yield* sessions.updateMessage({ id: MessageID.ascending(), @@ -581,7 +582,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the sessionID: assistantMessage.sessionID, type: "tool", callID: ulid(), - tool: "task", + tool: TaskTool.id, state: { status: "running", input: { diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 74a0e7378a..8a73163d5e 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -82,18 +82,10 @@ export namespace ToolRegistry { const config = yield* Config.Service const plugin = yield* Plugin.Service - const info = ( - tool: T | Effect.Effect, - ): Effect.Effect => (Effect.isEffect(tool) ? tool : Effect.succeed(tool)) - - const build = ( - tool: T | Effect.Effect, - ): Effect.Effect => info(tool).pipe(Effect.flatMap(Tool.init)) - - const task = yield* info(TaskTool) - const read = yield* info(ReadTool) - const askInfo = yield* info(QuestionTool) - const todoInfo = yield* info(TodoWriteTool) + const task = yield* TaskTool + const read = yield* ReadTool + const question = yield* QuestionTool + const todo = yield* TodoWriteTool const state = yield* InstanceState.make( Effect.fn("ToolRegistry.state")(function* (ctx) { @@ -147,47 +139,49 @@ export namespace ToolRegistry { } const cfg = yield* config.get() - const question = + const questionEnabled = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL - const invalid = yield* build(InvalidTool) - const bash = yield* build(BashTool) - const readDef = yield* build(read) - const glob = yield* build(GlobTool) - const grep = yield* build(GrepTool) - const edit = yield* build(EditTool) - const write = yield* build(WriteTool) - const taskDef = yield* build(task) - const fetch = yield* build(WebFetchTool) - const todo = yield* build(todoInfo) - const search = yield* build(WebSearchTool) - const code = yield* build(CodeSearchTool) - const skill = yield* build(SkillTool) - const patch = yield* build(ApplyPatchTool) - const ask = yield* build(askInfo) - const lsp = yield* build(LspTool) - const plan = yield* build(PlanExitTool) + const tool = yield* Effect.all({ + invalid: Tool.init(InvalidTool), + bash: Tool.init(BashTool), + read: Tool.init(read), + glob: Tool.init(GlobTool), + grep: Tool.init(GrepTool), + edit: Tool.init(EditTool), + write: Tool.init(WriteTool), + task: Tool.init(task), + fetch: Tool.init(WebFetchTool), + todo: Tool.init(todo), + search: Tool.init(WebSearchTool), + code: Tool.init(CodeSearchTool), + skill: Tool.init(SkillTool), + patch: Tool.init(ApplyPatchTool), + question: Tool.init(question), + lsp: Tool.init(LspTool), + plan: Tool.init(PlanExitTool), + }) return { custom, builtin: [ - invalid, - ...(question ? [ask] : []), - bash, - readDef, - glob, - grep, - edit, - write, - taskDef, - fetch, - todo, - search, - code, - skill, - patch, - ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [lsp] : []), - ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [plan] : []), + tool.invalid, + ...(questionEnabled ? [tool.question] : []), + tool.bash, + tool.read, + tool.glob, + tool.grep, + tool.edit, + tool.write, + tool.task, + tool.fetch, + tool.todo, + tool.search, + tool.code, + tool.skill, + tool.patch, + ...(Flag.OPENCODE_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []), + ...(Flag.OPENCODE_EXPERIMENTAL_PLAN_MODE && Flag.OPENCODE_CLIENT === "cli" ? [tool.plan] : []), ], } }), @@ -237,7 +231,7 @@ export namespace ToolRegistry { id: tool.id, description: [ output.description, - tool.id === "task" ? yield* TaskDescription(input.agent) : undefined, + tool.id === TaskTool.id ? yield* TaskDescription(input.agent) : undefined, tool.id === SkillTool.id ? yield* SkillDescription(input.agent) : undefined, ] .filter(Boolean) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 906c265a63..e9635e39ed 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -10,6 +10,8 @@ import { Config } from "../config/config" import { Permission } from "@/permission" import { Effect } from "effect" +const id = "task" + const parameters = z.object({ description: z.string().describe("A short (3-5 words) description of the task"), prompt: z.string().describe("The task for the agent to perform"), @@ -24,7 +26,7 @@ const parameters = z.object({ }) export const TaskTool = Tool.defineEffect( - "task", + id, Effect.gen(function* () { const agent = yield* Agent.Service const config = yield* Config.Service @@ -35,7 +37,7 @@ export const TaskTool = Tool.defineEffect( if (!ctx.extra?.bypassAgentCheck) { yield* Effect.promise(() => ctx.ask({ - permission: "task", + permission: id, patterns: [params.subagent_type], always: ["*"], metadata: { @@ -51,7 +53,7 @@ export const TaskTool = Tool.defineEffect( return yield* Effect.fail(new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`)) } - const canTask = next.permission.some((rule) => rule.permission === "task") + const canTask = next.permission.some((rule) => rule.permission === id) const canTodo = next.permission.some((rule) => rule.permission === "todowrite") const taskID = params.task_id @@ -81,7 +83,7 @@ export const TaskTool = Tool.defineEffect( ? [] : [ { - permission: "task" as const, + permission: id, pattern: "*" as const, action: "deny" as const, }, diff --git a/packages/opencode/src/tool/tool.ts b/packages/opencode/src/tool/tool.ts index 6d129f4271..66e1b8e786 100644 --- a/packages/opencode/src/tool/tool.ts +++ b/packages/opencode/src/tool/tool.ts @@ -98,24 +98,27 @@ export namespace Tool { } } - export function define( - id: string, + export function define( + id: ID, init: (() => Promise>) | DefWithoutID, - ): Info { + ): Info & { id: ID } { return { id, init: wrap(id, init), } } - export function defineEffect( - id: string, + export function defineEffect( + id: ID, init: Effect.Effect<(() => Promise>) | DefWithoutID, never, R>, - ): Effect.Effect, never, R> { - return Effect.map(init, (next) => ({ id, init: wrap(id, next) })) + ): Effect.Effect, never, R> & { id: ID } { + return Object.assign( + Effect.map(init, (next) => ({ id, init: wrap(id, next) })), + { id }, + ) } - export function init(info: Info): Effect.Effect { + export function init(info: Info): Effect.Effect { return Effect.gen(function* () { const init = yield* Effect.promise(() => info.init()) return {