feat: add command-aware permission request system for granular tool approval

This commit is contained in:
Dax Raad 2025-12-28 17:27:11 -05:00
commit f24683e661
44 changed files with 3176 additions and 1384 deletions

View file

@ -55,6 +55,8 @@ import type {
PartUpdateResponses,
PathGetResponses,
PermissionListResponses,
PermissionReplyErrors,
PermissionReplyResponses,
PermissionRespondErrors,
PermissionRespondResponses,
ProjectCurrentResponses,
@ -1626,6 +1628,43 @@ export class Permission extends HeyApiClient {
})
}
/**
* Respond to permission request
*
* Approve or deny a permission request from the AI assistant.
*/
public reply<ThrowOnError extends boolean = false>(
parameters: {
requestID: string
directory?: string
reply?: "once" | "always" | "reject"
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "path", key: "requestID" },
{ in: "query", key: "directory" },
{ in: "body", key: "reply" },
],
},
],
)
return (options?.client ?? this.client).post<PermissionReplyResponses, PermissionReplyErrors, ThrowOnError>({
url: "/permission/{requestID}/reply",
...options,
...params,
headers: {
"Content-Type": "application/json",
...options?.headers,
...params.headers,
},
})
}
/**
* List pending permissions
*

View file

@ -451,6 +451,33 @@ export type EventMessagePartRemoved = {
}
}
export type PermissionRequest = {
id: string
callID?: string
sessionID: string
permission: string
patterns: Array<string>
message: string
metadata: {
[key: string]: unknown
}
always: Array<string>
}
export type EventPermissionNextAsked = {
type: "permission.next.asked"
properties: PermissionRequest
}
export type EventPermissionNextReplied = {
type: "permission.next.replied"
properties: {
sessionID: string
requestID: string
reply: "once" | "always" | "reject"
}
}
export type Permission = {
id: string
type: string
@ -458,7 +485,7 @@ export type Permission = {
sessionID: string
messageID: string
callID?: string
title: string
message: string
metadata: {
[key: string]: unknown
}
@ -481,40 +508,6 @@ export type EventPermissionReplied = {
}
}
export type EventFileEdited = {
type: "file.edited"
properties: {
file: string
}
}
export type Todo = {
/**
* Brief description of the task
*/
content: string
/**
* Current status of the task: pending, in_progress, completed, cancelled
*/
status: string
/**
* Priority level of the task: high, medium, low
*/
priority: string
/**
* Unique identifier for the todo item
*/
id: string
}
export type EventTodoUpdated = {
type: "todo.updated"
properties: {
sessionID: string
todos: Array<Todo>
}
}
export type SessionStatus =
| {
type: "idle"
@ -551,6 +544,40 @@ export type EventSessionCompacted = {
}
}
export type EventFileEdited = {
type: "file.edited"
properties: {
file: string
}
}
export type Todo = {
/**
* Brief description of the task
*/
content: string
/**
* Current status of the task: pending, in_progress, completed, cancelled
*/
status: string
/**
* Priority level of the task: high, medium, low
*/
priority: string
/**
* Unique identifier for the todo item
*/
id: string
}
export type EventTodoUpdated = {
type: "todo.updated"
properties: {
sessionID: string
todos: Array<Todo>
}
}
export type EventTuiPromptAppend = {
type: "tui.prompt.append"
properties: {
@ -756,13 +783,15 @@ export type Event =
| EventMessageRemoved
| EventMessagePartUpdated
| EventMessagePartRemoved
| EventPermissionNextAsked
| EventPermissionNextReplied
| EventPermissionUpdated
| EventPermissionReplied
| EventFileEdited
| EventTodoUpdated
| EventSessionStatus
| EventSessionIdle
| EventSessionCompacted
| EventFileEdited
| EventTodoUpdated
| EventTuiPromptAppend
| EventTuiCommandExecute
| EventTuiToastShow
@ -1183,11 +1212,42 @@ export type ServerConfig = {
cors?: Array<string>
}
export type PermissionActionConfig = "ask" | "allow" | "deny"
export type PermissionObjectConfig = {
[key: string]: PermissionActionConfig
}
export type PermissionRuleConfig = PermissionActionConfig | PermissionObjectConfig
export type PermissionConfig =
| {
read?: PermissionRuleConfig
edit?: PermissionRuleConfig
glob?: PermissionRuleConfig
grep?: PermissionRuleConfig
list?: PermissionRuleConfig
bash?: PermissionRuleConfig
task?: PermissionRuleConfig
external_directory?: PermissionRuleConfig
todowrite?: PermissionActionConfig
todoread?: PermissionActionConfig
webfetch?: PermissionActionConfig
websearch?: PermissionActionConfig
codesearch?: PermissionActionConfig
doom_loop?: PermissionActionConfig
[key: string]: PermissionRuleConfig | PermissionActionConfig | undefined
}
| PermissionActionConfig
export type AgentConfig = {
model?: string
temperature?: number
top_p?: number
prompt?: string
/**
* @deprecated Use 'permission' field instead
*/
tools?: {
[key: string]: boolean
}
@ -1197,6 +1257,9 @@ export type AgentConfig = {
*/
description?: string
mode?: "subagent" | "primary" | "all"
options?: {
[key: string]: unknown
}
/**
* Hex color code for the agent (e.g., #FF5733)
*/
@ -1204,27 +1267,12 @@ export type AgentConfig = {
/**
* Maximum number of agentic iterations before forcing text-only response
*/
steps?: number
/**
* @deprecated Use 'steps' field instead.
*/
maxSteps?: number
permission?: {
edit?: "ask" | "allow" | "deny"
bash?:
| "ask"
| "allow"
| "deny"
| {
[key: string]: "ask" | "allow" | "deny"
}
skill?:
| "ask"
| "allow"
| "deny"
| {
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: "ask" | "allow" | "deny"
doom_loop?: "ask" | "allow" | "deny"
external_directory?: "ask" | "allow" | "deny"
}
permission?: PermissionConfig
[key: string]:
| unknown
| string
@ -1236,28 +1284,12 @@ export type AgentConfig = {
| "subagent"
| "primary"
| "all"
| {
[key: string]: unknown
}
| string
| number
| {
edit?: "ask" | "allow" | "deny"
bash?:
| "ask"
| "allow"
| "deny"
| {
[key: string]: "ask" | "allow" | "deny"
}
skill?:
| "ask"
| "allow"
| "deny"
| {
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: "ask" | "allow" | "deny"
doom_loop?: "ask" | "allow" | "deny"
external_directory?: "ask" | "allow" | "deny"
}
| PermissionConfig
| undefined
}
@ -1578,26 +1610,7 @@ export type Config = {
*/
instructions?: Array<string>
layout?: LayoutConfig
permission?: {
edit?: "ask" | "allow" | "deny"
bash?:
| "ask"
| "allow"
| "deny"
| {
[key: string]: "ask" | "allow" | "deny"
}
skill?:
| "ask"
| "allow"
| "deny"
| {
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: "ask" | "allow" | "deny"
doom_loop?: "ask" | "allow" | "deny"
external_directory?: "ask" | "allow" | "deny"
}
permission?: PermissionConfig
tools?: {
[key: string]: boolean
}
@ -1880,40 +1893,35 @@ export type File = {
status: "added" | "deleted" | "modified"
}
export type PermissionAction = "allow" | "deny" | "ask"
export type PermissionRule = {
permission: string
pattern: string
action: PermissionAction
}
export type PermissionRuleset = Array<PermissionRule>
export type Agent = {
name: string
description?: string
mode: "subagent" | "primary" | "all"
native?: boolean
hidden?: boolean
default?: boolean
topP?: number
temperature?: number
color?: string
permission: {
edit: "ask" | "allow" | "deny"
bash: {
[key: string]: "ask" | "allow" | "deny"
}
skill: {
[key: string]: "ask" | "allow" | "deny"
}
webfetch?: "ask" | "allow" | "deny"
doom_loop?: "ask" | "allow" | "deny"
external_directory?: "ask" | "allow" | "deny"
}
permission: PermissionRuleset
model?: {
modelID: string
providerID: string
}
prompt?: string
tools: {
[key: string]: boolean
}
options: {
[key: string]: unknown
}
maxSteps?: number
steps?: number
}
export type McpStatusConnected = {
@ -3391,6 +3399,41 @@ export type PermissionRespondResponses = {
export type PermissionRespondResponse = PermissionRespondResponses[keyof PermissionRespondResponses]
export type PermissionReplyData = {
body?: {
reply: "once" | "always" | "reject"
}
path: {
requestID: string
}
query?: {
directory?: string
}
url: "/permission/{requestID}/reply"
}
export type PermissionReplyErrors = {
/**
* Bad request
*/
400: BadRequestError
/**
* Not found
*/
404: NotFoundError
}
export type PermissionReplyError = PermissionReplyErrors[keyof PermissionReplyErrors]
export type PermissionReplyResponses = {
/**
* Permission processed successfully
*/
200: boolean
}
export type PermissionReplyResponse = PermissionReplyResponses[keyof PermissionReplyResponses]
export type PermissionListData = {
body?: never
path?: never

View file

@ -1,3 +1,4 @@
<<<<<<< HEAD
{
"openapi": "3.1.1",
"info": {
@ -9750,3 +9751,6 @@
}
}
}
=======
{}
>>>>>>> 4f732c838 (feat: add command-aware permission request system for granular tool approval)