From 9b915e70bd9b8a0c1abccd0ac47ebb9a56c28e12 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sat, 30 May 2026 18:57:42 -0400 Subject: [PATCH] v2: default agents --- packages/cli/package.json | 1 + packages/cli/src/debug/agents.ts | 13 ++++ packages/cli/src/debug/index.ts | 7 ++ packages/cli/src/index.ts | 39 ++++++++++- packages/core/src/plugin/agent.ts | 106 ++++++++++++++---------------- 5 files changed, 107 insertions(+), 59 deletions(-) create mode 100644 packages/cli/src/debug/agents.ts create mode 100644 packages/cli/src/debug/index.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 20eeed372..fff371269 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@effect/platform-node": "catalog:", + "@opencode-ai/core": "workspace:*", "effect": "catalog:" }, "devDependencies": { diff --git a/packages/cli/src/debug/agents.ts b/packages/cli/src/debug/agents.ts new file mode 100644 index 000000000..eb64f6046 --- /dev/null +++ b/packages/cli/src/debug/agents.ts @@ -0,0 +1,13 @@ +import { EOL } from "os" +import { AgentV2 } from "@opencode-ai/core/agent" +import { PluginBoot } from "@opencode-ai/core/plugin/boot" +import * as Effect from "effect/Effect" +import * as Command from "effect/unstable/cli/Command" + +export const AgentsCommand = Command.make("agents", {}, () => + Effect.gen(function* () { + yield* PluginBoot.Service.use((service) => service.wait()) + const agents = yield* AgentV2.Service.use((service) => service.all()) + process.stdout.write(JSON.stringify(agents.sort((a, b) => a.id.localeCompare(b.id)), null, 2) + EOL) + }), +).pipe(Command.withDescription("List all agents")) diff --git a/packages/cli/src/debug/index.ts b/packages/cli/src/debug/index.ts new file mode 100644 index 000000000..3e4e99022 --- /dev/null +++ b/packages/cli/src/debug/index.ts @@ -0,0 +1,7 @@ +import * as Command from "effect/unstable/cli/Command" +import { AgentsCommand } from "./agents" + +export const DebugCommand = Command.make("debug").pipe( + Command.withDescription("Debugging and troubleshooting tools"), + Command.withSubcommands([AgentsCommand]), +) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 51a4dfec8..1d16c33ad 100755 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -2,11 +2,48 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime" import * as NodeServices from "@effect/platform-node/NodeServices" +import { AccountV2 } from "@opencode-ai/core/account" +import { AgentV2 } from "@opencode-ai/core/agent" +import { Catalog } from "@opencode-ai/core/catalog" +import { Config } from "@opencode-ai/core/config" +import { EventV2 } from "@opencode-ai/core/event" +import { Location } from "@opencode-ai/core/location" +import { Npm } from "@opencode-ai/core/npm" +import { PluginV2 } from "@opencode-ai/core/plugin" +import { PluginBoot } from "@opencode-ai/core/plugin/boot" +import { Policy } from "@opencode-ai/core/policy" +import { AbsolutePath } from "@opencode-ai/core/schema" import * as Effect from "effect/Effect" +import * as Layer from "effect/Layer" import * as Command from "effect/unstable/cli/Command" +import { DebugCommand } from "./debug" const cli = Command.make("opencode", {}, () => Effect.void).pipe( Command.withDescription("OpenCode command line interface"), + Command.withSubcommands([DebugCommand]), ) -Command.run(cli, { version: "local" }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain) +const locationLayer = Location.defaultLayer({ + directory: AbsolutePath.make(process.cwd()), +}) + +const policyLayer = Policy.defaultLayer.pipe(Layer.provideMerge(locationLayer)) +const pluginLayer = PluginV2.defaultLayer +const eventLayer = EventV2.defaultLayer + +const layer = PluginBoot.layer.pipe( + Layer.provideMerge( + Layer.mergeAll( + NodeServices.layer, + Catalog.layer.pipe(Layer.provideMerge(Layer.mergeAll(eventLayer, pluginLayer, policyLayer))), + eventLayer, + pluginLayer, + AccountV2.defaultLayer, + AgentV2.defaultLayer, + Config.defaultLayer.pipe(Layer.provideMerge(policyLayer)), + Npm.defaultLayer, + ), + ), +) + +Command.run(cli, { version: "local" }).pipe(Effect.provide(layer), Effect.scoped, NodeRuntime.runMain) diff --git a/packages/core/src/plugin/agent.ts b/packages/core/src/plugin/agent.ts index 6780b9885..1102c169b 100644 --- a/packages/core/src/plugin/agent.ts +++ b/packages/core/src/plugin/agent.ts @@ -103,37 +103,34 @@ export const Plugin = PluginV2.define({ const location = yield* Location.Service const worktree = location.directory const whitelistedDirs = [TRUNCATION_GLOB, path.join(Global.Path.tmp, "*")] - const readonlyExternalDirectory = rules({ - external_directory: { - "*": "ask", - ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])), - }, - }) - const defaults = rules({ - "*": "allow", - doom_loop: "ask", - external_directory: { - "*": "ask", - ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])), - }, - question: "deny", - plan_enter: "deny", - plan_exit: "deny", - repo_clone: "deny", - repo_overview: "deny", - read: { - "*": "allow", - "*.env": "ask", - "*.env.*": "ask", - "*.env.example": "allow", - }, - }) + const readonlyExternalDirectory: PermissionV2.Ruleset = [ + { permission: "external_directory", pattern: "*", action: "ask" }, + ...whitelistedDirs.map((pattern): PermissionV2.Rule => ({ permission: "external_directory", pattern, action: "allow" })), + ] + const defaults: PermissionV2.Ruleset = [ + { permission: "*", pattern: "*", action: "allow" }, + ...readonlyExternalDirectory, + { permission: "question", pattern: "*", action: "deny" }, + { permission: "plan_enter", pattern: "*", action: "deny" }, + { permission: "plan_exit", pattern: "*", action: "deny" }, + { permission: "repo_clone", pattern: "*", action: "deny" }, + { permission: "repo_overview", pattern: "*", action: "deny" }, + { permission: "read", pattern: "*", action: "allow" }, + { permission: "read", pattern: "*.env", action: "ask" }, + { permission: "read", pattern: "*.env.*", action: "ask" }, + { permission: "read", pattern: "*.env.example", action: "allow" }, + ] yield* agent.update((editor) => { editor.update(AgentV2.ID.make("build"), (item) => { item.description = "The default agent. Executes tools based on configured permissions." item.mode = "primary" - item.permissions.push(...PermissionV2.merge(defaults, rules({ question: "allow", plan_enter: "allow" }))) + item.permissions.push( + ...PermissionV2.merge(defaults, [ + { permission: "question", pattern: "*", action: "allow" }, + { permission: "plan_enter", pattern: "*", action: "allow" }, + ]), + ) }) editor.update(AgentV2.ID.make("plan"), (item) => { @@ -142,18 +139,18 @@ export const Plugin = PluginV2.define({ item.permissions.push( ...PermissionV2.merge( defaults, - rules({ - question: "allow", - plan_exit: "allow", - external_directory: { - [path.join(Global.Path.data, "plans", "*")]: "allow", + [ + { permission: "question", pattern: "*", action: "allow" }, + { permission: "plan_exit", pattern: "*", action: "allow" }, + { permission: "external_directory", pattern: path.join(Global.Path.data, "plans", "*"), action: "allow" }, + { permission: "edit", pattern: "*", action: "deny" }, + { permission: "edit", pattern: path.join(".opencode", "plans", "*.md"), action: "allow" }, + { + permission: "edit", + pattern: path.relative(worktree, path.join(Global.Path.data, "plans", "*.md")), + action: "allow", }, - edit: { - "*": "deny", - [path.join(".opencode", "plans", "*.md")]: "allow", - [path.relative(worktree, path.join(Global.Path.data, "plans", "*.md"))]: "allow", - }, - }), + ], ), ) }) @@ -162,7 +159,7 @@ export const Plugin = PluginV2.define({ item.description = "General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel." item.mode = "subagent" - item.permissions.push(...PermissionV2.merge(defaults, rules({ todowrite: "deny" }))) + item.permissions.push(...PermissionV2.merge(defaults, [{ permission: "todowrite", pattern: "*", action: "deny" }])) }) editor.update(AgentV2.ID.make("explore"), (item) => { @@ -173,16 +170,16 @@ export const Plugin = PluginV2.define({ item.permissions.push( ...PermissionV2.merge( defaults, - rules({ - "*": "deny", - grep: "allow", - glob: "allow", - list: "allow", - bash: "allow", - webfetch: "allow", - websearch: "allow", - read: "allow", - }), + [ + { permission: "*", pattern: "*", action: "deny" }, + { permission: "grep", pattern: "*", action: "allow" }, + { permission: "glob", pattern: "*", action: "allow" }, + { permission: "list", pattern: "*", action: "allow" }, + { permission: "bash", pattern: "*", action: "allow" }, + { permission: "webfetch", pattern: "*", action: "allow" }, + { permission: "websearch", pattern: "*", action: "allow" }, + { permission: "read", pattern: "*", action: "allow" }, + ], readonlyExternalDirectory, ), ) @@ -192,29 +189,22 @@ export const Plugin = PluginV2.define({ item.mode = "primary" item.hidden = true item.system = PROMPT_COMPACTION - item.permissions.push(...PermissionV2.merge(defaults, rules({ "*": "deny" }))) + item.permissions.push(...PermissionV2.merge(defaults, [{ permission: "*", pattern: "*", action: "deny" }])) }) editor.update(AgentV2.ID.make("title"), (item) => { item.mode = "primary" item.hidden = true item.system = PROMPT_TITLE - item.permissions.push(...PermissionV2.merge(defaults, rules({ "*": "deny" }))) + item.permissions.push(...PermissionV2.merge(defaults, [{ permission: "*", pattern: "*", action: "deny" }])) }) editor.update(AgentV2.ID.make("summary"), (item) => { item.mode = "primary" item.hidden = true item.system = PROMPT_SUMMARY - item.permissions.push(...PermissionV2.merge(defaults, rules({ "*": "deny" }))) + item.permissions.push(...PermissionV2.merge(defaults, [{ permission: "*", pattern: "*", action: "deny" }])) }) }) }), }) - -function rules(input: Record>) { - return Object.entries(input).flatMap(([permission, value]) => { - if (typeof value === "string") return [{ permission, pattern: "*", action: value }] - return Object.entries(value).map(([pattern, action]) => ({ permission, pattern, action })) - }) -}