diff --git a/CHANGELOG.md b/CHANGELOG.md index c3baf63..b89e585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. This change ## [Unreleased] +### Changed (upstream PR #509 sync) +- **BREAKING**: Deny all permissions by default — `requestPermission` is now always `true` on the wire, and permission requests are denied when no `:on-permission-request` handler is configured. Previously, omitting the handler meant the CLI never asked for permission. To restore the old behavior, pass `:on-permission-request copilot/approve-all` in your session config. + +### Added (upstream PR #509 sync) +- `approve-all` — convenience permission handler that approves all requests (`copilot/approve-all`). Equivalent to the upstream Node.js SDK `approveAll` export. Use as `:on-permission-request copilot/approve-all` in session config. +- Integration tests for deny-by-default permission model: wire format assertions, `approve-all` behavior, handler dispatch with/without handler, custom selective handler + +### Changed +- MCP local server example now passes `:on-permission-request copilot/approve-all` (required for MCP tool execution under deny-by-default) + +### Fixed +- Permission denial result `:kind` now consistently uses keywords (not strings) in default handler responses, matching specs and `approve-all` behavior + ## [0.1.25.1] - 2026-02-18 ### Fixed - Release pipeline: GPG signing now fails fast with a clear error when no key is available, instead of silently producing unsigned artifacts that Maven Central rejects diff --git a/README.md b/README.md index 20e50a3..5d2a41d 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ See [doc/reference/API.md](./doc/reference/API.md) for the complete API referenc - **CopilotSession** - Session methods (`send!`, `send-and-wait!`, `}`: +The SDK uses a **deny-by-default** permission model. All permission requests +(file writes, shell commands, URL fetches, etc.) are denied unless your +session config provides an `:on-permission-request` handler. + +Use `approve-all` to opt into approving everything: + +```clojure +(def session (copilot/create-session client + {:on-permission-request copilot/approve-all})) +``` + +For fine-grained control, provide your own handler. When the CLI needs +approval, it sends a JSON-RPC `permission.request` to the SDK. Your +`:on-permission-request` callback must return a map compatible with the +permission result payload; the SDK wraps this into the JSON-RPC response +as `{:result }`: The `permission_bash.clj` example demonstrates both an allowed and a denied shell command and prints the full permission request payload so you can inspect @@ -1164,6 +1176,21 @@ fields like `:full-command-text`, `:commands`, and `:possible-paths`. {:kind :denied-interactively-by-user :feedback "Not allowed"} ``` +#### `approve-all` + +```clojure +(copilot/approve-all request ctx) +``` + +A convenience permission handler that approves all permission requests. +Equivalent to the upstream Node.js SDK `approveAll` export. + +Pass as the `:on-permission-request` value in session config: + +```clojure +(copilot/create-session client {:on-permission-request copilot/approve-all}) +``` + ### User Input Handling When the agent needs input from the user (via `ask_user` tool), the `:on-user-input-request` diff --git a/examples/README.md b/examples/README.md index 0c3fac0..d51984d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -166,7 +166,7 @@ clojure -A:examples -X helpers-query/run-multi :questions '["What is Rust?" "Wha (require '[github.copilot-sdk.helpers :as h]) ;; Simplest possible query - just get the answer -(h/query "What is 2+2?") +(h/query "What is 2+2?" :session {:model "gpt-5.2"}) ;; => "4" ;; With options @@ -180,7 +180,7 @@ clojure -A:examples -X helpers-query/run-multi :questions '["What is Rust?" "Wha (flush)) (defmethod handle-event :copilot/assistant.message [_] (println)) -(run! handle-event (h/query-seq! "Tell me a joke" :session {:streaming? true})) +(run! handle-event (h/query-seq! "Tell me a joke" :session {:model "gpt-5.2" :streaming? true})) ``` --- @@ -367,7 +367,11 @@ clojure -A:examples -X metadata-api/run ## Example 7: Permission Handling (`permission_bash.clj`) **Difficulty:** Intermediate -**Concepts:** permission requests, bash tool, approval callback +**Concepts:** permission requests, bash tool, approval callback, deny-by-default + +The SDK uses a **deny-by-default** permission model — all permission requests are +denied unless an `:on-permission-request` handler is provided. Use `copilot/approve-all` +for blanket approval, or provide a custom handler for fine-grained control. Shows how to: - handle `permission.request` via `:on-permission-request` @@ -550,6 +554,7 @@ Shows how to integrate MCP (Model Context Protocol) servers to extend the assist - Configuring `:mcp-servers` with a local stdio server - Using the `@modelcontextprotocol/server-filesystem` MCP server - Combining MCP server tools with custom tools +- Using `copilot/approve-all` to permit MCP tool execution (deny-by-default) ### Prerequisites @@ -603,7 +608,7 @@ await client.start(); **Clojure:** ```clojure (require '[github.copilot-sdk.helpers :as h]) -(h/query "What is 2+2?") +(h/query "What is 2+2?" :session {:model "gpt-5.2"}) ;; => "4" ``` @@ -625,7 +630,7 @@ session.on((event) => { (defmethod handle-event :copilot/assistant.message [{{:keys [content]} :data}] (println content)) -(run! handle-event (h/query-seq! "Hello" :session {:streaming? true})) +(run! handle-event (h/query-seq! "Hello" :session {:model "gpt-5.2" :streaming? true})) ``` ### Tool Definition diff --git a/examples/helpers_query.clj b/examples/helpers_query.clj index 3996b98..cdc88c0 100644 --- a/examples/helpers_query.clj +++ b/examples/helpers_query.clj @@ -8,10 +8,13 @@ (def defaults {:prompt "What is the capital of Japan? Answer in one sentence."}) +(def session-config + {:model "gpt-5.2"}) + (defn run [{:keys [prompt] :or {prompt (:prompt defaults)}}] (println "Query:" prompt) - (println "🤖:" (h/query prompt))) + (println "🤖:" (h/query prompt :session session-config))) (defn run-multi [{:keys [questions] :or {questions ["What is 2+2? Just the number." @@ -19,7 +22,7 @@ "Who wrote Hamlet? Just the name."]}}] (doseq [q questions] (println "Q:" q) - (println "A:" (h/query q)) + (println "A:" (h/query q :session session-config)) (println))) ;; Define a multimethod for handling events by type @@ -34,13 +37,13 @@ [{:keys [prompt] :or {prompt "Explain the concept of immutability in 2-3 sentences."}}] (println "Query:" prompt) (println) - (run! handle-event (h/query-seq! prompt :session {:streaming? true}))) + (run! handle-event (h/query-seq! prompt :session {:model "gpt-5.2" :streaming? true}))) (defn run-async [{:keys [prompt] :or {prompt "Tell me a short joke."}}] (println "Query:" prompt) (println) - (let [ch (h/query-chan prompt :session {:streaming? true})] + (let [ch (h/query-chan prompt :session {:model "gpt-5.2" :streaming? true})] (