mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat(auto-review) Handle request_permissions calls (#18393)
## Summary When auto-review is enabled, it should handle request_permissions tool. We'll need to clean up the UX but I'm planning to do that in a separate pass ## Testing - [x] Ran locally <img width="893" height="396" alt="Screenshot 2026-04-17 at 1 16 13 PM" src="https://github.com/user-attachments/assets/4c045c5f-1138-4c6c-ac6e-2cb6be4514d8" /> --------- Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
543a08dac9
commit
86535c9901
@@ -64,6 +64,57 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"read": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AgentMessageDeltaNotification": {
|
||||
"properties": {
|
||||
"delta": {
|
||||
@@ -1019,6 +1070,217 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "PathFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"glob_pattern"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pattern",
|
||||
"type"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"special"
|
||||
],
|
||||
"title": "SpecialFileSystemPathType",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/definitions/FileSystemSpecialPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"title": "SpecialFileSystemPath",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"root"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "RootFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"minimal"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "MinimalFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"current_working_directory"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"project_roots"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "KindFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"tmpdir"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "TmpdirFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"slash_tmp"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "SlashTmpFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileUpdateChange": {
|
||||
"properties": {
|
||||
"diff": {
|
||||
@@ -1368,6 +1630,32 @@
|
||||
],
|
||||
"title": "McpToolCallGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/RequestPermissionProfile"
|
||||
},
|
||||
"reason": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"requestPermissions"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewActionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"permissions",
|
||||
"type"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -2285,6 +2573,32 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RequestPermissionProfile": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalFileSystemPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalNetworkPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ServerRequestResolvedNotification": {
|
||||
"properties": {
|
||||
"requestId": {
|
||||
|
||||
+319
-293
@@ -5,63 +5,12 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"read": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalPermissionProfile": {
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalFileSystemPermissions"
|
||||
"$ref": "#/definitions/v2/AdditionalFileSystemPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -71,7 +20,7 @@
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalNetworkPermissions"
|
||||
"$ref": "#/definitions/v2/AdditionalNetworkPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -2268,217 +2217,6 @@
|
||||
"title": "FileChangeRequestApprovalResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "PathFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"glob_pattern"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pattern",
|
||||
"type"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"special"
|
||||
],
|
||||
"title": "SpecialFileSystemPathType",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/definitions/FileSystemSpecialPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"title": "SpecialFileSystemPath",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"root"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "RootFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"minimal"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "MinimalFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"current_working_directory"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"project_roots"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "KindFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"tmpdir"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "TmpdirFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"slash_tmp"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "SlashTmpFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FuzzyFileSearchMatchType": {
|
||||
"enum": [
|
||||
"file",
|
||||
@@ -2611,7 +2349,7 @@
|
||||
"fileSystem": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalFileSystemPermissions"
|
||||
"$ref": "#/definitions/v2/AdditionalFileSystemPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -2621,7 +2359,7 @@
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalNetworkPermissions"
|
||||
"$ref": "#/definitions/v2/AdditionalNetworkPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
@@ -3620,7 +3358,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/RequestPermissionProfile"
|
||||
"$ref": "#/definitions/v2/RequestPermissionProfile"
|
||||
},
|
||||
"reason": {
|
||||
"type": [
|
||||
@@ -3678,32 +3416,6 @@
|
||||
],
|
||||
"title": "RequestId"
|
||||
},
|
||||
"RequestPermissionProfile": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalFileSystemPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalNetworkPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ReviewDecision": {
|
||||
"description": "User's decision in response to an ExecApprovalRequest.",
|
||||
"oneOf": [
|
||||
@@ -5413,6 +5125,57 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"read": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AgentMessageDeltaNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -8041,6 +7804,217 @@
|
||||
"title": "FileChangePatchUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/v2/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "PathFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"glob_pattern"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pattern",
|
||||
"type"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"special"
|
||||
],
|
||||
"title": "SpecialFileSystemPathType",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/definitions/v2/FileSystemSpecialPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"title": "SpecialFileSystemPath",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/v2/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/v2/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"root"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "RootFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"minimal"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "MinimalFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"current_working_directory"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"project_roots"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "KindFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"tmpdir"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "TmpdirFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"slash_tmp"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "SlashTmpFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileUpdateChange": {
|
||||
"properties": {
|
||||
"diff": {
|
||||
@@ -8834,6 +8808,32 @@
|
||||
],
|
||||
"title": "McpToolCallGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/v2/RequestPermissionProfile"
|
||||
},
|
||||
"reason": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"requestPermissions"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewActionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"permissions",
|
||||
"type"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -11641,6 +11641,32 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RequestPermissionProfile": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/AdditionalFileSystemPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/AdditionalNetworkPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ResidencyRequirement": {
|
||||
"enum": [
|
||||
"us"
|
||||
|
||||
+314
@@ -128,6 +128,57 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"read": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AgentMessageDeltaNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -4370,6 +4421,217 @@
|
||||
"title": "FileChangePatchUpdatedNotification",
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "PathFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"glob_pattern"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pattern",
|
||||
"type"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"special"
|
||||
],
|
||||
"title": "SpecialFileSystemPathType",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/definitions/FileSystemSpecialPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"title": "SpecialFileSystemPath",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"root"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "RootFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"minimal"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "MinimalFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"current_working_directory"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"project_roots"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "KindFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"tmpdir"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "TmpdirFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"slash_tmp"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "SlashTmpFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileUpdateChange": {
|
||||
"properties": {
|
||||
"diff": {
|
||||
@@ -5274,6 +5536,32 @@
|
||||
],
|
||||
"title": "McpToolCallGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/RequestPermissionProfile"
|
||||
},
|
||||
"reason": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"requestPermissions"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewActionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"permissions",
|
||||
"type"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -8125,6 +8413,32 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"RequestPermissionProfile": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalFileSystemPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalNetworkPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ResidencyRequirement": {
|
||||
"enum": [
|
||||
"us"
|
||||
|
||||
Generated
+314
@@ -5,6 +5,57 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"read": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AutoReviewDecisionSource": {
|
||||
"description": "[UNSTABLE] Source that produced a terminal approval auto-review decision.",
|
||||
"enum": [
|
||||
@@ -12,6 +63,217 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "PathFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"glob_pattern"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pattern",
|
||||
"type"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"special"
|
||||
],
|
||||
"title": "SpecialFileSystemPathType",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/definitions/FileSystemSpecialPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"title": "SpecialFileSystemPath",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"root"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "RootFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"minimal"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "MinimalFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"current_working_directory"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"project_roots"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "KindFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"tmpdir"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "TmpdirFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"slash_tmp"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "SlashTmpFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"GuardianApprovalReview": {
|
||||
"description": "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
|
||||
"properties": {
|
||||
@@ -217,6 +479,32 @@
|
||||
],
|
||||
"title": "McpToolCallGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/RequestPermissionProfile"
|
||||
},
|
||||
"reason": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"requestPermissions"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewActionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"permissions",
|
||||
"type"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -266,6 +554,32 @@
|
||||
"socks5Udp"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RequestPermissionProfile": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalFileSystemPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalNetworkPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.",
|
||||
|
||||
Generated
+314
@@ -5,6 +5,268 @@
|
||||
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
|
||||
"type": "string"
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"read": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AdditionalNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemAccessMode": {
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"none"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"FileSystemPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"path"
|
||||
],
|
||||
"title": "PathFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "PathFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"glob_pattern"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPathType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"pattern",
|
||||
"type"
|
||||
],
|
||||
"title": "GlobPatternFileSystemPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"special"
|
||||
],
|
||||
"title": "SpecialFileSystemPathType",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"$ref": "#/definitions/FileSystemSpecialPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type",
|
||||
"value"
|
||||
],
|
||||
"title": "SpecialFileSystemPath",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"FileSystemSandboxEntry": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/FileSystemAccessMode"
|
||||
},
|
||||
"path": {
|
||||
"$ref": "#/definitions/FileSystemPath"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"access",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"FileSystemSpecialPath": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"root"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "RootFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"minimal"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "MinimalFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"current_working_directory"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "CurrentWorkingDirectoryFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"project_roots"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "KindFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"tmpdir"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "TmpdirFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"slash_tmp"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind"
|
||||
],
|
||||
"title": "SlashTmpFileSystemSpecialPath",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"kind": {
|
||||
"enum": [
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"subpath": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"GuardianApprovalReview": {
|
||||
"description": "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.",
|
||||
"properties": {
|
||||
@@ -210,6 +472,32 @@
|
||||
],
|
||||
"title": "McpToolCallGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"permissions": {
|
||||
"$ref": "#/definitions/RequestPermissionProfile"
|
||||
},
|
||||
"reason": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"requestPermissions"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewActionType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"permissions",
|
||||
"type"
|
||||
],
|
||||
"title": "RequestPermissionsGuardianApprovalReviewAction",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -259,6 +547,32 @@
|
||||
"socks5Udp"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"RequestPermissionProfile": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalFileSystemPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"network": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/AdditionalNetworkPermissions"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.",
|
||||
|
||||
+2
-1
@@ -4,5 +4,6 @@
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
import type { GuardianCommandSource } from "./GuardianCommandSource";
|
||||
import type { NetworkApprovalProtocol } from "./NetworkApprovalProtocol";
|
||||
import type { RequestPermissionProfile } from "./RequestPermissionProfile";
|
||||
|
||||
export type GuardianApprovalReviewAction = { "type": "command", source: GuardianCommandSource, command: string, cwd: AbsolutePathBuf, } | { "type": "execve", source: GuardianCommandSource, program: string, argv: Array<string>, cwd: AbsolutePathBuf, } | { "type": "applyPatch", cwd: AbsolutePathBuf, files: Array<AbsolutePathBuf>, } | { "type": "networkAccess", target: string, host: string, protocol: NetworkApprovalProtocol, port: number, } | { "type": "mcpToolCall", server: string, toolName: string, connectorId: string | null, connectorName: string | null, toolTitle: string | null, };
|
||||
export type GuardianApprovalReviewAction = { "type": "command", source: GuardianCommandSource, command: string, cwd: AbsolutePathBuf, } | { "type": "execve", source: GuardianCommandSource, program: string, argv: Array<string>, cwd: AbsolutePathBuf, } | { "type": "applyPatch", cwd: AbsolutePathBuf, files: Array<AbsolutePathBuf>, } | { "type": "networkAccess", target: string, host: string, protocol: NetworkApprovalProtocol, port: number, } | { "type": "mcpToolCall", server: string, toolName: string, connectorId: string | null, connectorName: string | null, toolTitle: string | null, } | { "type": "requestPermissions", reason: string | null, permissions: RequestPermissionProfile, };
|
||||
|
||||
@@ -199,7 +199,8 @@ pub fn build_item_from_guardian_event(
|
||||
}
|
||||
GuardianAssessmentAction::ApplyPatch { .. }
|
||||
| GuardianAssessmentAction::NetworkAccess { .. }
|
||||
| GuardianAssessmentAction::McpToolCall { .. } => None,
|
||||
| GuardianAssessmentAction::McpToolCall { .. }
|
||||
| GuardianAssessmentAction::RequestPermissions { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5142,6 +5142,14 @@ pub struct GuardianMcpToolCallReviewAction {
|
||||
pub tool_title: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct GuardianRequestPermissionsReviewAction {
|
||||
pub reason: Option<String>,
|
||||
pub permissions: RequestPermissionProfile,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[ts(tag = "type", rename_all = "camelCase")]
|
||||
@@ -5185,6 +5193,12 @@ pub enum GuardianApprovalReviewAction {
|
||||
connector_name: Option<String>,
|
||||
tool_title: Option<String>,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
RequestPermissions {
|
||||
reason: Option<String>,
|
||||
permissions: RequestPermissionProfile,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<CoreGuardianAssessmentAction> for GuardianApprovalReviewAction {
|
||||
@@ -5237,6 +5251,13 @@ impl From<CoreGuardianAssessmentAction> for GuardianApprovalReviewAction {
|
||||
connector_name,
|
||||
tool_title,
|
||||
},
|
||||
CoreGuardianAssessmentAction::RequestPermissions {
|
||||
reason,
|
||||
permissions,
|
||||
} => Self::RequestPermissions {
|
||||
reason,
|
||||
permissions: permissions.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5291,6 +5312,13 @@ impl From<GuardianApprovalReviewAction> for CoreGuardianAssessmentAction {
|
||||
connector_name,
|
||||
tool_title,
|
||||
},
|
||||
GuardianApprovalReviewAction::RequestPermissions {
|
||||
reason,
|
||||
permissions,
|
||||
} => Self::RequestPermissions {
|
||||
reason,
|
||||
permissions: permissions.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,15 +26,14 @@ use codex_protocol::user_input::UserInput;
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::time::timeout;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::guardian::GuardianApprovalRequest;
|
||||
use crate::guardian::new_guardian_review_id;
|
||||
use crate::guardian::review_approval_request_with_cancel;
|
||||
use crate::guardian::routes_approval_to_guardian;
|
||||
use crate::guardian::spawn_approval_request_review;
|
||||
use crate::mcp_tool_call::MCP_TOOL_APPROVAL_ACCEPT;
|
||||
use crate::mcp_tool_call::MCP_TOOL_APPROVAL_ACCEPT_FOR_SESSION;
|
||||
use crate::mcp_tool_call::MCP_TOOL_APPROVAL_DECLINE_SYNTHETIC;
|
||||
@@ -457,7 +456,7 @@ async fn handle_exec_approval(
|
||||
} = event;
|
||||
let decision = if routes_approval_to_guardian(parent_ctx) {
|
||||
let review_cancel = cancel_token.child_token();
|
||||
let review_rx = spawn_guardian_review(
|
||||
let review_rx = spawn_approval_request_review(
|
||||
Arc::clone(parent_session),
|
||||
Arc::clone(parent_ctx),
|
||||
new_guardian_review_id(),
|
||||
@@ -565,7 +564,7 @@ async fn handle_patch_approval(
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let review_rx = spawn_guardian_review(
|
||||
let review_rx = spawn_approval_request_review(
|
||||
Arc::clone(parent_session),
|
||||
Arc::clone(parent_ctx),
|
||||
new_guardian_review_id(),
|
||||
@@ -686,7 +685,7 @@ async fn maybe_auto_review_mcp_request_user_input(
|
||||
)
|
||||
.await;
|
||||
let review_cancel = cancel_token.child_token();
|
||||
let review_rx = spawn_guardian_review(
|
||||
let review_rx = spawn_approval_request_review(
|
||||
Arc::clone(parent_session),
|
||||
Arc::clone(parent_ctx),
|
||||
new_guardian_review_id(),
|
||||
@@ -730,36 +729,6 @@ async fn maybe_auto_review_mcp_request_user_input(
|
||||
})
|
||||
}
|
||||
|
||||
fn spawn_guardian_review(
|
||||
session: Arc<Session>,
|
||||
turn: Arc<TurnContext>,
|
||||
review_id: String,
|
||||
request: GuardianApprovalRequest,
|
||||
retry_reason: Option<String>,
|
||||
cancel_token: CancellationToken,
|
||||
) -> oneshot::Receiver<ReviewDecision> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
std::thread::spawn(move || {
|
||||
let Ok(runtime) = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
else {
|
||||
let _ = tx.send(ReviewDecision::Denied);
|
||||
return;
|
||||
};
|
||||
let decision = runtime.block_on(review_approval_request_with_cancel(
|
||||
&session,
|
||||
&turn,
|
||||
review_id,
|
||||
request,
|
||||
retry_reason,
|
||||
cancel_token,
|
||||
));
|
||||
let _ = tx.send(decision);
|
||||
});
|
||||
rx
|
||||
}
|
||||
|
||||
async fn handle_request_permissions(
|
||||
codex: &Codex,
|
||||
parent_session: &Arc<Session>,
|
||||
@@ -772,7 +741,8 @@ async fn handle_request_permissions(
|
||||
reason: event.reason,
|
||||
permissions: event.permissions,
|
||||
};
|
||||
let response_fut = parent_session.request_permissions(parent_ctx, call_id.clone(), args);
|
||||
let response_fut =
|
||||
parent_session.request_permissions(parent_ctx, call_id.clone(), args, cancel_token.clone());
|
||||
let response =
|
||||
await_request_permissions_with_cancel(response_fut, parent_session, &call_id, cancel_token)
|
||||
.await;
|
||||
|
||||
@@ -4,6 +4,7 @@ use codex_protocol::approvals::GuardianAssessmentAction;
|
||||
use codex_protocol::approvals::GuardianCommandSource;
|
||||
use codex_protocol::approvals::NetworkApprovalProtocol;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
@@ -65,6 +66,12 @@ pub(crate) enum GuardianApprovalRequest {
|
||||
tool_description: Option<String>,
|
||||
annotations: Option<GuardianMcpAnnotations>,
|
||||
},
|
||||
RequestPermissions {
|
||||
id: String,
|
||||
turn_id: String,
|
||||
reason: Option<String>,
|
||||
permissions: RequestPermissionProfile,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
@@ -123,6 +130,15 @@ struct McpToolCallApprovalAction<'a> {
|
||||
annotations: Option<&'a GuardianMcpAnnotations>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct RequestPermissionsApprovalAction<'a> {
|
||||
tool: &'static str,
|
||||
turn_id: &'a str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
reason: Option<&'a String>,
|
||||
permissions: &'a RequestPermissionProfile,
|
||||
}
|
||||
|
||||
fn serialize_guardian_action(value: impl Serialize) -> serde_json::Result<Value> {
|
||||
serde_json::to_value(value)
|
||||
}
|
||||
@@ -293,6 +309,17 @@ pub(crate) fn guardian_approval_request_to_json(
|
||||
tool_description: tool_description.as_ref(),
|
||||
annotations: annotations.as_ref(),
|
||||
}),
|
||||
GuardianApprovalRequest::RequestPermissions {
|
||||
id: _,
|
||||
turn_id,
|
||||
reason,
|
||||
permissions,
|
||||
} => serialize_guardian_action(RequestPermissionsApprovalAction {
|
||||
tool: "request_permissions",
|
||||
turn_id,
|
||||
reason: reason.as_ref(),
|
||||
permissions,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +379,14 @@ pub(crate) fn guardian_assessment_action(
|
||||
connector_name: connector_name.clone(),
|
||||
tool_title: tool_title.clone(),
|
||||
},
|
||||
GuardianApprovalRequest::RequestPermissions {
|
||||
reason,
|
||||
permissions,
|
||||
..
|
||||
} => GuardianAssessmentAction::RequestPermissions {
|
||||
reason: reason.clone(),
|
||||
permissions: permissions.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +395,8 @@ pub(crate) fn guardian_request_target_item_id(request: &GuardianApprovalRequest)
|
||||
GuardianApprovalRequest::Shell { id, .. }
|
||||
| GuardianApprovalRequest::ExecCommand { id, .. }
|
||||
| GuardianApprovalRequest::ApplyPatch { id, .. }
|
||||
| GuardianApprovalRequest::McpToolCall { id, .. } => Some(id),
|
||||
| GuardianApprovalRequest::McpToolCall { id, .. }
|
||||
| GuardianApprovalRequest::RequestPermissions { id, .. } => Some(id),
|
||||
GuardianApprovalRequest::NetworkAccess { .. } => None,
|
||||
#[cfg(unix)]
|
||||
GuardianApprovalRequest::Execve { id, .. } => Some(id),
|
||||
@@ -372,7 +408,8 @@ pub(crate) fn guardian_request_turn_id<'a>(
|
||||
default_turn_id: &'a str,
|
||||
) -> &'a str {
|
||||
match request {
|
||||
GuardianApprovalRequest::NetworkAccess { turn_id, .. } => turn_id,
|
||||
GuardianApprovalRequest::NetworkAccess { turn_id, .. }
|
||||
| GuardianApprovalRequest::RequestPermissions { turn_id, .. } => turn_id,
|
||||
GuardianApprovalRequest::Shell { .. }
|
||||
| GuardianApprovalRequest::ExecCommand { .. }
|
||||
| GuardianApprovalRequest::ApplyPatch { .. }
|
||||
|
||||
@@ -31,8 +31,10 @@ pub(crate) use review::guardian_timeout_message;
|
||||
pub(crate) use review::is_guardian_reviewer_source;
|
||||
pub(crate) use review::new_guardian_review_id;
|
||||
pub(crate) use review::review_approval_request;
|
||||
#[cfg(test)]
|
||||
pub(crate) use review::review_approval_request_with_cancel;
|
||||
pub(crate) use review::routes_approval_to_guardian;
|
||||
pub(crate) use review::spawn_approval_request_review;
|
||||
pub(crate) use review_session::GuardianReviewSessionManager;
|
||||
|
||||
const GUARDIAN_PREFERRED_MODEL: &str = "codex-auto-review";
|
||||
|
||||
@@ -11,6 +11,7 @@ use codex_protocol::protocol::GuardianUserAuthorization;
|
||||
use codex_protocol::protocol::ReviewDecision;
|
||||
use codex_protocol::protocol::SubAgentSource;
|
||||
use codex_protocol::protocol::WarningEvent;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::session::session::Session;
|
||||
@@ -89,12 +90,14 @@ fn guardian_risk_level_str(level: GuardianRiskLevel) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this turn should route `on-request` approval prompts through the
|
||||
/// guardian reviewer instead of surfacing them to the user. ARC may still
|
||||
/// block actions earlier in the flow.
|
||||
/// Whether this turn should route allowed approval prompts through the guardian
|
||||
/// reviewer instead of surfacing them to the user. ARC may still block actions
|
||||
/// earlier in the flow.
|
||||
pub(crate) fn routes_approval_to_guardian(turn: &TurnContext) -> bool {
|
||||
turn.approval_policy.value() == AskForApproval::OnRequest
|
||||
&& turn.config.approvals_reviewer == ApprovalsReviewer::GuardianSubagent
|
||||
matches!(
|
||||
turn.approval_policy.value(),
|
||||
AskForApproval::OnRequest | AskForApproval::Granular(_)
|
||||
) && turn.config.approvals_reviewer == ApprovalsReviewer::GuardianSubagent
|
||||
}
|
||||
|
||||
pub(crate) fn is_guardian_reviewer_source(
|
||||
@@ -335,6 +338,36 @@ pub(crate) async fn review_approval_request_with_cancel(
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) fn spawn_approval_request_review(
|
||||
session: Arc<Session>,
|
||||
turn: Arc<TurnContext>,
|
||||
review_id: String,
|
||||
request: GuardianApprovalRequest,
|
||||
retry_reason: Option<String>,
|
||||
cancel_token: CancellationToken,
|
||||
) -> oneshot::Receiver<ReviewDecision> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
std::thread::spawn(move || {
|
||||
let Ok(runtime) = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
else {
|
||||
let _ = tx.send(ReviewDecision::Denied);
|
||||
return;
|
||||
};
|
||||
let decision = runtime.block_on(review_approval_request_with_cancel(
|
||||
&session,
|
||||
&turn,
|
||||
review_id,
|
||||
request,
|
||||
retry_reason,
|
||||
cancel_token,
|
||||
));
|
||||
let _ = tx.send(decision);
|
||||
});
|
||||
rx
|
||||
}
|
||||
|
||||
/// Runs the guardian in a locked-down reusable review session.
|
||||
///
|
||||
/// The guardian itself should not mutate state or trigger further approvals, so
|
||||
|
||||
@@ -28,6 +28,7 @@ use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::GranularApprovalConfig;
|
||||
use codex_protocol::protocol::GuardianAssessmentStatus;
|
||||
use codex_protocol::protocol::GuardianRiskLevel;
|
||||
use codex_protocol::protocol::GuardianUserAuthorization;
|
||||
@@ -741,7 +742,7 @@ fn guardian_timeout_message_distinguishes_timeout_from_policy_denial() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn routes_approval_to_guardian_requires_auto_only_review_policy() {
|
||||
async fn routes_approval_to_guardian_requires_guardian_reviewer() {
|
||||
let (_session, mut turn) = crate::session::tests::make_session_and_context().await;
|
||||
let mut config = (*turn.config).clone();
|
||||
config.approvals_reviewer = ApprovalsReviewer::User;
|
||||
@@ -755,6 +756,25 @@ async fn routes_approval_to_guardian_requires_auto_only_review_policy() {
|
||||
assert!(routes_approval_to_guardian(&turn));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn routes_approval_to_guardian_allows_granular_review_policy() {
|
||||
let (_session, mut turn) = crate::session::tests::make_session_and_context().await;
|
||||
let mut config = (*turn.config).clone();
|
||||
config.approvals_reviewer = ApprovalsReviewer::GuardianSubagent;
|
||||
turn.config = Arc::new(config);
|
||||
turn.approval_policy
|
||||
.set(AskForApproval::Granular(GranularApprovalConfig {
|
||||
sandbox_approval: true,
|
||||
rules: true,
|
||||
skill_approval: true,
|
||||
request_permissions: true,
|
||||
mcp_elicitations: true,
|
||||
}))
|
||||
.expect("test setup should allow updating approval policy");
|
||||
|
||||
assert!(routes_approval_to_guardian(&turn));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_guardian_transcript_reserves_separate_budget_for_tool_evidence() {
|
||||
let repeated = "signal ".repeat(8_000);
|
||||
|
||||
@@ -1894,12 +1894,13 @@ impl Session {
|
||||
}
|
||||
|
||||
pub async fn request_permissions(
|
||||
&self,
|
||||
turn_context: &TurnContext,
|
||||
self: &Arc<Self>,
|
||||
turn_context: &Arc<TurnContext>,
|
||||
call_id: String,
|
||||
args: RequestPermissionsArgs,
|
||||
cancellation_token: CancellationToken,
|
||||
) -> Option<RequestPermissionsResponse> {
|
||||
match turn_context.approval_policy.value() {
|
||||
match turn_context.as_ref().approval_policy.value() {
|
||||
AskForApproval::Never => {
|
||||
return Some(RequestPermissionsResponse {
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
@@ -1920,6 +1921,72 @@ impl Session {
|
||||
| AskForApproval::Granular(_) => {}
|
||||
}
|
||||
|
||||
if crate::guardian::routes_approval_to_guardian(turn_context.as_ref()) {
|
||||
let requested_permissions = args.permissions;
|
||||
let originating_turn_state = {
|
||||
let active = self.active_turn.lock().await;
|
||||
active.as_ref().map(|active| Arc::clone(&active.turn_state))
|
||||
};
|
||||
let review_id = crate::guardian::new_guardian_review_id();
|
||||
let session = Arc::clone(self);
|
||||
let turn = Arc::clone(turn_context);
|
||||
let request = crate::guardian::GuardianApprovalRequest::RequestPermissions {
|
||||
id: call_id,
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
reason: args.reason,
|
||||
permissions: requested_permissions.clone(),
|
||||
};
|
||||
let review_rx = crate::guardian::spawn_approval_request_review(
|
||||
session,
|
||||
turn,
|
||||
review_id,
|
||||
request,
|
||||
/*retry_reason*/ None,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
let decision = tokio::select! {
|
||||
biased;
|
||||
_ = cancellation_token.cancelled() => return None,
|
||||
decision = review_rx => decision.unwrap_or(ReviewDecision::Denied),
|
||||
};
|
||||
let response = match decision {
|
||||
ReviewDecision::Approved | ReviewDecision::ApprovedExecpolicyAmendment { .. } => {
|
||||
RequestPermissionsResponse {
|
||||
permissions: requested_permissions,
|
||||
scope: PermissionGrantScope::Turn,
|
||||
}
|
||||
}
|
||||
ReviewDecision::ApprovedForSession => RequestPermissionsResponse {
|
||||
permissions: requested_permissions,
|
||||
scope: PermissionGrantScope::Session,
|
||||
},
|
||||
ReviewDecision::NetworkPolicyAmendment {
|
||||
network_policy_amendment,
|
||||
} => match network_policy_amendment.action {
|
||||
NetworkPolicyRuleAction::Allow => RequestPermissionsResponse {
|
||||
permissions: requested_permissions,
|
||||
scope: PermissionGrantScope::Turn,
|
||||
},
|
||||
NetworkPolicyRuleAction::Deny => RequestPermissionsResponse {
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
},
|
||||
},
|
||||
ReviewDecision::Abort | ReviewDecision::Denied | ReviewDecision::TimedOut => {
|
||||
RequestPermissionsResponse {
|
||||
permissions: RequestPermissionProfile::default(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
}
|
||||
}
|
||||
};
|
||||
self.record_granted_request_permissions_for_turn(
|
||||
&response,
|
||||
originating_turn_state.as_ref(),
|
||||
)
|
||||
.await;
|
||||
return Some(response);
|
||||
}
|
||||
|
||||
let (tx_response, rx_response) = oneshot::channel();
|
||||
let prev_entry = {
|
||||
let mut active = self.active_turn.lock().await;
|
||||
@@ -1935,17 +2002,25 @@ impl Session {
|
||||
warn!("Overwriting existing pending request_permissions for call_id: {call_id}");
|
||||
}
|
||||
|
||||
// TODO(ccunningham): Support auto-review for request_permissions /
|
||||
// with_additional_permissions. V0 still routes this surface through
|
||||
// the existing manual RequestPermissions event flow.
|
||||
let event = EventMsg::RequestPermissions(RequestPermissionsEvent {
|
||||
call_id,
|
||||
call_id: call_id.clone(),
|
||||
turn_id: turn_context.sub_id.clone(),
|
||||
reason: args.reason,
|
||||
permissions: args.permissions,
|
||||
});
|
||||
self.send_event(turn_context, event).await;
|
||||
rx_response.await.ok()
|
||||
self.send_event(turn_context.as_ref(), event).await;
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = cancellation_token.cancelled() => {
|
||||
let mut active = self.active_turn.lock().await;
|
||||
if let Some(at) = active.as_mut() {
|
||||
let mut ts = at.turn_state.lock().await;
|
||||
let _ = ts.remove_pending_request_permissions(&call_id);
|
||||
}
|
||||
None
|
||||
}
|
||||
response = rx_response => response.ok(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn request_user_input(
|
||||
@@ -2010,31 +2085,24 @@ impl Session {
|
||||
call_id: &str,
|
||||
response: RequestPermissionsResponse,
|
||||
) {
|
||||
let mut granted_for_session = None;
|
||||
let entry = {
|
||||
let (entry, originating_turn_state) = {
|
||||
let mut active = self.active_turn.lock().await;
|
||||
match active.as_mut() {
|
||||
Some(at) => {
|
||||
let mut ts = at.turn_state.lock().await;
|
||||
let entry = ts.remove_pending_request_permissions(call_id);
|
||||
if entry.is_some() && !response.permissions.is_empty() {
|
||||
match response.scope {
|
||||
PermissionGrantScope::Turn => {
|
||||
ts.record_granted_permissions(response.permissions.clone().into());
|
||||
}
|
||||
PermissionGrantScope::Session => {
|
||||
granted_for_session = Some(response.permissions.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
entry
|
||||
let originating_turn_state = entry.as_ref().map(|_| Arc::clone(&at.turn_state));
|
||||
(entry, originating_turn_state)
|
||||
}
|
||||
None => None,
|
||||
None => (None, None),
|
||||
}
|
||||
};
|
||||
if let Some(permissions) = granted_for_session {
|
||||
let mut state = self.state.lock().await;
|
||||
state.record_granted_permissions(permissions.into());
|
||||
if entry.is_some() {
|
||||
self.record_granted_request_permissions_for_turn(
|
||||
&response,
|
||||
originating_turn_state.as_ref(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
match entry {
|
||||
Some(tx_response) => {
|
||||
@@ -2046,6 +2114,28 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
async fn record_granted_request_permissions_for_turn(
|
||||
&self,
|
||||
response: &RequestPermissionsResponse,
|
||||
originating_turn_state: Option<&Arc<Mutex<crate::state::TurnState>>>,
|
||||
) {
|
||||
if response.permissions.is_empty() {
|
||||
return;
|
||||
}
|
||||
match response.scope {
|
||||
PermissionGrantScope::Turn => {
|
||||
if let Some(turn_state) = originating_turn_state {
|
||||
let mut ts = turn_state.lock().await;
|
||||
ts.record_granted_permissions(response.permissions.clone().into());
|
||||
}
|
||||
}
|
||||
PermissionGrantScope::Session => {
|
||||
let mut state = self.state.lock().await;
|
||||
state.record_granted_permissions(response.permissions.clone().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn granted_turn_permissions(&self) -> Option<PermissionProfile> {
|
||||
let active = self.active_turn.lock().await;
|
||||
let active = active.as_ref()?;
|
||||
|
||||
@@ -3438,6 +3438,41 @@ async fn notify_request_permissions_response_ignores_unmatched_call_id() {
|
||||
assert_eq!(session.granted_turn_permissions().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn record_granted_request_permissions_for_turn_uses_originating_turn() {
|
||||
let (session, _turn_context) = make_session_and_context().await;
|
||||
let originating_active_turn = ActiveTurn::default();
|
||||
let originating_turn_state = Arc::clone(&originating_active_turn.turn_state);
|
||||
*session.active_turn.lock().await = Some(originating_active_turn);
|
||||
|
||||
let current_active_turn = ActiveTurn::default();
|
||||
let current_turn_state = Arc::clone(¤t_active_turn.turn_state);
|
||||
*session.active_turn.lock().await = Some(current_active_turn);
|
||||
|
||||
let requested_permissions = RequestPermissionProfile {
|
||||
network: Some(codex_protocol::models::NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..RequestPermissionProfile::default()
|
||||
};
|
||||
session
|
||||
.record_granted_request_permissions_for_turn(
|
||||
&codex_protocol::request_permissions::RequestPermissionsResponse {
|
||||
permissions: requested_permissions.clone(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
},
|
||||
Some(&originating_turn_state),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
originating_turn_state.lock().await.granted_permissions(),
|
||||
Some(requested_permissions.into())
|
||||
);
|
||||
assert_eq!(current_turn_state.lock().await.granted_permissions(), None);
|
||||
assert_eq!(session.granted_turn_permissions().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_permissions_emits_event_when_granular_policy_allows_requests() {
|
||||
let (session, mut turn_context, rx) = make_session_and_context_with_rx().await;
|
||||
@@ -3474,7 +3509,7 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests()
|
||||
async move {
|
||||
session
|
||||
.request_permissions(
|
||||
turn_context.as_ref(),
|
||||
&turn_context,
|
||||
call_id,
|
||||
codex_protocol::request_permissions::RequestPermissionsArgs {
|
||||
reason: Some("need network".to_string()),
|
||||
@@ -3485,6 +3520,7 @@ async fn request_permissions_emits_event_when_granular_policy_allows_requests()
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
},
|
||||
CancellationToken::new(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -3532,7 +3568,7 @@ async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_req
|
||||
let call_id = "call-1".to_string();
|
||||
let response = session
|
||||
.request_permissions(
|
||||
turn_context.as_ref(),
|
||||
&turn_context,
|
||||
call_id,
|
||||
codex_protocol::request_permissions::RequestPermissionsArgs {
|
||||
reason: Some("need network".to_string()),
|
||||
@@ -3543,6 +3579,7 @@ async fn request_permissions_is_auto_denied_when_granular_policy_blocks_tool_req
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
},
|
||||
CancellationToken::new(),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -6360,6 +6397,7 @@ async fn fatal_tool_error_stops_turn_and_reports_error() {
|
||||
.dispatch_tool_call_with_code_mode_result(
|
||||
Arc::clone(&session),
|
||||
Arc::clone(&turn_context),
|
||||
CancellationToken::new(),
|
||||
tracker,
|
||||
call,
|
||||
ToolCallSource::Direct,
|
||||
@@ -6602,6 +6640,7 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::clone(&session),
|
||||
turn: Arc::clone(&turn_context),
|
||||
cancellation_token: CancellationToken::new(),
|
||||
tracker: Arc::clone(&turn_diff_tracker),
|
||||
call_id,
|
||||
tool_name: codex_tools::ToolName::plain(tool_name),
|
||||
@@ -6680,6 +6719,7 @@ async fn unified_exec_rejects_escalated_permissions_when_policy_not_on_request()
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::clone(&session),
|
||||
turn: Arc::clone(&turn_context),
|
||||
cancellation_token: CancellationToken::new(),
|
||||
tracker: Arc::clone(&tracker),
|
||||
call_id: "exec-call".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain("exec_command"),
|
||||
|
||||
@@ -17,6 +17,7 @@ use codex_execpolicy::Evaluation;
|
||||
use codex_execpolicy::RuleMatch;
|
||||
use codex_features::Feature;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::NetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -25,26 +26,214 @@ use codex_protocol::models::function_call_output_content_items_to_text;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile;
|
||||
use codex_protocol::request_permissions::RequestPermissionsArgs;
|
||||
use codex_protocol::request_permissions::RequestPermissionsResponse;
|
||||
use core_test_support::PathExt;
|
||||
use core_test_support::TempDirExt;
|
||||
use core_test_support::codex_linux_sandbox_exe_or_skip;
|
||||
use core_test_support::responses::ev_assistant_message;
|
||||
use core_test_support::responses::ev_completed;
|
||||
use core_test_support::responses::ev_response_created;
|
||||
use core_test_support::responses::mount_response_once;
|
||||
use core_test_support::responses::mount_sse_once;
|
||||
use core_test_support::responses::sse;
|
||||
use core_test_support::responses::sse_response;
|
||||
use core_test_support::responses::start_mock_server;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tempfile::tempdir;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
fn expect_text_output(output: &FunctionToolOutput) -> String {
|
||||
function_call_output_content_items_to_text(&output.body).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_permissions_routes_to_guardian_when_reviewer_is_enabled() {
|
||||
let server = start_mock_server().await;
|
||||
let guardian_request_log = mount_sse_once(
|
||||
&server,
|
||||
sse(vec![
|
||||
ev_response_created("resp-guardian"),
|
||||
ev_assistant_message(
|
||||
"msg-guardian",
|
||||
&serde_json::json!({
|
||||
"risk_level": "low",
|
||||
"user_authorization": "high",
|
||||
"outcome": "allow",
|
||||
"rationale": "The request grants narrowly scoped network access for this turn.",
|
||||
})
|
||||
.to_string(),
|
||||
),
|
||||
ev_completed("resp-guardian"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (mut session, mut turn_context_raw) = make_session_and_context().await;
|
||||
*session.active_turn.lock().await = Some(ActiveTurn::default());
|
||||
turn_context_raw
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("test setup should allow updating approval policy");
|
||||
turn_context_raw
|
||||
.features
|
||||
.enable(Feature::GuardianApproval)
|
||||
.expect("test setup should allow enabling guardian approvals");
|
||||
let mut config = (*turn_context_raw.config).clone();
|
||||
config.approvals_reviewer = ApprovalsReviewer::GuardianSubagent;
|
||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
let config = Arc::new(config);
|
||||
let models_manager = Arc::new(crate::test_support::models_manager_with_provider(
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::clone(&session.services.auth_manager),
|
||||
config.model_provider.clone(),
|
||||
));
|
||||
session.services.models_manager = models_manager;
|
||||
turn_context_raw.config = Arc::clone(&config);
|
||||
turn_context_raw.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context_raw.auth_manager.clone(),
|
||||
);
|
||||
let session = Arc::new(session);
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
|
||||
let requested_permissions = RequestPermissionProfile {
|
||||
network: Some(NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..RequestPermissionProfile::default()
|
||||
};
|
||||
let response = tokio::time::timeout(
|
||||
Duration::from_secs(45),
|
||||
session.request_permissions(
|
||||
&turn_context,
|
||||
"perm-call-1".to_string(),
|
||||
RequestPermissionsArgs {
|
||||
reason: Some("need network".to_string()),
|
||||
permissions: requested_permissions.clone(),
|
||||
},
|
||||
CancellationToken::new(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("request_permissions should not wait for a client approval");
|
||||
|
||||
assert_eq!(
|
||||
response,
|
||||
Some(RequestPermissionsResponse {
|
||||
permissions: requested_permissions.clone(),
|
||||
scope: PermissionGrantScope::Turn,
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
session.granted_turn_permissions().await,
|
||||
Some(requested_permissions.into())
|
||||
);
|
||||
|
||||
let guardian_request = guardian_request_log.single_request();
|
||||
assert_eq!(guardian_request.path(), "/v1/responses");
|
||||
assert!(guardian_request.body_contains_text("request_permissions"));
|
||||
assert!(guardian_request.body_contains_text("need network"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn request_permissions_guardian_review_stops_when_cancelled() {
|
||||
let server = start_mock_server().await;
|
||||
let _guardian_request_log = mount_response_once(
|
||||
&server,
|
||||
sse_response(sse(vec![ev_response_created("resp-guardian-delayed")]))
|
||||
.set_delay(Duration::from_secs(60)),
|
||||
)
|
||||
.await;
|
||||
|
||||
let (mut session, mut turn_context, rx_event) = make_session_and_context_with_rx().await;
|
||||
*session.active_turn.lock().await = Some(ActiveTurn::default());
|
||||
let turn_context_raw = Arc::get_mut(&mut turn_context).expect("single turn context ref");
|
||||
turn_context_raw
|
||||
.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("test setup should allow updating approval policy");
|
||||
turn_context_raw
|
||||
.features
|
||||
.enable(Feature::GuardianApproval)
|
||||
.expect("test setup should allow enabling guardian approvals");
|
||||
let mut config = (*turn_context_raw.config).clone();
|
||||
config.approvals_reviewer = ApprovalsReviewer::GuardianSubagent;
|
||||
config.model_provider.base_url = Some(format!("{}/v1", server.uri()));
|
||||
let config = Arc::new(config);
|
||||
let models_manager = Arc::new(crate::test_support::models_manager_with_provider(
|
||||
config.codex_home.to_path_buf(),
|
||||
Arc::clone(&session.services.auth_manager),
|
||||
config.model_provider.clone(),
|
||||
));
|
||||
Arc::get_mut(&mut session)
|
||||
.expect("single session ref")
|
||||
.services
|
||||
.models_manager = models_manager;
|
||||
turn_context_raw.config = Arc::clone(&config);
|
||||
turn_context_raw.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context_raw.auth_manager.clone(),
|
||||
);
|
||||
|
||||
let requested_permissions = RequestPermissionProfile {
|
||||
network: Some(NetworkPermissions {
|
||||
enabled: Some(true),
|
||||
}),
|
||||
..RequestPermissionProfile::default()
|
||||
};
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let request_handle = tokio::spawn({
|
||||
let session = Arc::clone(&session);
|
||||
let turn_context = Arc::clone(&turn_context);
|
||||
let requested_permissions = requested_permissions.clone();
|
||||
let cancellation_token = cancellation_token.clone();
|
||||
async move {
|
||||
session
|
||||
.request_permissions(
|
||||
&turn_context,
|
||||
"perm-call-cancelled".to_string(),
|
||||
RequestPermissionsArgs {
|
||||
reason: Some("need network".to_string()),
|
||||
permissions: requested_permissions,
|
||||
},
|
||||
cancellation_token,
|
||||
)
|
||||
.await
|
||||
}
|
||||
});
|
||||
|
||||
timeout(Duration::from_secs(5), async {
|
||||
loop {
|
||||
let event = rx_event.recv().await.expect("event channel should be open");
|
||||
if matches!(
|
||||
event.msg,
|
||||
codex_protocol::protocol::EventMsg::GuardianAssessment(_)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("guardian review should start before cancellation");
|
||||
|
||||
cancellation_token.cancel();
|
||||
|
||||
let response = timeout(Duration::from_secs(5), request_handle)
|
||||
.await
|
||||
.expect("request_permissions should stop when cancelled")
|
||||
.expect("request_permissions task should not panic");
|
||||
assert_eq!(response, None);
|
||||
assert_eq!(session.granted_turn_permissions().await, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn guardian_allows_shell_additional_permissions_requests_past_policy_validation() {
|
||||
let server = start_mock_server().await;
|
||||
@@ -146,6 +335,7 @@ async fn guardian_allows_shell_additional_permissions_requests_past_policy_valid
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::clone(&session),
|
||||
turn: Arc::clone(&turn_context),
|
||||
cancellation_token: CancellationToken::new(),
|
||||
tracker: Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())),
|
||||
call_id: "test-call".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain("shell"),
|
||||
@@ -212,6 +402,7 @@ async fn guardian_allows_unified_exec_additional_permissions_requests_past_polic
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::clone(&session),
|
||||
turn: Arc::clone(&turn_context),
|
||||
cancellation_token: CancellationToken::new(),
|
||||
tracker: Arc::clone(&tracker),
|
||||
call_id: "exec-call".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain("exec_command"),
|
||||
@@ -325,6 +516,7 @@ async fn shell_handler_allows_sticky_turn_permissions_without_inline_request_per
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::clone(&session),
|
||||
turn: Arc::clone(&turn_context),
|
||||
cancellation_token: CancellationToken::new(),
|
||||
tracker: Arc::new(tokio::sync::Mutex::new(TurnDiffTracker::new())),
|
||||
call_id: "sticky-turn-grant".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain("shell"),
|
||||
|
||||
@@ -26,6 +26,7 @@ use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
pub type SharedTurnDiffTracker = Arc<Mutex<TurnDiffTracker>>;
|
||||
|
||||
@@ -40,6 +41,7 @@ pub enum ToolCallSource {
|
||||
pub struct ToolInvocation {
|
||||
pub session: Arc<Session>,
|
||||
pub turn: Arc<TurnContext>,
|
||||
pub cancellation_token: CancellationToken,
|
||||
pub tracker: SharedTurnDiffTracker,
|
||||
pub call_id: String,
|
||||
pub tool_name: ToolName,
|
||||
|
||||
@@ -109,6 +109,7 @@ impl ToolHandler for JsReplHandler {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
cancellation_token,
|
||||
tracker,
|
||||
payload,
|
||||
call_id,
|
||||
@@ -134,7 +135,13 @@ impl ToolHandler for JsReplHandler {
|
||||
let started_at = Instant::now();
|
||||
emit_js_repl_exec_begin(session.as_ref(), turn.as_ref(), &call_id).await;
|
||||
let result = manager
|
||||
.execute(Arc::clone(&session), Arc::clone(&turn), tracker, args)
|
||||
.execute_with_cancellation(
|
||||
Arc::clone(&session),
|
||||
Arc::clone(&turn),
|
||||
cancellation_token,
|
||||
tracker,
|
||||
args,
|
||||
)
|
||||
.await;
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
|
||||
@@ -68,6 +68,7 @@ fn invocation(
|
||||
ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
cancellation_token: CancellationToken::new(),
|
||||
tracker: Arc::new(Mutex::new(TurnDiffTracker::default())),
|
||||
call_id: "call-1".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain(tool_name),
|
||||
|
||||
@@ -22,6 +22,7 @@ impl ToolHandler for RequestPermissionsHandler {
|
||||
let ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
cancellation_token,
|
||||
call_id,
|
||||
payload,
|
||||
..
|
||||
@@ -48,7 +49,7 @@ impl ToolHandler for RequestPermissionsHandler {
|
||||
}
|
||||
|
||||
let response = session
|
||||
.request_permissions(turn.as_ref(), call_id, args)
|
||||
.request_permissions(&turn, call_id, args, cancellation_token)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
FunctionCallError::RespondToModel(
|
||||
|
||||
@@ -27,6 +27,7 @@ async fn multi_agent_v2_request_user_input_rejects_subagent_threads() {
|
||||
.handle(ToolInvocation {
|
||||
session: Arc::new(session),
|
||||
turn: Arc::new(turn),
|
||||
cancellation_token: tokio_util::sync::CancellationToken::new(),
|
||||
tracker: Arc::new(Mutex::new(TurnDiffTracker::default())),
|
||||
call_id: "call-1".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain(REQUEST_USER_INPUT_TOOL_NAME),
|
||||
|
||||
@@ -225,6 +225,7 @@ async fn shell_pre_tool_use_payload_uses_joined_command() {
|
||||
handler.pre_tool_use_payload(&ToolInvocation {
|
||||
session: session.into(),
|
||||
turn: turn.into(),
|
||||
cancellation_token: tokio_util::sync::CancellationToken::new(),
|
||||
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
|
||||
call_id: "call-41".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain("shell"),
|
||||
@@ -250,6 +251,7 @@ async fn shell_command_pre_tool_use_payload_uses_raw_command() {
|
||||
handler.pre_tool_use_payload(&ToolInvocation {
|
||||
session: session.into(),
|
||||
turn: turn.into(),
|
||||
cancellation_token: tokio_util::sync::CancellationToken::new(),
|
||||
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
|
||||
call_id: "call-42".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain("shell_command"),
|
||||
|
||||
@@ -210,6 +210,7 @@ async fn exec_command_pre_tool_use_payload_uses_raw_command() {
|
||||
handler.pre_tool_use_payload(&ToolInvocation {
|
||||
session: session.into(),
|
||||
turn: turn.into(),
|
||||
cancellation_token: tokio_util::sync::CancellationToken::new(),
|
||||
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
|
||||
call_id: "call-43".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain("exec_command"),
|
||||
@@ -233,6 +234,7 @@ async fn exec_command_pre_tool_use_payload_skips_write_stdin() {
|
||||
handler.pre_tool_use_payload(&ToolInvocation {
|
||||
session: session.into(),
|
||||
turn: turn.into(),
|
||||
cancellation_token: tokio_util::sync::CancellationToken::new(),
|
||||
tracker: Arc::new(Mutex::new(TurnDiffTracker::new())),
|
||||
call_id: "call-44".to_string(),
|
||||
tool_name: codex_tools::ToolName::plain("write_stdin"),
|
||||
|
||||
@@ -132,6 +132,7 @@ struct KernelState {
|
||||
struct ExecContext {
|
||||
session: Arc<Session>,
|
||||
turn: Arc<TurnContext>,
|
||||
cancellation_token: CancellationToken,
|
||||
tracker: SharedTurnDiffTracker,
|
||||
}
|
||||
|
||||
@@ -836,12 +837,25 @@ impl JsReplManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
session: Arc<Session>,
|
||||
turn: Arc<TurnContext>,
|
||||
tracker: SharedTurnDiffTracker,
|
||||
args: JsReplArgs,
|
||||
) -> Result<JsExecResult, FunctionCallError> {
|
||||
self.execute_with_cancellation(session, turn, CancellationToken::new(), tracker, args)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn execute_with_cancellation(
|
||||
&self,
|
||||
session: Arc<Session>,
|
||||
turn: Arc<TurnContext>,
|
||||
cancellation_token: CancellationToken,
|
||||
tracker: SharedTurnDiffTracker,
|
||||
args: JsReplArgs,
|
||||
) -> Result<JsExecResult, FunctionCallError> {
|
||||
let _permit = self.exec_lock.clone().acquire_owned().await.map_err(|_| {
|
||||
FunctionCallError::RespondToModel("js_repl execution unavailable".to_string())
|
||||
@@ -892,6 +906,7 @@ impl JsReplManager {
|
||||
ExecContext {
|
||||
session: Arc::clone(&session),
|
||||
turn: Arc::clone(&turn),
|
||||
cancellation_token,
|
||||
tracker,
|
||||
},
|
||||
);
|
||||
@@ -1640,12 +1655,14 @@ impl JsReplManager {
|
||||
|
||||
let session = Arc::clone(&exec.session);
|
||||
let turn = Arc::clone(&exec.turn);
|
||||
let cancellation_token = exec.cancellation_token.clone();
|
||||
let tracker = Arc::clone(&exec.tracker);
|
||||
|
||||
match router
|
||||
.dispatch_tool_call_with_code_mode_result(
|
||||
session,
|
||||
turn,
|
||||
cancellation_token,
|
||||
tracker,
|
||||
call,
|
||||
crate::tools::router::ToolCallSource::JsRepl,
|
||||
|
||||
@@ -92,6 +92,7 @@ impl ToolCallRuntime {
|
||||
let turn = Arc::clone(&self.turn_context);
|
||||
let tracker = Arc::clone(&self.tracker);
|
||||
let lock = Arc::clone(&self.parallel_execution);
|
||||
let invocation_cancellation_token = cancellation_token.clone();
|
||||
let started = Instant::now();
|
||||
let display_name = call.tool_name.display();
|
||||
|
||||
@@ -122,6 +123,7 @@ impl ToolCallRuntime {
|
||||
.dispatch_tool_call_with_code_mode_result(
|
||||
session,
|
||||
turn,
|
||||
invocation_cancellation_token,
|
||||
tracker,
|
||||
call.clone(),
|
||||
source,
|
||||
|
||||
@@ -24,6 +24,7 @@ use codex_tools::ToolsConfig;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::instrument;
|
||||
|
||||
pub use crate::tools::context::ToolCallSource;
|
||||
@@ -267,6 +268,7 @@ impl ToolRouter {
|
||||
&self,
|
||||
session: Arc<Session>,
|
||||
turn: Arc<TurnContext>,
|
||||
cancellation_token: CancellationToken,
|
||||
tracker: SharedTurnDiffTracker,
|
||||
call: ToolCall,
|
||||
source: ToolCallSource,
|
||||
@@ -292,6 +294,7 @@ impl ToolRouter {
|
||||
let invocation = ToolInvocation {
|
||||
session,
|
||||
turn,
|
||||
cancellation_token,
|
||||
tracker,
|
||||
call_id,
|
||||
tool_name,
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::tools::context::ToolPayload;
|
||||
use crate::turn_diff_tracker::TurnDiffTracker;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_tools::ToolName;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use super::ToolCall;
|
||||
use super::ToolCallSource;
|
||||
@@ -52,6 +53,7 @@ async fn js_repl_tools_only_blocks_direct_tool_calls() -> anyhow::Result<()> {
|
||||
.dispatch_tool_call_with_code_mode_result(
|
||||
session,
|
||||
turn,
|
||||
CancellationToken::new(),
|
||||
tracker,
|
||||
call,
|
||||
ToolCallSource::Direct,
|
||||
@@ -106,6 +108,7 @@ async fn js_repl_tools_only_allows_js_repl_source_calls() -> anyhow::Result<()>
|
||||
.dispatch_tool_call_with_code_mode_result(
|
||||
session,
|
||||
turn,
|
||||
CancellationToken::new(),
|
||||
tracker,
|
||||
call,
|
||||
ToolCallSource::JsRepl,
|
||||
@@ -155,6 +158,7 @@ async fn js_repl_tools_only_blocks_namespaced_js_repl_tool() -> anyhow::Result<(
|
||||
.dispatch_tool_call_with_code_mode_result(
|
||||
session,
|
||||
turn,
|
||||
CancellationToken::new(),
|
||||
tracker,
|
||||
call,
|
||||
ToolCallSource::Direct,
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::permissions::NetworkSandboxPolicy;
|
||||
use crate::protocol::FileChange;
|
||||
use crate::protocol::ReviewDecision;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::request_permissions::RequestPermissionProfile;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
@@ -163,6 +164,10 @@ pub enum GuardianAssessmentAction {
|
||||
connector_name: Option<String>,
|
||||
tool_title: Option<String>,
|
||||
},
|
||||
RequestPermissions {
|
||||
reason: Option<String>,
|
||||
permissions: RequestPermissionProfile,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
|
||||
|
||||
@@ -3607,6 +3607,14 @@ impl ChatWidget {
|
||||
/// render the final approved/denied history cell when guardian returns a
|
||||
/// decision.
|
||||
fn on_guardian_assessment(&mut self, ev: GuardianAssessmentEvent) {
|
||||
let permission_request_summary = |subject: &str, reason: &Option<String>| {
|
||||
reason
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|reason| !reason.is_empty())
|
||||
.map(|reason| format!("{subject}: {reason}"))
|
||||
.unwrap_or_else(|| subject.to_string())
|
||||
};
|
||||
let guardian_action_summary = |action: &GuardianAssessmentAction| match action {
|
||||
GuardianAssessmentAction::Command { command, .. } => Some(command.clone()),
|
||||
GuardianAssessmentAction::Execve { program, argv, .. } => {
|
||||
@@ -3636,6 +3644,9 @@ impl ChatWidget {
|
||||
let label = connector_name.as_deref().unwrap_or(server.as_str());
|
||||
Some(format!("MCP {tool_name} on {label}"))
|
||||
}
|
||||
GuardianAssessmentAction::RequestPermissions { reason, .. } => {
|
||||
Some(permission_request_summary("permission request", reason))
|
||||
}
|
||||
};
|
||||
let guardian_command = |action: &GuardianAssessmentAction| match action {
|
||||
GuardianAssessmentAction::Command { command, .. } => shlex::split(command)
|
||||
@@ -3649,7 +3660,8 @@ impl ChatWidget {
|
||||
.filter(|command| !command.is_empty()),
|
||||
GuardianAssessmentAction::ApplyPatch { .. }
|
||||
| GuardianAssessmentAction::NetworkAccess { .. }
|
||||
| GuardianAssessmentAction::McpToolCall { .. } => None,
|
||||
| GuardianAssessmentAction::McpToolCall { .. }
|
||||
| GuardianAssessmentAction::RequestPermissions { .. } => None,
|
||||
};
|
||||
|
||||
if ev.status == GuardianAssessmentStatus::InProgress
|
||||
@@ -3740,6 +3752,11 @@ impl ChatWidget {
|
||||
"codex could access {target}"
|
||||
))
|
||||
}
|
||||
GuardianAssessmentAction::RequestPermissions { reason, .. } => {
|
||||
history_cell::new_guardian_timed_out_action_request(
|
||||
permission_request_summary("codex could request permissions", reason),
|
||||
)
|
||||
}
|
||||
GuardianAssessmentAction::Command { .. } => unreachable!(),
|
||||
GuardianAssessmentAction::Execve { .. } => unreachable!(),
|
||||
}
|
||||
@@ -3778,6 +3795,12 @@ impl ChatWidget {
|
||||
"codex to access {target}"
|
||||
))
|
||||
}
|
||||
GuardianAssessmentAction::RequestPermissions { reason, .. } => {
|
||||
history_cell::new_guardian_denied_action_request(permission_request_summary(
|
||||
"codex to request permissions",
|
||||
reason,
|
||||
))
|
||||
}
|
||||
GuardianAssessmentAction::Command { .. } => unreachable!(),
|
||||
GuardianAssessmentAction::Execve { .. } => unreachable!(),
|
||||
}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests/guardian.rs
|
||||
expression: normalize_snapshot_paths(term.backend().vt100().screen().contents())
|
||||
---
|
||||
|
||||
|
||||
|
||||
✔ Request approved for permission request: Need write access for generated
|
||||
report assets.
|
||||
|
||||
• Working (0s • esc to interrupt)
|
||||
|
||||
|
||||
› Ask Codex to do anything
|
||||
|
||||
gpt-5.4 default · /tmp/project
|
||||
@@ -121,6 +121,86 @@ async fn guardian_approved_exec_renders_approved_request() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn guardian_approved_request_permissions_renders_request_summary() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
chat.show_welcome_banner = false;
|
||||
let action = GuardianAssessmentAction::RequestPermissions {
|
||||
reason: Some("Need write access for generated report assets.".to_string()),
|
||||
permissions: RequestPermissionProfile {
|
||||
file_system: Some(FileSystemPermissions::from_read_write_roots(
|
||||
/*read*/ None,
|
||||
Some(vec![test_path_buf("/tmp/reports").abs()]),
|
||||
)),
|
||||
..RequestPermissionProfile::default()
|
||||
},
|
||||
};
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "guardian-in-progress".into(),
|
||||
msg: EventMsg::GuardianAssessment(GuardianAssessmentEvent {
|
||||
id: "guardian-request-permissions".into(),
|
||||
target_item_id: None,
|
||||
turn_id: "turn-1".into(),
|
||||
status: GuardianAssessmentStatus::InProgress,
|
||||
risk_level: None,
|
||||
user_authorization: None,
|
||||
rationale: None,
|
||||
decision_source: None,
|
||||
action: action.clone(),
|
||||
}),
|
||||
});
|
||||
|
||||
let status = chat
|
||||
.bottom_pane
|
||||
.status_widget()
|
||||
.expect("status indicator should be visible");
|
||||
assert_eq!(status.header(), "Reviewing approval request");
|
||||
assert_eq!(
|
||||
status.details(),
|
||||
Some("permission request: Need write access for generated report assets.")
|
||||
);
|
||||
|
||||
chat.handle_codex_event(Event {
|
||||
id: "guardian-assessment".into(),
|
||||
msg: EventMsg::GuardianAssessment(GuardianAssessmentEvent {
|
||||
id: "guardian-request-permissions".into(),
|
||||
target_item_id: None,
|
||||
turn_id: "turn-1".into(),
|
||||
status: GuardianAssessmentStatus::Approved,
|
||||
risk_level: Some(GuardianRiskLevel::Low),
|
||||
user_authorization: Some(GuardianUserAuthorization::High),
|
||||
rationale: Some("Request is scoped to report output.".into()),
|
||||
decision_source: Some(GuardianAssessmentDecisionSource::Agent),
|
||||
action,
|
||||
}),
|
||||
});
|
||||
|
||||
let width: u16 = 110;
|
||||
let ui_height: u16 = chat.desired_height(width);
|
||||
let vt_height: u16 = 12;
|
||||
let viewport = Rect::new(0, vt_height - ui_height - 1, width, ui_height);
|
||||
|
||||
let backend = VT100Backend::new(width, vt_height);
|
||||
let mut term = crate::custom_terminal::Terminal::with_options(backend).expect("terminal");
|
||||
term.set_viewport_area(viewport);
|
||||
|
||||
for lines in drain_insert_history(&mut rx) {
|
||||
crate::insert_history::insert_history_lines(&mut term, lines)
|
||||
.expect("Failed to insert history lines in test");
|
||||
}
|
||||
|
||||
term.draw(|f| {
|
||||
chat.render(f.area(), f.buffer_mut());
|
||||
})
|
||||
.expect("draw guardian request permissions approval history");
|
||||
|
||||
assert_chatwidget_snapshot!(
|
||||
"guardian_approved_request_permissions_renders_request_summary",
|
||||
normalize_snapshot_paths(term.backend().vt100().screen().contents())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn guardian_timed_out_exec_renders_warning_and_timed_out_request() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||
|
||||
Reference in New Issue
Block a user