mirror of
https://fastgit.cc/https://github.com/anomalyco/opencode
synced 2026-04-22 05:42:35 +08:00
refactor(agent): remove async facade exports (#22341)
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<T> = {
|
||||
input: Tool.InferParameters<T>
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<State>(
|
||||
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,
|
||||
|
||||
@@ -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<A>(dir: string, fn: (svc: Agent.Interface) => Effect.Effect<A>) {
|
||||
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")
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 = <A>(dir: string, fn: (svc: AgentSvc.Interface) => Effect.Effect<A>) =>
|
||||
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")
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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<A>(dir: string, fn: (svc: Agent.Interface) => Effect.Effect<A>) {
|
||||
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!)
|
||||
|
||||
Reference in New Issue
Block a user