diff --git a/packages/web/src/content/docs/agents.mdx b/packages/web/src/content/docs/agents.mdx index 1922fece77f..456069cd21d 100644 --- a/packages/web/src/content/docs/agents.mdx +++ b/packages/web/src/content/docs/agents.mdx @@ -384,7 +384,7 @@ You can also use wildcards to control multiple tools at once. For example, to di ### Permissions -You can configure permissions to manage what actions an agent can take. Currently, the permissions for the `edit`, `bash`, and `webfetch` tools can be configured to: +You can configure permissions to manage what actions an agent can take. Most built-in tools support permissions (for example, `edit`, `bash`, `read`, `webfetch`, `glob`, `grep`, `task`, etc.), plus special gates like `external_directory` and `doom_loop`. - `"ask"` — Prompt for approval before running the tool - `"allow"` — Allow all operations without approval diff --git a/packages/web/src/content/docs/permissions.mdx b/packages/web/src/content/docs/permissions.mdx index 754a6c875dd..0c878cc507f 100644 --- a/packages/web/src/content/docs/permissions.mdx +++ b/packages/web/src/content/docs/permissions.mdx @@ -11,6 +11,7 @@ By default, OpenCode allows most operations without approval, except `doom_loop` "permission": { "edit": "allow", "bash": "ask", + "read": "allow", "skill": "ask", "webfetch": "deny", "doom_loop": "ask", @@ -19,7 +20,7 @@ By default, OpenCode allows most operations without approval, except `doom_loop` } ``` -This lets you configure granular controls for the `edit`, `bash`, `skill`, `webfetch`, `doom_loop`, and `external_directory` tools. +This lets you configure granular controls for most built-in tools (and many plugin tools). Note that `write`, `patch`, and `multiedit` are all covered by `permission.edit`. - `"ask"` — Prompt for approval before running the tool - `"allow"` — Allow all operations without approval @@ -29,7 +30,26 @@ This lets you configure granular controls for the `edit`, `bash`, `skill`, `webf ## Tools -Currently, the permissions for the `edit`, `bash`, `skill`, `webfetch`, `doom_loop`, and `external_directory` tools can be configured through the `permission` option. +Any tool that might access your filesystem, run commands, or use the network can be gated behind permissions. + +Common permission keys: + +- `edit` (also covers `write`, `patch`, `multiedit`) +- `bash` +- `read` +- `list` +- `glob` +- `grep` +- `lsp` +- `task` +- `todowrite` / `todoread` +- `skill` +- `webfetch` +- `websearch` / `codesearch` +- `doom_loop` +- `external_directory` + +Each permission key can be either a single action (string), or an object mapping patterns to actions. --- @@ -135,13 +155,38 @@ The wildcard uses simple regex globbing patterns. #### Scope of the `"ask"` option -When the agent asks for permission to run a particular bash command, it will -request feedback with the three options "accept", "accept always" and "deny". -The "accept always" answer applies for the rest of the current session. +When the agent asks for permission to run a particular bash command, it will request feedback with three options: `once`, `always`, and `reject`. + +The `always` option stores a rule for a normalized **command prefix** (for example, `git status*` or `npm run dev*`). The prefix is derived from a built-in command-arity table (flags don’t count); for unknown commands it falls back to the first token. + +When an agent asks for permission to run a command in a pipeline, each command is parsed separately, and the saved `always` rule applies per command. + +--- + +### read + +Use the `permission.read` key to control whether file reads require approval. + +The permission patterns for `read` are absolute file paths. + +```json title="opencode.json" {4} +{ + "$schema": "https://opencode.ai/config.json", + "permission": { + "read": "ask" + } +} +``` + +--- + +### list, glob, grep -In addition, command permissions are applied to the first two elements of a command. So, an "accept always" response for a command like `git log` would whitelist `git log *` but not `git commit ...`. +You can also gate other "read-only" tools: -When an agent asks for permission to run a command in a pipeline, we use tree sitter to parse each command in the pipeline. The "accept always" permission thus applies separately to each command in the pipeline. +- `permission.list` — directory listings (patterns are directory paths) +- `permission.glob` — file globs (patterns are the glob patterns) +- `permission.grep` — content search (patterns are the regex patterns) --- diff --git a/packages/web/src/content/docs/plugins.mdx b/packages/web/src/content/docs/plugins.mdx index 59a7010833d..d297213853f 100644 --- a/packages/web/src/content/docs/plugins.mdx +++ b/packages/web/src/content/docs/plugins.mdx @@ -170,8 +170,8 @@ Plugins can subscribe to events as seen below in the Examples section. Here is a #### Permission Events +- `permission.asked` - `permission.replied` -- `permission.updated` #### Server Events diff --git a/packages/web/src/content/docs/sdk.mdx b/packages/web/src/content/docs/sdk.mdx index 5fe738407c9..2bebd0bf5e9 100644 --- a/packages/web/src/content/docs/sdk.mdx +++ b/packages/web/src/content/docs/sdk.mdx @@ -226,27 +226,36 @@ const { providers, default: defaults } = await client.config.providers() ### Sessions -| Method | Description | Notes | -| ---------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `session.list()` | List sessions | Returns Session[] | -| `session.get({ path })` | Get session | Returns Session | -| `session.children({ path })` | List child sessions | Returns Session[] | -| `session.create({ body })` | Create session | Returns Session | -| `session.delete({ path })` | Delete session | Returns `boolean` | -| `session.update({ path, body })` | Update session properties | Returns Session | -| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` | -| `session.abort({ path })` | Abort a running session | Returns `boolean` | -| `session.share({ path })` | Share session | Returns Session | -| `session.unshare({ path })` | Unshare session | Returns Session | -| `session.summarize({ path, body })` | Summarize session | Returns `boolean` | -| `session.messages({ path })` | List messages in a session | Returns `{ info: `Message`, parts: `Part[]`}[]` | -| `session.message({ path })` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | -| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns AssistantMessage with AI response | -| `session.command({ path, body })` | Send command to session | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | -| `session.shell({ path, body })` | Run a shell command | Returns AssistantMessage | -| `session.revert({ path, body })` | Revert a message | Returns Session | -| `session.unrevert({ path })` | Restore reverted messages | Returns Session | -| `postSessionByIdPermissionsByPermissionId({ path, body })` | Respond to a permission request | Returns `boolean` | +| Method | Description | Notes | +| ----------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `session.list()` | List sessions | Returns Session[] | +| `session.get({ path })` | Get session | Returns Session | +| `session.children({ path })` | List child sessions | Returns Session[] | +| `session.create({ body })` | Create session | Returns Session | +| `session.delete({ path })` | Delete session | Returns `boolean` | +| `session.update({ path, body })` | Update session properties | Returns Session | +| `session.init({ path, body })` | Analyze app and create `AGENTS.md` | Returns `boolean` | +| `session.abort({ path })` | Abort a running session | Returns `boolean` | +| `session.share({ path })` | Share session | Returns Session | +| `session.unshare({ path })` | Unshare session | Returns Session | +| `session.summarize({ path, body })` | Summarize session | Returns `boolean` | +| `session.messages({ path })` | List messages in a session | Returns `{ info: `Message`, parts: `Part[]`}[]` | +| `session.message({ path })` | Get message details | Returns `{ info: `Message`, parts: `Part[]`}` | +| `session.prompt({ path, body })` | Send prompt message | `body.noReply: true` returns UserMessage (context only). Default returns AssistantMessage with AI response | +| `session.command({ path, body })` | Send command to session | Returns `{ info: `AssistantMessage`, parts: `Part[]`}` | +| `session.shell({ path, body })` | Run a shell command | Returns AssistantMessage | +| `session.revert({ path, body })` | Revert a message | Returns Session | +| `session.unrevert({ path })` | Restore reverted messages | Returns Session | + +--- + +### Permissions + +| Method | Description | Notes | +| ----------------------------------------------------------- | ------------------------------- | ------------------------------------------------------------------ | +| `permission.list()` | List pending permissions | Returns PermissionRequest[] | +| `permission.reply({ requestID, reply })` | Respond to a permission request | `reply` is `"once"`, `"always"`, or `"reject"` (returns `boolean`) | +| `permission.respond({ sessionID, permissionID, response })` | Respond to a permission request | Deprecated alias (returns `boolean`) | --- diff --git a/packages/web/src/content/docs/server.mdx b/packages/web/src/content/docs/server.mdx index a61d7bae157..e842a514d64 100644 --- a/packages/web/src/content/docs/server.mdx +++ b/packages/web/src/content/docs/server.mdx @@ -134,26 +134,37 @@ The opencode server exposes the following APIs. ### Sessions -| Method | Path | Description | Notes | -| -------- | ---------------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------- | -| `GET` | `/session` | List all sessions | Returns Session[] | -| `POST` | `/session` | Create a new session | body: `{ parentID?, title? }`, returns Session | -| `GET` | `/session/status` | Get session status for all sessions | Returns `{ [sessionID: string]: `SessionStatus` }` | -| `GET` | `/session/:id` | Get session details | Returns Session | -| `DELETE` | `/session/:id` | Delete a session and all its data | Returns `boolean` | -| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns Session | -| `GET` | `/session/:id/children` | Get a session's child sessions | Returns Session[] | -| `GET` | `/session/:id/todo` | Get the todo list for a session | Returns Todo[] | -| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/fork` | Fork an existing session at a message | body: `{ messageID? }`, returns Session | -| `POST` | `/session/:id/abort` | Abort a running session | Returns `boolean` | -| `POST` | `/session/:id/share` | Share a session | Returns Session | -| `DELETE` | `/session/:id/share` | Unshare a session | Returns Session | -| `GET` | `/session/:id/diff` | Get the diff for this session | query: `messageID?`, returns FileDiff[] | -| `POST` | `/session/:id/summarize` | Summarize the session | body: `{ providerID, modelID }`, returns `boolean` | -| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID, partID? }`, returns `boolean` | -| `POST` | `/session/:id/unrevert` | Restore all reverted messages | Returns `boolean` | -| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request | body: `{ response, remember? }`, returns `boolean` | +| Method | Path | Description | Notes | +| -------- | ------------------------ | ------------------------------------- | ---------------------------------------------------------------------------------- | +| `GET` | `/session` | List all sessions | Returns Session[] | +| `POST` | `/session` | Create a new session | body: `{ parentID?, title? }`, returns Session | +| `GET` | `/session/status` | Get session status for all sessions | Returns `{ [sessionID: string]: `SessionStatus` }` | +| `GET` | `/session/:id` | Get session details | Returns Session | +| `DELETE` | `/session/:id` | Delete a session and all its data | Returns `boolean` | +| `PATCH` | `/session/:id` | Update session properties | body: `{ title? }`, returns Session | +| `GET` | `/session/:id/children` | Get a session's child sessions | Returns Session[] | +| `GET` | `/session/:id/todo` | Get the todo list for a session | Returns Todo[] | +| `POST` | `/session/:id/init` | Analyze app and create `AGENTS.md` | body: `{ messageID, providerID, modelID }`, returns `boolean` | +| `POST` | `/session/:id/fork` | Fork an existing session at a message | body: `{ messageID? }`, returns Session | +| `POST` | `/session/:id/abort` | Abort a running session | Returns `boolean` | +| `POST` | `/session/:id/share` | Share a session | Returns Session | +| `DELETE` | `/session/:id/share` | Unshare a session | Returns Session | +| `GET` | `/session/:id/diff` | Get the diff for this session | query: `messageID?`, returns FileDiff[] | +| `POST` | `/session/:id/summarize` | Summarize the session | body: `{ providerID, modelID }`, returns `boolean` | +| `POST` | `/session/:id/revert` | Revert a message | body: `{ messageID, partID? }`, returns `boolean` | +| `POST` | `/session/:id/unrevert` | Restore all reverted messages | Returns `boolean` | + +--- + +### Permissions + +| Method | Path | Description | Notes | +| ------ | ---------------------------------------- | -------------------------------------------- | --------------------------------------------------------------- | +| `GET` | `/permission` | List pending permissions | Returns PermissionRequest[] | +| `POST` | `/permission/:requestID/reply` | Respond to a permission request | body: `{ reply }`, returns `boolean` | +| `POST` | `/session/:id/permissions/:permissionID` | Respond to a permission request (deprecated) | body: `{ response }`, returns `boolean` | + +`reply` / `response` can be one of: `"once"`, `"always"`, or `"reject"`. ---