Compare commits

..

1 Commits

Author SHA1 Message Date
Michael Bolin
541ee41211 fix: preserve zsh-fork escalation fds in unified-exec PTYs 2026-03-06 09:12:19 -08:00
297 changed files with 8029 additions and 31455 deletions

View File

@@ -643,29 +643,6 @@ jobs:
exit "${publish_status}"
done
winget:
name: winget
needs: release
# Only publish stable/mainline releases to WinGet; pre-releases include a
# '-' in the semver string (e.g., 1.2.3-alpha.1).
if: ${{ !contains(needs.release.outputs.version, '-') }}
# This job only invokes a GitHub Action to open/update the winget-pkgs PR;
# it does not execute Windows-only tooling, so Linux is sufficient.
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Publish to WinGet
uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
with:
identifier: OpenAI.Codex
version: ${{ needs.release.outputs.version }}
release-tag: ${{ needs.release.outputs.tag }}
fork-user: openai-oss-forks
installers-regex: '^codex-(?:x86_64|aarch64)-pc-windows-msvc\.exe\.zip$'
token: ${{ secrets.WINGET_PUBLISH_PAT }}
update-branch:
name: Update latest-alpha-cli branch
permissions:

6
codex-rs/Cargo.lock generated
View File

@@ -1647,8 +1647,6 @@ dependencies = [
"tokio",
"toml 0.9.11+spec-1.1.0",
"tracing",
"tracing-appender",
"tracing-subscriber",
]
[[package]]
@@ -2090,7 +2088,6 @@ dependencies = [
"codex-app-server-protocol",
"codex-core",
"core_test_support",
"pretty_assertions",
"rand 0.9.2",
"reqwest",
"serde",
@@ -2099,7 +2096,6 @@ dependencies = [
"tempfile",
"tiny_http",
"tokio",
"tracing",
"url",
"urlencoding",
"webbrowser",
@@ -2306,9 +2302,7 @@ dependencies = [
"serde_json",
"serial_test",
"sha2",
"sse-stream",
"tempfile",
"thiserror 2.0.18",
"tiny_http",
"tokio",
"tracing",

View File

@@ -51,7 +51,6 @@ You can enable notifications by configuring a script that is run whenever the ag
### `codex exec` to run Codex programmatically/non-interactively
To run Codex non-interactively, run `codex exec PROMPT` (you can also pass the prompt via `stdin`) and Codex will work on your task until it decides that it is done and exits. Output is printed to the terminal directly. You can set the `RUST_LOG` environment variable to see more about what's going on.
Use `codex exec --fork <SESSION_ID> PROMPT` to fork an existing session without launching the interactive picker/UI.
Use `codex exec --ephemeral ...` to run without persisting session rollout files to disk.
### Experimenting with the Codex Sandbox

View File

@@ -953,34 +953,25 @@
},
"PluginInstallParams": {
"properties": {
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
"cwd": {
"type": [
"string",
"null"
]
},
"marketplaceName": {
"type": "string"
},
"pluginName": {
"type": "string"
}
},
"required": [
"marketplacePath",
"marketplaceName",
"pluginName"
],
"type": "object"
},
"PluginListParams": {
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
}
},
"type": "object"
},
"ProductSurface": {
"enum": [
"chatgpt",
@@ -3273,30 +3264,6 @@
"title": "Skills/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/list"
],
"title": "Plugin/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -31,24 +31,38 @@
"AdditionalMacOsPermissions": {
"properties": {
"accessibility": {
"type": "boolean"
"type": [
"boolean",
"null"
]
},
"automations": {
"$ref": "#/definitions/MacOsAutomationPermission"
"anyOf": [
{
"$ref": "#/definitions/MacOsAutomationValue"
},
{
"type": "null"
}
]
},
"calendar": {
"type": "boolean"
"type": [
"boolean",
"null"
]
},
"preferences": {
"$ref": "#/definitions/MacOsPreferencesPermission"
"anyOf": [
{
"$ref": "#/definitions/MacOsPreferencesValue"
},
{
"type": "null"
}
]
}
},
"required": [
"accessibility",
"automations",
"calendar",
"preferences"
],
"type": "object"
},
"AdditionalNetworkPermissions": {
@@ -286,40 +300,28 @@
}
]
},
"MacOsAutomationPermission": {
"oneOf": [
"MacOsAutomationValue": {
"anyOf": [
{
"enum": [
"none",
"all"
],
"type": "string"
"type": "boolean"
},
{
"additionalProperties": false,
"properties": {
"bundle_ids": {
"items": {
"type": "string"
},
"type": "array"
}
"items": {
"type": "string"
},
"required": [
"bundle_ids"
],
"title": "BundleIdsMacOsAutomationPermission",
"type": "object"
"type": "array"
}
]
},
"MacOsPreferencesPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
"MacOsPreferencesValue": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
},
"NetworkApprovalContext": {
"properties": {

View File

@@ -29,14 +29,6 @@
}
]
},
"AgentSpawnMode": {
"enum": [
"spawn",
"fork",
"watchdog"
],
"type": "string"
},
"AgentStatus": {
"description": "Agent lifecycle status, derived from emitted events.",
"oneOf": [
@@ -556,7 +548,6 @@
"oneOf": [
{
"properties": {
"_meta": true,
"message": {
"type": "string"
},
@@ -577,7 +568,6 @@
},
{
"properties": {
"_meta": true,
"elicitation_id": {
"type": "string"
},
@@ -1481,12 +1471,6 @@
"null"
]
},
"saved_path": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
@@ -2035,13 +2019,6 @@
"server_name": {
"type": "string"
},
"turn_id": {
"description": "Turn ID that this elicitation belongs to, when known.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"elicitation_request"
@@ -2997,15 +2974,6 @@
],
"description": "Thread ID of the sender."
},
"spawn_mode": {
"allOf": [
{
"$ref": "#/definitions/AgentSpawnMode"
}
],
"default": "spawn",
"description": "Spawn mode used for this agent."
},
"status": {
"allOf": [
{
@@ -3788,70 +3756,66 @@
],
"type": "string"
},
"MacOsAutomationPermission": {
"oneOf": [
"MacOsAutomationValue": {
"anyOf": [
{
"enum": [
"none",
"all"
],
"type": "string"
"type": "boolean"
},
{
"additionalProperties": false,
"properties": {
"bundle_ids": {
"items": {
"type": "string"
},
"type": "array"
}
"items": {
"type": "string"
},
"required": [
"bundle_ids"
],
"title": "BundleIdsMacOsAutomationPermission",
"type": "object"
"type": "array"
}
]
},
"MacOsPreferencesPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
},
"MacOsSeatbeltProfileExtensions": {
"MacOsPermissions": {
"properties": {
"macos_accessibility": {
"default": false,
"type": "boolean"
"accessibility": {
"type": [
"boolean",
"null"
]
},
"macos_automation": {
"allOf": [
"automations": {
"anyOf": [
{
"$ref": "#/definitions/MacOsAutomationPermission"
}
],
"default": "none"
},
"macos_calendar": {
"default": false,
"type": "boolean"
},
"macos_preferences": {
"allOf": [
"$ref": "#/definitions/MacOsAutomationValue"
},
{
"$ref": "#/definitions/MacOsPreferencesPermission"
"type": "null"
}
],
"default": "read_only"
]
},
"calendar": {
"type": [
"boolean",
"null"
]
},
"preferences": {
"anyOf": [
{
"$ref": "#/definitions/MacOsPreferencesValue"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"MacOsPreferencesValue": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
},
"McpAuthStatus": {
"enum": [
"unsupported",
@@ -4195,7 +4159,7 @@
"macos": {
"anyOf": [
{
"$ref": "#/definitions/MacOsSeatbeltProfileExtensions"
"$ref": "#/definitions/MacOsPermissions"
},
{
"type": "null"
@@ -5641,6 +5605,9 @@
},
"SessionNetworkProxyRuntime": {
"properties": {
"admin_addr": {
"type": "string"
},
"http_addr": {
"type": "string"
},
@@ -5649,6 +5616,7 @@
}
},
"required": [
"admin_addr",
"http_addr",
"socks_addr"
],
@@ -6151,12 +6119,6 @@
"null"
]
},
"saved_path": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
@@ -6298,7 +6260,7 @@
"type": "object"
},
{
"description": "Explicit structured mention selected by the user.\n\n`path` identifies the exact mention target, for example `app://<connector-id>` or `plugin://<plugin-name>@<marketplace-name>`.",
"description": "Explicit mention selected by the user (name + app://connector id).",
"properties": {
"name": {
"type": "string"
@@ -7302,12 +7264,6 @@
"null"
]
},
"saved_path": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
@@ -7856,13 +7812,6 @@
"server_name": {
"type": "string"
},
"turn_id": {
"description": "Turn ID that this elicitation belongs to, when known.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"elicitation_request"
@@ -8818,15 +8767,6 @@
],
"description": "Thread ID of the sender."
},
"spawn_mode": {
"allOf": [
{
"$ref": "#/definitions/AgentSpawnMode"
}
],
"default": "spawn",
"description": "Spawn mode used for this agent."
},
"status": {
"allOf": [
{

View File

@@ -1,542 +1,8 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"McpElicitationArrayType": {
"enum": [
"array"
],
"type": "string"
},
"McpElicitationBooleanSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"boolean",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationBooleanType"
}
},
"required": [
"type"
],
"type": "object"
},
"McpElicitationBooleanType": {
"enum": [
"boolean"
],
"type": "string"
},
"McpElicitationConstOption": {
"additionalProperties": false,
"properties": {
"const": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"const",
"title"
],
"type": "object"
},
"McpElicitationEnumSchema": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationSingleSelectEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationMultiSelectEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationLegacyTitledEnumSchema"
}
]
},
"McpElicitationLegacyTitledEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"enum": {
"items": {
"type": "string"
},
"type": "array"
},
"enumNames": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"enum",
"type"
],
"type": "object"
},
"McpElicitationMultiSelectEnumSchema": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationUntitledMultiSelectEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationTitledMultiSelectEnumSchema"
}
]
},
"McpElicitationNumberSchema": {
"additionalProperties": false,
"properties": {
"default": {
"format": "double",
"type": [
"number",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"maximum": {
"format": "double",
"type": [
"number",
"null"
]
},
"minimum": {
"format": "double",
"type": [
"number",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationNumberType"
}
},
"required": [
"type"
],
"type": "object"
},
"McpElicitationNumberType": {
"enum": [
"number",
"integer"
],
"type": "string"
},
"McpElicitationObjectType": {
"enum": [
"object"
],
"type": "string"
},
"McpElicitationPrimitiveSchema": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationStringSchema"
},
{
"$ref": "#/definitions/McpElicitationNumberSchema"
},
{
"$ref": "#/definitions/McpElicitationBooleanSchema"
}
]
},
"McpElicitationSchema": {
"additionalProperties": false,
"description": "Typed form schema for MCP `elicitation/create` requests.\n\nThis matches the `requestedSchema` shape from the MCP 2025-11-25 `ElicitRequestFormParams` schema.",
"properties": {
"$schema": {
"type": [
"string",
"null"
]
},
"properties": {
"additionalProperties": {
"$ref": "#/definitions/McpElicitationPrimitiveSchema"
},
"type": "object"
},
"required": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationObjectType"
}
},
"required": [
"properties",
"type"
],
"type": "object"
},
"McpElicitationSingleSelectEnumSchema": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationUntitledSingleSelectEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationTitledSingleSelectEnumSchema"
}
]
},
"McpElicitationStringFormat": {
"enum": [
"email",
"uri",
"date",
"date-time"
],
"type": "string"
},
"McpElicitationStringSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"format": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationStringFormat"
},
{
"type": "null"
}
]
},
"maxLength": {
"format": "uint32",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"minLength": {
"format": "uint32",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"type"
],
"type": "object"
},
"McpElicitationStringType": {
"enum": [
"string"
],
"type": "string"
},
"McpElicitationTitledEnumItems": {
"additionalProperties": false,
"properties": {
"anyOf": {
"items": {
"$ref": "#/definitions/McpElicitationConstOption"
},
"type": "array"
}
},
"required": [
"anyOf"
],
"type": "object"
},
"McpElicitationTitledMultiSelectEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"items": {
"$ref": "#/definitions/McpElicitationTitledEnumItems"
},
"maxItems": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"minItems": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationArrayType"
}
},
"required": [
"items",
"type"
],
"type": "object"
},
"McpElicitationTitledSingleSelectEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"oneOf": {
"items": {
"$ref": "#/definitions/McpElicitationConstOption"
},
"type": "array"
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"oneOf",
"type"
],
"type": "object"
},
"McpElicitationUntitledEnumItems": {
"additionalProperties": false,
"properties": {
"enum": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"enum",
"type"
],
"type": "object"
},
"McpElicitationUntitledMultiSelectEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"items": {
"$ref": "#/definitions/McpElicitationUntitledEnumItems"
},
"maxItems": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"minItems": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationArrayType"
}
},
"required": [
"items",
"type"
],
"type": "object"
},
"McpElicitationUntitledSingleSelectEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"enum": {
"items": {
"type": "string"
},
"type": "array"
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"enum",
"type"
],
"type": "object"
}
},
"oneOf": [
{
"properties": {
"_meta": true,
"message": {
"type": "string"
},
@@ -546,9 +12,7 @@
],
"type": "string"
},
"requestedSchema": {
"$ref": "#/definitions/McpElicitationSchema"
}
"requestedSchema": true
},
"required": [
"message",
@@ -559,7 +23,6 @@
},
{
"properties": {
"_meta": true,
"elicitationId": {
"type": "string"
},

View File

@@ -11,9 +11,6 @@
}
},
"properties": {
"_meta": {
"description": "Optional client metadata for form-mode action handling."
},
"action": {
"$ref": "#/definitions/McpServerElicitationAction"
},

View File

@@ -31,24 +31,38 @@
"AdditionalMacOsPermissions": {
"properties": {
"accessibility": {
"type": "boolean"
"type": [
"boolean",
"null"
]
},
"automations": {
"$ref": "#/definitions/MacOsAutomationPermission"
"anyOf": [
{
"$ref": "#/definitions/MacOsAutomationValue"
},
{
"type": "null"
}
]
},
"calendar": {
"type": "boolean"
"type": [
"boolean",
"null"
]
},
"preferences": {
"$ref": "#/definitions/MacOsPreferencesPermission"
"anyOf": [
{
"$ref": "#/definitions/MacOsPreferencesValue"
},
{
"type": "null"
}
]
}
},
"required": [
"accessibility",
"automations",
"calendar",
"preferences"
],
"type": "object"
},
"AdditionalNetworkPermissions": {
@@ -615,577 +629,33 @@
],
"type": "object"
},
"MacOsAutomationPermission": {
"oneOf": [
"MacOsAutomationValue": {
"anyOf": [
{
"enum": [
"none",
"all"
],
"type": "string"
"type": "boolean"
},
{
"additionalProperties": false,
"properties": {
"bundle_ids": {
"items": {
"type": "string"
},
"type": "array"
}
"items": {
"type": "string"
},
"required": [
"bundle_ids"
],
"title": "BundleIdsMacOsAutomationPermission",
"type": "object"
"type": "array"
}
]
},
"MacOsPreferencesPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
},
"McpElicitationArrayType": {
"enum": [
"array"
],
"type": "string"
},
"McpElicitationBooleanSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"boolean",
"null"
]
"MacOsPreferencesValue": {
"anyOf": [
{
"type": "boolean"
},
"description": {
"type": [
"string",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationBooleanType"
}
},
"required": [
"type"
],
"type": "object"
},
"McpElicitationBooleanType": {
"enum": [
"boolean"
],
"type": "string"
},
"McpElicitationConstOption": {
"additionalProperties": false,
"properties": {
"const": {
"type": "string"
},
"title": {
{
"type": "string"
}
},
"required": [
"const",
"title"
],
"type": "object"
},
"McpElicitationEnumSchema": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationSingleSelectEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationMultiSelectEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationLegacyTitledEnumSchema"
}
]
},
"McpElicitationLegacyTitledEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"enum": {
"items": {
"type": "string"
},
"type": "array"
},
"enumNames": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"enum",
"type"
],
"type": "object"
},
"McpElicitationMultiSelectEnumSchema": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationUntitledMultiSelectEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationTitledMultiSelectEnumSchema"
}
]
},
"McpElicitationNumberSchema": {
"additionalProperties": false,
"properties": {
"default": {
"format": "double",
"type": [
"number",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"maximum": {
"format": "double",
"type": [
"number",
"null"
]
},
"minimum": {
"format": "double",
"type": [
"number",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationNumberType"
}
},
"required": [
"type"
],
"type": "object"
},
"McpElicitationNumberType": {
"enum": [
"number",
"integer"
],
"type": "string"
},
"McpElicitationObjectType": {
"enum": [
"object"
],
"type": "string"
},
"McpElicitationPrimitiveSchema": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationStringSchema"
},
{
"$ref": "#/definitions/McpElicitationNumberSchema"
},
{
"$ref": "#/definitions/McpElicitationBooleanSchema"
}
]
},
"McpElicitationSchema": {
"additionalProperties": false,
"description": "Typed form schema for MCP `elicitation/create` requests.\n\nThis matches the `requestedSchema` shape from the MCP 2025-11-25 `ElicitRequestFormParams` schema.",
"properties": {
"$schema": {
"type": [
"string",
"null"
]
},
"properties": {
"additionalProperties": {
"$ref": "#/definitions/McpElicitationPrimitiveSchema"
},
"type": "object"
},
"required": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationObjectType"
}
},
"required": [
"properties",
"type"
],
"type": "object"
},
"McpElicitationSingleSelectEnumSchema": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationUntitledSingleSelectEnumSchema"
},
{
"$ref": "#/definitions/McpElicitationTitledSingleSelectEnumSchema"
}
]
},
"McpElicitationStringFormat": {
"enum": [
"email",
"uri",
"date",
"date-time"
],
"type": "string"
},
"McpElicitationStringSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"format": {
"anyOf": [
{
"$ref": "#/definitions/McpElicitationStringFormat"
},
{
"type": "null"
}
]
},
"maxLength": {
"format": "uint32",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"minLength": {
"format": "uint32",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"type"
],
"type": "object"
},
"McpElicitationStringType": {
"enum": [
"string"
],
"type": "string"
},
"McpElicitationTitledEnumItems": {
"additionalProperties": false,
"properties": {
"anyOf": {
"items": {
"$ref": "#/definitions/McpElicitationConstOption"
},
"type": "array"
}
},
"required": [
"anyOf"
],
"type": "object"
},
"McpElicitationTitledMultiSelectEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"items": {
"$ref": "#/definitions/McpElicitationTitledEnumItems"
},
"maxItems": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"minItems": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationArrayType"
}
},
"required": [
"items",
"type"
],
"type": "object"
},
"McpElicitationTitledSingleSelectEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"oneOf": {
"items": {
"$ref": "#/definitions/McpElicitationConstOption"
},
"type": "array"
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"oneOf",
"type"
],
"type": "object"
},
"McpElicitationUntitledEnumItems": {
"additionalProperties": false,
"properties": {
"enum": {
"items": {
"type": "string"
},
"type": "array"
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"enum",
"type"
],
"type": "object"
},
"McpElicitationUntitledMultiSelectEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"items": {
"$ref": "#/definitions/McpElicitationUntitledEnumItems"
},
"maxItems": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"minItems": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationArrayType"
}
},
"required": [
"items",
"type"
],
"type": "object"
},
"McpElicitationUntitledSingleSelectEnumSchema": {
"additionalProperties": false,
"properties": {
"default": {
"type": [
"string",
"null"
]
},
"description": {
"type": [
"string",
"null"
]
},
"enum": {
"items": {
"type": "string"
},
"type": "array"
},
"title": {
"type": [
"string",
"null"
]
},
"type": {
"$ref": "#/definitions/McpElicitationStringType"
}
},
"required": [
"enum",
"type"
],
"type": "object"
},
"McpServerElicitationRequestParams": {
"oneOf": [
{
"properties": {
"_meta": true,
"message": {
"type": "string"
},
@@ -1195,9 +665,7 @@
],
"type": "string"
},
"requestedSchema": {
"$ref": "#/definitions/McpElicitationSchema"
}
"requestedSchema": true
},
"required": [
"message",
@@ -1208,7 +676,6 @@
},
{
"properties": {
"_meta": true,
"elicitationId": {
"type": "string"
},

View File

@@ -163,14 +163,6 @@
"title": "AgentMessageDeltaNotification",
"type": "object"
},
"AgentSpawnMode": {
"enum": [
"spawn",
"fork",
"watchdog"
],
"type": "string"
},
"AgentStatus": {
"description": "Agent lifecycle status, derived from emitted events.",
"oneOf": [
@@ -568,34 +560,6 @@
],
"type": "object"
},
"AppSummary": {
"description": "EXPERIMENTAL - app metadata summary for plugin-install responses.",
"properties": {
"description": {
"type": [
"string",
"null"
]
},
"id": {
"type": "string"
},
"installUrl": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"type": "object"
},
"AppToolApproval": {
"enum": [
"auto",
@@ -1247,30 +1211,6 @@
"title": "Skills/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/list"
],
"title": "Plugin/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -3357,7 +3297,6 @@
"oneOf": [
{
"properties": {
"_meta": true,
"message": {
"type": "string"
},
@@ -3378,7 +3317,6 @@
},
{
"properties": {
"_meta": true,
"elicitation_id": {
"type": "string"
},
@@ -4308,12 +4246,6 @@
"null"
]
},
"saved_path": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},
@@ -4862,13 +4794,6 @@
"server_name": {
"type": "string"
},
"turn_id": {
"description": "Turn ID that this elicitation belongs to, when known.",
"type": [
"string",
"null"
]
},
"type": {
"enum": [
"elicitation_request"
@@ -5824,15 +5749,6 @@
],
"description": "Thread ID of the sender."
},
"spawn_mode": {
"allOf": [
{
"$ref": "#/definitions/AgentSpawnMode"
}
],
"default": "spawn",
"description": "Spawn mode used for this agent."
},
"status": {
"allOf": [
{
@@ -7434,70 +7350,66 @@
"title": "LogoutAccountResponse",
"type": "object"
},
"MacOsAutomationPermission": {
"oneOf": [
"MacOsAutomationValue": {
"anyOf": [
{
"enum": [
"none",
"all"
],
"type": "string"
"type": "boolean"
},
{
"additionalProperties": false,
"properties": {
"bundle_ids": {
"items": {
"type": "string"
},
"type": "array"
}
"items": {
"type": "string"
},
"required": [
"bundle_ids"
],
"title": "BundleIdsMacOsAutomationPermission",
"type": "object"
"type": "array"
}
]
},
"MacOsPreferencesPermission": {
"enum": [
"none",
"read_only",
"read_write"
],
"type": "string"
},
"MacOsSeatbeltProfileExtensions": {
"MacOsPermissions": {
"properties": {
"macos_accessibility": {
"default": false,
"type": "boolean"
"accessibility": {
"type": [
"boolean",
"null"
]
},
"macos_automation": {
"allOf": [
"automations": {
"anyOf": [
{
"$ref": "#/definitions/MacOsAutomationPermission"
}
],
"default": "none"
},
"macos_calendar": {
"default": false,
"type": "boolean"
},
"macos_preferences": {
"allOf": [
"$ref": "#/definitions/MacOsAutomationValue"
},
{
"$ref": "#/definitions/MacOsPreferencesPermission"
"type": "null"
}
],
"default": "read_only"
]
},
"calendar": {
"type": [
"boolean",
"null"
]
},
"preferences": {
"anyOf": [
{
"$ref": "#/definitions/MacOsPreferencesValue"
},
{
"type": "null"
}
]
}
},
"type": "object"
},
"MacOsPreferencesValue": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
},
"McpAuthStatus": {
"enum": [
"unsupported",
@@ -8118,6 +8030,12 @@
"null"
]
},
"dangerouslyAllowNonLoopbackAdmin": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowNonLoopbackProxy": {
"type": [
"boolean",
@@ -8368,7 +8286,7 @@
"macos": {
"anyOf": [
{
"$ref": "#/definitions/MacOsSeatbeltProfileExtensions"
"$ref": "#/definitions/MacOsPermissions"
},
{
"type": "null"
@@ -8455,15 +8373,21 @@
"PluginInstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
"cwd": {
"type": [
"string",
"null"
]
},
"marketplaceName": {
"type": "string"
},
"pluginName": {
"type": "string"
}
},
"required": [
"marketplacePath",
"marketplaceName",
"pluginName"
],
"title": "PluginInstallParams",
@@ -8471,118 +8395,9 @@
},
"PluginInstallResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"appsNeedingAuth": {
"items": {
"$ref": "#/definitions/AppSummary"
},
"type": "array"
}
},
"required": [
"appsNeedingAuth"
],
"title": "PluginInstallResponse",
"type": "object"
},
"PluginListParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
}
},
"title": "PluginListParams",
"type": "object"
},
"PluginListResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"marketplaces": {
"items": {
"$ref": "#/definitions/PluginMarketplaceEntry"
},
"type": "array"
}
},
"required": [
"marketplaces"
],
"title": "PluginListResponse",
"type": "object"
},
"PluginMarketplaceEntry": {
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"plugins": {
"items": {
"$ref": "#/definitions/PluginSummary"
},
"type": "array"
}
},
"required": [
"name",
"path",
"plugins"
],
"type": "object"
},
"PluginSource": {
"oneOf": [
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"local"
],
"title": "LocalPluginSourceType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalPluginSource",
"type": "object"
}
]
},
"PluginSummary": {
"properties": {
"enabled": {
"type": "boolean"
},
"name": {
"type": "string"
},
"source": {
"$ref": "#/definitions/PluginSource"
}
},
"required": [
"enabled",
"name",
"source"
],
"type": "object"
},
"ProductSurface": {
"enum": [
"chatgpt",
@@ -11159,6 +10974,9 @@
},
"SessionNetworkProxyRuntime": {
"properties": {
"admin_addr": {
"type": "string"
},
"http_addr": {
"type": "string"
},
@@ -11167,6 +10985,7 @@
}
},
"required": [
"admin_addr",
"http_addr",
"socks_addr"
],
@@ -14085,12 +13904,6 @@
"null"
]
},
"saved_path": {
"type": [
"string",
"null"
]
},
"status": {
"type": "string"
},

