Browse Source

v2: default agents

Dax Raad 2 months ago
parent
commit
9b915e70bd

+ 1 - 0
packages/cli/package.json

@@ -14,6 +14,7 @@
   },
   "dependencies": {
     "@effect/platform-node": "catalog:",
+    "@opencode-ai/core": "workspace:*",
     "effect": "catalog:"
   },
   "devDependencies": {

+ 13 - 0
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"))

+ 7 - 0
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]),
+)

+ 38 - 1
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)

+ 48 - 58
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",
-              },
-              edit: {
-                "*": "deny",
-                [path.join(".opencode", "plans", "*.md")]: "allow",
-                [path.relative(worktree, path.join(Global.Path.data, "plans", "*.md"))]: "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",
               },
-            }),
+            ],
           ),
         )
       })
@@ -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<string, PermissionV2.Action | Record<string, PermissionV2.Action>>) {
-  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 }))
-  })
-}