Skip to content

Commit bbbe611

Browse files
committed
refactor(cli): convert debug agent command to effectCmd
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.
1 parent 43e2087 commit bbbe611

1 file changed

Lines changed: 89 additions & 100 deletions

File tree

  • packages/opencode/src/cli/cmd/debug

packages/opencode/src/cli/cmd/debug/agent.ts

Lines changed: 89 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import { Session } from "@/session/session"
77
import type { MessageV2 } from "../../../session/message-v2"
88
import { MessageID, PartID } from "../../../session/schema"
99
import { ToolRegistry } from "@/tool/registry"
10-
import { Instance } from "../../../project/instance"
1110
import { Permission } from "../../../permission"
1211
import { iife } from "../../../util/iife"
13-
import { bootstrap } from "../../bootstrap"
14-
import { cmd } from "../cmd"
15-
import { AppRuntime } from "@/effect/app-runtime"
12+
import { effectCmd, fail } from "../../effect-cmd"
13+
import { InstanceRef } from "@/effect/instance-ref"
14+
import { InstanceStore } from "@/project/instance-store"
15+
import type { InstanceContext } from "@/project/instance"
1616

17-
export const AgentCommand = cmd({
17+
export const AgentCommand = effectCmd({
1818
command: "agent <name>",
1919
describe: "show agent configuration details",
2020
builder: (yargs) =>
@@ -32,60 +32,61 @@ export const AgentCommand = cmd({
3232
type: "string",
3333
description: "Tool params as JSON or a JS object literal",
3434
}),
35-
async handler(args) {
36-
await bootstrap(process.cwd(), async () => {
37-
const agentName = args.name as string
38-
const agent = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(agentName)))
39-
if (!agent) {
40-
process.stderr.write(
41-
`Agent ${agentName} not found, run '${basename(process.execPath)} agent list' to get an agent list` + EOL,
42-
)
43-
process.exit(1)
44-
}
45-
const availableTools = await getAvailableTools(agent)
46-
const resolvedTools = await resolveTools(agent, availableTools)
47-
const toolID = args.tool as string | undefined
48-
if (toolID) {
49-
const tool = availableTools.find((item) => item.id === toolID)
50-
if (!tool) {
51-
process.stderr.write(`Tool ${toolID} not found for agent ${agentName}` + EOL)
52-
process.exit(1)
53-
}
54-
if (resolvedTools[toolID] === false) {
55-
process.stderr.write(`Tool ${toolID} is disabled for agent ${agentName}` + EOL)
56-
process.exit(1)
57-
}
58-
const params = parseToolParams(args.params as string | undefined)
59-
const ctx = await createToolContext(agent)
60-
const result = await tool.execute(params, ctx)
61-
process.stdout.write(JSON.stringify({ tool: toolID, input: params, result }, null, 2) + EOL)
62-
return
63-
}
35+
handler: Effect.fn("Cli.debug.agent")(function* (args) {
36+
const ctx = yield* InstanceRef
37+
if (!ctx) return
38+
const store = yield* InstanceStore.Service
39+
return yield* run(args, ctx).pipe(Effect.ensuring(store.dispose(ctx)))
40+
}),
41+
})
6442

65-
const output = {
66-
...agent,
67-
tools: resolvedTools,
68-
}
69-
process.stdout.write(JSON.stringify(output, null, 2) + EOL)
70-
})
71-
},
43+
const run = Effect.fn("Cli.debug.agent.body")(function* (
44+
args: { name: string; tool?: string; params?: string },
45+
ctx: InstanceContext,
46+
) {
47+
const agentName = args.name
48+
const agent = yield* Agent.Service.use((svc) => svc.get(agentName))
49+
if (!agent) {
50+
process.stderr.write(
51+
`Agent ${agentName} not found, run '${basename(process.execPath)} agent list' to get an agent list` + EOL,
52+
)
53+
return yield* fail("", 1)
54+
}
55+
const availableTools = yield* getAvailableTools(agent)
56+
const resolvedTools = resolveTools(agent, availableTools)
57+
const toolID = args.tool
58+
if (toolID) {
59+
const tool = availableTools.find((item) => item.id === toolID)
60+
if (!tool) {
61+
process.stderr.write(`Tool ${toolID} not found for agent ${agentName}` + EOL)
62+
return yield* fail("", 1)
63+
}
64+
if (resolvedTools[toolID] === false) {
65+
process.stderr.write(`Tool ${toolID} is disabled for agent ${agentName}` + EOL)
66+
return yield* fail("", 1)
67+
}
68+
const params = parseToolParams(args.params)
69+
const toolCtx = yield* createToolContext(agent, ctx)
70+
const result = yield* tool.execute(params, toolCtx)
71+
process.stdout.write(JSON.stringify({ tool: toolID, input: params, result }, null, 2) + EOL)
72+
return
73+
}
74+
75+
const output = {
76+
...agent,
77+
tools: resolvedTools,
78+
}
79+
process.stdout.write(JSON.stringify(output, null, 2) + EOL)
7280
})
7381