View File

@@ -132,6 +132,12 @@
"null"
]
},
"dangerouslyAllowNonLoopbackAdmin": {
"type": [
"boolean",
"null"
]
},
"dangerouslyAllowNonLoopbackProxy": {
"type": [
"boolean",

View File

@@ -1,21 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"properties": {
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
"cwd": {
"type": [
"string",
"null"
]
},
"marketplaceName": {
"type": "string"
},
"pluginName": {
"type": "string"
}
},
"required": [
"marketplacePath",
"marketplaceName",
"pluginName"
],
"title": "PluginInstallParams",

View File

@@ -1,46 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AppSummary": {
"description": "EXPERIMENTAL - app metadata summary for plugin-install responses.",
"properties": {
"description": {
"type": [
"string",
"null"
]
},
"id": {
"type": "string"
},
"installUrl": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"type": "object"
}
},
"properties": {
"appsNeedingAuth": {
"items": {
"$ref": "#/definitions/AppSummary"
},
"type": "array"
}
},
"required": [
"appsNeedingAuth"
],
"title": "PluginInstallResponse",
"type": "object"
}

View File

@@ -1,23 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"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"
}
},
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
}
},
"title": "PluginListParams",
"type": "object"
}

View File

@@ -1,83 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"PluginMarketplaceEntry": {
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"plugins": {
"items": {
"$ref": "#/definitions/PluginSummary"
},
"type": "array"
}
},
"required": [
"name",
"path",
"plugins"
],
"type": "object"
},
"PluginSource": {
"oneOf": [
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"local"
],
"title": "LocalPluginSourceType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalPluginSource",
"type": "object"
}
]
},
"PluginSummary": {
"properties": {
"enabled": {
"type": "boolean"
},
"name": {
"type": "string"
},
"source": {
"$ref": "#/definitions/PluginSource"
}
},
"required": [
"enabled",
"name",
"source"
],
"type": "object"
}
},
"properties": {
"marketplaces": {
"items": {
"$ref": "#/definitions/PluginMarketplaceEntry"
},
"type": "array"
}
},
"required": [
"marketplaces"
],
"title": "PluginListResponse",
"type": "object"
}

View File

@@ -1,5 +0,0 @@
// 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 AgentSpawnMode = "spawn" | "fork" | "watchdog";

View File

@@ -23,7 +23,6 @@ import type { LoginAccountParams } from "./v2/LoginAccountParams";
import type { McpServerOauthLoginParams } from "./v2/McpServerOauthLoginParams";
import type { ModelListParams } from "./v2/ModelListParams";
import type { PluginInstallParams } from "./v2/PluginInstallParams";
import type { PluginListParams } from "./v2/PluginListParams";
import type { ReviewStartParams } from "./v2/ReviewStartParams";
import type { SkillsConfigWriteParams } from "./v2/SkillsConfigWriteParams";
import type { SkillsListParams } from "./v2/SkillsListParams";
@@ -50,4 +49,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta
/**
* Request from the client to the server.
*/
export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, };
export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, };

View File

@@ -1,7 +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 { AgentSpawnMode } from "./AgentSpawnMode";
import type { AgentStatus } from "./AgentStatus";
import type { ThreadId } from "./ThreadId";
@@ -31,10 +30,6 @@ new_agent_role?: string | null,
* beginning.
*/
prompt: string,
/**
* Spawn mode used for this agent.
*/
spawn_mode: AgentSpawnMode,
/**
* Last known status of the new agent reported to the sender agent.
*/

View File

@@ -3,4 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from "./serde_json/JsonValue";
export type ElicitationRequest = { "mode": "form", _meta?: JsonValue, message: string, requested_schema: JsonValue, } | { "mode": "url", _meta?: JsonValue, message: string, url: string, elicitation_id: string, };
export type ElicitationRequest = { "mode": "form", message: string, requested_schema: JsonValue, } | { "mode": "url", message: string, url: string, elicitation_id: string, };

View File

@@ -3,8 +3,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ElicitationRequest } from "./ElicitationRequest";
export type ElicitationRequestEvent = {
/**
* Turn ID that this elicitation belongs to, when known.
*/
turn_id?: string, server_name: string, id: string | number, request: ElicitationRequest, };
export type ElicitationRequestEvent = { server_name: string, id: string | number, request: ElicitationRequest, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ImageGenerationEndEvent = { call_id: string, status: string, revised_prompt?: string, result: string, saved_path?: string, };
export type ImageGenerationEndEvent = { call_id: string, status: string, revised_prompt?: string, result: string, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ImageGenerationItem = { id: string, status: string, revised_prompt?: string, result: string, saved_path?: string, };
export type ImageGenerationItem = { id: string, status: string, revised_prompt?: string, result: string, };

View File

@@ -1,5 +0,0 @@
// 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 MacOsAutomationPermission = "none" | "all" | { "bundle_ids": Array<string> };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type McpElicitationArrayType = "array";
export type MacOsAutomationValue = boolean | Array<string>;

View 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 { MacOsAutomationValue } from "./MacOsAutomationValue";
import type { MacOsPreferencesValue } from "./MacOsPreferencesValue";
export type MacOsPermissions = { preferences: MacOsPreferencesValue | null, automations: MacOsAutomationValue | null, accessibility: boolean | null, calendar: boolean | null, };

View File

@@ -1,5 +0,0 @@
// 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 MacOsPreferencesPermission = "none" | "read_only" | "read_write";

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type McpElicitationObjectType = "object";
export type MacOsPreferencesValue = boolean | string;

View File

@@ -1,7 +0,0 @@
// 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 { MacOsAutomationPermission } from "./MacOsAutomationPermission";
import type { MacOsPreferencesPermission } from "./MacOsPreferencesPermission";
export type MacOsSeatbeltProfileExtensions = { macos_preferences: MacOsPreferencesPermission, macos_automation: MacOsAutomationPermission, macos_accessibility: boolean, macos_calendar: boolean, };

View File

@@ -2,7 +2,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { FileSystemPermissions } from "./FileSystemPermissions";
import type { MacOsSeatbeltProfileExtensions } from "./MacOsSeatbeltProfileExtensions";
import type { MacOsPermissions } from "./MacOsPermissions";
import type { NetworkPermissions } from "./NetworkPermissions";
export type PermissionProfile = { network: NetworkPermissions | null, file_system: FileSystemPermissions | null, macos: MacOsSeatbeltProfileExtensions | null, };
export type PermissionProfile = { network: NetworkPermissions | null, file_system: FileSystemPermissions | null, macos: MacOsPermissions | null, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type SessionNetworkProxyRuntime = { http_addr: string, socks_addr: string, };
export type SessionNetworkProxyRuntime = { http_addr: string, socks_addr: string, admin_addr: string, };

View File

@@ -11,7 +11,6 @@ export type { AgentReasoningEvent } from "./AgentReasoningEvent";
export type { AgentReasoningRawContentDeltaEvent } from "./AgentReasoningRawContentDeltaEvent";
export type { AgentReasoningRawContentEvent } from "./AgentReasoningRawContentEvent";
export type { AgentReasoningSectionBreakEvent } from "./AgentReasoningSectionBreakEvent";
export type { AgentSpawnMode } from "./AgentSpawnMode";
export type { AgentStatus } from "./AgentStatus";
export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams";
export type { ApplyPatchApprovalRequestEvent } from "./ApplyPatchApprovalRequestEvent";
@@ -101,9 +100,9 @@ export type { ListSkillsResponseEvent } from "./ListSkillsResponseEvent";
export type { LocalShellAction } from "./LocalShellAction";
export type { LocalShellExecAction } from "./LocalShellExecAction";
export type { LocalShellStatus } from "./LocalShellStatus";
export type { MacOsAutomationPermission } from "./MacOsAutomationPermission";
export type { MacOsPreferencesPermission } from "./MacOsPreferencesPermission";
export type { MacOsSeatbeltProfileExtensions } from "./MacOsSeatbeltProfileExtensions";
export type { MacOsAutomationValue } from "./MacOsAutomationValue";
export type { MacOsPermissions } from "./MacOsPermissions";
export type { MacOsPreferencesValue } from "./MacOsPreferencesValue";
export type { McpAuthStatus } from "./McpAuthStatus";
export type { McpInvocation } from "./McpInvocation";
export type { McpListToolsResponseEvent } from "./McpListToolsResponseEvent";

View File

@@ -1,7 +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 { MacOsAutomationPermission } from "../MacOsAutomationPermission";
import type { MacOsPreferencesPermission } from "../MacOsPreferencesPermission";
import type { MacOsAutomationValue } from "../MacOsAutomationValue";
import type { MacOsPreferencesValue } from "../MacOsPreferencesValue";
export type AdditionalMacOsPermissions = { preferences: MacOsPreferencesPermission, automations: MacOsAutomationPermission, accessibility: boolean, calendar: boolean, };
export type AdditionalMacOsPermissions = { preferences: MacOsPreferencesValue | null, automations: MacOsAutomationValue | null, accessibility: boolean | null, calendar: boolean | null, };

View File

@@ -1,8 +0,0 @@
// 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.
/**
* EXPERIMENTAL - app metadata summary for plugin-install responses.
*/
export type AppSummary = { id: string, name: string, description: string | null, installUrl: string | null, };

View File

@@ -1,6 +0,0 @@
// 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 { McpElicitationBooleanType } from "./McpElicitationBooleanType";
export type McpElicitationBooleanSchema = { type: McpElicitationBooleanType, title?: string, description?: string, default?: boolean, };

View File

@@ -1,5 +0,0 @@
// 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 McpElicitationBooleanType = "boolean";

View File

@@ -1,5 +0,0 @@
// 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 McpElicitationConstOption = { const: string, title: string, };

View File

@@ -1,8 +0,0 @@
// 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 { McpElicitationLegacyTitledEnumSchema } from "./McpElicitationLegacyTitledEnumSchema";
import type { McpElicitationMultiSelectEnumSchema } from "./McpElicitationMultiSelectEnumSchema";
import type { McpElicitationSingleSelectEnumSchema } from "./McpElicitationSingleSelectEnumSchema";
export type McpElicitationEnumSchema = McpElicitationSingleSelectEnumSchema | McpElicitationMultiSelectEnumSchema | McpElicitationLegacyTitledEnumSchema;

View File

@@ -1,6 +0,0 @@
// 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 { McpElicitationStringType } from "./McpElicitationStringType";
export type McpElicitationLegacyTitledEnumSchema = { type: McpElicitationStringType, title?: string, description?: string, enum: Array<string>, enumNames?: Array<string>, default?: string, };

View File

@@ -1,7 +0,0 @@
// 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 { McpElicitationTitledMultiSelectEnumSchema } from "./McpElicitationTitledMultiSelectEnumSchema";
import type { McpElicitationUntitledMultiSelectEnumSchema } from "./McpElicitationUntitledMultiSelectEnumSchema";
export type McpElicitationMultiSelectEnumSchema = McpElicitationUntitledMultiSelectEnumSchema | McpElicitationTitledMultiSelectEnumSchema;

View File

@@ -1,6 +0,0 @@
// 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 { McpElicitationNumberType } from "./McpElicitationNumberType";
export type McpElicitationNumberSchema = { type: McpElicitationNumberType, title?: string, description?: string, minimum?: number, maximum?: number, default?: number, };

View File

@@ -1,5 +0,0 @@
// 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 McpElicitationNumberType = "number" | "integer";

View File

@@ -1,9 +0,0 @@
// 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 { McpElicitationBooleanSchema } from "./McpElicitationBooleanSchema";
import type { McpElicitationEnumSchema } from "./McpElicitationEnumSchema";
import type { McpElicitationNumberSchema } from "./McpElicitationNumberSchema";
import type { McpElicitationStringSchema } from "./McpElicitationStringSchema";
export type McpElicitationPrimitiveSchema = McpElicitationEnumSchema | McpElicitationStringSchema | McpElicitationNumberSchema | McpElicitationBooleanSchema;

View File

@@ -1,13 +0,0 @@
// 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 { McpElicitationObjectType } from "./McpElicitationObjectType";
import type { McpElicitationPrimitiveSchema } from "./McpElicitationPrimitiveSchema";
/**
* Typed form schema for MCP `elicitation/create` requests.
*
* This matches the `requestedSchema` shape from the MCP 2025-11-25
* `ElicitRequestFormParams` schema.
*/
export type McpElicitationSchema = { $schema?: string, type: McpElicitationObjectType, properties: { [key in string]?: McpElicitationPrimitiveSchema }, required?: Array<string>, };

View File

@@ -1,7 +0,0 @@
// 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 { McpElicitationTitledSingleSelectEnumSchema } from "./McpElicitationTitledSingleSelectEnumSchema";
import type { McpElicitationUntitledSingleSelectEnumSchema } from "./McpElicitationUntitledSingleSelectEnumSchema";
export type McpElicitationSingleSelectEnumSchema = McpElicitationUntitledSingleSelectEnumSchema | McpElicitationTitledSingleSelectEnumSchema;

View File

@@ -1,5 +0,0 @@
// 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 McpElicitationStringFormat = "email" | "uri" | "date" | "date-time";

View File

@@ -1,7 +0,0 @@
// 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 { McpElicitationStringFormat } from "./McpElicitationStringFormat";
import type { McpElicitationStringType } from "./McpElicitationStringType";
export type McpElicitationStringSchema = { type: McpElicitationStringType, title?: string, description?: string, minLength?: number, maxLength?: number, format?: McpElicitationStringFormat, default?: string, };

View File

@@ -1,5 +0,0 @@
// 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 McpElicitationStringType = "string";

View File

@@ -1,6 +0,0 @@
// 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 { McpElicitationConstOption } from "./McpElicitationConstOption";
export type McpElicitationTitledEnumItems = { anyOf: Array<McpElicitationConstOption>, };

View File

@@ -1,7 +0,0 @@
// 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 { McpElicitationArrayType } from "./McpElicitationArrayType";
import type { McpElicitationTitledEnumItems } from "./McpElicitationTitledEnumItems";
export type McpElicitationTitledMultiSelectEnumSchema = { type: McpElicitationArrayType, title?: string, description?: string, minItems?: bigint, maxItems?: bigint, items: McpElicitationTitledEnumItems, default?: Array<string>, };

View File

@@ -1,7 +0,0 @@
// 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 { McpElicitationConstOption } from "./McpElicitationConstOption";
import type { McpElicitationStringType } from "./McpElicitationStringType";
export type McpElicitationTitledSingleSelectEnumSchema = { type: McpElicitationStringType, title?: string, description?: string, oneOf: Array<McpElicitationConstOption>, default?: string, };

View File

@@ -1,6 +0,0 @@
// 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 { McpElicitationStringType } from "./McpElicitationStringType";
export type McpElicitationUntitledEnumItems = { type: McpElicitationStringType, enum: Array<string>, };

View File

@@ -1,7 +0,0 @@
// 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 { McpElicitationArrayType } from "./McpElicitationArrayType";
import type { McpElicitationUntitledEnumItems } from "./McpElicitationUntitledEnumItems";
export type McpElicitationUntitledMultiSelectEnumSchema = { type: McpElicitationArrayType, title?: string, description?: string, minItems?: bigint, maxItems?: bigint, items: McpElicitationUntitledEnumItems, default?: Array<string>, };

View File

@@ -1,6 +0,0 @@
// 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 { McpElicitationStringType } from "./McpElicitationStringType";
export type McpElicitationUntitledSingleSelectEnumSchema = { type: McpElicitationStringType, title?: string, description?: string, enum: Array<string>, default?: string, };

View File

@@ -2,7 +2,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from "../serde_json/JsonValue";
import type { McpElicitationSchema } from "./McpElicitationSchema";
export type McpServerElicitationRequestParams = { threadId: string,
/**
@@ -13,4 +12,4 @@ export type McpServerElicitationRequestParams = { threadId: string,
* context is app-server correlation rather than part of the protocol identity of the
* elicitation itself.
*/
turnId: string | null, serverName: string, } & ({ "mode": "form", _meta: JsonValue | null, message: string, requestedSchema: McpElicitationSchema, } | { "mode": "url", _meta: JsonValue | null, message: string, url: string, elicitationId: string, });
turnId: string | null, serverName: string, } & ({ "mode": "form", message: string, requestedSchema: JsonValue, } | { "mode": "url", message: string, url: string, elicitationId: string, });

View File

@@ -10,8 +10,4 @@ export type McpServerElicitationRequestResponse = { action: McpServerElicitation
*
* This is nullable because decline/cancel responses have no content.
*/
content: JsonValue | null,
/**
* Optional client metadata for form-mode action handling.
*/
_meta: JsonValue | null, };
content: JsonValue | null, };

View File

@@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type NetworkRequirements = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, allowedDomains: Array<string> | null, deniedDomains: Array<string> | null, allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };
export type NetworkRequirements = { enabled: boolean | null, httpPort: number | null, socksPort: number | null, allowUpstreamProxy: boolean | null, dangerouslyAllowNonLoopbackProxy: boolean | null, dangerouslyAllowNonLoopbackAdmin: boolean | null, dangerouslyAllowAllUnixSockets: boolean | null, allowedDomains: Array<string> | null, deniedDomains: Array<string> | null, allowUnixSockets: Array<string> | null, allowLocalBinding: boolean | null, };

