diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 6e87e7642d..73a7ebe48d 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -40,6 +40,7 @@ import type { ACPConfig } from "./types" import { Provider } from "../provider/provider" import { ModelID, ProviderID } from "../provider/schema" import { Agent as AgentModule } from "../agent/agent" +import { AppRuntime } from "@/effect/app-runtime" import { Installation } from "@/installation" import { MessageV2 } from "@/session/message-v2" import { Config } from "@/config/config" @@ -1166,7 +1167,7 @@ export namespace ACP { this.sessionManager.get(sessionId).modeId || (await (async () => { if (!availableModes.length) return undefined - const defaultAgentName = await AgentModule.defaultAgent() + const defaultAgentName = await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultAgent())) const resolvedModeId = availableModes.find((mode) => mode.name === defaultAgentName)?.id ?? availableModes[0].id this.sessionManager.setMode(sessionId, resolvedModeId) @@ -1367,7 +1368,8 @@ export namespace ACP { if (!current) { this.sessionManager.setModel(session.id, model) } - const agent = session.modeId ?? (await AgentModule.defaultAgent()) + const agent = + session.modeId ?? (await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultAgent()))) const parts: Array< | { type: "text"; text: string; synthetic?: boolean; ignored?: boolean } diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index fd9ac43e8b..cebcaa048f 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -21,7 +21,6 @@ import { Plugin } from "@/plugin" import { Skill } from "../skill" import { Effect, Context, Layer } from "effect" import { InstanceState } from "@/effect/instance-state" -import { makeRuntime } from "@/effect/run-service" export namespace Agent { export const Info = z @@ -404,22 +403,4 @@ export namespace Agent { Layer.provide(Config.defaultLayer), Layer.provide(Skill.defaultLayer), ) - - const { runPromise } = makeRuntime(Service, defaultLayer) - - export async function get(agent: string) { - return runPromise((svc) => svc.get(agent)) - } - - export async function list() { - return runPromise((svc) => svc.list()) - } - - export async function defaultAgent() { - return runPromise((svc) => svc.defaultAgent()) - } - - export async function generate(input: { description: string; model?: { providerID: ProviderID; modelID: ModelID } }) { - return runPromise((svc) => svc.generate(input)) - } } diff --git a/packages/opencode/src/cli/cmd/agent.ts b/packages/opencode/src/cli/cmd/agent.ts index 70082c8e2e..60f52e403b 100644 --- a/packages/opencode/src/cli/cmd/agent.ts +++ b/packages/opencode/src/cli/cmd/agent.ts @@ -1,5 +1,6 @@ import { cmd } from "./cmd" import * as prompts from "@clack/prompts" +import { AppRuntime } from "@/effect/app-runtime" import { UI } from "../ui" import { Global } from "../../global" import { Agent } from "../../agent/agent" @@ -110,7 +111,9 @@ const AgentCreateCommand = cmd({ const spinner = prompts.spinner() spinner.start("Generating agent configuration...") const model = args.model ? Provider.parseModel(args.model) : undefined - const generated = await Agent.generate({ description, model }).catch((error) => { + const generated = await AppRuntime.runPromise( + Agent.Service.use((svc) => svc.generate({ description, model })), + ).catch((error) => { spinner.stop(`LLM failed to generate agent: ${error.message}`, 1) if (isFullyNonInteractive) process.exit(1) throw new UI.CancelledError() @@ -220,7 +223,7 @@ const AgentListCommand = cmd({ await Instance.provide({ directory: process.cwd(), async fn() { - const agents = await Agent.list() + const agents = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.list())) const sortedAgents = agents.sort((a, b) => { if (a.native !== b.native) { return a.native ? -1 : 1 diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts index fbaf8d78dc..51de43f671 100644 --- a/packages/opencode/src/cli/cmd/debug/agent.ts +++ b/packages/opencode/src/cli/cmd/debug/agent.ts @@ -35,7 +35,7 @@ export const AgentCommand = cmd({ async handler(args) { await bootstrap(process.cwd(), async () => { const agentName = args.name as string - const agent = await Agent.get(agentName) + 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, diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts index 04130aa951..17fc4bc087 100644 --- a/packages/opencode/src/cli/cmd/run.ts +++ b/packages/opencode/src/cli/cmd/run.ts @@ -27,6 +27,7 @@ import { SkillTool } from "../../tool/skill" import { BashTool } from "../../tool/bash" import { TodoWriteTool } from "../../tool/todo" import { Locale } from "../../util/locale" +import { AppRuntime } from "@/effect/app-runtime" type ToolProps = { input: Tool.InferParameters @@ -573,6 +574,7 @@ export const RunCommand = cmd({ // Validate agent if specified const agent = await (async () => { if (!args.agent) return undefined + const name = args.agent // When attaching, validate against the running server instead of local Instance state. if (args.attach) { @@ -590,12 +592,12 @@ export const RunCommand = cmd({ return undefined } - const agent = modes.find((a) => a.name === args.agent) + const agent = modes.find((a) => a.name === name) if (!agent) { UI.println( UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_NORMAL, - `agent "${args.agent}" not found. Falling back to default agent`, + `agent "${name}" not found. Falling back to default agent`, ) return undefined } @@ -604,20 +606,20 @@ export const RunCommand = cmd({ UI.println( UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_NORMAL, - `agent "${args.agent}" is a subagent, not a primary agent. Falling back to default agent`, + `agent "${name}" is a subagent, not a primary agent. Falling back to default agent`, ) return undefined } - return args.agent + return name } - const entry = await Agent.get(args.agent) + const entry = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.get(name))) if (!entry) { UI.println( UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_NORMAL, - `agent "${args.agent}" not found. Falling back to default agent`, + `agent "${name}" not found. Falling back to default agent`, ) return undefined } @@ -625,11 +627,11 @@ export const RunCommand = cmd({ UI.println( UI.Style.TEXT_WARNING_BOLD + "!", UI.Style.TEXT_NORMAL, - `agent "${args.agent}" is a subagent, not a primary agent. Falling back to default agent`, + `agent "${name}" is a subagent, not a primary agent. Falling back to default agent`, ) return undefined } - return args.agent + return name })() const sessionID = await session(sdk) diff --git a/packages/opencode/src/server/instance/index.ts b/packages/opencode/src/server/instance/index.ts index 6d383afa7c..86a18dc673 100644 --- a/packages/opencode/src/server/instance/index.ts +++ b/packages/opencode/src/server/instance/index.ts @@ -207,7 +207,7 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => }, }), async (c) => { - const modes = await Agent.list() + const modes = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.list())) return c.json(modes) }, ) diff --git a/packages/opencode/src/server/instance/session.ts b/packages/opencode/src/server/instance/session.ts index b28db3a894..6f8feea8e6 100644 --- a/packages/opencode/src/server/instance/session.ts +++ b/packages/opencode/src/server/instance/session.ts @@ -550,11 +550,12 @@ export const SessionRoutes = lazy(() => const session = await Session.get(sessionID) await SessionRevert.cleanup(session) const msgs = await Session.messages({ sessionID }) - let currentAgent = await Agent.defaultAgent() + const defaultAgent = await AppRuntime.runPromise(Agent.Service.use((svc) => svc.defaultAgent())) + let currentAgent = defaultAgent for (let i = msgs.length - 1; i >= 0; i--) { const info = msgs[i].info if (info.role === "user") { - currentAgent = info.agent || (await Agent.defaultAgent()) + currentAgent = info.agent || defaultAgent break } } diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 3ed9e4b185..d6daa87f55 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -121,6 +121,7 @@ export namespace ToolRegistry { const greptool = yield* GrepTool const patchtool = yield* ApplyPatchTool const skilltool = yield* SkillTool + const agent = yield* Agent.Service const state = yield* InstanceState.make( Effect.fn("ToolRegistry.state")(function* (ctx) { @@ -140,8 +141,8 @@ export namespace ToolRegistry { worktree: ctx.worktree, } const result = yield* Effect.promise(() => def.execute(args as any, pluginCtx)) - const agent = yield* Effect.promise(() => Agent.get(toolCtx.agent)) - const out = yield* truncate.output(result, {}, agent) + const info = yield* agent.get(toolCtx.agent) + const out = yield* truncate.output(result, {}, info) return { title: "", output: out.truncated ? out.content : result, diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index 98a0fd4c6e..409a0ed606 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -1,6 +1,7 @@ import { afterEach, test, expect } from "bun:test" +import { Effect } from "effect" import path from "path" -import { tmpdir } from "../fixture/fixture" +import { provideInstance, tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { Agent } from "../../src/agent/agent" import { Permission } from "../../src/permission" @@ -11,6 +12,10 @@ function evalPerm(agent: Agent.Info | undefined, permission: string): Permission return Permission.evaluate(permission, "*", agent.permission).action } +function load(dir: string, fn: (svc: Agent.Interface) => Effect.Effect) { + return Effect.runPromise(provideInstance(dir)(Agent.Service.use(fn)).pipe(Effect.provide(Agent.defaultLayer))) +} + afterEach(async () => { await Instance.disposeAll() }) @@ -20,7 +25,7 @@ test("returns default native agents when no config", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const agents = await Agent.list() + const agents = await load(tmp.path, (svc) => svc.list()) const names = agents.map((a) => a.name) expect(names).toContain("build") expect(names).toContain("plan") @@ -38,7 +43,7 @@ test("build agent has correct default properties", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(build).toBeDefined() expect(build?.mode).toBe("primary") expect(build?.native).toBe(true) @@ -53,7 +58,7 @@ test("plan agent denies edits except .opencode/plans/*", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const plan = await Agent.get("plan") + const plan = await load(tmp.path, (svc) => svc.get("plan")) expect(plan).toBeDefined() // Wildcard is denied expect(evalPerm(plan, "edit")).toBe("deny") @@ -68,7 +73,7 @@ test("explore agent denies edit and write", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const explore = await Agent.get("explore") + const explore = await load(tmp.path, (svc) => svc.get("explore")) expect(explore).toBeDefined() expect(explore?.mode).toBe("subagent") expect(evalPerm(explore, "edit")).toBe("deny") @@ -84,7 +89,7 @@ test("explore agent asks for external directories and allows Truncate.GLOB", asy await Instance.provide({ directory: tmp.path, fn: async () => { - const explore = await Agent.get("explore") + const explore = await load(tmp.path, (svc) => svc.get("explore")) expect(explore).toBeDefined() expect(Permission.evaluate("external_directory", "/some/other/path", explore!.permission).action).toBe("ask") expect(Permission.evaluate("external_directory", Truncate.GLOB, explore!.permission).action).toBe("allow") @@ -97,7 +102,7 @@ test("general agent denies todo tools", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const general = await Agent.get("general") + const general = await load(tmp.path, (svc) => svc.get("general")) expect(general).toBeDefined() expect(general?.mode).toBe("subagent") expect(general?.hidden).toBeUndefined() @@ -111,7 +116,7 @@ test("compaction agent denies all permissions", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const compaction = await Agent.get("compaction") + const compaction = await load(tmp.path, (svc) => svc.get("compaction")) expect(compaction).toBeDefined() expect(compaction?.hidden).toBe(true) expect(evalPerm(compaction, "bash")).toBe("deny") @@ -137,7 +142,7 @@ test("custom agent from config creates new agent", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const custom = await Agent.get("my_custom_agent") + const custom = await load(tmp.path, (svc) => svc.get("my_custom_agent")) expect(custom).toBeDefined() expect(String(custom?.model?.providerID)).toBe("openai") expect(String(custom?.model?.modelID)).toBe("gpt-4") @@ -166,7 +171,7 @@ test("custom agent config overrides native agent properties", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(build).toBeDefined() expect(String(build?.model?.providerID)).toBe("anthropic") expect(String(build?.model?.modelID)).toBe("claude-3") @@ -189,9 +194,9 @@ test("agent disable removes agent from list", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const explore = await Agent.get("explore") + const explore = await load(tmp.path, (svc) => svc.get("explore")) expect(explore).toBeUndefined() - const agents = await Agent.list() + const agents = await load(tmp.path, (svc) => svc.list()) const names = agents.map((a) => a.name) expect(names).not.toContain("explore") }, @@ -215,7 +220,7 @@ test("agent permission config merges with defaults", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(build).toBeDefined() // Specific pattern is denied expect(Permission.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny") @@ -236,7 +241,7 @@ test("global permission config applies to all agents", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(build).toBeDefined() expect(evalPerm(build, "bash")).toBe("deny") }, @@ -255,8 +260,8 @@ test("agent steps/maxSteps config sets steps property", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") - const plan = await Agent.get("plan") + const build = await load(tmp.path, (svc) => svc.get("build")) + const plan = await load(tmp.path, (svc) => svc.get("plan")) expect(build?.steps).toBe(50) expect(plan?.steps).toBe(100) }, @@ -274,7 +279,7 @@ test("agent mode can be overridden", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const explore = await Agent.get("explore") + const explore = await load(tmp.path, (svc) => svc.get("explore")) expect(explore?.mode).toBe("primary") }, }) @@ -291,7 +296,7 @@ test("agent name can be overridden", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(build?.name).toBe("Builder") }, }) @@ -308,7 +313,7 @@ test("agent prompt can be set from config", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(build?.prompt).toBe("Custom system prompt") }, }) @@ -328,7 +333,7 @@ test("unknown agent properties are placed into options", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(build?.options.random_property).toBe("hello") expect(build?.options.another_random).toBe(123) }, @@ -351,7 +356,7 @@ test("agent options merge correctly", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(build?.options.custom_option).toBe(true) expect(build?.options.another_option).toBe("value") }, @@ -376,8 +381,8 @@ test("multiple custom agents can be defined", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const agentA = await Agent.get("agent_a") - const agentB = await Agent.get("agent_b") + const agentA = await load(tmp.path, (svc) => svc.get("agent_a")) + const agentB = await load(tmp.path, (svc) => svc.get("agent_b")) expect(agentA?.description).toBe("Agent A") expect(agentA?.mode).toBe("subagent") expect(agentB?.description).toBe("Agent B") @@ -405,7 +410,7 @@ test("Agent.list keeps the default agent first and sorts the rest by name", asyn await Instance.provide({ directory: tmp.path, fn: async () => { - const names = (await Agent.list()).map((a) => a.name) + const names = (await load(tmp.path, (svc) => svc.list())).map((a) => a.name) expect(names[0]).toBe("plan") expect(names.slice(1)).toEqual(names.slice(1).toSorted((a, b) => a.localeCompare(b))) }, @@ -417,7 +422,7 @@ test("Agent.get returns undefined for non-existent agent", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const nonExistent = await Agent.get("does_not_exist") + const nonExistent = await load(tmp.path, (svc) => svc.get("does_not_exist")) expect(nonExistent).toBeUndefined() }, }) @@ -428,7 +433,7 @@ test("default permission includes doom_loop and external_directory as ask", asyn await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(evalPerm(build, "doom_loop")).toBe("ask") expect(evalPerm(build, "external_directory")).toBe("ask") }, @@ -440,7 +445,7 @@ test("webfetch is allowed by default", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(evalPerm(build, "webfetch")).toBe("allow") }, }) @@ -462,7 +467,7 @@ test("legacy tools config converts to permissions", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(evalPerm(build, "bash")).toBe("deny") expect(evalPerm(build, "read")).toBe("deny") }, @@ -484,7 +489,7 @@ test("legacy tools config maps write/edit/patch/multiedit to edit permission", a await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(evalPerm(build, "edit")).toBe("deny") }, }) @@ -502,7 +507,7 @@ test("Truncate.GLOB is allowed even when user denies external_directory globally await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("allow") expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny") expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny") @@ -526,7 +531,7 @@ test("Truncate.GLOB is allowed even when user denies external_directory per-agen await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("allow") expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny") expect(Permission.evaluate("external_directory", "/some/other/path", build!.permission).action).toBe("deny") @@ -549,7 +554,7 @@ test("explicit Truncate.GLOB deny is respected", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) expect(Permission.evaluate("external_directory", Truncate.GLOB, build!.permission).action).toBe("deny") expect(Permission.evaluate("external_directory", Truncate.DIR, build!.permission).action).toBe("deny") }, @@ -581,7 +586,7 @@ description: Permission skill. await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) const skillDir = path.join(tmp.path, ".opencode", "skill", "perm-skill") const target = path.join(skillDir, "reference", "notes.md") expect(Permission.evaluate("external_directory", target, build!.permission).action).toBe("allow") @@ -597,7 +602,7 @@ test("defaultAgent returns build when no default_agent config", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const agent = await Agent.defaultAgent() + const agent = await load(tmp.path, (svc) => svc.defaultAgent()) expect(agent).toBe("build") }, }) @@ -612,7 +617,7 @@ test("defaultAgent respects default_agent config set to plan", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const agent = await Agent.defaultAgent() + const agent = await load(tmp.path, (svc) => svc.defaultAgent()) expect(agent).toBe("plan") }, }) @@ -632,7 +637,7 @@ test("defaultAgent respects default_agent config set to custom agent with mode a await Instance.provide({ directory: tmp.path, fn: async () => { - const agent = await Agent.defaultAgent() + const agent = await load(tmp.path, (svc) => svc.defaultAgent()) expect(agent).toBe("my_custom") }, }) @@ -647,7 +652,7 @@ test("defaultAgent throws when default_agent points to subagent", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - await expect(Agent.defaultAgent()).rejects.toThrow('default agent "explore" is a subagent') + await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow('default agent "explore" is a subagent') }, }) }) @@ -661,7 +666,7 @@ test("defaultAgent throws when default_agent points to hidden agent", async () = await Instance.provide({ directory: tmp.path, fn: async () => { - await expect(Agent.defaultAgent()).rejects.toThrow('default agent "compaction" is hidden') + await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow('default agent "compaction" is hidden') }, }) }) @@ -675,7 +680,9 @@ test("defaultAgent throws when default_agent points to non-existent agent", asyn await Instance.provide({ directory: tmp.path, fn: async () => { - await expect(Agent.defaultAgent()).rejects.toThrow('default agent "does_not_exist" not found') + await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow( + 'default agent "does_not_exist" not found', + ) }, }) }) @@ -691,7 +698,7 @@ test("defaultAgent returns plan when build is disabled and default_agent not set await Instance.provide({ directory: tmp.path, fn: async () => { - const agent = await Agent.defaultAgent() + const agent = await load(tmp.path, (svc) => svc.defaultAgent()) // build is disabled, so it should return plan (next primary agent) expect(agent).toBe("plan") }, @@ -711,7 +718,7 @@ test("defaultAgent throws when all primary agents are disabled", async () => { directory: tmp.path, fn: async () => { // build and plan are disabled, no primary-capable agents remain - await expect(Agent.defaultAgent()).rejects.toThrow("no primary visible agent found") + await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow("no primary visible agent found") }, }) }) diff --git a/packages/opencode/test/config/agent-color.test.ts b/packages/opencode/test/config/agent-color.test.ts index 0dc2653c2d..af9565cba8 100644 --- a/packages/opencode/test/config/agent-color.test.ts +++ b/packages/opencode/test/config/agent-color.test.ts @@ -1,6 +1,7 @@ import { test, expect } from "bun:test" +import { Effect } from "effect" import path from "path" -import { tmpdir } from "../fixture/fixture" +import { provideInstance, tmpdir } from "../fixture/fixture" import { Instance } from "../../src/project/instance" import { Config } from "../../src/config/config" import { Agent as AgentSvc } from "../../src/agent/agent" @@ -8,6 +9,8 @@ import { Color } from "../../src/util/color" import { AppRuntime } from "../../src/effect/app-runtime" const load = () => AppRuntime.runPromise(Config.Service.use((svc) => svc.get())) +const agent = (dir: string, fn: (svc: AgentSvc.Interface) => Effect.Effect) => + Effect.runPromise(provideInstance(dir)(AgentSvc.Service.use(fn)).pipe(Effect.provide(AgentSvc.defaultLayer))) test("agent color parsed from project config", async () => { await using tmp = await tmpdir({ @@ -52,9 +55,9 @@ test("Agent.get includes color from config", async () => { await Instance.provide({ directory: tmp.path, fn: async () => { - const plan = await AgentSvc.get("plan") + const plan = await agent(tmp.path, (svc) => svc.get("plan")) expect(plan?.color).toBe("#A855F7") - const build = await AgentSvc.get("build") + const build = await agent(tmp.path, (svc) => svc.get("build")) expect(build?.color).toBe("accent") }, }) diff --git a/packages/opencode/test/session/system.test.ts b/packages/opencode/test/session/system.test.ts index 6f1047a97d..33123acce6 100644 --- a/packages/opencode/test/session/system.test.ts +++ b/packages/opencode/test/session/system.test.ts @@ -4,7 +4,11 @@ import { Effect } from "effect" import { Agent } from "../../src/agent/agent" import { Instance } from "../../src/project/instance" import { SystemPrompt } from "../../src/session/system" -import { tmpdir } from "../fixture/fixture" +import { provideInstance, tmpdir } from "../fixture/fixture" + +function load(dir: string, fn: (svc: Agent.Interface) => Effect.Effect) { + return Effect.runPromise(provideInstance(dir)(Agent.Service.use(fn)).pipe(Effect.provide(Agent.defaultLayer))) +} describe("session.system", () => { test("skills output is sorted by name and stable across calls", async () => { @@ -38,7 +42,7 @@ description: ${description} await Instance.provide({ directory: tmp.path, fn: async () => { - const build = await Agent.get("build") + const build = await load(tmp.path, (svc) => svc.get("build")) const runSkills = Effect.gen(function* () { const svc = yield* SystemPrompt.Service return yield* svc.skills(build!)