From bbbe611d5ba049f4da388fe16d770cdf904acaa4 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Sat, 2 May 2026 17:19:02 -0400 Subject: [PATCH] refactor(cli): convert debug agent command to effectCmd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops bootstrap() + 3 AppRuntime.runPromise wrappers. Helpers (getAvailableTools, createToolContext) now return Effects yielded directly. Instance.directory/.worktree → ctx.directory/.worktree from InstanceRef. process.exit(1) → fail("", 1) for the three error paths (stderr message printed inline, exit code 1). Bonus fix: --tool execution path was awaiting tool.execute() which returns an Effect (not a Promise), so result was the Effect object itself — JSON.stringify produced garbage. Now properly yields the Effect to get the ExecuteResult. --- packages/opencode/src/cli/cmd/debug/agent.ts | 189 +++++++++---------- 1 file changed, 89 insertions(+), 100 deletions(-) diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts index cff9a7f9cc6a..831ca08b698e 100644 --- a/packages/opencode/src/cli/cmd/debug/agent.ts +++ b/packages/opencode/src/cli/cmd/debug/agent.ts @@ -7,14 +7,14 @@ import { Session } from "@/session/session" import type { MessageV2 } from "../../../session/message-v2" import { MessageID, PartID } from "../../../session/schema" import { ToolRegistry } from "@/tool/registry" -import { Instance } from "../../../project/instance" import { Permission } from "../../../permission" import { iife } from "../../../util/iife" -import { bootstrap } from "../../bootstrap" -import { cmd } from "../cmd" -import { AppRuntime } from "@/effect/app-runtime" +import { effectCmd, fail } from "../../effect-cmd" +import { InstanceRef } from "@/effect/instance-ref" +import { InstanceStore } from "@/project/instance-store" +import type { InstanceContext } from "@/project/instance" -export const AgentCommand = cmd({ +export const AgentCommand = effectCmd({ command: "agent ", describe: "show agent configuration details", builder: (yargs) => @@ -32,60 +32,61 @@ export const AgentCommand = cmd({ type: "string", description: "Tool params as JSON or a JS object literal", }), - async handler(args) { - await bootstrap(process.cwd(), async () => { - const agentName = args.name as string - const agent = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(agentName))) - if (!agent) { - process.stderr.write( - `Agent ${agentName} not found, run '${basename(process.execPath)} agent list' to get an agent list` + EOL, - ) - process.exit(1) - } - const availableTools = await getAvailableTools(agent) - const resolvedTools = await resolveTools(agent, availableTools) - const toolID = args.tool as string | undefined - if (toolID) { - const tool = availableTools.find((item) => item.id === toolID) - if (!tool) { - process.stderr.write(`Tool ${toolID} not found for agent ${agentName}` + EOL) - process.exit(1) - } - if (resolvedTools[toolID] === false) { - process.stderr.write(`Tool ${toolID} is disabled for agent ${agentName}` + EOL) - process.exit(1) - } - const params = parseToolParams(args.params as string | undefined) - const ctx = await createToolContext(agent) - const result = await tool.execute(params, ctx) - process.stdout.write(JSON.stringify({ tool: toolID, input: params, result }, null, 2) + EOL) - return - } + handler: Effect.fn("Cli.debug.agent")(function* (args) { + const ctx = yield* InstanceRef + if (!ctx) return + const store = yield* InstanceStore.Service + return yield* run(args, ctx).pipe(Effect.ensuring(store.dispose(ctx))) + }), +}) - const output = { - ...agent, - tools: resolvedTools, - } - process.stdout.write(JSON.stringify(output, null, 2) + EOL) - }) - }, +const run = Effect.fn("Cli.debug.agent.body")(function* ( + args: { name: string; tool?: string; params?: string }, + ctx: InstanceContext, +) { + const agentName = args.name + const agent = yield* Agent.Service.use((svc) => svc.get(agentName)) + if (!agent) { + process.stderr.write( + `Agent ${agentName} not found, run '${basename(process.execPath)} agent list' to get an agent list` + EOL, + ) + return yield* fail("", 1) + } + const availableTools = yield* getAvailableTools(agent) + const resolvedTools = resolveTools(agent, availableTools) + const toolID = args.tool + if (toolID) { + const tool = availableTools.find((item) => item.id === toolID) + if (!tool) { + process.stderr.write(`Tool ${toolID} not found for agent ${agentName}` + EOL) + return yield* fail("", 1) + } + if (resolvedTools[toolID] === false) { + process.stderr.write(`Tool ${toolID} is disabled for agent ${agentName}` + EOL) + return yield* fail("", 1) + } + const params = parseToolParams(args.params) + const toolCtx = yield* createToolContext(agent, ctx) + const result = yield* tool.execute(params, toolCtx) + process.stdout.write(JSON.stringify({ tool: toolID, input: params, result }, null, 2) + EOL) + return + } + + const output = { + ...agent, + tools: resolvedTools, + } + process.stdout.write(JSON.stringify(output, null, 2) + EOL) }) -async function getAvailableTools(agent: Agent.Info) { - return AppRuntime.runPromise( - Effect.gen(function* () { - const provider = yield* Provider.Service - const registry = yield* ToolRegistry.Service - const model = agent.model ?? (yield* provider.defaultModel()) - return yield* registry.tools({ - ...model, - agent, - }) - }), - ) -} +const getAvailableTools = Effect.fn("Cli.debug.agent.getAvailableTools")(function* (agent: Agent.Info) { + const provider = yield* Provider.Service + const registry = yield* ToolRegistry.Service + const model = agent.model ?? (yield* provider.defaultModel()) + return yield* registry.tools({ ...model, agent }) +}) -async function resolveTools(agent: Agent.Info, availableTools: Awaited>) { +function resolveTools(agent: Agent.Info, availableTools: { id: string }[]) { const disabled = Permission.disabled( availableTools.map((tool) => tool.id), agent.permission, @@ -123,50 +124,38 @@ function parseToolParams(input?: string) { return parsed as Record } -async function createToolContext(agent: Agent.Info) { - const { session, messageID } = await AppRuntime.runPromise( - Effect.gen(function* () { - const session = yield* Session.Service - const result = yield* session.create({ title: `Debug tool run (${agent.name})` }) - const messageID = MessageID.ascending() - const model = agent.model - ? agent.model - : yield* Effect.gen(function* () { - const provider = yield* Provider.Service - return yield* provider.defaultModel() - }) - const now = Date.now() - const message: MessageV2.Assistant = { - id: messageID, - sessionID: result.id, - role: "assistant", - time: { - created: now, - }, - parentID: messageID, - modelID: model.modelID, - providerID: model.providerID, - mode: "debug", - agent: agent.name, - path: { - cwd: Instance.directory, - root: Instance.worktree, - }, - cost: 0, - tokens: { - input: 0, - output: 0, - reasoning: 0, - cache: { - read: 0, - write: 0, - }, - }, - } - yield* session.updateMessage(message) - return { session: result, messageID } - }), - ) +const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(function* ( + agent: Agent.Info, + ctx: InstanceContext, +) { + const sessionSvc = yield* Session.Service + const session = yield* sessionSvc.create({ title: `Debug tool run (${agent.name})` }) + const messageID = MessageID.ascending() + const model = agent.model + ? agent.model + : yield* Effect.gen(function* () { + const provider = yield* Provider.Service + return yield* provider.defaultModel() + }) + const now = Date.now() + const message: MessageV2.Assistant = { + id: messageID, + sessionID: session.id, + role: "assistant", + time: { created: now }, + parentID: messageID, + modelID: model.modelID, + providerID: model.providerID, + mode: "debug", + agent: agent.name, + path: { + cwd: ctx.directory, + root: ctx.worktree, + }, + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + } + yield* sessionSvc.updateMessage(message) const ruleset = Permission.merge(agent.permission, session.permission ?? []) @@ -189,4 +178,4 @@ async function createToolContext(agent: Agent.Info) { }) }, } -} +})