View File

@@ -1,6 +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.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PluginInstallParams = { marketplacePath: AbsolutePathBuf, pluginName: string, };
export type PluginInstallParams = { marketplaceName: string, pluginName: string, cwd?: string | null, };

View File

@@ -1,6 +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.
import type { AppSummary } from "./AppSummary";
export type PluginInstallResponse = { appsNeedingAuth: Array<AppSummary>, };
export type PluginInstallResponse = Record<string, never>;

View File

@@ -1,11 +0,0 @@
// 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 { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PluginListParams = {
/**
* Optional working directories used to discover repo marketplaces. When omitted,
* only home-scoped marketplaces are considered.
*/
cwds?: Array<AbsolutePathBuf> | null, };

View File

@@ -1,6 +0,0 @@
// 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 { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
export type PluginListResponse = { marketplaces: Array<PluginMarketplaceEntry>, };

View File

@@ -1,6 +0,0 @@
// 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 { PluginSummary } from "./PluginSummary";
export type PluginMarketplaceEntry = { name: string, path: string, plugins: Array<PluginSummary>, };

View File

@@ -1,5 +0,0 @@
// 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 PluginSource = { "type": "local", path: string, };

View File

@@ -1,6 +0,0 @@
// 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 { PluginSource } from "./PluginSource";
export type PluginSummary = { name: string, source: PluginSource, enabled: boolean, };

View File

@@ -16,7 +16,6 @@ export type { AppListUpdatedNotification } from "./AppListUpdatedNotification";
export type { AppMetadata } from "./AppMetadata";
export type { AppReview } from "./AppReview";
export type { AppScreenshot } from "./AppScreenshot";
export type { AppSummary } from "./AppSummary";
export type { AppToolApproval } from "./AppToolApproval";
export type { AppToolsConfig } from "./AppToolsConfig";
export type { AppsConfig } from "./AppsConfig";
@@ -98,28 +97,6 @@ export type { LoginAccountParams } from "./LoginAccountParams";
export type { LoginAccountResponse } from "./LoginAccountResponse";
export type { LogoutAccountResponse } from "./LogoutAccountResponse";
export type { McpAuthStatus } from "./McpAuthStatus";
export type { McpElicitationArrayType } from "./McpElicitationArrayType";
export type { McpElicitationBooleanSchema } from "./McpElicitationBooleanSchema";
export type { McpElicitationBooleanType } from "./McpElicitationBooleanType";
export type { McpElicitationConstOption } from "./McpElicitationConstOption";
export type { McpElicitationEnumSchema } from "./McpElicitationEnumSchema";
export type { McpElicitationLegacyTitledEnumSchema } from "./McpElicitationLegacyTitledEnumSchema";
export type { McpElicitationMultiSelectEnumSchema } from "./McpElicitationMultiSelectEnumSchema";
export type { McpElicitationNumberSchema } from "./McpElicitationNumberSchema";
export type { McpElicitationNumberType } from "./McpElicitationNumberType";
export type { McpElicitationObjectType } from "./McpElicitationObjectType";
export type { McpElicitationPrimitiveSchema } from "./McpElicitationPrimitiveSchema";
export type { McpElicitationSchema } from "./McpElicitationSchema";
export type { McpElicitationSingleSelectEnumSchema } from "./McpElicitationSingleSelectEnumSchema";
export type { McpElicitationStringFormat } from "./McpElicitationStringFormat";
export type { McpElicitationStringSchema } from "./McpElicitationStringSchema";
export type { McpElicitationStringType } from "./McpElicitationStringType";
export type { McpElicitationTitledEnumItems } from "./McpElicitationTitledEnumItems";
export type { McpElicitationTitledMultiSelectEnumSchema } from "./McpElicitationTitledMultiSelectEnumSchema";
export type { McpElicitationTitledSingleSelectEnumSchema } from "./McpElicitationTitledSingleSelectEnumSchema";
export type { McpElicitationUntitledEnumItems } from "./McpElicitationUntitledEnumItems";
export type { McpElicitationUntitledMultiSelectEnumSchema } from "./McpElicitationUntitledMultiSelectEnumSchema";
export type { McpElicitationUntitledSingleSelectEnumSchema } from "./McpElicitationUntitledSingleSelectEnumSchema";
export type { McpServerElicitationAction } from "./McpServerElicitationAction";
export type { McpServerElicitationRequestParams } from "./McpServerElicitationRequestParams";
export type { McpServerElicitationRequestResponse } from "./McpServerElicitationRequestResponse";
@@ -152,11 +129,6 @@ export type { PatchChangeKind } from "./PatchChangeKind";
export type { PlanDeltaNotification } from "./PlanDeltaNotification";
export type { PluginInstallParams } from "./PluginInstallParams";
export type { PluginInstallResponse } from "./PluginInstallResponse";
export type { PluginListParams } from "./PluginListParams";
export type { PluginListResponse } from "./PluginListResponse";
export type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
export type { PluginSource } from "./PluginSource";
export type { PluginSummary } from "./PluginSummary";
export type { ProductSurface } from "./ProductSurface";
export type { ProfileV2 } from "./ProfileV2";
export type { RateLimitSnapshot } from "./RateLimitSnapshot";

View File

