Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).

## [Unreleased]
### Added
- `:no-result` permission outcome — extensions can attach to sessions without actively answering permission requests by returning `{:kind :no-result}` from their `:on-permission-request` handler. On v3 protocol, the `handlePendingPermissionRequest` RPC is skipped; on v2, an error is propagated to the CLI (upstream PR #802).

## [0.1.32.0] - 2026-03-12
### Added (upstream sync)
Expand Down
3 changes: 3 additions & 0 deletions doc/reference/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,9 @@ fields like `:full-command-text`, `:commands`, and `:possible-paths`.

;; Deny after user interaction (optional feedback)
{:kind :denied-interactively-by-user :feedback "Not allowed"}

;; Extension declines to answer (another handler may respond)
{:kind :no-result}
```

#### `approve-all`
Expand Down
33 changes: 22 additions & 11 deletions src/github/copilot_sdk/client.clj
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@
(defn- handle-v3-permission-requested!
"Handle v3 permission.requested broadcast event.
Calls the session's permission handler and responds via the
session.permissions.handlePendingPermissionRequest RPC method."
session.permissions.handlePendingPermissionRequest RPC method.
When the handler returns :no-result, the RPC call is skipped
so the extension does not answer this permission request."
[client session-id event]
(let [data (:data event)
request-id (:request-id data)
Expand All @@ -257,13 +259,15 @@
(try
(let [perm-response (<! (session/handle-permission-request!
client session-id permission-request))
result (:result perm-response)
conn (:connection-io @(:state client))]
(when conn
(<! (proto/send-request conn "session.permissions.handlePendingPermissionRequest"
{:session-id session-id
:request-id request-id
:result result}))))
result (:result perm-response)]
;; :no-result — extension declines to answer; skip the RPC call
(when-not (= :no-result result)
(let [conn (:connection-io @(:state client))]
(when conn
(<! (proto/send-request conn "session.permissions.handlePendingPermissionRequest"
{:session-id session-id
:request-id request-id
:result result}))))))
Comment thread
krukow marked this conversation as resolved.
(catch Exception e
(log/debug "v3 permission request error for " request-id ": " (ex-message e))
(try
Expand Down Expand Up @@ -495,9 +499,16 @@
(let [{:keys [session-id permission-request]} params]
(if-not (get-in @(:state client) [:sessions session-id])
{:result {:kind :denied-no-approval-rule-and-could-not-request-from-user}}
(let [result (<! (session/handle-permission-request! client session-id permission-request))]
(log/debug "Permission response for session " session-id ": " result)
{:result result})))
(let [perm-response (<! (session/handle-permission-request! client session-id permission-request))
result (:result perm-response)]
(if (= :no-result result)
;; no-result must propagate as an error on v2 protocol
;; so the CLI knows no answer was given (matches upstream -32603)
{:error {:code -32603
:message "Permission handler returned no-result on protocol v2"}}
(do
(log/debug "Permission response for session " session-id ": " result)
{:result perm-response})))))

;; User input request (PR #269)
"userInput.request"
Expand Down
14 changes: 13 additions & 1 deletion src/github/copilot_sdk/session.clj
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,10 @@
:mixed))

(defn handle-permission-request!
"Handle an incoming permission request. Returns a channel with the result."
"Handle an incoming permission request. Returns a channel with the result.
When the handler returns `{:kind :no-result}`, the result is
`{:result :no-result}` — callers must check for this sentinel and
skip responding to the CLI (extensions that don't answer permissions)."
Comment thread
krukow marked this conversation as resolved.
Outdated
[client session-id request]
(async/thread-call
(fn []
Expand All @@ -210,9 +213,18 @@
(<!! result)
result)]
(cond
;; no-result: extension doesn't answer this permission request
(and (map? result) (= :no-result (:kind result)))
{:result :no-result}

(and (map? result) (contains? result :kind))
{:result result}

;; Wrapped form: {:result {:kind ...}}
(and (map? result) (contains? result :result)
(map? (:result result)) (= :no-result (:kind (:result result))))
{:result :no-result}

(and (map? result) (contains? result :result)
(map? (:result result)) (contains? (:result result) :kind))
result
Expand Down
3 changes: 2 additions & 1 deletion src/github/copilot_sdk/specs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,8 @@
#{:approved
:denied-by-rules
:denied-no-approval-rule-and-could-not-request-from-user
:denied-interactively-by-user})
:denied-interactively-by-user
:no-result})

(s/def ::permission-result
(s/keys :req-un [::permission-result-kind]
Expand Down
30 changes: 30 additions & 0 deletions test/github/copilot_sdk/integration_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,36 @@
(is (= :approved (get-in approved [:result :result :kind])))
(is (= :denied-by-rules (get-in denied [:result :result :kind]))))))

(deftest test-permission-no-result-v2
(testing "no-result permission handler returns error on v2 protocol"
(let [session (sdk/create-session *test-client*
{:on-permission-request
(fn [_request _ctx]
{:kind :no-result})})
session-id (sdk/session-id session)
handler (get-in @(:state *test-client*) [:connection :request-handler])
response (<!! (handler "permission.request"
{:session-id session-id
:permission-request {:permission-kind "shell"
:full-command-text "echo test"}}))]
;; v2: no-result should propagate as a JSON-RPC internal error
(is (= -32603 (get-in response [:error :code]))
"no-result on v2 should produce a -32603 internal error")))

(testing "wrapped no-result permission handler returns error on v2 protocol"
(let [session (sdk/create-session *test-client*
{:on-permission-request
(fn [_request _ctx]
{:result {:kind :no-result}})})
session-id (sdk/session-id session)
handler (get-in @(:state *test-client*) [:connection :request-handler])
response (<!! (handler "permission.request"
{:session-id session-id
:permission-request {:permission-kind "shell"
:full-command-text "echo test"}}))]
(is (= -32603 (get-in response [:error :code]))
"wrapped no-result on v2 should also produce a -32603 error"))))

;; -----------------------------------------------------------------------------
;; Last Session ID Tests
;; -----------------------------------------------------------------------------
Expand Down
Loading