v2: default agents
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
|
"@opencode-ai/core": "workspace:*",
|
||||||
"effect": "catalog:"
|
"effect": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -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"))
|
||||||
@@ -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]),
|
||||||
|
)
|
||||||
@@ -2,11 +2,48 @@
|
|||||||
|
|
||||||
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
|
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
|
||||||
import * as NodeServices from "@effect/platform-node/NodeServices"
|
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 Effect from "effect/Effect"
|
||||||
|
import * as Layer from "effect/Layer"
|
||||||
import * as Command from "effect/unstable/cli/Command"
|
import * as Command from "effect/unstable/cli/Command"
|
||||||
|
import { DebugCommand } from "./debug"
|
||||||
|
|
||||||
const cli = Command.make("opencode", {}, () => Effect.void).pipe(
|
const cli = Command.make("opencode", {}, () => Effect.void).pipe(
|
||||||
Command.withDescription("OpenCode command line interface"),
|
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)
|
||||||
|
|||||||
@@ -103,37 +103,34 @@ export const Plugin = PluginV2.define({
|
|||||||
const location = yield* Location.Service
|
const location = yield* Location.Service
|
||||||
const worktree = location.directory
|
const worktree = location.directory
|
||||||
const whitelistedDirs = [TRUNCATION_GLOB, path.join(Global.Path.tmp, "*")]
|
const whitelistedDirs = [TRUNCATION_GLOB, path.join(Global.Path.tmp, "*")]
|
||||||
const readonlyExternalDirectory = rules({
|
const readonlyExternalDirectory: PermissionV2.Ruleset = [
|
||||||
external_directory: {
|
{ permission: "external_directory", pattern: "*", action: "ask" },
|
||||||
"*": "ask",
|
...whitelistedDirs.map((pattern): PermissionV2.Rule => ({ permission: "external_directory", pattern, action: "allow" })),
|
||||||
...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
]
|
||||||
},
|
const defaults: PermissionV2.Ruleset = [
|
||||||
})
|
{ permission: "*", pattern: "*", action: "allow" },
|
||||||
const defaults = rules({
|
...readonlyExternalDirectory,
|
||||||
"*": "allow",
|
{ permission: "question", pattern: "*", action: "deny" },
|
||||||
doom_loop: "ask",
|
{ permission: "plan_enter", pattern: "*", action: "deny" },
|
||||||
external_directory: {
|
{ permission: "plan_exit", pattern: "*", action: "deny" },
|
||||||
"*": "ask",
|
{ permission: "repo_clone", pattern: "*", action: "deny" },
|
||||||
...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
{ permission: "repo_overview", pattern: "*", action: "deny" },
|
||||||
},
|
{ permission: "read", pattern: "*", action: "allow" },
|
||||||
question: "deny",
|
{ permission: "read", pattern: "*.env", action: "ask" },
|
||||||
plan_enter: "deny",
|
{ permission: "read", pattern: "*.env.*", action: "ask" },
|
||||||
plan_exit: "deny",
|
{ permission: "read", pattern: "*.env.example", action: "allow" },
|
||||||
repo_clone: "deny",
|
]
|
||||||
repo_overview: "deny",
|
|
||||||
read: {
|
|
||||||
"*": "allow",
|
|
||||||
"*.env": "ask",
|
|
||||||
"*.env.*": "ask",
|
|
||||||
"*.env.example": "allow",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
yield* agent.update((editor) => {
|
yield* agent.update((editor) => {
|
||||||
editor.update(AgentV2.ID.make("build"), (item) => {
|
editor.update(AgentV2.ID.make("build"), (item) => {
|
||||||
item.description = "The default agent. Executes tools based on configured permissions."
|
item.description = "The default agent. Executes tools based on configured permissions."
|
||||||
item.mode = "primary"
|
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) => {
|
editor.update(AgentV2.ID.make("plan"), (item) => {
|
||||||
@@ -142,18 +139,18 @@ export const Plugin = PluginV2.define({
|
|||||||
item.permissions.push(
|
item.permissions.push(
|
||||||
...PermissionV2.merge(
|
...PermissionV2.merge(
|
||||||
defaults,
|
defaults,
|
||||||
rules({
|
[
|
||||||
question: "allow",
|
{ permission: "question", pattern: "*", action: "allow" },
|
||||||
plan_exit: "allow",
|
{ permission: "plan_exit", pattern: "*", action: "allow" },
|
||||||
external_directory: {
|
{ permission: "external_directory", pattern: path.join(Global.Path.data, "plans", "*"), action: "allow" },
|
||||||
[path.join(Global.Path.data, "plans", "*")]: "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 =
|
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."
|
"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.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) => {
|
editor.update(AgentV2.ID.make("explore"), (item) => {
|
||||||
@@ -173,16 +170,16 @@ export const Plugin = PluginV2.define({
|
|||||||
item.permissions.push(
|
item.permissions.push(
|
||||||
...PermissionV2.merge(
|
...PermissionV2.merge(
|
||||||
defaults,
|
defaults,
|
||||||
rules({
|
[
|
||||||
"*": "deny",
|
{ permission: "*", pattern: "*", action: "deny" },
|
||||||
grep: "allow",
|
{ permission: "grep", pattern: "*", action: "allow" },
|
||||||
glob: "allow",
|
{ permission: "glob", pattern: "*", action: "allow" },
|
||||||
list: "allow",
|
{ permission: "list", pattern: "*", action: "allow" },
|
||||||
bash: "allow",
|
{ permission: "bash", pattern: "*", action: "allow" },
|
||||||
webfetch: "allow",
|
{ permission: "webfetch", pattern: "*", action: "allow" },
|
||||||
websearch: "allow",
|
{ permission: "websearch", pattern: "*", action: "allow" },
|
||||||
read: "allow",
|
{ permission: "read", pattern: "*", action: "allow" },
|
||||||
}),
|
],
|
||||||
readonlyExternalDirectory,
|
readonlyExternalDirectory,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -192,29 +189,22 @@ export const Plugin = PluginV2.define({
|
|||||||
item.mode = "primary"
|
item.mode = "primary"
|
||||||
item.hidden = true
|
item.hidden = true
|
||||||
item.system = PROMPT_COMPACTION
|
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) => {
|
editor.update(AgentV2.ID.make("title"), (item) => {
|
||||||
item.mode = "primary"
|
item.mode = "primary"
|
||||||
item.hidden = true
|
item.hidden = true
|
||||||
item.system = PROMPT_TITLE
|
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) => {
|
editor.update(AgentV2.ID.make("summary"), (item) => {
|
||||||
item.mode = "primary"
|
item.mode = "primary"
|
||||||
item.hidden = true
|
item.hidden = true
|
||||||
item.system = PROMPT_SUMMARY
|
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 }))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user