@@ -248,10 +248,6 @@ client_request_definitions! {
params: v2::SkillsListParams,
response: v2::SkillsListResponse,
},
PluginList => "plugin/list" {
params: v2::PluginListParams,
response: v2::PluginListResponse,
},
SkillsRemoteList => "skills/remote/list" {
params: v2::SkillsRemoteReadParams,
response: v2::SkillsRemoteReadResponse,
@@ -1058,23 +1054,21 @@ mod tests {
#[test]
fn serialize_mcp_server_elicitation_request() -> Result<()> {
let requested_schema: v2::McpElicitationSchema = serde_json::from_value(json!({
"type": "object",
"properties": {
"confirmed": {
"type": "boolean"
}
},
"required": ["confirmed"]
}))?;
let params = v2::McpServerElicitationRequestParams {
thread_id: "thr_123".to_string(),
turn_id: Some("turn_123".to_string()),
server_name: "codex_apps".to_string(),
request: v2::McpServerElicitationRequest::Form {
meta: None,
message: "Allow this request?".to_string(),
requested_schema,
requested_schema: json!({
"type": "object",
"properties": {
"confirmed": {
"type": "boolean"
}
},
"required": ["confirmed"]
}),
},
};
let request = ServerRequest::McpServerElicitationRequest {
@@ -1091,7 +1085,6 @@ mod tests {
"turnId": "turn_123",
"serverName": "codex_apps",
"mode": "form",
"_meta": null,
"message": "Allow this request?",
"requestedSchema": {
"type": "object",

View File

@@ -183,7 +183,6 @@ impl ThreadHistoryBuilder {
RolloutItem::Compacted(payload) => self.handle_compacted(payload),
RolloutItem::TurnContext(_)
| RolloutItem::SessionMeta(_)
| RolloutItem::ForkReference(_)
| RolloutItem::ResponseItem(_) => {}
}
}

View File

@@ -28,9 +28,9 @@ use codex_protocol::mcp::Resource as McpResource;
use codex_protocol::mcp::ResourceTemplate as McpResourceTemplate;
use codex_protocol::mcp::Tool as McpTool;
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
use codex_protocol::models::MacOsAutomationPermission as CoreMacOsAutomationPermission;
use codex_protocol::models::MacOsPreferencesPermission as CoreMacOsPreferencesPermission;
use codex_protocol::models::MacOsSeatbeltProfileExtensions as CoreMacOsSeatbeltProfileExtensions;
use codex_protocol::models::MacOsAutomationValue as CoreMacOsAutomationValue;
use codex_protocol::models::MacOsPermissions as CoreMacOsPermissions;
use codex_protocol::models::MacOsPreferencesValue as CoreMacOsPreferencesValue;
use codex_protocol::models::MessagePhase;
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
use codex_protocol::models::PermissionProfile as CorePermissionProfile;
@@ -629,6 +629,7 @@ pub struct NetworkRequirements {
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
@@ -836,19 +837,19 @@ impl From<CoreFileSystemPermissions> for AdditionalFileSystemPermissions {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct AdditionalMacOsPermissions {
pub preferences: CoreMacOsPreferencesPermission,
pub automations: CoreMacOsAutomationPermission,
pub accessibility: bool,
pub calendar: bool,
pub preferences: Option<CoreMacOsPreferencesValue>,
pub automations: Option<CoreMacOsAutomationValue>,
pub accessibility: Option<bool>,
pub calendar: Option<bool>,
}
impl From<CoreMacOsSeatbeltProfileExtensions> for AdditionalMacOsPermissions {
fn from(value: CoreMacOsSeatbeltProfileExtensions) -> Self {
impl From<CoreMacOsPermissions> for AdditionalMacOsPermissions {
fn from(value: CoreMacOsPermissions) -> Self {
Self {
preferences: value.macos_preferences,
automations: value.macos_automation,
accessibility: value.macos_accessibility,
calendar: value.macos_calendar,
preferences: value.preferences,
automations: value.automations,
accessibility: value.accessibility,
calendar: value.calendar,
}
}
}
@@ -1712,28 +1713,6 @@ pub struct AppInfo {
pub plugin_display_names: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
/// EXPERIMENTAL - app metadata summary for plugin-install responses.
pub struct AppSummary {
pub id: String,
pub name: String,
pub description: Option<String>,
pub install_url: Option<String>,
}
impl From<AppInfo> for AppSummary {
fn from(value: AppInfo) -> Self {
Self {
id: value.id,
name: value.name,
description: value.description,
install_url: value.install_url,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2392,23 +2371,6 @@ pub struct SkillsListResponse {
pub data: Vec<SkillsListEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginListParams {
/// Optional working directories used to discover repo marketplaces. When omitted,
/// only home-scoped marketplaces are considered.
#[ts(optional = nullable)]
pub cwds: Option<Vec<AbsolutePathBuf>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginListResponse {
pub marketplaces: Vec<PluginMarketplaceEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2572,34 +2534,6 @@ pub struct SkillsListEntry {
pub errors: Vec<SkillErrorInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginMarketplaceEntry {
pub name: String,
pub path: PathBuf,
pub plugins: Vec<PluginSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginSummary {
pub name: String,
pub source: PluginSource,
pub enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum PluginSource {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Local { path: PathBuf },
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2619,16 +2553,16 @@ pub struct SkillsConfigWriteResponse {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginInstallParams {
pub marketplace_path: AbsolutePathBuf,
pub marketplace_name: String,
pub plugin_name: String,
#[ts(optional = nullable)]
pub cwd: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginInstallResponse {
pub apps_needing_auth: Vec<AppSummary>,
}
pub struct PluginInstallResponse {}
impl From<CoreSkillMetadata> for SkillMetadata {
fn from(value: CoreSkillMetadata) -> Self {
@@ -4210,323 +4144,6 @@ pub struct McpServerElicitationRequestParams {
// association.
}
/// Typed form schema for MCP `elicitation/create` requests.
///
/// This matches the `requestedSchema` shape from the MCP 2025-11-25
/// `ElicitRequestFormParams` schema.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationSchema {
#[serde(rename = "$schema", skip_serializing_if = "Option::is_none")]
#[ts(optional, rename = "$schema")]
pub schema_uri: Option<String>,
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationObjectType,
pub properties: BTreeMap<String, McpElicitationPrimitiveSchema>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub required: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationObjectType {
Object,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
#[ts(export_to = "v2/")]
pub enum McpElicitationPrimitiveSchema {
Enum(McpElicitationEnumSchema),
String(McpElicitationStringSchema),
Number(McpElicitationNumberSchema),
Boolean(McpElicitationBooleanSchema),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationStringSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub min_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub max_length: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub format: Option<McpElicitationStringFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationStringType {
String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "kebab-case")]
#[ts(rename_all = "kebab-case", export_to = "v2/")]
pub enum McpElicitationStringFormat {
Email,
Uri,
Date,
DateTime,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationNumberSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationNumberType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub minimum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub maximum: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<f64>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationNumberType {
Number,
Integer,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationBooleanSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationBooleanType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<bool>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationBooleanType {
Boolean,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
#[ts(export_to = "v2/")]
pub enum McpElicitationEnumSchema {
SingleSelect(McpElicitationSingleSelectEnumSchema),
MultiSelect(McpElicitationMultiSelectEnumSchema),
Legacy(McpElicitationLegacyTitledEnumSchema),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationLegacyTitledEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "enum")]
#[ts(rename = "enum")]
pub enum_: Vec<String>,
#[serde(rename = "enumNames", skip_serializing_if = "Option::is_none")]
#[ts(optional, rename = "enumNames")]
pub enum_names: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
#[ts(export_to = "v2/")]
pub enum McpElicitationSingleSelectEnumSchema {
Untitled(McpElicitationUntitledSingleSelectEnumSchema),
Titled(McpElicitationTitledSingleSelectEnumSchema),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationUntitledSingleSelectEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "enum")]
#[ts(rename = "enum")]
pub enum_: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationTitledSingleSelectEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(rename = "oneOf")]
#[ts(rename = "oneOf")]
pub one_of: Vec<McpElicitationConstOption>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(untagged)]
#[ts(export_to = "v2/")]
pub enum McpElicitationMultiSelectEnumSchema {
Untitled(McpElicitationUntitledMultiSelectEnumSchema),
Titled(McpElicitationTitledMultiSelectEnumSchema),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationUntitledMultiSelectEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationArrayType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub min_items: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub max_items: Option<u64>,
pub items: McpElicitationUntitledEnumItems,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationTitledMultiSelectEnumSchema {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationArrayType,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub min_items: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub max_items: Option<u64>,
pub items: McpElicitationTitledEnumItems,
#[serde(skip_serializing_if = "Option::is_none")]
#[ts(optional)]
pub default: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export_to = "v2/")]
pub enum McpElicitationArrayType {
Array,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationUntitledEnumItems {
#[serde(rename = "type")]
#[ts(rename = "type")]
pub type_: McpElicitationStringType,
#[serde(rename = "enum")]
#[ts(rename = "enum")]
pub enum_: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationTitledEnumItems {
#[serde(rename = "anyOf", alias = "oneOf")]
#[ts(rename = "anyOf")]
pub any_of: Vec<McpElicitationConstOption>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(deny_unknown_fields)]
#[ts(export_to = "v2/")]
pub struct McpElicitationConstOption {
#[serde(rename = "const")]
#[ts(rename = "const")]
pub const_: String,
pub title: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "mode", rename_all = "camelCase")]
#[ts(tag = "mode")]
@@ -4535,49 +4152,37 @@ pub enum McpServerElicitationRequest {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Form {
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
meta: Option<JsonValue>,
message: String,
requested_schema: McpElicitationSchema,
requested_schema: JsonValue,
},
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Url {
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
meta: Option<JsonValue>,
message: String,
url: String,
elicitation_id: String,
},
}
impl TryFrom<CoreElicitationRequest> for McpServerElicitationRequest {
type Error = serde_json::Error;
fn try_from(value: CoreElicitationRequest) -> Result<Self, Self::Error> {
impl From<CoreElicitationRequest> for McpServerElicitationRequest {
fn from(value: CoreElicitationRequest) -> Self {
match value {
CoreElicitationRequest::Form {
meta,
message,
requested_schema,
} => Ok(Self::Form {
meta,
} => Self::Form {
message,
requested_schema: serde_json::from_value(requested_schema)?,
}),
requested_schema,
},
CoreElicitationRequest::Url {
meta,
message,
url,
elicitation_id,
} => Ok(Self::Url {
meta,
} => Self::Url {
message,
url,
elicitation_id,
}),
},
}
}
}
@@ -4591,10 +4196,6 @@ pub struct McpServerElicitationRequestResponse {
///
/// This is nullable because decline/cancel responses have no content.
pub content: Option<JsonValue>,
/// Optional client metadata for form-mode action handling.
#[serde(rename = "_meta")]
#[ts(rename = "_meta")]
pub meta: Option<JsonValue>,
}
impl From<McpServerElicitationRequestResponse> for rmcp::model::CreateElicitationResult {
@@ -4611,7 +4212,6 @@ impl From<rmcp::model::CreateElicitationResult> for McpServerElicitationRequestR
Self {
action: value.action.into(),
content: value.content,
meta: None,
}
}
}
@@ -4907,46 +4507,6 @@ mod tests {
);
}
#[test]
fn command_execution_request_approval_accepts_macos_automation_bundle_ids_object() {
let params = serde_json::from_value::<CommandExecutionRequestApprovalParams>(json!({
"threadId": "thr_123",
"turnId": "turn_123",
"itemId": "call_123",
"command": "cat file",
"cwd": "/tmp",
"commandActions": null,
"reason": null,
"networkApprovalContext": null,
"additionalPermissions": {
"network": null,
"fileSystem": null,
"macos": {
"preferences": "read_only",
"automations": {
"bundle_ids": ["com.apple.Notes"]
},
"accessibility": false,
"calendar": false
}
},
"proposedExecpolicyAmendment": null,
"proposedNetworkPolicyAmendments": null,
"availableDecisions": null
}))
.expect("bundle_ids object should deserialize");
assert_eq!(
params
.additional_permissions
.and_then(|permissions| permissions.macos)
.map(|macos| macos.automations),
Some(CoreMacOsAutomationPermission::BundleIds(vec![
"com.apple.Notes".to_string(),
]))
);
}
#[test]
fn sandbox_policy_round_trips_external_sandbox_network_access() {
let v2_policy = SandboxPolicy::ExternalSandbox {
@@ -5009,7 +4569,6 @@ mod tests {
content: Some(json!({
"confirmed": true,
})),
meta: None,
}
);
assert_eq!(
@@ -5020,18 +4579,15 @@ mod tests {
#[test]
fn mcp_server_elicitation_request_from_core_url_request() {
let request = McpServerElicitationRequest::try_from(CoreElicitationRequest::Url {
meta: None,
let request = McpServerElicitationRequest::from(CoreElicitationRequest::Url {
message: "Finish sign-in".to_string(),
url: "https://example.com/complete".to_string(),
elicitation_id: "elicitation-123".to_string(),
})
.expect("URL request should convert");
});
assert_eq!(
request,
McpServerElicitationRequest::Url {
meta: None,
message: "Finish sign-in".to_string(),
url: "https://example.com/complete".to_string(),
elicitation_id: "elicitation-123".to_string(),
@@ -5039,178 +4595,11 @@ mod tests {
);
}
#[test]
fn mcp_server_elicitation_request_from_core_form_request() {
let request = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form {
meta: None,
message: "Allow this request?".to_string(),
requested_schema: json!({
"type": "object",
"properties": {
"confirmed": {
"type": "boolean",
}
},
"required": ["confirmed"],
}),
})
.expect("form request should convert");
let expected_schema: McpElicitationSchema = serde_json::from_value(json!({
"type": "object",
"properties": {
"confirmed": {
"type": "boolean",
}
},
"required": ["confirmed"],
}))
.expect("expected schema should deserialize");
assert_eq!(
request,
McpServerElicitationRequest::Form {
meta: None,
message: "Allow this request?".to_string(),
requested_schema: expected_schema,
}
);
}
#[test]
fn mcp_elicitation_schema_matches_mcp_2025_11_25_primitives() {
let schema: McpElicitationSchema = serde_json::from_value(json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"email": {
"type": "string",
"title": "Email",
"description": "Work email address",
"format": "email",
"default": "dev@example.com",
},
"count": {
"type": "integer",
"title": "Count",
"description": "How many items to create",
"minimum": 1,
"maximum": 5,
"default": 3,
},
"confirmed": {
"type": "boolean",
"title": "Confirm",
"description": "Approve the pending action",
"default": true,
},
"legacyChoice": {
"type": "string",
"title": "Action",
"description": "Legacy titled enum form",
"enum": ["allow", "deny"],
"enumNames": ["Allow", "Deny"],
"default": "allow",
},
},
"required": ["email", "confirmed"],
}))
.expect("schema should deserialize");
assert_eq!(
schema,
McpElicitationSchema {
schema_uri: Some("https://json-schema.org/draft/2020-12/schema".to_string()),
type_: McpElicitationObjectType::Object,
properties: BTreeMap::from([
(
"confirmed".to_string(),
McpElicitationPrimitiveSchema::Boolean(McpElicitationBooleanSchema {
type_: McpElicitationBooleanType::Boolean,
title: Some("Confirm".to_string()),
description: Some("Approve the pending action".to_string()),
default: Some(true),
}),
),
(
"count".to_string(),
McpElicitationPrimitiveSchema::Number(McpElicitationNumberSchema {
type_: McpElicitationNumberType::Integer,
title: Some("Count".to_string()),
description: Some("How many items to create".to_string()),
minimum: Some(1.0),
maximum: Some(5.0),
default: Some(3.0),
}),
),
(
"email".to_string(),
McpElicitationPrimitiveSchema::String(McpElicitationStringSchema {
type_: McpElicitationStringType::String,
title: Some("Email".to_string()),
description: Some("Work email address".to_string()),
min_length: None,
max_length: None,
format: Some(McpElicitationStringFormat::Email),
default: Some("dev@example.com".to_string()),
}),
),
(
"legacyChoice".to_string(),
McpElicitationPrimitiveSchema::Enum(McpElicitationEnumSchema::Legacy(
McpElicitationLegacyTitledEnumSchema {
type_: McpElicitationStringType::String,
title: Some("Action".to_string()),
description: Some("Legacy titled enum form".to_string()),
enum_: vec!["allow".to_string(), "deny".to_string()],
enum_names: Some(vec!["Allow".to_string(), "Deny".to_string(),]),
default: Some("allow".to_string()),
},
)),
),
]),
required: Some(vec!["email".to_string(), "confirmed".to_string()]),
}
);
}
#[test]
fn mcp_server_elicitation_request_rejects_null_core_form_schema() {
let result = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form {
meta: Some(json!({
"persist": "session",
})),
message: "Allow this request?".to_string(),
requested_schema: JsonValue::Null,
});
assert!(result.is_err());
}
#[test]
fn mcp_server_elicitation_request_rejects_invalid_core_form_schema() {
let result = McpServerElicitationRequest::try_from(CoreElicitationRequest::Form {
meta: None,
message: "Allow this request?".to_string(),
requested_schema: json!({
"type": "object",
"properties": {
"confirmed": {
"type": "object",
}
},
}),
});
assert!(result.is_err());
}
#[test]
fn mcp_server_elicitation_response_serializes_nullable_content() {
let response = McpServerElicitationRequestResponse {
action: McpServerElicitationAction::Decline,
content: None,
meta: None,
};
assert_eq!(
@@ -5218,7 +4607,6 @@ mod tests {
json!({
"action": "decline",
"content": null,
"_meta": null,
})
);
}

View File

@@ -130,7 +130,7 @@ Example with notification opt-out:
- `thread/status/changed` — notification emitted when a loaded threads status changes (`threadId` + new `status`).
- `thread/archive` — move a threads rollout file into the archived directory; returns `{}` on success and emits `thread/archived`.
- `thread/unsubscribe` — unsubscribe this connection from thread turn/item events. If this was the last subscriber, the server shuts down and unloads the thread, then emits `thread/closed`.
- `thread/name/set` — set or update a threads user-facing name for either a loaded thread or a persisted rollout; returns `{}` on success and emits `thread/name/updated` to initialized, opted-in clients. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.
- `thread/name/set` — set or update a threads user-facing name for either a loaded thread or a persisted rollout; returns `{}` on success. Thread names are not required to be unique; name lookups resolve to the most recently updated thread.
- `thread/unarchive` — move an archived rollout file back into the sessions directory; returns the restored `thread` on success and emits `thread/unarchived`.
- `thread/compact/start` — trigger conversation history compaction for a thread; returns `{}` immediately while progress streams through standard turn/item notifications.
- `thread/backgroundTerminals/clean` — terminate all running background terminals for a thread (experimental; requires `capabilities.experimentalApi`); returns `{}` when the cleanup request is accepted.
@@ -148,13 +148,12 @@ Example with notification opt-out:
- `experimentalFeature/list` — list feature flags with stage metadata (`beta`, `underDevelopment`, `stable`, etc.), enabled/default-enabled state, and cursor pagination. For non-beta flags, `displayName`/`description`/`announcement` are `null`.
- `collaborationMode/list` — list available collaboration mode presets (experimental, no pagination). This response omits built-in developer instructions; clients should either pass `settings.developer_instructions: null` when setting a mode to use Codex's built-in instructions, or provide their own instructions explicitly.
- `skills/list` — list skills for one or more `cwd` values (optional `forceReload`).
- `plugin/list` — list discovered marketplaces reachable from optional `cwds` (unioned into a single list). When `cwds` is omitted, only home-scoped marketplaces are considered. Includes each plugin's current `enabled` state from config (**under development; do not call from production clients yet**).
- `skills/changed` — notification emitted when watched local skill files change.
- `skills/remote/list` — list public remote skills (**under development; do not call from production clients yet**).
- `skills/remote/export` — download a remote skill by `hazelnutId` into `skills` under `codex_home` (**under development; do not call from production clients yet**).
- `app/list` — list available apps.
- `skills/config/write` — write user-level skill config by path.
- `plugin/install` — install a plugin from a discovered marketplace entry by `pluginName` and `marketplacePath`; on success it returns `appsNeedingAuth` for any plugin-declared apps that still are not accessible in the current ChatGPT auth context (**under development; do not call from production clients yet**).
- `plugin/install` — install a plugin from a discovered marketplace entry by `pluginName` and `marketplaceName` (**under development; do not call from production clients yet**).
- `mcpServer/oauth/login` — start an OAuth login for a configured MCP server; returns an `authorization_url` and later emits `mcpServer/oauthLogin/completed` once the browser flow finishes.
- `tool/requestUserInput` — prompt the user with 13 short questions for a tool call and return their answers (experimental).
- `config/mcpServer/reload` — reload MCP server config from disk and queue a refresh for loaded threads (applied on each thread's next active turn); returns `{}`. Use this after editing `config.toml` without restarting the server.
@@ -476,26 +475,6 @@ Invoke an app by including `$<app-slug>` in the text input and adding a `mention
} } }
```
### Example: Start a turn (invoke a plugin)
Invoke a plugin by including a UI mention token such as `@sample` in the text input and adding a `mention` input item with the exact `plugin://<plugin-name>@<marketplace-name>` path returned by `plugin/list`.
```json
{ "method": "turn/start", "id": 35, "params": {
"threadId": "thr_123",
"input": [
{ "type": "text", "text": "@sample Summarize the latest updates." },
{ "type": "mention", "name": "Sample Plugin", "path": "plugin://sample@test" }
]
} }
{ "id": 35, "result": { "turn": {
"id": "turn_459",
"status": "inProgress",
"items": [],
"error": null
} } }
```
### Example: Interrupt an active turn
You can cancel a running Turn with `turn/interrupt`.
@@ -996,7 +975,7 @@ The server also emits `app/list/updated` notifications whenever either source (a
}
```
Invoke an app by inserting `$<app-slug>` in the text input. The slug is derived from the app name and lowercased with non-alphanumeric characters replaced by `-` (for example, "Demo App" becomes `$demo-app`). Add a `mention` input item (recommended) so the server uses the exact `app://<connector-id>` path rather than guessing by name. Plugins use the same `mention` item shape, but with `plugin://<plugin-name>@<marketplace-name>` paths from `plugin/list`.
Invoke an app by inserting `$<app-slug>` in the text input. The slug is derived from the app name and lowercased with non-alphanumeric characters replaced by `-` (for example, "Demo App" becomes `$demo-app`). Add a `mention` input item (recommended) so the server uses the exact `app://<connector-id>` path rather than guessing by name.
Example:

View File

@@ -617,43 +617,15 @@ pub(crate) async fn apply_bespoke_event_handling(
let permission_guard = thread_watch_manager
.note_permission_requested(&conversation_id.to_string())
.await;
let turn_id = match request.turn_id.clone() {
Some(turn_id) => Some(turn_id),
None => {
let state = thread_state.lock().await;
state.active_turn_snapshot().map(|turn| turn.id)
}
};
let server_name = request.server_name.clone();
let request_body = match request.request.try_into() {
Ok(request_body) => request_body,
Err(err) => {
error!(
error = %err,
server_name,
request_id = ?request.id,
"failed to parse typed MCP elicitation schema"
);
if let Err(err) = conversation
.submit(Op::ResolveElicitation {
server_name: request.server_name,
request_id: request.id,
decision: codex_protocol::approvals::ElicitationAction::Cancel,
content: None,
meta: None,
})
.await
{
error!("failed to submit ResolveElicitation: {err}");
}
return;
}
let turn_id = {
let state = thread_state.lock().await;
state.active_turn_snapshot().map(|turn| turn.id)
};
let params = McpServerElicitationRequestParams {
thread_id: conversation_id.to_string(),
turn_id,
server_name: request.server_name.clone(),
request: request_body,
request: request.request.into(),
};
let (pending_request_id, rx) = outgoing
.send_request(ServerRequestPayload::McpServerElicitationRequest(params))
@@ -1590,9 +1562,7 @@ pub(crate) async fn apply_bespoke_event_handling(
thread_name: thread_name_event.thread_name,
};
outgoing
.send_global_server_notification(ServerNotification::ThreadNameUpdated(
notification,
))
.send_server_notification(ServerNotification::ThreadNameUpdated(notification))
.await;
}
}
@@ -2074,7 +2044,6 @@ async fn on_mcp_server_elicitation_response(
request_id,
decision: response.action.to_core(),
content: response.content,
meta: response.meta,
})
.await
{
@@ -2092,14 +2061,12 @@ fn mcp_server_elicitation_response_from_client_result(
McpServerElicitationRequestResponse {
action: McpServerElicitationAction::Decline,
content: None,
meta: None,
}
}),
Ok(Err(err)) if is_turn_transition_server_request_error(&err) => {
McpServerElicitationRequestResponse {
action: McpServerElicitationAction::Cancel,
content: None,
meta: None,
}
}
Ok(Err(err)) => {
@@ -2107,7 +2074,6 @@ fn mcp_server_elicitation_response_from_client_result(
McpServerElicitationRequestResponse {
action: McpServerElicitationAction::Decline,
content: None,
meta: None,
}
}
Err(err) => {
@@ -2115,7 +2081,6 @@ fn mcp_server_elicitation_response_from_client_result(
McpServerElicitationRequestResponse {
action: McpServerElicitationAction::Decline,
content: None,
meta: None,
}
}
}
@@ -2526,7 +2491,6 @@ mod tests {
McpServerElicitationRequestResponse {
action: McpServerElicitationAction::Cancel,
content: None,
meta: None,
}
);
}

View File

@@ -22,7 +22,6 @@ use codex_app_server_protocol::AccountLoginCompletedNotification;
use codex_app_server_protocol::AccountUpdatedNotification;
use codex_app_server_protocol::AppInfo;
use codex_app_server_protocol::AppListUpdatedNotification;
use codex_app_server_protocol::AppSummary;
use codex_app_server_protocol::AppsListParams;
use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::AskForApproval;
@@ -80,11 +79,6 @@ use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::ModelListResponse;
use codex_app_server_protocol::PluginInstallParams;
use codex_app_server_protocol::PluginInstallResponse;
use codex_app_server_protocol::PluginListParams;
use codex_app_server_protocol::PluginListResponse;
use codex_app_server_protocol::PluginMarketplaceEntry;
use codex_app_server_protocol::PluginSource;
use codex_app_server_protocol::PluginSummary;
use codex_app_server_protocol::ProductSurface as ApiProductSurface;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ReviewDelivery as ApiReviewDelivery;
@@ -188,8 +182,6 @@ use codex_core::config::edit::ConfigEdit;
use codex_core::config::edit::ConfigEditsBuilder;
use codex_core::config::types::McpServerTransportConfig;
use codex_core::config_loader::CloudRequirementsLoader;
use codex_core::connectors::filter_disallowed_connectors;
use codex_core::connectors::merge_plugin_apps;
use codex_core::default_client::set_default_client_residency_requirement;
use codex_core::error::CodexErr;
use codex_core::exec::ExecParams;
@@ -206,15 +198,10 @@ use codex_core::mcp::collect_mcp_snapshot;
use codex_core::mcp::group_tools_by_server;
use codex_core::models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_core::parse_cursor;
use codex_core::plugins::AppConnectorId;
use codex_core::plugins::MarketplaceError;
use codex_core::plugins::MarketplacePluginSourceSummary;
use codex_core::plugins::PluginInstallError as CorePluginInstallError;
use codex_core::plugins::PluginInstallRequest;
use codex_core::plugins::load_plugin_apps;
use codex_core::read_head_for_summary;
use codex_core::read_session_meta_line;
use codex_core::resolve_fork_reference_rollout_path;
use codex_core::rollout_date_parts;
use codex_core::sandboxing::SandboxPermissions;
use codex_core::skills::remote::export_remote_skill;
@@ -474,14 +461,10 @@ impl CodexMessageProcessor {
}
}
async fn load_latest_config(
&self,
fallback_cwd: Option<PathBuf>,
) -> Result<Config, JSONRPCErrorError> {
async fn load_latest_config(&self) -> Result<Config, JSONRPCErrorError> {
let cloud_requirements = self.current_cloud_requirements();
let mut config = codex_core::config::ConfigBuilder::default()
.cli_overrides(self.cli_overrides.clone())
.fallback_cwd(fallback_cwd)
.cloud_requirements(cloud_requirements)
.build()
.await
@@ -663,10 +646,6 @@ impl CodexMessageProcessor {
self.skills_list(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::PluginList { request_id, params } => {
self.plugin_list(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::SkillsRemoteList { request_id, params } => {
self.skills_remote_list(to_connection_request_id(request_id), params)
.await;
@@ -2729,18 +2708,8 @@ impl CodexMessageProcessor {
} else {
read_summary_from_state_db_by_thread_id(&self.config, thread_uuid).await
};
let loaded_rollout_path = loaded_thread
.as_ref()
.and_then(|thread| thread.rollout_path());
let mut rollout_path = db_summary.as_ref().map(|summary| summary.path.clone());
if rollout_path.is_none()
&& let Some(path) = loaded_rollout_path.as_ref()
&& tokio::fs::try_exists(path).await.unwrap_or(false)
{
rollout_path = Some(path.clone());
}
let should_lookup_rollout = rollout_path.is_none() && loaded_thread.is_none();
if should_lookup_rollout {
if rollout_path.is_none() || include_turns {
rollout_path =
match find_thread_path_by_id_str(&self.config.codex_home, &thread_uuid.to_string())
.await
@@ -2801,6 +2770,7 @@ impl CodexMessageProcessor {
return;
};
let config_snapshot = thread.config_snapshot().await;
let loaded_rollout_path = thread.rollout_path();
if include_turns && loaded_rollout_path.is_none() {
self.send_invalid_request_error(
request_id,
@@ -3932,7 +3902,7 @@ impl CodexMessageProcessor {
params: ExperimentalFeatureListParams,
) {
let ExperimentalFeatureListParams { cursor, limit } = params;
let config = match self.load_latest_config(None).await {
let config = match self.load_latest_config().await {
Ok(config) => config,
Err(error) => {
self.outgoing.send_error(request_id, error).await;
@@ -4047,7 +4017,7 @@ impl CodexMessageProcessor {
}
async fn mcp_server_refresh(&self, request_id: ConnectionRequestId, _params: Option<()>) {
let config = match self.load_latest_config(None).await {
let config = match self.load_latest_config().await {
Ok(config) => config,
Err(error) => {
self.outgoing.send_error(request_id, error).await;
@@ -4106,7 +4076,7 @@ impl CodexMessageProcessor {
request_id: ConnectionRequestId,
params: McpServerOauthLoginParams,
) {
let config = match self.load_latest_config(None).await {
let config = match self.load_latest_config().await {
Ok(config) => config,
Err(error) => {
self.outgoing.send_error(request_id, error).await;
@@ -4212,7 +4182,7 @@ impl CodexMessageProcessor {
let request = request_id.clone();
let outgoing = Arc::clone(&self.outgoing);
let config = match self.load_latest_config(None).await {
let config = match self.load_latest_config().await {
Ok(config) => config,
Err(error) => {
self.outgoing.send_error(request, error).await;
@@ -4348,30 +4318,6 @@ impl CodexMessageProcessor {
self.outgoing.send_error(request_id, error).await;
}
async fn send_marketplace_error(
&self,
request_id: ConnectionRequestId,
err: MarketplaceError,
action: &str,
) {
match err {
MarketplaceError::MarketplaceNotFound { .. } => {
self.send_invalid_request_error(request_id, err.to_string())
.await;
}
MarketplaceError::Io { .. } => {
self.send_internal_error(request_id, format!("failed to {action}: {err}"))
.await;
}
MarketplaceError::InvalidMarketplaceFile { .. }
| MarketplaceError::PluginNotFound { .. }
| MarketplaceError::InvalidPlugin(_) => {
self.send_invalid_request_error(request_id, err.to_string())
.await;
}
}
}
async fn wait_for_thread_shutdown(thread: &Arc<CodexThread>) -> ThreadShutdownResult {
match thread.submit(Op::Shutdown).await {
Ok(_) => {
@@ -4635,7 +4581,7 @@ impl CodexMessageProcessor {
}
async fn apps_list(&self, request_id: ConnectionRequestId, params: AppsListParams) {
let mut config = match self.load_latest_config(None).await {
let mut config = match self.load_latest_config().await {
Ok(config) => config,
Err(error) => {
self.outgoing.send_error(request_id, error).await;
@@ -4866,36 +4812,6 @@ impl CodexMessageProcessor {
connectors::merge_connectors_with_accessible(all, accessible, all_connectors_loaded)
}
fn plugin_apps_needing_auth(
all_connectors: &[AppInfo],
accessible_connectors: &[AppInfo],
plugin_apps: &[AppConnectorId],
codex_apps_ready: bool,
) -> Vec<AppSummary> {
if !codex_apps_ready {
return Vec::new();
}
let accessible_ids = accessible_connectors
.iter()
.map(|connector| connector.id.as_str())
.collect::<HashSet<_>>();
let plugin_app_ids = plugin_apps
.iter()
.map(|connector_id| connector_id.0.as_str())
.collect::<HashSet<_>>();
all_connectors
.iter()
.filter(|connector| {
plugin_app_ids.contains(connector.id.as_str())
&& !accessible_ids.contains(connector.id.as_str())
})
.cloned()
.map(AppSummary::from)
.collect()
}
fn should_send_app_list_updated_notification(
connectors: &[AppInfo],
accessible_loaded: bool,
@@ -5008,66 +4924,6 @@ impl CodexMessageProcessor {
.await;
}
async fn plugin_list(&self, request_id: ConnectionRequestId, params: PluginListParams) {
let plugins_manager = self.thread_manager.plugins_manager();
let roots = params.cwds.unwrap_or_default();
let config = match self.load_latest_config(None).await {
Ok(config) => config,
Err(err) => {
self.outgoing.send_error(request_id, err).await;
return;
}
};
let data = match tokio::task::spawn_blocking(move || {
let marketplaces = plugins_manager.list_marketplaces_for_config(&config, &roots)?;
Ok::<Vec<PluginMarketplaceEntry>, MarketplaceError>(
marketplaces
.into_iter()
.map(|marketplace| PluginMarketplaceEntry {
name: marketplace.name,
path: marketplace.path,
plugins: marketplace
.plugins
.into_iter()
.map(|plugin| PluginSummary {
enabled: plugin.enabled,
name: plugin.name,
source: match plugin.source {
MarketplacePluginSourceSummary::Local { path } => {
PluginSource::Local { path }
}
},
})
.collect(),
})
.collect(),
)
})
.await
{
Ok(Ok(data)) => data,
Ok(Err(err)) => {
self.send_marketplace_error(request_id, err, "list marketplace plugins")
.await;
return;
}
Err(err) => {
self.send_internal_error(
request_id,
format!("failed to list marketplace plugins: {err}"),
)
.await;
return;
}
};
self.outgoing
.send_response(request_id, PluginListResponse { marketplaces: data })
.await;
}
async fn skills_remote_list(
&self,
request_id: ConnectionRequestId,
@@ -5178,96 +5034,24 @@ impl CodexMessageProcessor {
async fn plugin_install(&self, request_id: ConnectionRequestId, params: PluginInstallParams) {
let PluginInstallParams {
marketplace_path,
marketplace_name,
plugin_name,
cwd,
} = params;
let config_cwd = marketplace_path.as_path().parent().map(Path::to_path_buf);
let plugins_manager = self.thread_manager.plugins_manager();
let request = PluginInstallRequest {
plugin_name,
marketplace_path,
marketplace_name,
cwd: cwd.unwrap_or_else(|| self.config.cwd.clone()),
};
match plugins_manager.install_plugin(request).await {
Ok(result) => {
let config = match self.load_latest_config(config_cwd).await {
Ok(config) => config,
Err(err) => {
warn!(
"failed to reload config after plugin install, using current config: {err:?}"
);
self.config.as_ref().clone()
}
};
let plugin_apps = load_plugin_apps(&result.installed_path);
let apps_needing_auth = if plugin_apps.is_empty()
|| !config.features.enabled(Feature::Apps)
{
Vec::new()
} else {
let (all_connectors_result, accessible_connectors_result) = tokio::join!(
connectors::list_all_connectors_with_options(&config, true),
connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status(
&config, true
),
);
let all_connectors = match all_connectors_result {
Ok(connectors) => filter_disallowed_connectors(merge_plugin_apps(
connectors,
plugin_apps.clone(),
)),
Err(err) => {
warn!(
plugin = result.plugin_id.as_key(),
"failed to load app metadata after plugin install: {err:#}"
);
filter_disallowed_connectors(merge_plugin_apps(
connectors::list_cached_all_connectors(&config)
.await
.unwrap_or_default(),
plugin_apps.clone(),
))
}
};
let (accessible_connectors, codex_apps_ready) =
match accessible_connectors_result {
Ok(status) => (status.connectors, status.codex_apps_ready),
Err(err) => {
warn!(
plugin = result.plugin_id.as_key(),
"failed to load accessible apps after plugin install: {err:#}"
);
(
connectors::list_cached_accessible_connectors_from_mcp_tools(
&config,
)
.await
.unwrap_or_default(),
false,
)
}
};
if !codex_apps_ready {
warn!(
plugin = result.plugin_id.as_key(),
"codex_apps MCP not ready after plugin install; skipping appsNeedingAuth check"
);
}
Self::plugin_apps_needing_auth(
&all_connectors,
&accessible_connectors,
&plugin_apps,
codex_apps_ready,
)
};
Ok(_) => {
plugins_manager.clear_cache();
self.thread_manager.skills_manager().clear_cache();
self.outgoing
.send_response(request_id, PluginInstallResponse { apps_needing_auth })
.send_response(request_id, PluginInstallResponse {})
.await;
}
Err(err) => {
@@ -5278,10 +5062,6 @@ impl CodexMessageProcessor {
}
match err {
CorePluginInstallError::Marketplace(err) => {
self.send_marketplace_error(request_id, err, "install plugin")
.await;
}
CorePluginInstallError::Config(err) => {
self.send_internal_error(
request_id,
@@ -5296,13 +5076,7 @@ impl CodexMessageProcessor {
)
.await;
}
CorePluginInstallError::Store(err) => {
self.send_internal_error(
request_id,
format!("failed to install plugin: {err}"),
)
.await;
}
CorePluginInstallError::Marketplace(_) | CorePluginInstallError::Store(_) => {}
}
}
}
@@ -7214,17 +6988,13 @@ pub(crate) async fn read_summary_from_rollout(
.unwrap_or_else(|| fallback_provider.to_string());
let git_info = git.as_ref().map(map_git_info);
let updated_at = updated_at.or_else(|| timestamp.clone());
let preview = read_rollout_items_from_rollout(path)
.await
.map(|items| preview_from_rollout_items(&items))
.unwrap_or_default();
Ok(ConversationSummary {
conversation_id: session_meta.id,
timestamp,
updated_at,
path: path.to_path_buf(),
preview,
preview: String::new(),
model_provider,
cwd: session_meta.cwd,
cli_version: session_meta.cli_version,
@@ -7242,7 +7012,7 @@ pub(crate) async fn read_rollout_items_from_rollout(
InitialHistory::Resumed(resumed) => resumed.history,
};
Ok(materialize_rollout_items_for_replay(codex_home_from_rollout_path(path), &items).await)
Ok(items)
}
fn extract_conversation_summary(
@@ -7343,137 +7113,6 @@ fn preview_from_rollout_items(items: &[RolloutItem]) -> String {
.unwrap_or_default()
}
fn user_message_positions_in_rollout(items: &[RolloutItem]) -> Vec<usize> {
let mut user_positions = Vec::new();
for (idx, item) in items.iter().enumerate() {
match item {
RolloutItem::ResponseItem(item)
if matches!(
codex_core::parse_turn_item(item),
Some(TurnItem::UserMessage(_))
) =>
{
user_positions.push(idx);
}
RolloutItem::EventMsg(EventMsg::ThreadRolledBack(rollback)) => {
let num_turns = usize::try_from(rollback.num_turns).unwrap_or(usize::MAX);
let new_len = user_positions.len().saturating_sub(num_turns);
user_positions.truncate(new_len);
}
RolloutItem::ResponseItem(_) => {}
RolloutItem::SessionMeta(_)
| RolloutItem::ForkReference(_)
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::EventMsg(_) => {}
}
}
user_positions
}
fn truncate_rollout_before_nth_user_message_from_start(
items: &[RolloutItem],
n_from_start: usize,
) -> Vec<RolloutItem> {
if n_from_start == usize::MAX {
return items.to_vec();
}
let user_positions = user_message_positions_in_rollout(items);
if user_positions.len() <= n_from_start {
return Vec::new();
}
let cut_idx = user_positions[n_from_start];
items[..cut_idx].to_vec()
}
fn codex_home_from_rollout_path(path: &Path) -> Option<&Path> {
path.ancestors().find_map(|ancestor| {
let name = ancestor.file_name().and_then(OsStr::to_str)?;
if name == codex_core::SESSIONS_SUBDIR || name == codex_core::ARCHIVED_SESSIONS_SUBDIR {
ancestor.parent()
} else {
None
}
})
}
async fn materialize_rollout_items_for_replay(
codex_home: Option<&Path>,
rollout_items: &[RolloutItem],
) -> Vec<RolloutItem> {
const MAX_FORK_REFERENCE_DEPTH: usize = 8;
let mut materialized = Vec::new();
let mut stack: Vec<(Vec<RolloutItem>, usize, usize)> = vec![(rollout_items.to_vec(), 0, 0)];
while let Some((items, mut idx, depth)) = stack.pop() {
while idx < items.len() {
match &items[idx] {
RolloutItem::ForkReference(reference) => {
if depth >= MAX_FORK_REFERENCE_DEPTH {
warn!(
"skipping fork reference recursion at depth {} for {:?}",
depth, reference.rollout_path
);
idx += 1;
continue;
}
let resolved_rollout_path = if let Some(codex_home) = codex_home {
match resolve_fork_reference_rollout_path(
codex_home,
&reference.rollout_path,
)
.await
{
Ok(path) => path,
Err(err) => {
warn!(
"failed to resolve fork reference rollout {:?}: {err}",
reference.rollout_path
);
idx += 1;
continue;
}
}
} else {
reference.rollout_path.clone()
};
let parent_history = match RolloutRecorder::get_rollout_history(
&resolved_rollout_path,
)
.await
{
Ok(history) => history,
Err(err) => {
warn!(
"failed to load fork reference rollout {:?} (resolved from {:?}): {err}",
resolved_rollout_path, reference.rollout_path
);
idx += 1;
continue;
}
};
let parent_items = truncate_rollout_before_nth_user_message_from_start(
&parent_history.get_rollout_items(),
reference.nth_user_message,
);
stack.push((items, idx + 1, depth));
stack.push((parent_items, 0, depth + 1));
break;
}
item => materialized.push(item.clone()),
}
idx += 1;
}
}
materialized
}
fn with_thread_spawn_agent_metadata(
source: codex_protocol::protocol::SessionSource,
agent_nickname: Option<String>,
@@ -7628,35 +7267,6 @@ mod tests {
validate_dynamic_tools(&tools).expect("valid schema");
}
#[test]
fn plugin_apps_needing_auth_returns_empty_when_codex_apps_is_not_ready() {
let all_connectors = vec![AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
}];
assert_eq!(
CodexMessageProcessor::plugin_apps_needing_auth(
&all_connectors,
&[],
&[AppConnectorId("alpha".to_string())],
false,
),
Vec::<AppSummary>::new()
);
}
#[test]
fn collect_resume_override_mismatches_includes_service_tier() {
let request = ThreadResumeParams {
@@ -7683,7 +7293,6 @@ mod tests {
sandbox_policy: codex_protocol::protocol::SandboxPolicy::DangerFullAccess,
cwd: PathBuf::from("/tmp"),
ephemeral: false,
agent_use_function_call_inbox: false,
reasoning_effort: None,
personality: None,
session_source: SessionSource::Cli,

View File

@@ -163,6 +163,7 @@ fn map_network_requirements_to_api(
socks_port: network.socks_port,
allow_upstream_proxy: network.allow_upstream_proxy,
dangerously_allow_non_loopback_proxy: network.dangerously_allow_non_loopback_proxy,
dangerously_allow_non_loopback_admin: network.dangerously_allow_non_loopback_admin,
dangerously_allow_all_unix_sockets: network.dangerously_allow_all_unix_sockets,
allowed_domains: network.allowed_domains,
denied_domains: network.denied_domains,
@@ -229,6 +230,7 @@ mod tests {
socks_port: Some(1080),
allow_upstream_proxy: Some(false),
dangerously_allow_non_loopback_proxy: Some(false),
dangerously_allow_non_loopback_admin: Some(false),
dangerously_allow_all_unix_sockets: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["example.com".to_string()]),
@@ -273,6 +275,7 @@ mod tests {
socks_port: Some(1080),
allow_upstream_proxy: Some(false),
dangerously_allow_non_loopback_proxy: Some(false),
dangerously_allow_non_loopback_admin: Some(false),
dangerously_allow_all_unix_sockets: Some(true),
allowed_domains: Some(vec!["api.openai.com".to_string()]),
denied_domains: Some(vec!["example.com".to_string()]),

View File

@@ -38,6 +38,7 @@ use codex_core::ExecPolicyError;
use codex_core::check_execpolicy_for_warnings;
use codex_core::config_loader::ConfigLoadError;
use codex_core::config_loader::TextRange as CoreTextRange;
use codex_core::features::Feature;
use codex_feedback::CodexFeedback;
use codex_state::log_db;
use tokio::sync::mpsc;
@@ -498,14 +499,18 @@ pub async fn run_main_with_transport(
let feedback_layer = feedback.logger_layer();
let feedback_metadata_layer = feedback.metadata_layer();
let log_db = codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
None,
)
.await
.ok()
.map(log_db::start);
let log_db = if config.features.enabled(Feature::Sqlite) {
codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
None,
)
.await
.ok()
.map(log_db::start)
} else {
None
};
let log_db_layer = log_db
.clone()
.map(|layer| layer.with_filter(Targets::new().with_default(Level::TRACE)));

View File

@@ -188,7 +188,6 @@ impl MessageProcessor {
auth_manager.clone(),
SessionSource::VSCode,
config.model_catalog.clone(),
config.custom_models.clone(),
CollaborationModesConfig {
default_mode_request_user_input: config
.features

View File

@@ -101,10 +101,6 @@ impl ThreadScopedOutgoingMessageSender {
.await;
}
pub(crate) async fn send_global_server_notification(&self, notification: ServerNotification) {
self.outgoing.send_server_notification(notification).await;
}
pub(crate) async fn abort_pending_server_requests(&self) {
self.outgoing
.cancel_requests_for_thread(

View File

@@ -35,8 +35,6 @@ use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::LoginAccountParams;
use codex_app_server_protocol::MockExperimentalMethodParams;
use codex_app_server_protocol::ModelListParams;
use codex_app_server_protocol::PluginInstallParams;
use codex_app_server_protocol::PluginListParams;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::ReviewStartParams;
use codex_app_server_protocol::ServerRequest;
@@ -441,32 +439,6 @@ impl McpProcess {
self.send_request("skills/list", params).await
}
/// Send a `plugin/install` JSON-RPC request.
pub async fn send_plugin_install_request(
&mut self,
params: PluginInstallParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("plugin/install", params).await
}
/// Send a `plugin/list` JSON-RPC request.
pub async fn send_plugin_list_request(
&mut self,
params: PluginListParams,
) -> anyhow::Result<i64> {
let params = Some(serde_json::to_value(params)?);
self.send_request("plugin/list", params).await
}
/// Send a JSON-RPC request with raw params for protocol-level validation tests.
pub async fn send_raw_request(
&mut self,
method: &str,
params: Option<serde_json::Value>,
) -> anyhow::Result<i64> {
self.send_request(method, params).await
}
/// Send a `collaborationMode/list` JSON-RPC request.
pub async fn send_list_collaboration_modes_request(
&mut self,

View File

@@ -15,7 +15,6 @@ use std::path::Path;
fn preset_to_info(preset: &ModelPreset, priority: i32) -> ModelInfo {
ModelInfo {
slug: preset.id.clone(),
request_model: None,
display_name: preset.display_name.clone(),
description: Some(preset.description.clone()),
default_reasoning_level: Some(preset.default_reasoning_effort),

View File

@@ -6,7 +6,6 @@ use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::InitializeParams;
use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCRequest;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
@@ -203,56 +202,6 @@ pub(super) async fn read_response_for_id(
}
}
pub(super) async fn read_notification_for_method(
stream: &mut WsClient,
method: &str,
) -> Result<JSONRPCNotification> {
loop {
let message = read_jsonrpc_message(stream).await?;
if let JSONRPCMessage::Notification(notification) = message
&& notification.method == method
{
return Ok(notification);
}
}
}
pub(super) async fn read_response_and_notification_for_method(
stream: &mut WsClient,
id: i64,
method: &str,
) -> Result<(JSONRPCResponse, JSONRPCNotification)> {
let target_id = RequestId::Integer(id);
let mut response = None;
let mut notification = None;
while response.is_none() || notification.is_none() {
let message = read_jsonrpc_message(stream).await?;
match message {
JSONRPCMessage::Response(candidate) if candidate.id == target_id => {
response = Some(candidate);
}
JSONRPCMessage::Notification(candidate) if candidate.method == method => {
if notification.replace(candidate).is_some() {
bail!(
"received duplicate notification for method `{method}` before completing paired read"
);
}
}
_ => {}
}
}
let Some(response) = response else {
bail!("response must be set before returning");
};
let Some(notification) = notification else {
bail!("notification must be set before returning");
};
Ok((response, notification))
}
async fn read_error_for_id(stream: &mut WsClient, id: i64) -> Result<JSONRPCError> {
let target_id = RequestId::Integer(id);
loop {
@@ -288,7 +237,7 @@ async fn read_jsonrpc_message(stream: &mut WsClient) -> Result<JSONRPCMessage> {
}
}
pub(super) async fn assert_no_message(stream: &mut WsClient, wait_for: Duration) -> Result<()> {
async fn assert_no_message(stream: &mut WsClient, wait_for: Duration) -> Result<()> {
match timeout(wait_for, stream.next()).await {
Ok(Some(Ok(frame))) => bail!("unexpected frame while waiting for silence: {frame:?}"),
Ok(Some(Err(err))) => bail!("unexpected websocket read error: {err}"),

View File

@@ -18,17 +18,11 @@ use core_test_support::fs_wait;
use pretty_assertions::assert_eq;
use serde_json::Value;
use std::path::Path;
use std::time::Duration;
use tempfile::TempDir;
use tokio::time::timeout;
#[cfg(windows)]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
#[cfg(not(windows))]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[cfg(windows)]
const DEFAULT_NOTIFY_FILE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
#[cfg(not(windows))]
const DEFAULT_NOTIFY_FILE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
#[tokio::test]
async fn initialize_uses_client_info_name_as_originator() -> Result<()> {
@@ -267,7 +261,7 @@ tmp_path.replace(payload_path)
)
.await??;
fs_wait::wait_for_path_exists(&notify_file, DEFAULT_NOTIFY_FILE_TIMEOUT).await?;
fs_wait::wait_for_path_exists(&notify_file, Duration::from_secs(5)).await?;
let payload_raw = tokio::fs::read_to_string(&notify_file).await?;
let payload: Value = serde_json::from_str(&payload_raw)?;
assert_eq!(payload["client"], "xcode");

View File

@@ -16,7 +16,6 @@ use axum::http::header::AUTHORIZATION;
use axum::routing::get;
use codex_app_server_protocol::JSONRPCMessage;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::McpElicitationSchema;
use codex_app_server_protocol::McpServerElicitationAction;
use codex_app_server_protocol::McpServerElicitationRequest;
use codex_app_server_protocol::McpServerElicitationRequestParams;
@@ -187,12 +186,12 @@ async fn mcp_server_elicitation_round_trip() -> Result<()> {
let ServerRequest::McpServerElicitationRequest { request_id, params } = server_req else {
panic!("expected McpServerElicitationRequest request, got: {server_req:?}");
};
let requested_schema: McpElicitationSchema = serde_json::from_value(serde_json::to_value(
let requested_schema = serde_json::to_value(
ElicitationSchema::builder()
.required_property("confirmed", PrimitiveSchema::Boolean(BooleanSchema::new()))
.build()
.map_err(anyhow::Error::msg)?,
)?)?;
)?;
assert_eq!(
params,
@@ -201,7 +200,6 @@ async fn mcp_server_elicitation_round_trip() -> Result<()> {
turn_id: Some(turn.id.clone()),
server_name: "codex_apps".to_string(),
request: McpServerElicitationRequest::Form {
meta: None,
message: ELICITATION_MESSAGE.to_string(),
requested_schema,
},
@@ -216,7 +214,6 @@ async fn mcp_server_elicitation_round_trip() -> Result<()> {
content: Some(json!({
"confirmed": true,
})),
meta: None,
})?,
)
.await?;

View File

@@ -15,8 +15,6 @@ mod mcp_server_elicitation;
mod model_list;
mod output_schema;
mod plan_item;
mod plugin_install;
mod plugin_list;
mod rate_limits;
mod realtime_conversation;
mod request_user_input;
@@ -28,7 +26,6 @@ mod thread_fork;
mod thread_list;
mod thread_loaded_list;
mod thread_metadata_update;
mod thread_name_websocket;
mod thread_read;
mod thread_resume;
mod thread_rollback;

View File

@@ -1,468 +0,0 @@
use std::borrow::Cow;
use std::sync::Arc;
use std::sync::Mutex as StdMutex;
use std::time::Duration;
use anyhow::Result;
use app_test_support::ChatGptAuthFixture;
use app_test_support::McpProcess;
use app_test_support::to_response;
use app_test_support::write_chatgpt_auth;
use axum::Json;
use axum::Router;
use axum::extract::State;
use axum::http::HeaderMap;
use axum::http::StatusCode;
use axum::http::Uri;
use axum::http::header::AUTHORIZATION;
use axum::routing::get;
use codex_app_server_protocol::AppInfo;
use codex_app_server_protocol::AppSummary;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::PluginInstallParams;
use codex_app_server_protocol::PluginInstallResponse;
use codex_app_server_protocol::RequestId;
use codex_core::auth::AuthCredentialsStoreMode;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use rmcp::handler::server::ServerHandler;
use rmcp::model::JsonObject;
use rmcp::model::ListToolsResult;
use rmcp::model::Meta;
use rmcp::model::ServerCapabilities;
use rmcp::model::ServerInfo;
use rmcp::model::Tool;
use rmcp::model::ToolAnnotations;
use rmcp::transport::StreamableHttpServerConfig;
use rmcp::transport::StreamableHttpService;
use rmcp::transport::streamable_http_server::session::local::LocalSessionManager;
use serde_json::json;
use tempfile::TempDir;
use tokio::net::TcpListener;
use tokio::task::JoinHandle;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
#[tokio::test]
async fn plugin_install_rejects_relative_marketplace_paths() -> Result<()> {
let codex_home = TempDir::new()?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_raw_request(
"plugin/install",
Some(serde_json::json!({
"marketplacePath": "relative-marketplace.json",
"pluginName": "missing-plugin",
})),
)
.await?;
let err = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await??;
assert_eq!(err.error.code, -32600);
assert!(err.error.message.contains("Invalid request"));
Ok(())
}
#[tokio::test]
async fn plugin_install_returns_invalid_request_for_missing_marketplace_file() -> Result<()> {
let codex_home = TempDir::new()?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_install_request(PluginInstallParams {
marketplace_path: AbsolutePathBuf::try_from(
codex_home.path().join("missing-marketplace.json"),
)?,
plugin_name: "missing-plugin".to_string(),
})
.await?;
let err = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await??;
assert_eq!(err.error.code, -32600);
assert!(err.error.message.contains("marketplace file"));
assert!(err.error.message.contains("does not exist"));
Ok(())
}
#[tokio::test]
async fn plugin_install_returns_apps_needing_auth() -> Result<()> {
let connectors = vec![
AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: Some("featured".to_string()),
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
},
AppInfo {
id: "beta".to_string(),
name: "Beta".to_string(),
description: Some("Beta connector".to_string()),
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
},
];
let tools = vec![connector_tool("beta", "Beta App")?];
let (server_url, server_handle) = start_apps_server(connectors, tools).await?;
let codex_home = TempDir::new()?;
write_connectors_config(codex_home.path(), &server_url)?;
write_chatgpt_auth(
codex_home.path(),
ChatGptAuthFixture::new("chatgpt-token")
.account_id("account-123")
.chatgpt_user_id("user-123")
.chatgpt_account_id("account-123"),
AuthCredentialsStoreMode::File,
)?;
let repo_root = TempDir::new()?;
write_plugin_marketplace(
repo_root.path(),
"debug",
"sample-plugin",
"./sample-plugin",
)?;
write_plugin_source(repo_root.path(), "sample-plugin", &["alpha", "beta"])?;
let marketplace_path =
AbsolutePathBuf::try_from(repo_root.path().join(".agents/plugins/marketplace.json"))?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_install_request(PluginInstallParams {
marketplace_path,
plugin_name: "sample-plugin".to_string(),
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: PluginInstallResponse = to_response(response)?;
assert_eq!(
response,
PluginInstallResponse {
apps_needing_auth: vec![AppSummary {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
}],
}
);
server_handle.abort();
let _ = server_handle.await;
Ok(())
}
#[tokio::test]
async fn plugin_install_filters_disallowed_apps_needing_auth() -> Result<()> {
let connectors = vec![AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: Some("https://example.com/alpha.png".to_string()),
logo_url_dark: None,
distribution_channel: Some("featured".to_string()),
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
}];
let (server_url, server_handle) = start_apps_server(connectors, Vec::new()).await?;
let codex_home = TempDir::new()?;
write_connectors_config(codex_home.path(), &server_url)?;
write_chatgpt_auth(
codex_home.path(),
ChatGptAuthFixture::new("chatgpt-token")
.account_id("account-123")
.chatgpt_user_id("user-123")
.chatgpt_account_id("account-123"),
AuthCredentialsStoreMode::File,
)?;
let repo_root = TempDir::new()?;
write_plugin_marketplace(
repo_root.path(),
"debug",
"sample-plugin",
"./sample-plugin",
)?;
write_plugin_source(
repo_root.path(),
"sample-plugin",
&["alpha", "asdk_app_6938a94a61d881918ef32cb999ff937c"],
)?;
let marketplace_path =
AbsolutePathBuf::try_from(repo_root.path().join(".agents/plugins/marketplace.json"))?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_install_request(PluginInstallParams {
marketplace_path,
plugin_name: "sample-plugin".to_string(),
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: PluginInstallResponse = to_response(response)?;
assert_eq!(
response,
PluginInstallResponse {
apps_needing_auth: vec![AppSummary {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
install_url: Some("https://chatgpt.com/apps/alpha/alpha".to_string()),
}],
}
);
server_handle.abort();
let _ = server_handle.await;
Ok(())
}
#[derive(Clone)]
struct AppsServerState {
response: Arc<StdMutex<serde_json::Value>>,
}
#[derive(Clone)]
struct PluginInstallMcpServer {
tools: Arc<StdMutex<Vec<Tool>>>,
}
impl ServerHandler for PluginInstallMcpServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_tools().build(),
..ServerInfo::default()
}
}
fn list_tools(
&self,
_request: Option<rmcp::model::PaginatedRequestParams>,
_context: rmcp::service::RequestContext<rmcp::service::RoleServer>,
) -> impl std::future::Future<Output = Result<ListToolsResult, rmcp::ErrorData>> + Send + '_
{
let tools = self.tools.clone();
async move {
let tools = tools
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
Ok(ListToolsResult {
tools,
next_cursor: None,
meta: None,
})
}
}
}
async fn start_apps_server(
connectors: Vec<AppInfo>,
tools: Vec<Tool>,
) -> Result<(String, JoinHandle<()>)> {
let state = Arc::new(AppsServerState {
response: Arc::new(StdMutex::new(
json!({ "apps": connectors, "next_token": null }),
)),
});
let tools = Arc::new(StdMutex::new(tools));
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let mcp_service = StreamableHttpService::new(
{
let tools = tools.clone();
move || {
Ok(PluginInstallMcpServer {
tools: tools.clone(),
})
}
},
Arc::new(LocalSessionManager::default()),
StreamableHttpServerConfig::default(),
);
let router = Router::new()
.route("/connectors/directory/list", get(list_directory_connectors))
.route(
"/connectors/directory/list_workspace",
get(list_directory_connectors),
)
.with_state(state)
.nest_service("/api/codex/apps", mcp_service);
let handle = tokio::spawn(async move {
let _ = axum::serve(listener, router).await;
});
Ok((format!("http://{addr}"), handle))
}
async fn list_directory_connectors(
State(state): State<Arc<AppsServerState>>,
headers: HeaderMap,
uri: Uri,
) -> Result<impl axum::response::IntoResponse, StatusCode> {
let bearer_ok = headers
.get(AUTHORIZATION)
.and_then(|value| value.to_str().ok())
.is_some_and(|value| value == "Bearer chatgpt-token");
let account_ok = headers
.get("chatgpt-account-id")
.and_then(|value| value.to_str().ok())
.is_some_and(|value| value == "account-123");
let external_logos_ok = uri
.query()
.is_some_and(|query| query.split('&').any(|pair| pair == "external_logos=true"));
if !bearer_ok || !account_ok {
Err(StatusCode::UNAUTHORIZED)
} else if !external_logos_ok {
Err(StatusCode::BAD_REQUEST)
} else {
let response = state
.response
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone();
Ok(Json(response))
}
}
fn connector_tool(connector_id: &str, connector_name: &str) -> Result<Tool> {
let schema: JsonObject = serde_json::from_value(json!({
"type": "object",
"additionalProperties": false
}))?;
let mut tool = Tool::new(
Cow::Owned(format!("connector_{connector_id}")),
Cow::Borrowed("Connector test tool"),
Arc::new(schema),
);
tool.annotations = Some(ToolAnnotations::new().read_only(true));
let mut meta = Meta::new();
meta.0
.insert("connector_id".to_string(), json!(connector_id));
meta.0
.insert("connector_name".to_string(), json!(connector_name));
tool.meta = Some(meta);
Ok(tool)
}
fn write_connectors_config(codex_home: &std::path::Path, base_url: &str) -> std::io::Result<()> {
std::fs::write(
codex_home.join("config.toml"),
format!(
r#"
chatgpt_base_url = "{base_url}"
mcp_oauth_credentials_store = "file"
[features]
connectors = true
"#
),
)
}
fn write_plugin_marketplace(
repo_root: &std::path::Path,
marketplace_name: &str,
plugin_name: &str,
source_path: &str,
) -> std::io::Result<()> {
std::fs::create_dir_all(repo_root.join(".git"))?;
std::fs::create_dir_all(repo_root.join(".agents/plugins"))?;
std::fs::write(
repo_root.join(".agents/plugins/marketplace.json"),
format!(
r#"{{
"name": "{marketplace_name}",
"plugins": [
{{
"name": "{plugin_name}",
"source": {{
"source": "local",
"path": "{source_path}"
}}
}}
]
}}"#
),
)
}
fn write_plugin_source(
repo_root: &std::path::Path,
plugin_name: &str,
app_ids: &[&str],
) -> Result<()> {
let plugin_root = repo_root.join(".agents/plugins").join(plugin_name);
std::fs::create_dir_all(plugin_root.join(".codex-plugin"))?;
std::fs::write(
plugin_root.join(".codex-plugin/plugin.json"),
format!(r#"{{"name":"{plugin_name}"}}"#),
)?;
let apps = app_ids
.iter()
.map(|app_id| ((*app_id).to_string(), json!({ "id": app_id })))
.collect::<serde_json::Map<_, _>>();
std::fs::write(
plugin_root.join(".app.json"),
serde_json::to_vec_pretty(&json!({ "apps": apps }))?,
)?;
Ok(())
}

View File

@@ -1,299 +0,0 @@
use std::time::Duration;
use anyhow::Result;
use app_test_support::McpProcess;
use app_test_support::to_response;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::PluginListParams;
use codex_app_server_protocol::PluginListResponse;
use codex_app_server_protocol::RequestId;
use codex_core::config::set_project_trust_level;
use codex_protocol::config_types::TrustLevel;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
#[tokio::test]
async fn plugin_list_returns_invalid_request_for_invalid_marketplace_file() -> Result<()> {
let codex_home = TempDir::new()?;
let repo_root = TempDir::new()?;
std::fs::create_dir_all(repo_root.path().join(".git"))?;
std::fs::create_dir_all(repo_root.path().join(".agents/plugins"))?;
std::fs::write(
repo_root.path().join(".agents/plugins/marketplace.json"),
"{not json",
)?;
let home = codex_home.path().to_string_lossy().into_owned();
let mut mcp = McpProcess::new_with_env(
codex_home.path(),
&[
("HOME", Some(home.as_str())),
("USERPROFILE", Some(home.as_str())),
],
)
.await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
})
.await?;
let err = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await??;
assert_eq!(err.error.code, -32600);
assert!(err.error.message.contains("invalid marketplace file"));
Ok(())
}
#[tokio::test]
async fn plugin_list_rejects_relative_cwds() -> Result<()> {
let codex_home = TempDir::new()?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_raw_request(
"plugin/list",
Some(serde_json::json!({
"cwds": ["relative-root"],
})),
)
.await?;
let err = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_error_message(RequestId::Integer(request_id)),
)
.await??;
assert_eq!(err.error.code, -32600);
assert!(err.error.message.contains("Invalid request"));
Ok(())
}
#[tokio::test]
async fn plugin_list_accepts_omitted_cwds() -> Result<()> {
let codex_home = TempDir::new()?;
std::fs::create_dir_all(codex_home.path().join(".agents/plugins"))?;
std::fs::write(
codex_home.path().join(".agents/plugins/marketplace.json"),
r#"{
"name": "codex-curated",
"plugins": [
{
"name": "home-plugin",
"source": {
"source": "local",
"path": "./home-plugin"
}
}
]
}"#,
)?;
let home = codex_home.path().to_string_lossy().into_owned();
let mut mcp = McpProcess::new_with_env(
codex_home.path(),
&[
("HOME", Some(home.as_str())),
("USERPROFILE", Some(home.as_str())),
],
)
.await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_list_request(PluginListParams { cwds: None })
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let _: PluginListResponse = to_response(response)?;
Ok(())
}
#[tokio::test]
async fn plugin_list_includes_enabled_state_from_config() -> Result<()> {
let codex_home = TempDir::new()?;
let repo_root = TempDir::new()?;
std::fs::create_dir_all(repo_root.path().join(".git"))?;
std::fs::create_dir_all(repo_root.path().join(".agents/plugins"))?;
std::fs::write(
repo_root.path().join(".agents/plugins/marketplace.json"),
r#"{
"name": "codex-curated",
"plugins": [
{
"name": "enabled-plugin",
"source": {
"source": "local",
"path": "./enabled-plugin"
}
},
{
"name": "disabled-plugin",
"source": {
"source": "local",
"path": "./disabled-plugin"
}
}
]
}"#,
)?;
std::fs::write(
codex_home.path().join("config.toml"),
r#"[features]
plugins = true
[plugins."enabled-plugin@codex-curated"]
enabled = true
[plugins."disabled-plugin@codex-curated"]
enabled = false
"#,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![AbsolutePathBuf::try_from(repo_root.path())?]),
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: PluginListResponse = to_response(response)?;
let marketplace = response
.marketplaces
.into_iter()
.find(|marketplace| {
marketplace.path == repo_root.path().join(".agents/plugins/marketplace.json")
})
.expect("expected repo marketplace entry");
assert_eq!(marketplace.name, "codex-curated");
assert_eq!(marketplace.plugins.len(), 2);
assert_eq!(marketplace.plugins[0].name, "enabled-plugin");
assert_eq!(marketplace.plugins[0].enabled, true);
assert_eq!(marketplace.plugins[1].name, "disabled-plugin");
assert_eq!(marketplace.plugins[1].enabled, false);
Ok(())
}
#[tokio::test]
async fn plugin_list_uses_home_config_for_enabled_state() -> Result<()> {
let codex_home = TempDir::new()?;
std::fs::create_dir_all(codex_home.path().join(".agents/plugins"))?;
std::fs::write(
codex_home.path().join(".agents/plugins/marketplace.json"),
r#"{
"name": "codex-curated",
"plugins": [
{
"name": "shared-plugin",
"source": {
"source": "local",
"path": "./shared-plugin"
}
}
]
}"#,
)?;
std::fs::write(
codex_home.path().join("config.toml"),
r#"[features]
plugins = true
[plugins."shared-plugin@codex-curated"]
enabled = true
"#,
)?;
let workspace_enabled = TempDir::new()?;
std::fs::create_dir_all(workspace_enabled.path().join(".git"))?;
std::fs::create_dir_all(workspace_enabled.path().join(".agents/plugins"))?;
std::fs::write(
workspace_enabled
.path()
.join(".agents/plugins/marketplace.json"),
r#"{
"name": "codex-curated",
"plugins": [
{
"name": "shared-plugin",
"source": {
"source": "local",
"path": "./shared-plugin"
}
}
]
}"#,
)?;
std::fs::create_dir_all(workspace_enabled.path().join(".codex"))?;
std::fs::write(
workspace_enabled.path().join(".codex/config.toml"),
r#"[plugins."shared-plugin@codex-curated"]
enabled = false
"#,
)?;
set_project_trust_level(
codex_home.path(),
workspace_enabled.path(),
TrustLevel::Trusted,
)?;
let workspace_default = TempDir::new()?;
let home = codex_home.path().to_string_lossy().into_owned();
let mut mcp = McpProcess::new_with_env(
codex_home.path(),
&[
("HOME", Some(home.as_str())),
("USERPROFILE", Some(home.as_str())),
],
)
.await?;
timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_plugin_list_request(PluginListParams {
cwds: Some(vec![
AbsolutePathBuf::try_from(workspace_enabled.path())?,
AbsolutePathBuf::try_from(workspace_default.path())?,
]),
})
.await?;
let response: JSONRPCResponse = timeout(
DEFAULT_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let response: PluginListResponse = to_response(response)?;
let shared_plugin = response
.marketplaces
.iter()
.flat_map(|marketplace| marketplace.plugins.iter())
.find(|plugin| plugin.name == "shared-plugin")
.expect("expected shared-plugin entry");
assert_eq!(shared_plugin.enabled, true);
Ok(())
}

View File

@@ -36,7 +36,6 @@ use tempfile::TempDir;
use tokio::time::timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
const STARTUP_CONTEXT_HEADER: &str = "Startup context from Codex.";
#[tokio::test]
async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
@@ -115,18 +114,6 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
assert_eq!(started.thread_id, thread_start.thread.id);
assert!(started.session_id.is_some());
let startup_context_request = realtime_server.wait_for_request(0, 0).await;
assert_eq!(
startup_context_request.body_json()["type"].as_str(),
Some("session.update")
);
assert!(
startup_context_request.body_json()["session"]["instructions"]
.as_str()
.context("expected startup context instructions")?
.contains(STARTUP_CONTEXT_HEADER)
);
let audio_append_request_id = mcp
.send_thread_realtime_append_audio_request(ThreadRealtimeAppendAudioParams {
thread_id: started.thread_id.clone(),
@@ -196,12 +183,6 @@ async fn realtime_conversation_streams_v2_notifications() -> Result<()> {
connection[0].body_json()["type"].as_str(),
Some("session.update")
);
assert!(
connection[0].body_json()["session"]["instructions"]
.as_str()
.context("expected startup context instructions")?
.contains(STARTUP_CONTEXT_HEADER)
);
let mut request_types = [
connection[1].body_json()["type"]
.as_str()

View File

@@ -1,171 +0,0 @@
use super::connection_handling_websocket::DEFAULT_READ_TIMEOUT;
use super::connection_handling_websocket::WsClient;
use super::connection_handling_websocket::assert_no_message;
use super::connection_handling_websocket::connect_websocket;
use super::connection_handling_websocket::create_config_toml;
use super::connection_handling_websocket::read_notification_for_method;
use super::connection_handling_websocket::read_response_and_notification_for_method;
use super::connection_handling_websocket::read_response_for_id;
use super::connection_handling_websocket::reserve_local_addr;
use super::connection_handling_websocket::send_initialize_request;
use super::connection_handling_websocket::send_request;
use super::connection_handling_websocket::spawn_websocket_server;
use anyhow::Context;
use anyhow::Result;
use app_test_support::create_fake_rollout_with_text_elements;
use app_test_support::create_mock_responses_server_repeating_assistant;
use app_test_support::to_response;
use codex_app_server_protocol::JSONRPCNotification;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::ThreadNameUpdatedNotification;
use codex_app_server_protocol::ThreadResumeParams;
use codex_app_server_protocol::ThreadResumeResponse;
use codex_app_server_protocol::ThreadSetNameParams;
use codex_app_server_protocol::ThreadSetNameResponse;
use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::Duration;
use tokio::time::timeout;
#[tokio::test]
async fn thread_name_updated_broadcasts_for_loaded_threads() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri(), "never")?;
let conversation_id = create_rollout(codex_home.path(), "2025-01-05T12-00-00")?;
let bind_addr = reserve_local_addr()?;
let mut process = spawn_websocket_server(codex_home.path(), bind_addr).await?;
let result = async {
let mut ws1 = connect_websocket(bind_addr).await?;
let mut ws2 = connect_websocket(bind_addr).await?;
initialize_both_clients(&mut ws1, &mut ws2).await?;
send_request(
&mut ws1,
"thread/resume",
10,
Some(serde_json::to_value(ThreadResumeParams {
thread_id: conversation_id.clone(),
..Default::default()
})?),
)
.await?;
let resume_resp: JSONRPCResponse = read_response_for_id(&mut ws1, 10).await?;
let resume: ThreadResumeResponse = to_response::<ThreadResumeResponse>(resume_resp)?;
assert_eq!(resume.thread.id, conversation_id);
let renamed = "Loaded rename";
send_request(
&mut ws1,
"thread/name/set",
11,
Some(serde_json::to_value(ThreadSetNameParams {
thread_id: conversation_id.clone(),
name: renamed.to_string(),
})?),
)
.await?;
let (rename_resp, ws1_notification) =
read_response_and_notification_for_method(&mut ws1, 11, "thread/name/updated").await?;
let _: ThreadSetNameResponse = to_response::<ThreadSetNameResponse>(rename_resp)?;
assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?;
let ws2_notification =
read_notification_for_method(&mut ws2, "thread/name/updated").await?;
assert_thread_name_updated(ws2_notification, &conversation_id, renamed)?;
assert_no_message(&mut ws1, Duration::from_millis(250)).await?;
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
Ok(())
}
.await;
process
.kill()
.await
.context("failed to stop websocket app-server process")?;
result
}
#[tokio::test]
async fn thread_name_updated_broadcasts_for_not_loaded_threads() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri(), "never")?;
let conversation_id = create_rollout(codex_home.path(), "2025-01-05T12-05-00")?;
let bind_addr = reserve_local_addr()?;
let mut process = spawn_websocket_server(codex_home.path(), bind_addr).await?;
let result = async {
let mut ws1 = connect_websocket(bind_addr).await?;
let mut ws2 = connect_websocket(bind_addr).await?;
initialize_both_clients(&mut ws1, &mut ws2).await?;
let renamed = "Stored rename";
send_request(
&mut ws1,
"thread/name/set",
20,
Some(serde_json::to_value(ThreadSetNameParams {
thread_id: conversation_id.clone(),
name: renamed.to_string(),
})?),
)
.await?;
let (rename_resp, ws1_notification) =
read_response_and_notification_for_method(&mut ws1, 20, "thread/name/updated").await?;
let _: ThreadSetNameResponse = to_response::<ThreadSetNameResponse>(rename_resp)?;
assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?;
let ws2_notification =
read_notification_for_method(&mut ws2, "thread/name/updated").await?;
assert_thread_name_updated(ws2_notification, &conversation_id, renamed)?;
assert_no_message(&mut ws1, Duration::from_millis(250)).await?;
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
Ok(())
}
.await;
process
.kill()
.await
.context("failed to stop websocket app-server process")?;
result
}
async fn initialize_both_clients(ws1: &mut WsClient, ws2: &mut WsClient) -> Result<()> {
send_initialize_request(ws1, 1, "ws_client_one").await?;
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws1, 1)).await??;
send_initialize_request(ws2, 2, "ws_client_two").await?;
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws2, 2)).await??;
Ok(())
}
fn create_rollout(codex_home: &std::path::Path, filename_ts: &str) -> Result<String> {
create_fake_rollout_with_text_elements(
codex_home,
filename_ts,
"2025-01-05T12:00:00Z",
"Saved user message",
Vec::new(),
Some("mock_provider"),
None,
)
}
fn assert_thread_name_updated(
notification: JSONRPCNotification,
thread_id: &str,
thread_name: &str,
) -> Result<()> {
let notification: ThreadNameUpdatedNotification =
serde_json::from_value(notification.params.context("thread/name/updated params")?)?;
assert_eq!(notification.thread_id, thread_id);
assert_eq!(notification.thread_name.as_deref(), Some(thread_name));
Ok(())
}

View File

@@ -7,10 +7,6 @@ use codex_app_server_protocol::JSONRPCError;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_app_server_protocol::SessionSource;
use codex_app_server_protocol::ThreadArchiveParams;
use codex_app_server_protocol::ThreadArchiveResponse;
use codex_app_server_protocol::ThreadForkParams;
use codex_app_server_protocol::ThreadForkResponse;
use codex_app_server_protocol::ThreadItem;
use codex_app_server_protocol::ThreadListParams;
use codex_app_server_protocol::ThreadListResponse;
@@ -24,8 +20,6 @@ use codex_app_server_protocol::ThreadSetNameResponse;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
use codex_app_server_protocol::ThreadStatus;
use codex_app_server_protocol::ThreadUnarchiveParams;
use codex_app_server_protocol::ThreadUnarchiveResponse;
use codex_app_server_protocol::TurnStartParams;
use codex_app_server_protocol::TurnStartResponse;
use codex_app_server_protocol::TurnStatus;
@@ -158,150 +152,6 @@ async fn thread_read_can_include_turns() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn thread_read_include_turns_keeps_fork_history_after_parent_archive_and_unarchive()
-> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri())?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let start_id = mcp
.send_thread_start_request(ThreadStartParams {
model: Some("mock-model".to_string()),
..Default::default()
})
.await?;
let start_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread: parent, .. } =
to_response::<ThreadStartResponse>(start_resp)?;
let turn_start_id = mcp
.send_turn_start_request(TurnStartParams {
thread_id: parent.id.clone(),
input: vec![UserInput::Text {
text: "parent message".to_string(),
text_elements: Vec::new(),
}],
..Default::default()
})
.await?;
let turn_start_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(turn_start_id)),
)
.await??;
let _: TurnStartResponse = to_response::<TurnStartResponse>(turn_start_resp)?;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("turn/completed"),
)
.await??;
let fork_id = mcp
.send_thread_fork_request(ThreadForkParams {
thread_id: parent.id.clone(),
..Default::default()
})
.await?;
let fork_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(fork_id)),
)
.await??;
let ThreadForkResponse { thread: child, .. } = to_response::<ThreadForkResponse>(fork_resp)?;
let read_child_id = mcp
.send_thread_read_request(ThreadReadParams {
thread_id: child.id.clone(),
include_turns: true,
})
.await?;
let read_child_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(read_child_id)),
)
.await??;
let ThreadReadResponse {
thread: child_before_archive,
} = to_response::<ThreadReadResponse>(read_child_resp)?;
assert_eq!(child_before_archive.turns.len(), 1);
let archive_id = mcp
.send_thread_archive_request(ThreadArchiveParams {
thread_id: parent.id.clone(),
})
.await?;
let archive_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(archive_id)),
)
.await??;
let _: ThreadArchiveResponse = to_response::<ThreadArchiveResponse>(archive_resp)?;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("thread/archived"),
)
.await??;
let read_child_id = mcp
.send_thread_read_request(ThreadReadParams {
thread_id: child.id.clone(),
include_turns: true,
})
.await?;
let read_child_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(read_child_id)),
)
.await??;
let ThreadReadResponse {
thread: child_after_archive,
} = to_response::<ThreadReadResponse>(read_child_resp)?;
assert_eq!(child_after_archive.turns, child_before_archive.turns);
let unarchive_id = mcp
.send_thread_unarchive_request(ThreadUnarchiveParams {
thread_id: parent.id,
})
.await?;
let unarchive_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(unarchive_id)),
)
.await??;
let _: ThreadUnarchiveResponse = to_response::<ThreadUnarchiveResponse>(unarchive_resp)?;
timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_notification_message("thread/unarchived"),
)
.await??;
let read_child_id = mcp
.send_thread_read_request(ThreadReadParams {
thread_id: child.id,
include_turns: true,
})
.await?;
let read_child_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(read_child_id)),
)
.await??;
let ThreadReadResponse {
thread: child_after_unarchive,
} = to_response::<ThreadReadResponse>(read_child_resp)?;
assert_eq!(child_after_unarchive.turns, child_before_archive.turns);
Ok(())
}
#[tokio::test]
async fn thread_read_loaded_thread_returns_precomputed_path_before_materialization() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
@@ -557,62 +407,6 @@ async fn thread_read_include_turns_rejects_unmaterialized_loaded_thread() -> Res
Ok(())
}
#[tokio::test]
async fn thread_read_loaded_ephemeral_thread_ignores_unrelated_rollout_mentions() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;
let codex_home = TempDir::new()?;
create_config_toml(codex_home.path(), &server.uri())?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let start_id = mcp
.send_thread_start_request(ThreadStartParams {
model: Some("mock-model".to_string()),
ephemeral: Some(true),
..Default::default()
})
.await?;
let start_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(start_id)),
)
.await??;
let ThreadStartResponse { thread, .. } = to_response::<ThreadStartResponse>(start_resp)?;
let unrelated_preview = thread.id.clone();
let _unrelated_rollout_id = create_fake_rollout_with_text_elements(
codex_home.path(),
"2025-01-05T13-00-00",
"2025-01-05T13:00:00Z",
&unrelated_preview,
vec![],
Some("mock_provider"),
None,
)?;
let read_id = mcp
.send_thread_read_request(ThreadReadParams {
thread_id: thread.id.clone(),
include_turns: false,
})
.await?;
let read_resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(read_id)),
)
.await??;
let ThreadReadResponse { thread: read } = to_response::<ThreadReadResponse>(read_resp)?;
assert_eq!(read.id, thread.id);
assert!(read.ephemeral);
assert_eq!(read.path, None);
assert!(read.preview.is_empty());
assert_eq!(read.status, ThreadStatus::Idle);
Ok(())
}
#[tokio::test]
async fn thread_read_reports_system_error_idle_flag_after_failed_turn() -> Result<()> {
let server = responses::start_mock_server().await;

View File

@@ -53,14 +53,9 @@ use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use tempfile::TempDir;
use tokio::time::sleep;
use tokio::time::timeout;
use uuid::Uuid;
use wiremock::MockServer;
#[cfg(windows)]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
#[cfg(not(windows))]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const CODEX_5_2_INSTRUCTIONS_TEMPLATE_DEFAULT: &str = "You are Codex, a coding agent based on GPT-5. You and the user share the same workspace and collaborate to achieve the user's goals.";
@@ -863,7 +858,16 @@ async fn thread_resume_rejoins_running_thread_even_with_override_mismatch() -> R
async fn thread_resume_replays_pending_command_execution_request_approval() -> Result<()> {
let responses = vec![
create_final_assistant_message_sse_response("seeded")?,
create_shell_command_sse_response(fast_shell_command(), None, Some(1000), "call-1")?,
create_shell_command_sse_response(
vec![
"python3".to_string(),
"-c".to_string(),
"print(42)".to_string(),
],
None,
Some(5000),
"call-1",
)?,
create_final_assistant_message_sse_response("done")?,
];
let server = create_mock_responses_server_sequence(responses).await;
@@ -981,7 +985,6 @@ async fn thread_resume_replays_pending_command_execution_request_approval() -> R
primary.read_stream_until_notification_message("turn/completed"),
)
.await??;
wait_for_mock_request_count(&server, 3).await?;
Ok(())
}
@@ -1147,50 +1150,10 @@ async fn thread_resume_replays_pending_file_change_request_approval() -> Result<
primary.read_stream_until_notification_message("turn/completed"),
)
.await??;
wait_for_mock_request_count(&server, 3).await?;
Ok(())
}
fn fast_shell_command() -> Vec<String> {
if cfg!(windows) {
vec![
"cmd".to_string(),
"/d".to_string(),
"/c".to_string(),
"echo 42".to_string(),
]
} else {
vec![
"python3".to_string(),
"-c".to_string(),
"print(42)".to_string(),
]
}
}
async fn wait_for_mock_request_count(server: &MockServer, expected: usize) -> Result<()> {
let deadline = tokio::time::Instant::now() + DEFAULT_READ_TIMEOUT;
loop {
let requests = server
.received_requests()
.await
.ok_or_else(|| anyhow::anyhow!("failed to fetch received requests"))?;
if requests.len() >= expected {
return Ok(());
}
if tokio::time::Instant::now() >= deadline {
anyhow::bail!(
"expected at least {expected} mock requests, observed {}",
requests.len()
);
}
sleep(std::time::Duration::from_millis(50)).await;
}
}
#[tokio::test]
async fn thread_resume_with_overrides_defers_updated_at_until_turn_start() -> Result<()> {
let server = create_mock_responses_server_repeating_assistant("Done").await;

View File

@@ -32,9 +32,6 @@ use pretty_assertions::assert_eq;
use tempfile::TempDir;
use tokio::time::timeout;
#[cfg(windows)]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(25);
#[cfg(not(windows))]
const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
#[tokio::test]

View File

@@ -1016,9 +1016,27 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> {
// Mock server: first turn requests a shell call (elicitation), then completes.
// Second turn same, but we'll set approval_policy=never to avoid elicitation.
let responses = vec![
create_shell_command_sse_response(fast_shell_command(), None, Some(1000), "call1")?,
create_shell_command_sse_response(
vec![
"python3".to_string(),
"-c".to_string(),
"print(42)".to_string(),
],
None,
Some(5000),
"call1",
)?,
create_final_assistant_message_sse_response("done 1")?,
create_shell_command_sse_response(fast_shell_command(), None, Some(1000), "call2")?,
create_shell_command_sse_response(
vec![
"python3".to_string(),
"-c".to_string(),
"print(42)".to_string(),
],
None,
Some(5000),
"call2",
)?,
create_final_assistant_message_sse_response("done 2")?,
];
let server = create_mock_responses_server_sequence(responses).await;
@@ -1148,23 +1166,6 @@ async fn turn_start_exec_approval_toggle_v2() -> Result<()> {
Ok(())
}
fn fast_shell_command() -> Vec<String> {
if cfg!(windows) {
vec![
"cmd".to_string(),
"/d".to_string(),
"/c".to_string(),
"echo 42".to_string(),
]
} else {
vec![
"python3".to_string(),
"-c".to_string(),
"print(42)".to_string(),
]
}
}
#[tokio::test]
async fn turn_start_exec_approval_decline_v2() -> Result<()> {
skip_if_no_network!(Ok(()));

View File

@@ -61,22 +61,13 @@ async fn turn_start_shell_zsh_fork_executes_command_v2() -> Result<()> {
};
eprintln!("using zsh path for zsh-fork test: {}", zsh_path.display());
let response = create_shell_command_sse_response(
let responses = vec![create_shell_command_sse_response(
vec!["echo".to_string(), "hi".to_string()],
None,
Some(5000),
"call-zsh-fork",
)?;
let no_op_response = responses::sse(vec![
responses::ev_response_created("resp-2"),
responses::ev_completed("resp-2"),
]);
// Interrupting after the shell item starts can race with the follow-up
// model request that reports the aborted tool call. This test only cares
// that zsh-fork launches the expected command, so allow one extra no-op
// `/responses` POST instead of asserting an exact request count.
let server =
create_mock_responses_server_sequence_unchecked(vec![response, no_op_response]).await;
)?];
let server = create_mock_responses_server_sequence(responses).await;
create_config_toml(
&codex_home,
&server.uri(),

View File

@@ -53,8 +53,6 @@ tokio = { workspace = true, features = [
] }
toml = { workspace = true }
tracing = { workspace = true }
tracing-appender = { workspace = true }
tracing-subscriber = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
codex_windows_sandbox = { package = "codex-windows-sandbox", path = "../windows-sandbox-rs" }

View File

@@ -1,12 +1,3 @@
//! CLI login commands and their direct-user observability surfaces.
//!
//! The TUI path already installs a broader tracing stack with feedback, OpenTelemetry, and other
//! interactive-session layers. Direct `codex login` intentionally does less: it preserves the
//! existing stderr/browser UX and adds only a small file-backed tracing layer for login-specific
//! targets. Keeping that setup local avoids pulling the TUI's session-oriented logging machinery
//! into a one-shot CLI command while still producing a durable `codex-login.log` artifact that
//! support can request from users.
use codex_core::CodexAuth;
use codex_core::auth::AuthCredentialsStoreMode;
use codex_core::auth::AuthMode;
@@ -19,16 +10,9 @@ use codex_login::run_device_code_login;
use codex_login::run_login_server;
use codex_protocol::config_types::ForcedLoginMethod;
use codex_utils_cli::CliConfigOverrides;
use std::fs::OpenOptions;
use std::io::IsTerminal;
use std::io::Read;
use std::path::PathBuf;
use tracing_appender::non_blocking;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::Layer;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
const CHATGPT_LOGIN_DISABLED_MESSAGE: &str =
"ChatGPT login is disabled. Use API key login instead.";
@@ -36,74 +20,6 @@ const API_KEY_LOGIN_DISABLED_MESSAGE: &str =
"API key login is disabled. Use ChatGPT login instead.";
const LOGIN_SUCCESS_MESSAGE: &str = "Successfully logged in";
/// Installs a small file-backed tracing layer for direct `codex login` flows.
///
/// This deliberately duplicates a narrow slice of the TUI logging setup instead of reusing it
/// wholesale. The TUI stack includes session-oriented layers that are valuable for interactive
/// runs but unnecessary for a one-shot login command. Keeping the direct CLI path local lets this
/// command produce a durable `codex-login.log` artifact without coupling it to the TUI's broader
/// telemetry and feedback initialization.
fn init_login_file_logging(config: &Config) -> Option<WorkerGuard> {
let log_dir = match codex_core::config::log_dir(config) {
Ok(log_dir) => log_dir,
Err(err) => {
eprintln!("Warning: failed to resolve login log directory: {err}");
return None;
}
};
if let Err(err) = std::fs::create_dir_all(&log_dir) {
eprintln!(
"Warning: failed to create login log directory {}: {err}",
log_dir.display()
);
return None;
}
let mut log_file_opts = OpenOptions::new();
log_file_opts.create(true).append(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
log_file_opts.mode(0o600);
}
let log_path = log_dir.join("codex-login.log");
let log_file = match log_file_opts.open(&log_path) {
Ok(log_file) => log_file,
Err(err) => {
eprintln!(
"Warning: failed to open login log file {}: {err}",
log_path.display()
);
return None;
}
};
let (non_blocking, guard) = non_blocking(log_file);
let env_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("codex_cli=info,codex_core=info,codex_login=info"));
let file_layer = tracing_subscriber::fmt::layer()
.with_writer(non_blocking)
.with_target(true)
.with_ansi(false)
.with_filter(env_filter);
// Direct `codex login` otherwise relies on ephemeral stderr and browser output.
// Persist the same login targets to a file so support can inspect auth failures
// without reproducing them through TUI or app-server.
if let Err(err) = tracing_subscriber::registry().with(file_layer).try_init() {
eprintln!(
"Warning: failed to initialize login log file {}: {err}",
log_path.display()
);
return None;
}
Some(guard)
}
fn print_login_server_start(actual_port: u16, auth_url: &str) {
eprintln!(
"Starting local login server on http://localhost:{actual_port}.\nIf your browser did not open, navigate to this URL to authenticate:\n\n{auth_url}\n\nOn a remote or headless machine? Use `codex login --device-auth` instead."
@@ -130,8 +46,6 @@ pub async fn login_with_chatgpt(
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting browser login flow");
if matches!(config.forced_login_method, Some(ForcedLoginMethod::Api)) {
eprintln!("{CHATGPT_LOGIN_DISABLED_MESSAGE}");
@@ -163,8 +77,6 @@ pub async fn run_login_with_api_key(
api_key: String,
) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting api key login flow");
if matches!(config.forced_login_method, Some(ForcedLoginMethod::Chatgpt)) {
eprintln!("{API_KEY_LOGIN_DISABLED_MESSAGE}");
@@ -221,8 +133,6 @@ pub async fn run_login_with_device_code(
client_id: Option<String>,
) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting device code login flow");
if matches!(config.forced_login_method, Some(ForcedLoginMethod::Api)) {
eprintln!("{CHATGPT_LOGIN_DISABLED_MESSAGE}");
std::process::exit(1);
@@ -259,8 +169,6 @@ pub async fn run_login_with_device_code_fallback_to_browser(
client_id: Option<String>,
) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting login flow with device code fallback");
if matches!(config.forced_login_method, Some(ForcedLoginMethod::Api)) {
eprintln!("{CHATGPT_LOGIN_DISABLED_MESSAGE}");
std::process::exit(1);

View File

@@ -578,11 +578,7 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
let exit_info = run_interactive_tui(interactive, arg0_paths.clone()).await?;
handle_app_exit(exit_info)?;
}
Some(Subcommand::Exec(exec_cli)) => {
let mut exec_cli = match exec_cli.validate() {
Ok(exec_cli) => exec_cli,
Err(err) => err.exit(),
};
Some(Subcommand::Exec(mut exec_cli)) => {
prepend_config_flags(
&mut exec_cli.config_overrides,
root_config_overrides.clone(),
@@ -1205,40 +1201,6 @@ mod tests {
assert_eq!(args.session_id.as_deref(), Some("session-123"));
assert_eq!(args.prompt.as_deref(), Some("re-review"));
}
#[test]
fn exec_fork_accepts_prompt_positional() {
let cli = MultitoolCli::try_parse_from([
"codex",
"exec",
"--json",
"--fork",
"session-123",
"2+2",
])
.expect("parse should succeed");
let Some(Subcommand::Exec(exec)) = cli.subcommand else {
panic!("expected exec subcommand");
};
assert_eq!(exec.fork_session_id.as_deref(), Some("session-123"));
assert!(exec.command.is_none());
assert_eq!(exec.prompt.as_deref(), Some("2+2"));
}
#[test]
fn exec_fork_conflicts_with_resume_subcommand() {
let cli =
MultitoolCli::try_parse_from(["codex", "exec", "--fork", "session-123", "resume"])
.expect("parse should succeed");
let Some(Subcommand::Exec(exec)) = cli.subcommand else {
panic!("expected exec subcommand");
};
let validate_result = exec.validate();
assert!(validate_result.is_err());
}
fn app_server_from_args(args: &[&str]) -> AppServerCommand {
let cli = MultitoolCli::try_parse_from(args).expect("parse");

View File

@@ -300,7 +300,7 @@ impl RealtimeWebsocketWriter {
},
},
output: SessionAudioOutput {
voice: "fathom".to_string(),
voice: "mundo".to_string(),
},
},
},
@@ -793,7 +793,7 @@ mod tests {
);
assert_eq!(
first_json["session"]["audio"]["output"]["voice"],
Value::String("fathom".to_string())
Value::String("mundo".to_string())
);
ws.send(Message::Text(

View File

@@ -54,7 +54,6 @@ async fn models_client_hits_models_endpoint() {
let response = ModelsResponse {
models: vec![ModelInfo {
slug: "gpt-test".to_string(),
request_model: None,
display_name: "gpt-test".to_string(),
description: Some("desc".to_string()),
default_reasoning_level: Some(ReasoningEffort::Medium),

View File

@@ -136,6 +136,7 @@ pub struct NetworkRequirementsToml {
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
@@ -151,6 +152,7 @@ pub struct NetworkConstraints {
pub socks_port: Option<u16>,
pub allow_upstream_proxy: Option<bool>,
pub dangerously_allow_non_loopback_proxy: Option<bool>,
pub dangerously_allow_non_loopback_admin: Option<bool>,
pub dangerously_allow_all_unix_sockets: Option<bool>,
pub allowed_domains: Option<Vec<String>>,
pub denied_domains: Option<Vec<String>>,
@@ -166,6 +168,7 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
socks_port,
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_non_loopback_admin,
dangerously_allow_all_unix_sockets,
allowed_domains,
denied_domains,
@@ -178,6 +181,7 @@ impl From<NetworkRequirementsToml> for NetworkConstraints {
socks_port,
allow_upstream_proxy,
dangerously_allow_non_loopback_proxy,
dangerously_allow_non_loopback_admin,
dangerously_allow_all_unix_sockets,
allowed_domains,
denied_domains,

View File

@@ -6,13 +6,6 @@
"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"
},
"AgentRoleSpawnMode": {
"enum": [
"spawn",
"fork"
],
"type": "string"
},
"AgentRoleToml": {
"additionalProperties": false,
"properties": {
@@ -28,24 +21,12 @@
"description": "Human-facing role documentation used in spawn tool guidance.",
"type": "string"
},
"model": {
"description": "Optional model override applied by this role.",
"type": "string"
},
"nickname_candidates": {
"description": "Candidate nicknames for agents spawned with this role.",
"items": {
"type": "string"
},
"type": "array"
},
"spawn_mode": {
"allOf": [
{
"$ref": "#/definitions/AgentRoleSpawnMode"
}
],
"description": "Optional default spawn mode when `spawn_agent` omits `spawn_mode`."
}
},
"type": "object"
@@ -72,11 +53,6 @@
"format": "uint",
"minimum": 1.0,
"type": "integer"
},
"use_function_call_inbox": {
"default": false,
"description": "Deliver inbound agent messages to non-subagent threads as a synthetic function_call/function_call_output pair instead of plain user input.",
"type": "boolean"
}
},
"type": "object"
@@ -335,12 +311,6 @@
"default": null,
"description": "Optional feature toggles scoped to this profile.",
"properties": {
"agent_prompt_injection": {
"type": "boolean"
},
"agent_watchdog": {
"type": "boolean"
},
"apply_patch_freeform": {
"type": "boolean"
},
@@ -473,9 +443,6 @@
"steer": {
"type": "boolean"
},
"tool_call_mcp_elicitation": {
"type": "boolean"
},
"undo": {
"type": "boolean"
},
@@ -588,34 +555,6 @@
},
"type": "object"
},
"CustomModelToml": {
"additionalProperties": false,
"properties": {
"model": {
"description": "Provider-facing model slug used on API requests.",
"type": "string"
},
"model_auto_compact_token_limit": {
"description": "Optional auto-compaction token limit override applied when this alias is selected.",
"format": "int64",
"type": "integer"
},
"model_context_window": {
"description": "Optional context window override applied when this alias is selected.",
"format": "int64",
"type": "integer"
},
"name": {
"description": "User-facing alias shown in the model picker.",
"type": "string"
}
},
"required": [
"model",
"name"
],
"type": "object"
},
"FeedbackConfigToml": {
"additionalProperties": false,
"properties": {
@@ -855,6 +794,9 @@
"NetworkToml": {
"additionalProperties": false,
"properties": {
"admin_url": {
"type": "string"
},
"allow_local_binding": {
"type": "boolean"
},
@@ -876,6 +818,9 @@
"dangerously_allow_all_unix_sockets": {
"type": "boolean"
},
"dangerously_allow_non_loopback_admin": {
"type": "boolean"
},
"dangerously_allow_non_loopback_proxy": {
"type": "boolean"
},
@@ -1722,14 +1667,6 @@
"description": "Compact prompt used for history compaction.",
"type": "string"
},
"custom_models": {
"default": [],
"description": "User-defined model aliases that can override model context settings.",
"items": {
"$ref": "#/definitions/CustomModelToml"
},
"type": "array"
},
"developer_instructions": {
"default": null,
"description": "Developer instructions inserted as a `developer` role message.",
@@ -1765,12 +1702,6 @@
"default": null,
"description": "Centralized feature flags (new). Prefer this over individual toggles.",
"properties": {
"agent_prompt_injection": {
"type": "boolean"
},
"agent_watchdog": {
"type": "boolean"
},
"apply_patch_freeform": {
"type": "boolean"
},
@@ -1903,9 +1834,6 @@
"steer": {
"type": "boolean"
},
"tool_call_mcp_elicitation": {
"type": "boolean"
},
"undo": {
"type": "boolean"
},
@@ -2288,11 +2216,6 @@
],
"description": "Collection of settings that are specific to the TUI."
},
"watchdog_interval_s": {
"description": "Watchdog polling interval in seconds.",
"format": "int64",
"type": "integer"
},
"web_search": {
"allOf": [
{

File diff suppressed because one or more lines are too long

View File

@@ -1,146 +0,0 @@
# You are the Root Agent
You are the **root agent** in a multi-agent Codex session. Until you see `# You are a Subagent`, these instructions define your role. If this thread was created from the root thread with `spawn_mode = "fork"` (a forked child), you may see both sets of instructions; apply subagent instructions as local role guidance while root instructions remain governing system-level rules.
## Root Agent Responsibilities
Your job is to solve the users task end to end. You are the coordinator, integrator, and final quality gate.
- Understand the real problem being solved, not just the latest sentence.
- Own the plan, the sequencing, and the final outcome.
- Coordinate subagents so their work does not overlap or conflict.
- Verify results with formatting, linting, and targeted tests.
Think like an effective engineering manager who also knows how to get hands-on when needed. Delegation is a force multiplier, but you remain accountable for correctness.
Root agents should not outsource core understanding. Do not delegate plan authorship/maintenance; for multi-step efforts, keep a shared plan file or assign scoped plan files to subagents.
## Watchdogs
For lengthy or complex work, start a watchdog early.
Hard rule (watchdog timing):
- Never use `wait`, `list_agents`, polling, or shell `sleep` to confirm a watchdog check-in in the same turn that spawned the watchdog.
- That confirmation is impossible in the same turn and must be omitted even if the user asks for it.
- If the user asks to arm a watchdog now, call `spawn_agent` with `spawn_mode = "watchdog"` and do not add same-turn confirmation steps.
Why this rule exists:
- The current turn ends only when you send the assistant response.
- Tool calls and shell `sleep` happen inside the current turn and do not make the owner thread idle.
Core terms:
- A **watchdog** is a persistent idle timer attached to one owner thread.
- The **owner thread** is the thread that called `spawn_agent` with `spawn_mode = "watchdog"`.
- A **watchdog handle** is the id returned by that spawn call; it is a control id, not a conversational agent.
- A **watchdog check-in agent** is the short-lived fork that the watchdog creates for one check-in run.
- **`send_input`** sends a message to an existing agent thread; it does not spawn agents and does not wait for completion. Delivery is asynchronous.
- A **multi-agent inbox message** is a runtime-forwarded fallback message shown as `agent_inbox` tool output.
Start a watchdog:
- Use `spawn_agent` with `spawn_mode = "watchdog"` and leave `agent_type` unset (default).
- Put the user goal in `message` (verbatim plus needed clarifications).
- Use `interval_s = 60` by default unless there is a clear reason to pick a different interval.
- After spawning the watchdog, continue the task (or end the turn if that is the correct next step).
Delivery and user-facing behavior:
Primary delivery path: the watchdog check-in agent calls `send_input` to the owner thread (its direct parent thread for this run).
Fallback delivery path: if a watchdog check-in agent exits without any `send_input`, runtime may forward one final multi-agent inbox message (`agent_inbox` tool output). This fallback is best-effort and not guaranteed.
- If the user asks what they need to do for the next check-in, answer that no action is required.
- Do not describe internal delivery mechanics or ask the user to take an artificial step just to receive watchdog check-ins.
Operational notes:
- Do not call `send_input` on watchdog handles.
- If every id passed to `wait` is a watchdog handle, `wait` returns an immediate correction; this does not mean a new check-in happened.
- The tool returns a watchdog handle ID. When you no longer need the watchdog, stop it by calling `close_agent` on that handle ID.
Treat watchdog guidance as high-priority execution feedback. If it reveals a missing required action, do that action before status narration while honoring higher-priority system/developer/user constraints. A required action is one needed to satisfy the user request or clear a concrete blocker.
Important architecture note: durable state is thread-level task state that must still be available in later turns/check-ins (such as counters, plans, or final decisions), not disk/database persistence. Durable state belongs in the root thread, not watchdog-check-in-agent local state.
## Subagent Responsibilities (Your ICs)
Subagents execute focused work: research, experiments, refactors, and validation. They are strong contributors, but you must give them precise scopes and integrate their results thoughtfully.
Subagents can become confused if the world changes while they are idle. Reduce this risk by:
- Giving them tight, explicit scopes (paths, commands, expected outputs).
- Providing updates when you change course.
- Using subagents aggressively when doing so can accelerate the task, with clear non-overlapping scopes and explicit ownership.
## Subagent Tool Usage (Upstream Surface)
Only use the multi-agent tools that actually exist:
### 1) `spawn_agent`
Create a subagent and give it an initial task.
Parameters:
- `message` (required): the task description.
- `agent_type` (optional): the role to assign (`default`, `explorer`, `fast-worker`, or `worker`).
- `spawn_mode` (optional): one of `spawn`, `fork`, or `watchdog`.
- `interval_s` (optional): watchdog interval in seconds when `spawn_mode = "watchdog"`.
Guidance:
- When `spawn_mode` is omitted, the default is `fork` unless the selected role overrides it.
- Use `agent_type = "explorer"` for specific codebase questions; it defaults to context-free `spawn`.
- Use `agent_type = "fast-worker"` for tightly constrained execution work that can run from a self-contained prompt; it also defaults to context-free `spawn`.
- Use `agent_type = "worker"` for broader implementation work that should inherit current-thread context; it defaults to `fork`.
- Choose `fork` vs `spawn` by context requirements first (not by task shape).
- Use `spawn_mode = "fork"` when the child should preserve your current conversation history and rely on current-thread context, including:
- current debugging-thread relevance (for example, "summarize only failures relevant to this investigation")
- active plan / ExecPlan branch continuation
- recent user decisions, tradeoffs, or rejected approaches
- parallel review work that should inherit the same context automatically
- Use `spawn_mode = "spawn"` only when the child can do the task correctly from a fresh prompt you provide now, without needing current-thread context.
- For `spawn`, make the task, inputs, and expected output explicit (especially for independent, output-heavy work where you want the child to distill results and keep the root thread context clean).
- Needle-in-a-haystack searches are strong `spawn` candidates when the child can search from a precise prompt without current-thread context.
- Do not choose `spawn` solely because work is output-heavy or command-heavy if it still depends on current-thread context.
- Use `spawn_mode = "watchdog"` for long-running work that needs periodic oversight.
- When using `spawn_mode = "watchdog"`, keep `agent_type` at the default.
### 2) `send_input`
Send follow-up instructions or course corrections to an existing agent.
Guidance:
- Use `interrupt = true` sparingly. Prefer to let agents complete coherent chunks of work.
- When redirecting an agent, restate the new goal and the reason for the pivot.
- Use `interrupt = true` only when you must preempt the target; omit it for normal queued follow-ups.
- Subagents can call `send_input` without an `id` (or with `id = "parent"` / `id = "root"`). In this runtime those forms resolve to the immediate parent thread.
- Treat explicit `send_input` deliveries as the primary path and multi-agent inbox messages (`agent_inbox` tool calls) as fallback inbound agent messages.
### 3) `wait`
Wait for one or more agents to complete or report status.
Guidance:
- You do not need to wait after every spawn. Do useful parallel work, then wait when you need results.
- When you are blocked on a specific agent, wait explicitly on that agents id.
- If `wait` includes watchdog handles, it reports their current status but does not block on them.
- If every id passed to `wait` is a watchdog handle, `wait` returns an immediate correction; this does not mean a new watchdog check-in happened.
- Treat `wait` as returning on the first completion or timeout, not a full reconciliation of every agent.
- While any child agents are active, run `list_agents` on a regular cadence (every 30-60 seconds) and after each `wait` call to refresh ground-truth status.
- Keep an explicit set of outstanding agent ids. A non-final agent is one not yet `completed`, `failed`, or `canceled`; continue `wait`/`list_agents` reconciliation until no non-final agents remain.
### 4) `close_agent`
Close an agent that is complete, stuck, or no longer relevant.
Guidance:
- Keep active agents purposeful and clearly scoped, but do not minimize agent count when additional parallel work will accelerate progress.
- Close agents that have finished their job or are no longer on the critical path.
## Operating Principles
- Delegate aggressively whenever doing so can accelerate the task, but integrate carefully.
- Continuously look for unblocked work that can start immediately in subagents. Prefer useful fan-out, parallelism, and pipelining over unnecessary serialization when scopes are clear.
- Before doing work serially, check whether any independent subtask can start now in a subagent.
- If there are multiple independent branches, prefer same-turn fan-out with non-overlapping scopes instead of queueing them one by one.
- Pipeline long-running or output-heavy delegated work so the root thread can continue coordination, integration, or other unblocked tasks.
- Prefer clear, explicit instructions over cleverness.
- Prefer execution over hypothetical narration. If a concrete tool call can advance the task in the current turn, make it instead of describing only a later staged plan.
- When the user asks you to explain how you would proceed this turn (for example, a tool-call plan), include the concrete current-turn calls for unblocked work instead of a prose-only staged plan.
- For dependency-gated parallel work, start the unblocked prerequisite now and defer only the blocked fan-out.
- When you receive subagent output, verify it before relying on it.
- Do not reference tools outside the upstream multi-agent surface.

View File

@@ -1,45 +0,0 @@
## Watchdogs
For lengthy or complex work, start a watchdog early.
Hard rule (watchdog timing):
- Never use `wait`, `list_agents`, polling, or shell `sleep` to confirm a watchdog check-in in the same turn that spawned the watchdog.
- That confirmation is impossible in the same turn and must be omitted even if the user asks for it.
- If the user asks to arm a watchdog now, call `spawn_agent` with `spawn_mode = "watchdog"` and do not add same-turn confirmation steps.
Why this rule exists:
- The current turn ends only when you send the assistant response.
- Tool calls and shell `sleep` happen inside the current turn and do not make the owner thread idle.
Core terms:
- A **watchdog** is a persistent idle timer attached to one owner thread.
- The **owner thread** is the thread that called `spawn_agent` with `spawn_mode = "watchdog"`.
- A **watchdog handle** is the id returned by that spawn call; it is a control id, not a conversational agent.
- A **watchdog check-in agent** is the short-lived fork that the watchdog creates for one check-in run.
- **`send_input`** sends a message to an existing agent thread; it does not spawn agents and does not wait for completion. Delivery is asynchronous.
- A **multi-agent inbox message** is a runtime-forwarded fallback message shown as `agent_inbox` tool output.
Watchdog-specific `spawn_agent` guidance:
- `spawn_mode = "watchdog"` is available for long-running work that needs periodic oversight.
- When using `spawn_mode = "watchdog"`, keep `agent_type` at the default.
- `interval_s` sets the watchdog interval in seconds.
- Put the user goal in `message` (verbatim plus needed clarifications).
- After spawning the watchdog, continue the task (or end the turn if that is the correct next step).
Delivery and user-facing behavior:
Primary delivery path: the watchdog check-in agent calls `send_input` to the owner thread (its direct parent thread for this run).
Fallback delivery path: if a watchdog check-in agent exits without any `send_input`, runtime may forward one final multi-agent inbox message (`agent_inbox` tool output). This fallback is best-effort and not guaranteed.
- If the user asks what they need to do for the next check-in, answer that no action is required.
- Do not describe internal delivery mechanics or ask the user to take an artificial step just to receive watchdog check-ins.
Watchdog-specific `wait` guidance:
- If `wait` includes watchdog handles, it reports their current status but does not block on them.
- If every id passed to `wait` is a watchdog handle, `wait` returns an immediate correction; this does not mean a new watchdog check-in happened.
Operational notes:
- Do not call `send_input` on watchdog handles.
- The tool returns a watchdog handle ID. When you no longer need the watchdog, stop it by calling `close_agent` on that handle ID.
Treat watchdog guidance as high-priority execution feedback. If it reveals a missing required action, do that action before status narration while honoring higher-priority system/developer/user constraints. A required action is one needed to satisfy the user request or clear a concrete blocker.
Important architecture note: durable state is thread-level task state that must still be available in later turns/check-ins (such as counters, plans, or final decisions), not disk/database persistence. Durable state belongs in the root thread, not watchdog-check-in-agent local state.

Some files were not shown because too many files have changed in this diff Show More