mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Fix turn context update ordering
This commit is contained in:
@@ -77,14 +77,6 @@
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
|
||||
"type": "string"
|
||||
},
|
||||
"modifications": {
|
||||
"default": [],
|
||||
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ActivePermissionProfileModification"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -92,31 +84,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ActivePermissionProfileModification": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "AdditionalWritableRootActivePermissionProfileModification",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"AdditionalFileSystemPermissions": {
|
||||
"properties": {
|
||||
"entries": {
|
||||
@@ -2211,6 +2178,57 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ManagedFileSystemPermissions": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
],
|
||||
"title": "RestrictedManagedFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedManagedFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedManagedFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedManagedFileSystemPermissions",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"McpServerOauthLoginCompletedNotification": {
|
||||
"properties": {
|
||||
"error": {
|
||||
@@ -2470,6 +2488,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkSandboxPolicy": {
|
||||
"enum": [
|
||||
"enabled",
|
||||
"restricted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NonSteerableTurnKind": {
|
||||
"enum": [
|
||||
"review",
|
||||
@@ -2547,13 +2572,12 @@
|
||||
"PermissionProfile": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Codex owns sandbox construction for this profile.",
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
|
||||
"$ref": "#/definitions/ManagedFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
|
||||
"$ref": "#/definitions/NetworkSandboxPolicy"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
@@ -2572,7 +2596,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Do not apply an outer sandbox.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
@@ -2589,10 +2612,9 @@
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Filesystem isolation is enforced by an external caller.",
|
||||
"properties": {
|
||||
"network": {
|
||||
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
|
||||
"$ref": "#/definitions/NetworkSandboxPolicy"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
@@ -2611,68 +2633,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileFileSystemPermissions": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -10430,6 +10430,57 @@
|
||||
"title": "LogoutAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ManagedFileSystemPermissions": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
],
|
||||
"title": "RestrictedManagedFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedManagedFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedManagedFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedManagedFileSystemPermissions",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ManagedHooksRequirements": {
|
||||
"properties": {
|
||||
"PermissionRequest": {
|
||||
@@ -11541,6 +11592,13 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkSandboxPolicy": {
|
||||
"enum": [
|
||||
"enabled",
|
||||
"restricted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkUnixSocketPermission": {
|
||||
"enum": [
|
||||
"allow",
|
||||
@@ -11639,6 +11697,70 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfile": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/v2/ManagedFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/v2/NetworkSandboxPolicy"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"managed"
|
||||
],
|
||||
"title": "ManagedPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fileSystem",
|
||||
"network",
|
||||
"type"
|
||||
],
|
||||
"title": "ManagedPermissionProfile",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"disabled"
|
||||
],
|
||||
"title": "DisabledPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "DisabledPermissionProfile",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"network": {
|
||||
"$ref": "#/definitions/v2/NetworkSandboxPolicy"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"external"
|
||||
],
|
||||
"title": "ExternalPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"network",
|
||||
"type"
|
||||
],
|
||||
"title": "ExternalPermissionProfile",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -6959,6 +6959,57 @@
|
||||
"title": "LogoutAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"ManagedFileSystemPermissions": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"entries": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/FileSystemSandboxEntry"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"globScanMaxDepth": {
|
||||
"format": "uint",
|
||||
"minimum": 1.0,
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"restricted"
|
||||
],
|
||||
"title": "RestrictedManagedFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entries",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedManagedFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedManagedFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedManagedFileSystemPermissions",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ManagedHooksRequirements": {
|
||||
"properties": {
|
||||
"PermissionRequest": {
|
||||
@@ -8070,6 +8121,13 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"NetworkSandboxPolicy": {
|
||||
"enum": [
|
||||
"enabled",
|
||||
"restricted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkUnixSocketPermission": {
|
||||
"enum": [
|
||||
"allow",
|
||||
@@ -8168,6 +8226,70 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfile": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/ManagedFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/NetworkSandboxPolicy"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"managed"
|
||||
],
|
||||
"title": "ManagedPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fileSystem",
|
||||
"network",
|
||||
"type"
|
||||
],
|
||||
"title": "ManagedPermissionProfile",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"disabled"
|
||||
],
|
||||
"title": "DisabledPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "DisabledPermissionProfile",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"network": {
|
||||
"$ref": "#/definitions/NetworkSandboxPolicy"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"external"
|
||||
],
|
||||
"title": "ExternalPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"network",
|
||||
"type"
|
||||
],
|
||||
"title": "ExternalPermissionProfile",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
"none",
|
||||
|
||||
@@ -18,14 +18,6 @@
|
||||
"id": {
|
||||
"description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.<id>]` profile.",
|
||||
"type": "string"
|
||||
},
|
||||
"modifications": {
|
||||
"default": [],
|
||||
"description": "Bounded user-requested modifications applied on top of the named profile, if any.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/ActivePermissionProfileModification"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -33,31 +25,6 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ActivePermissionProfileModification": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Additional concrete directory that should be writable.",
|
||||
"properties": {
|
||||
"path": {
|
||||
"$ref": "#/definitions/AbsolutePathBuf"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"additionalWritableRoot"
|
||||
],
|
||||
"title": "AdditionalWritableRootActivePermissionProfileModificationType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path",
|
||||
"type"
|
||||
],
|
||||
"title": "AdditionalWritableRootActivePermissionProfileModification",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApprovalsReviewer": {
|
||||
"description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
||||
"enum": [
|
||||
@@ -329,89 +296,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ModeKind": {
|
||||
"description": "Initial collaboration mode to use when the TUI starts.",
|
||||
"enum": [
|
||||
"plan",
|
||||
"default"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkAccess": {
|
||||
"enum": [
|
||||
"restricted",
|
||||
"enabled"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PermissionProfile": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Codex owns sandbox construction for this profile.",
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/PermissionProfileFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"managed"
|
||||
],
|
||||
"title": "ManagedPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fileSystem",
|
||||
"network",
|
||||
"type"
|
||||
],
|
||||
"title": "ManagedPermissionProfile",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Do not apply an outer sandbox.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"disabled"
|
||||
],
|
||||
"title": "DisabledPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "DisabledPermissionProfile",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Filesystem isolation is enforced by an external caller.",
|
||||
"properties": {
|
||||
"network": {
|
||||
"$ref": "#/definitions/PermissionProfileNetworkPermissions"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"external"
|
||||
],
|
||||
"title": "ExternalPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"network",
|
||||
"type"
|
||||
],
|
||||
"title": "ExternalPermissionProfile",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileFileSystemPermissions": {
|
||||
"ManagedFileSystemPermissions": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
@@ -433,7 +318,7 @@
|
||||
"enum": [
|
||||
"restricted"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "RestrictedManagedFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -441,7 +326,7 @@
|
||||
"entries",
|
||||
"type"
|
||||
],
|
||||
"title": "RestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "RestrictedManagedFileSystemPermissions",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
@@ -450,28 +335,103 @@
|
||||
"enum": [
|
||||
"unrestricted"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissionsType",
|
||||
"title": "UnrestrictedManagedFileSystemPermissionsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "UnrestrictedPermissionProfileFileSystemPermissions",
|
||||
"title": "UnrestrictedManagedFileSystemPermissions",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"PermissionProfileNetworkPermissions": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"ModeKind": {
|
||||
"description": "Initial collaboration mode to use when the TUI starts.",
|
||||
"enum": [
|
||||
"plan",
|
||||
"default"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkAccess": {
|
||||
"enum": [
|
||||
"restricted",
|
||||
"enabled"
|
||||
],
|
||||
"type": "object"
|
||||
"type": "string"
|
||||
},
|
||||
"NetworkSandboxPolicy": {
|
||||
"enum": [
|
||||
"enabled",
|
||||
"restricted"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PermissionProfile": {
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"fileSystem": {
|
||||
"$ref": "#/definitions/ManagedFileSystemPermissions"
|
||||
},
|
||||
"network": {
|
||||
"$ref": "#/definitions/NetworkSandboxPolicy"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"managed"
|
||||
],
|
||||
"title": "ManagedPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fileSystem",
|
||||
"network",
|
||||
"type"
|
||||
],
|
||||
"title": "ManagedPermissionProfile",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"disabled"
|
||||
],
|
||||
"title": "DisabledPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "DisabledPermissionProfile",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"network": {
|
||||
"$ref": "#/definitions/NetworkSandboxPolicy"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"external"
|
||||
],
|
||||
"title": "ExternalPermissionProfileType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"network",
|
||||
"type"
|
||||
],
|
||||
"title": "ExternalPermissionProfile",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Personality": {
|
||||
"enum": [
|
||||
|
||||
6
codex-rs/app-server-protocol/schema/typescript/v2/ManagedFileSystemPermissions.ts
generated
Normal file
6
codex-rs/app-server-protocol/schema/typescript/v2/ManagedFileSystemPermissions.ts
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { FileSystemSandboxEntry } from "./FileSystemSandboxEntry";
|
||||
|
||||
export type ManagedFileSystemPermissions = { "type": "restricted", entries: Array<FileSystemSandboxEntry>, globScanMaxDepth?: number, } | { "type": "unrestricted" };
|
||||
5
codex-rs/app-server-protocol/schema/typescript/v2/NetworkSandboxPolicy.ts
generated
Normal file
5
codex-rs/app-server-protocol/schema/typescript/v2/NetworkSandboxPolicy.ts
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type NetworkSandboxPolicy = "enabled" | "restricted";
|
||||
7
codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfile.ts
generated
Normal file
7
codex-rs/app-server-protocol/schema/typescript/v2/PermissionProfile.ts
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ManagedFileSystemPermissions } from "./ManagedFileSystemPermissions";
|
||||
import type { NetworkSandboxPolicy } from "./NetworkSandboxPolicy";
|
||||
|
||||
export type PermissionProfile = { "type": "managed", fileSystem: ManagedFileSystemPermissions, network: NetworkSandboxPolicy, } | { "type": "disabled" } | { "type": "external", network: NetworkSandboxPolicy, };
|
||||
@@ -173,6 +173,7 @@ export type { ListMcpServerStatusResponse } from "./ListMcpServerStatusResponse"
|
||||
export type { LoginAccountParams } from "./LoginAccountParams";
|
||||
export type { LoginAccountResponse } from "./LoginAccountResponse";
|
||||
export type { LogoutAccountResponse } from "./LogoutAccountResponse";
|
||||
export type { ManagedFileSystemPermissions } from "./ManagedFileSystemPermissions";
|
||||
export type { ManagedHooksRequirements } from "./ManagedHooksRequirements";
|
||||
export type { MarketplaceAddParams } from "./MarketplaceAddParams";
|
||||
export type { MarketplaceAddResponse } from "./MarketplaceAddResponse";
|
||||
@@ -249,12 +250,14 @@ export type { NetworkDomainPermission } from "./NetworkDomainPermission";
|
||||
export type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
|
||||
export type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction";
|
||||
export type { NetworkRequirements } from "./NetworkRequirements";
|
||||
export type { NetworkSandboxPolicy } from "./NetworkSandboxPolicy";
|
||||
export type { NetworkUnixSocketPermission } from "./NetworkUnixSocketPermission";
|
||||
export type { NonSteerableTurnKind } from "./NonSteerableTurnKind";
|
||||
export type { OverriddenMetadata } from "./OverriddenMetadata";
|
||||
export type { PatchApplyStatus } from "./PatchApplyStatus";
|
||||
export type { PatchChangeKind } from "./PatchChangeKind";
|
||||
export type { PermissionGrantScope } from "./PermissionGrantScope";
|
||||
export type { PermissionProfile } from "./PermissionProfile";
|
||||
export type { PermissionsRequestApprovalParams } from "./PermissionsRequestApprovalParams";
|
||||
export type { PermissionsRequestApprovalResponse } from "./PermissionsRequestApprovalResponse";
|
||||
export type { PlanDeltaNotification } from "./PlanDeltaNotification";
|
||||
|
||||
@@ -1458,6 +1458,7 @@ server_notification_definitions! {
|
||||
Error => "error" (v2::ErrorNotification),
|
||||
ThreadStarted => "thread/started" (v2::ThreadStartedNotification),
|
||||
ThreadStatusChanged => "thread/status/changed" (v2::ThreadStatusChangedNotification),
|
||||
#[experimental("thread/turnContext/updated")]
|
||||
ThreadTurnContextUpdated => "thread/turnContext/updated" (v2::ThreadTurnContextUpdatedNotification),
|
||||
ThreadArchived => "thread/archived" (v2::ThreadArchivedNotification),
|
||||
ThreadUnarchived => "thread/unarchived" (v2::ThreadUnarchivedNotification),
|
||||
|
||||
@@ -7,11 +7,14 @@ use codex_protocol::approvals::NetworkPolicyRuleAction as CoreNetworkPolicyRuleA
|
||||
use codex_protocol::models::ActivePermissionProfile as CoreActivePermissionProfile;
|
||||
use codex_protocol::models::AdditionalPermissionProfile as CoreAdditionalPermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
|
||||
use codex_protocol::models::ManagedFileSystemPermissions as CoreManagedFileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
|
||||
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode as CoreFileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath as CoreFileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry as CoreFileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath as CoreFileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy as CoreNetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::NetworkAccess as CoreNetworkAccess;
|
||||
use codex_protocol::request_permissions::PermissionGrantScope as CorePermissionGrantScope;
|
||||
use codex_protocol::request_permissions::RequestPermissionProfile as CoreRequestPermissionProfile;
|
||||
@@ -184,6 +187,13 @@ v2_enum_from_core!(
|
||||
}
|
||||
);
|
||||
|
||||
v2_enum_from_core!(
|
||||
pub enum NetworkSandboxPolicy from CoreNetworkSandboxPolicy {
|
||||
Enabled,
|
||||
Restricted
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
#[ts(tag = "kind")]
|
||||
@@ -289,6 +299,113 @@ impl From<FileSystemSandboxEntry> for CoreFileSystemSandboxEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ManagedFileSystemPermissions {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Restricted {
|
||||
entries: Vec<FileSystemSandboxEntry>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
glob_scan_max_depth: Option<NonZeroUsize>,
|
||||
},
|
||||
Unrestricted,
|
||||
}
|
||||
|
||||
impl From<CoreManagedFileSystemPermissions> for ManagedFileSystemPermissions {
|
||||
fn from(value: CoreManagedFileSystemPermissions) -> Self {
|
||||
match value {
|
||||
CoreManagedFileSystemPermissions::Restricted {
|
||||
entries,
|
||||
glob_scan_max_depth,
|
||||
} => Self::Restricted {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(FileSystemSandboxEntry::from)
|
||||
.collect(),
|
||||
glob_scan_max_depth,
|
||||
},
|
||||
CoreManagedFileSystemPermissions::Unrestricted => Self::Unrestricted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ManagedFileSystemPermissions> for CoreManagedFileSystemPermissions {
|
||||
fn from(value: ManagedFileSystemPermissions) -> Self {
|
||||
match value {
|
||||
ManagedFileSystemPermissions::Restricted {
|
||||
entries,
|
||||
glob_scan_max_depth,
|
||||
} => Self::Restricted {
|
||||
entries: entries
|
||||
.into_iter()
|
||||
.map(CoreFileSystemSandboxEntry::from)
|
||||
.collect(),
|
||||
glob_scan_max_depth,
|
||||
},
|
||||
ManagedFileSystemPermissions::Unrestricted => Self::Unrestricted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[ts(tag = "type")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PermissionProfile {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
Managed {
|
||||
file_system: ManagedFileSystemPermissions,
|
||||
network: NetworkSandboxPolicy,
|
||||
},
|
||||
Disabled,
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(rename_all = "camelCase")]
|
||||
External {
|
||||
network: NetworkSandboxPolicy,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<CorePermissionProfile> for PermissionProfile {
|
||||
fn from(value: CorePermissionProfile) -> Self {
|
||||
match value {
|
||||
CorePermissionProfile::Managed {
|
||||
file_system,
|
||||
network,
|
||||
} => Self::Managed {
|
||||
file_system: file_system.into(),
|
||||
network: network.into(),
|
||||
},
|
||||
CorePermissionProfile::Disabled => Self::Disabled,
|
||||
CorePermissionProfile::External { network } => Self::External {
|
||||
network: network.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PermissionProfile> for CorePermissionProfile {
|
||||
fn from(value: PermissionProfile) -> Self {
|
||||
match value {
|
||||
PermissionProfile::Managed {
|
||||
file_system,
|
||||
network,
|
||||
} => Self::Managed {
|
||||
file_system: file_system.into(),
|
||||
network: network.to_core(),
|
||||
},
|
||||
PermissionProfile::Disabled => Self::Disabled,
|
||||
PermissionProfile::External { network } => Self::External {
|
||||
network: network.to_core(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::ActivePermissionProfile;
|
||||
use super::ApprovalsReviewer;
|
||||
use super::AskForApproval;
|
||||
use super::PermissionProfile;
|
||||
use super::PermissionProfileSelectionParams;
|
||||
use super::SandboxMode;
|
||||
use super::SandboxPolicy;
|
||||
@@ -15,7 +16,6 @@ use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::config_types::CollaborationMode;
|
||||
use codex_protocol::config_types::Personality;
|
||||
use codex_protocol::config_types::ReasoningSummary;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::protocol::ThreadGoalStatus as CoreThreadGoalStatus;
|
||||
|
||||
@@ -2691,8 +2691,17 @@ impl ThreadRequestProcessor {
|
||||
|
||||
let thread_state = self
|
||||
.thread_state_manager
|
||||
.thread_state(existing_thread_id)
|
||||
.await;
|
||||
.try_ensure_connection_subscribed(
|
||||
existing_thread_id,
|
||||
request_id.connection_id,
|
||||
/*experimental_raw_events*/ false,
|
||||
)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
internal_error(format!(
|
||||
"failed to subscribe connection for running thread {existing_thread_id}"
|
||||
))
|
||||
})?;
|
||||
self.ensure_listener_task_running(
|
||||
existing_thread_id,
|
||||
existing_thread.clone(),
|
||||
|
||||
@@ -85,6 +85,35 @@ fn resolve_runtime_workspace_roots(
|
||||
resolved_roots
|
||||
}
|
||||
|
||||
fn effective_workspace_roots(
|
||||
base_snapshot: &ThreadConfigSnapshot,
|
||||
effective_cwd: &AbsolutePathBuf,
|
||||
runtime_workspace_roots: Option<&[AbsolutePathBuf]>,
|
||||
) -> Vec<AbsolutePathBuf> {
|
||||
if let Some(workspace_roots) = runtime_workspace_roots {
|
||||
return workspace_roots.to_vec();
|
||||
}
|
||||
|
||||
if effective_cwd != &base_snapshot.cwd
|
||||
&& base_snapshot.workspace_roots.contains(&base_snapshot.cwd)
|
||||
{
|
||||
let mut retargeted_roots = Vec::with_capacity(base_snapshot.workspace_roots.len());
|
||||
for root in &base_snapshot.workspace_roots {
|
||||
let root = if root == &base_snapshot.cwd {
|
||||
effective_cwd.clone()
|
||||
} else {
|
||||
root.clone()
|
||||
};
|
||||
if !retargeted_roots.contains(&root) {
|
||||
retargeted_roots.push(root);
|
||||
}
|
||||
}
|
||||
retargeted_roots
|
||||
} else {
|
||||
base_snapshot.workspace_roots.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl TurnRequestProcessor {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
@@ -411,22 +440,22 @@ impl TurnRequestProcessor {
|
||||
}
|
||||
|
||||
let cwd = request.cwd;
|
||||
let effective_cwd = cwd
|
||||
.as_ref()
|
||||
.map(|cwd| AbsolutePathBuf::resolve_path_against_base(cwd, base_snapshot.cwd.as_path()))
|
||||
.unwrap_or_else(|| base_snapshot.cwd.clone());
|
||||
let runtime_workspace_roots =
|
||||
request
|
||||
.runtime_workspace_roots
|
||||
.clone()
|
||||
.map(|workspace_roots| {
|
||||
let base_cwd = cwd
|
||||
.as_ref()
|
||||
.map(|cwd| {
|
||||
AbsolutePathBuf::resolve_path_against_base(
|
||||
cwd,
|
||||
base_snapshot.cwd.as_path(),
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| base_snapshot.cwd.clone());
|
||||
resolve_runtime_workspace_roots(workspace_roots, &base_cwd)
|
||||
resolve_runtime_workspace_roots(workspace_roots, &effective_cwd)
|
||||
});
|
||||
let effective_workspace_roots = effective_workspace_roots(
|
||||
base_snapshot,
|
||||
&effective_cwd,
|
||||
runtime_workspace_roots.as_deref(),
|
||||
);
|
||||
let approval_policy = request.approval_policy.map(AskForApproval::to_core);
|
||||
let approvals_reviewer = request
|
||||
.approvals_reviewer
|
||||
@@ -436,15 +465,12 @@ impl TurnRequestProcessor {
|
||||
if let Some(permissions) = request.permissions {
|
||||
let mut overrides = ConfigOverrides {
|
||||
cwd: cwd.clone(),
|
||||
workspace_roots: Some(request.runtime_workspace_roots.clone().unwrap_or_else(
|
||||
|| {
|
||||
base_snapshot
|
||||
.workspace_roots
|
||||
.iter()
|
||||
.map(AbsolutePathBuf::to_path_buf)
|
||||
.collect()
|
||||
},
|
||||
)),
|
||||
workspace_roots: Some(
|
||||
effective_workspace_roots
|
||||
.iter()
|
||||
.map(AbsolutePathBuf::to_path_buf)
|
||||
.collect(),
|
||||
),
|
||||
codex_linux_sandbox_exe: self.arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: self.arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
..Default::default()
|
||||
@@ -507,7 +533,8 @@ impl TurnRequestProcessor {
|
||||
|
||||
async fn maybe_emit_turn_context_updated(
|
||||
&self,
|
||||
thread_id: &str,
|
||||
thread_id: ThreadId,
|
||||
api_thread_id: &str,
|
||||
before: &ThreadTurnContext,
|
||||
after: ThreadTurnContext,
|
||||
) {
|
||||
@@ -515,14 +542,60 @@ impl TurnRequestProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
self.outgoing
|
||||
.send_server_notification(ServerNotification::ThreadTurnContextUpdated(
|
||||
ThreadTurnContextUpdatedNotification {
|
||||
thread_id: thread_id.to_string(),
|
||||
turn_context: after,
|
||||
},
|
||||
))
|
||||
let connection_ids = self
|
||||
.thread_state_manager
|
||||
.subscribed_connection_ids(thread_id)
|
||||
.await;
|
||||
if connection_ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.outgoing
|
||||
.send_server_notification_to_connections(
|
||||
&connection_ids,
|
||||
ServerNotification::ThreadTurnContextUpdated(
|
||||
ThreadTurnContextUpdatedNotification {
|
||||
thread_id: api_thread_id.to_string(),
|
||||
turn_context: after,
|
||||
},
|
||||
),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn wait_for_pending_turn_contexts(
|
||||
&self,
|
||||
thread_id: ThreadId,
|
||||
) -> Result<(), JSONRPCErrorError> {
|
||||
let pending = {
|
||||
let thread_state = self.thread_state_manager.thread_state(thread_id).await;
|
||||
let mut thread_state = thread_state.lock().await;
|
||||
thread_state.track_current_pending_turn_contexts()
|
||||
};
|
||||
|
||||
for turn_context_applied in pending {
|
||||
match tokio::time::timeout(TURN_CONTEXT_OVERRIDE_ACK_TIMEOUT, turn_context_applied)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(Ok(_))) => {}
|
||||
Ok(Ok(Err(err))) => {
|
||||
return Err(internal_error(format!(
|
||||
"failed to apply pending turn context override: {err}"
|
||||
)));
|
||||
}
|
||||
Ok(Err(_)) => {
|
||||
return Err(internal_error(
|
||||
"pending turn context override waiter was cancelled".to_string(),
|
||||
));
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(internal_error(
|
||||
"timed out waiting for pending turn context overrides to apply".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn turn_start_inner(
|
||||
@@ -648,6 +721,7 @@ impl TurnRequestProcessor {
|
||||
let after_turn_context = thread_turn_context_from_applied_event(&payload);
|
||||
processor
|
||||
.maybe_emit_turn_context_updated(
|
||||
thread_id,
|
||||
&api_thread_id,
|
||||
&before_turn_context,
|
||||
after_turn_context,
|
||||
@@ -713,6 +787,7 @@ impl TurnRequestProcessor {
|
||||
.inspect_err(|error| {
|
||||
self.track_error_response(request_id, error, /*error_type*/ None);
|
||||
})?;
|
||||
self.wait_for_pending_turn_contexts(thread_id).await?;
|
||||
let before_snapshot = thread.config_snapshot().await;
|
||||
let before_turn_context = thread_turn_context_from_snapshot(&before_snapshot);
|
||||
let resolved_overrides = self
|
||||
@@ -783,6 +858,7 @@ impl TurnRequestProcessor {
|
||||
before_turn_context.clone()
|
||||
};
|
||||
self.maybe_emit_turn_context_updated(
|
||||
thread_id,
|
||||
¶ms.thread_id,
|
||||
&before_turn_context,
|
||||
after_turn_context.clone(),
|
||||
|
||||
@@ -147,6 +147,18 @@ impl ThreadState {
|
||||
rx
|
||||
}
|
||||
|
||||
pub(crate) fn track_current_pending_turn_contexts(
|
||||
&mut self,
|
||||
) -> Vec<oneshot::Receiver<TurnContextAck>> {
|
||||
let mut receivers = Vec::with_capacity(self.pending_turn_context_waiters.len());
|
||||
for waiters in self.pending_turn_context_waiters.values_mut() {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
waiters.push(tx);
|
||||
receivers.push(rx);
|
||||
}
|
||||
receivers
|
||||
}
|
||||
|
||||
pub(crate) fn cancel_pending_turn_context(&mut self, submission_id: &str) {
|
||||
self.pending_turn_context_waiters.remove(submission_id);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use app_test_support::to_response;
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::InitializeCapabilities;
|
||||
use codex_app_server_protocol::InitializeParams;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCMessage;
|
||||
@@ -107,7 +108,7 @@ async fn websocket_transport_routes_per_connection_handshake_and_responses() ->
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn websocket_turn_context_updates_broadcast_to_other_connections() -> Result<()> {
|
||||
async fn websocket_turn_context_updates_stay_on_subscribed_connections() -> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(Vec::new()).await;
|
||||
let codex_home = TempDir::new()?;
|
||||
create_config_toml(codex_home.path(), &server.uri(), "never")?;
|
||||
@@ -117,9 +118,9 @@ async fn websocket_turn_context_updates_broadcast_to_other_connections() -> Resu
|
||||
let mut ws1 = connect_websocket(bind_addr).await?;
|
||||
let mut ws2 = connect_websocket(bind_addr).await?;
|
||||
|
||||
send_initialize_request(&mut ws1, /*id*/ 1, "ws_context_owner").await?;
|
||||
send_initialize_experimental_request(&mut ws1, /*id*/ 1, "ws_context_owner").await?;
|
||||
read_response_for_id(&mut ws1, /*id*/ 1).await?;
|
||||
send_initialize_request(&mut ws2, /*id*/ 2, "ws_context_peer").await?;
|
||||
send_initialize_experimental_request(&mut ws2, /*id*/ 2, "ws_context_peer").await?;
|
||||
read_response_for_id(&mut ws2, /*id*/ 2).await?;
|
||||
|
||||
let thread_id = start_thread(&mut ws1, /*id*/ 3).await?;
|
||||
@@ -141,21 +142,19 @@ async fn websocket_turn_context_updates_broadcast_to_other_connections() -> Resu
|
||||
"thread/turnContext/updated",
|
||||
)
|
||||
.await?;
|
||||
let peer_notification =
|
||||
read_notification_for_method(&mut ws2, "thread/turnContext/updated").await?;
|
||||
|
||||
let ServerNotification::ThreadTurnContextUpdated(caller) =
|
||||
ServerNotification::try_from(caller_notification)?
|
||||
else {
|
||||
bail!("expected caller thread/turnContext/updated notification");
|
||||
};
|
||||
let ServerNotification::ThreadTurnContextUpdated(peer) =
|
||||
ServerNotification::try_from(peer_notification)?
|
||||
else {
|
||||
bail!("expected peer thread/turnContext/updated notification");
|
||||
};
|
||||
assert_eq!(caller.thread_id, thread_id);
|
||||
assert_eq!(peer, caller);
|
||||
assert_no_notification_for_method(
|
||||
&mut ws2,
|
||||
"thread/turnContext/updated",
|
||||
Duration::from_millis(250),
|
||||
)
|
||||
.await?;
|
||||
|
||||
process
|
||||
.kill()
|
||||
@@ -651,6 +650,32 @@ pub(super) async fn send_initialize_request(
|
||||
stream: &mut WsClient,
|
||||
id: i64,
|
||||
client_name: &str,
|
||||
) -> Result<()> {
|
||||
send_initialize_request_with_capabilities(stream, id, client_name, None).await
|
||||
}
|
||||
|
||||
async fn send_initialize_experimental_request(
|
||||
stream: &mut WsClient,
|
||||
id: i64,
|
||||
client_name: &str,
|
||||
) -> Result<()> {
|
||||
send_initialize_request_with_capabilities(
|
||||
stream,
|
||||
id,
|
||||
client_name,
|
||||
Some(InitializeCapabilities {
|
||||
experimental_api: true,
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_initialize_request_with_capabilities(
|
||||
stream: &mut WsClient,
|
||||
id: i64,
|
||||
client_name: &str,
|
||||
capabilities: Option<InitializeCapabilities>,
|
||||
) -> Result<()> {
|
||||
let params = InitializeParams {
|
||||
client_info: ClientInfo {
|
||||
@@ -658,7 +683,7 @@ pub(super) async fn send_initialize_request(
|
||||
title: Some("WebSocket Test Client".to_string()),
|
||||
version: "0.1.0".to_string(),
|
||||
},
|
||||
capabilities: None,
|
||||
capabilities,
|
||||
};
|
||||
send_request(
|
||||
stream,
|
||||
@@ -883,6 +908,44 @@ pub(super) async fn assert_no_message(stream: &mut WsClient, wait_for: Duration)
|
||||
}
|
||||
}
|
||||
|
||||
async fn assert_no_notification_for_method(
|
||||
stream: &mut WsClient,
|
||||
method: &str,
|
||||
wait_for: Duration,
|
||||
) -> Result<()> {
|
||||
let deadline = Instant::now() + wait_for;
|
||||
loop {
|
||||
let remaining = deadline.saturating_duration_since(Instant::now());
|
||||
if remaining.is_zero() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let frame = match timeout(remaining, stream.next()).await {
|
||||
Ok(Some(Ok(frame))) => frame,
|
||||
Ok(Some(Err(err))) => bail!("unexpected websocket read error: {err}"),
|
||||
Ok(None) => bail!("websocket closed unexpectedly while waiting for notification"),
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
match frame {
|
||||
WebSocketMessage::Text(text) => {
|
||||
let message: JSONRPCMessage = serde_json::from_str(text.as_ref())?;
|
||||
if let JSONRPCMessage::Notification(notification) = message
|
||||
&& notification.method == method
|
||||
{
|
||||
bail!("unexpected notification for method `{method}`");
|
||||
}
|
||||
}
|
||||
WebSocketMessage::Ping(payload) => {
|
||||
stream.send(WebSocketMessage::Pong(payload)).await?;
|
||||
}
|
||||
WebSocketMessage::Pong(_) | WebSocketMessage::Frame(_) => {}
|
||||
WebSocketMessage::Close(frame) => bail!("websocket closed unexpectedly: {frame:?}"),
|
||||
WebSocketMessage::Binary(_) => bail!("unexpected binary websocket frame"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn create_config_toml(
|
||||
codex_home: &Path,
|
||||
server_uri: &str,
|
||||
|
||||
@@ -7,6 +7,7 @@ use app_test_support::to_response;
|
||||
use app_test_support::write_mock_responses_config_toml;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::PermissionProfile;
|
||||
use codex_app_server_protocol::PermissionProfileSelectionParams;
|
||||
use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::SandboxPolicy;
|
||||
@@ -20,7 +21,11 @@ use codex_app_server_protocol::ThreadTurnContextUpdatedNotification;
|
||||
use codex_app_server_protocol::TurnStartParams;
|
||||
use codex_app_server_protocol::UserInput as V2UserInput;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
|
||||
use codex_protocol::openai_models::ReasoningEffort;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::BTreeMap;
|
||||
use tempfile::TempDir;
|
||||
@@ -74,6 +79,31 @@ async fn read_turn_context_updated(
|
||||
Ok(notification)
|
||||
}
|
||||
|
||||
fn assert_permission_profile_write_root(
|
||||
permission_profile: &PermissionProfile,
|
||||
expected_root: &AbsolutePathBuf,
|
||||
unexpected_root: &AbsolutePathBuf,
|
||||
) {
|
||||
let permission_profile: CorePermissionProfile = permission_profile.clone().into();
|
||||
let sandbox_policy = permission_profile.file_system_sandbox_policy();
|
||||
assert!(
|
||||
sandbox_policy.entries.iter().any(|entry| {
|
||||
entry.access == FileSystemAccessMode::Write
|
||||
&& matches!(&entry.path, FileSystemPath::Path { path } if path == expected_root)
|
||||
}),
|
||||
"expected permission profile write entries to contain {expected_root:?}; got {:?}",
|
||||
sandbox_policy.entries
|
||||
);
|
||||
assert!(
|
||||
!sandbox_policy.entries.iter().any(|entry| {
|
||||
entry.access == FileSystemAccessMode::Write
|
||||
&& matches!(&entry.path, FileSystemPath::Path { path } if path == unexpected_root)
|
||||
}),
|
||||
"did not expect permission profile write entries to contain {unexpected_root:?}; got {:?}",
|
||||
sandbox_policy.entries
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_turn_context_update_applies_partial_patch_and_emits_full_state() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
@@ -133,6 +163,43 @@ async fn thread_turn_context_update_applies_partial_patch_and_emits_full_state()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_turn_context_update_retargets_permissions_when_cwd_changes() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
write_config(&codex_home, &server.uri())?;
|
||||
let next_cwd = TempDir::new()?;
|
||||
let next_cwd_abs = AbsolutePathBuf::try_from(next_cwd.path())?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
let ThreadStartResponse { thread, .. } = start_thread(&mut mcp).await?;
|
||||
|
||||
let request_id = mcp
|
||||
.send_thread_turn_context_update_request(ThreadTurnContextUpdateParams {
|
||||
thread_id: thread.id,
|
||||
cwd: Some(next_cwd.path().to_path_buf()),
|
||||
permissions: Some(PermissionProfileSelectionParams::new(":workspace")),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
|
||||
)
|
||||
.await??;
|
||||
let response = to_response::<ThreadTurnContextUpdateResponse>(response)?;
|
||||
|
||||
assert_eq!(response.turn_context.cwd, next_cwd_abs);
|
||||
assert_permission_profile_write_root(
|
||||
&response.turn_context.permission_profile,
|
||||
&next_cwd_abs,
|
||||
&thread.cwd,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_turn_context_update_clears_service_tier_with_explicit_null() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
@@ -200,6 +267,68 @@ async fn thread_turn_context_update_rejects_sandbox_policy_with_permissions() ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_turn_context_update_waits_for_pending_cwd_before_permissions() -> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(vec![
|
||||
create_final_assistant_message_sse_response("Done")?,
|
||||
])
|
||||
.await;
|
||||
let codex_home = TempDir::new()?;
|
||||
write_config(&codex_home, &server.uri())?;
|
||||
let next_cwd = TempDir::new()?;
|
||||
let next_cwd_abs = AbsolutePathBuf::try_from(next_cwd.path())?;
|
||||
|
||||
let mut mcp = McpProcess::new(codex_home.path()).await?;
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
|
||||
let ThreadStartResponse { thread, .. } = start_thread(&mut mcp).await?;
|
||||
|
||||
let turn_request_id = mcp
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: thread.id.clone(),
|
||||
input: vec![V2UserInput::Text {
|
||||
text: "Hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
}],
|
||||
cwd: Some(next_cwd.path().to_path_buf()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let update_request_id = mcp
|
||||
.send_thread_turn_context_update_request(ThreadTurnContextUpdateParams {
|
||||
thread_id: thread.id,
|
||||
permissions: Some(PermissionProfileSelectionParams::new(":workspace")),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(turn_request_id)),
|
||||
)
|
||||
.await??;
|
||||
let update_response: JSONRPCResponse = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_response_message(RequestId::Integer(update_request_id)),
|
||||
)
|
||||
.await??;
|
||||
let update_response = to_response::<ThreadTurnContextUpdateResponse>(update_response)?;
|
||||
|
||||
assert_eq!(update_response.turn_context.cwd, next_cwd_abs);
|
||||
assert_permission_profile_write_root(
|
||||
&update_response.turn_context.permission_profile,
|
||||
&next_cwd_abs,
|
||||
&thread.cwd,
|
||||
);
|
||||
|
||||
timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_notification_message("turn/completed"),
|
||||
)
|
||||
.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_start_emits_turn_context_updated_when_overrides_change_defaults() -> Result<()> {
|
||||
let server = create_mock_responses_server_sequence_unchecked(vec![
|
||||
|
||||
Reference in New Issue
Block a user