74-
async function getAvailableTools(agent: Agent.Info) {
75-
return AppRuntime.runPromise(
76-
Effect.gen(function* () {
77-
const provider = yield* Provider.Service
78-
const registry = yield* ToolRegistry.Service
79-
const model = agent.model ?? (yield* provider.defaultModel())
80-
return yield* registry.tools({
81-
...model,
82-
agent,
83-
})
84-
}),
85-
)
86-
}
82+
const getAvailableTools = Effect.fn("Cli.debug.agent.getAvailableTools")(function* (agent: Agent.Info) {
83+
const provider = yield* Provider.Service
84+
const registry = yield* ToolRegistry.Service
85+
const model = agent.model ?? (yield* provider.defaultModel())
86+
return yield* registry.tools({ ...model, agent })
87+
})
8788

88-
async function resolveTools(agent: Agent.Info, availableTools: Awaited<ReturnType<typeof getAvailableTools>>) {
89+
function resolveTools(agent: Agent.Info, availableTools: { id: string }[]) {
8990
const disabled = Permission.disabled(
9091
availableTools.map((tool) => tool.id),
9192
agent.permission,
@@ -123,50 +124,38 @@ function parseToolParams(input?: string) {
123124
return parsed as Record<string, unknown>
124125
}
125126

126-
async function createToolContext(agent: Agent.Info) {
127-
const { session, messageID } = await AppRuntime.runPromise(
128-
Effect.gen(function* () {
129-
const session = yield* Session.Service
130-
const result = yield* session.create({ title: `Debug tool run (${agent.name})` })
131-
const messageID = MessageID.ascending()
132-
const model = agent.model
133-
? agent.model
134-
: yield* Effect.gen(function* () {
135-
const provider = yield* Provider.Service
136-
return yield* provider.defaultModel()
137-
})
138-
const now = Date.now()
139-
const message: MessageV2.Assistant = {
140-
id: messageID,
141-
sessionID: result.id,
142-
role: "assistant",
143-
time: {
144-
created: now,
145-
},
146-
parentID: messageID,
147-
modelID: model.modelID,
148-
providerID: model.providerID,
149-
mode: "debug",
150-
agent: agent.name,
151-
path: {
152-
cwd: Instance.directory,
153-
root: Instance.worktree,
154-
},
155-
cost: 0,
156-
tokens: {
157-
input: 0,
158-
output: 0,
159-
reasoning: 0,
160-
cache: {
161-
read: 0,
162-
write: 0,
163-
},
164-
},
165-
}
166-
yield* session.updateMessage(message)
167-
return { session: result, messageID }
168-
}),
169-
)
127+
const createToolContext = Effect.fn("Cli.debug.agent.createToolContext")(function* (
128+
agent: Agent.Info,
129+
ctx: InstanceContext,
130+
) {
131+
const sessionSvc = yield* Session.Service
132+
const session = yield* sessionSvc.create({ title: `Debug tool run (${agent.name})` })
133+
const messageID = MessageID.ascending()
134+
const model = agent.model
135+
? agent.model
136+
: yield* Effect.gen(function* () {
137+
const provider = yield* Provider.Service
138+
return yield* provider.defaultModel()
139+
})
140+
const now = Date.now()
141+
const message: MessageV2.Assistant = {
142+
id: messageID,
143+
sessionID: session.id,
144+
role: "assistant",
145+
time: { created: now },
146+
parentID: messageID,
147+
modelID: model.modelID,
148+
providerID: model.providerID,
149+
mode: "debug",
150+
agent: agent.name,
151+
path: {
152+
cwd: ctx.directory,
153+
root: ctx.worktree,
154+
},
155+
cost: 0,
156+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
157+
}
158+
yield* sessionSvc.updateMessage(message)
170159

171160
const ruleset = Permission.merge(agent.permission, session.permission ?? [])
172161

@@ -189,4 +178,4 @@ async function createToolContext(agent: Agent.Info) {
189178
})
190179
},
191180
}
192-
}
181+
})

0 commit comments

Comments
 (0)