From f35285dc7839bbd2c258e8dfae2aa5b2cbf54ae9 Mon Sep 17 00:00:00 2001 From: iceweasel-oai Date: Tue, 5 May 2026 09:58:23 -0700 Subject: [PATCH] Add Windows sandbox readiness RPC (#20708) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Why The desktop app on Windows needs a read-only way to tell, before the next tool call, whether the local Windows sandbox setup is in a state that should block the user and ask for setup again. The main case we want to cover is the elevated sandbox setup version bump. Today, if the app is configured for elevated Windows sandboxing and the installed setup is stale, the next sandboxed shell/exec path can end up triggering the elevated setup flow directly. That means the user can see an unexpected UAC prompt with no UI explanation. This change adds a small app-server preflight so the desktop app can ask “is Windows sandbox ready, not configured, or update-required?” during startup and show the appropriate blocking UI before the user hits a tool call. ## What changed - Added a new read-only app-server RPC: `windowsSandbox/readiness` - Added a new protocol enum and response type: - `WindowsSandboxReadiness` - `WindowsSandboxReadinessResponse` - Added core readiness logic in `core/src/windows_sandbox.rs`: - `ready` - `notConfigured` - `updateRequired` - Wired the new request through `codex_message_processor` - Regenerated the vendored app-server schema fixtures ## Readiness semantics This is intentionally a coarse startup/version-bump readiness check, not a full predictor of every runtime repair case. For now, readiness is determined from: - the configured Windows sandbox level - `sandbox_setup_is_complete()` for elevated mode That means: - `disabled` maps to `notConfigured` - `restricted token` maps to `ready` - `elevated` maps to `ready` or `updateRequired` depending on `sandbox_setup_is_complete()` This is deliberate for the first UI integration because the common case we want to catch is “the app updated, the elevated setup version bumped, and the user should see an update-required blocker instead of a surprise UAC prompt”. It does not attempt to model every case where the deeper runtime path might decide to repair or re-run setup. ## Testing - Ran `cargo fmt --all -- app-server-protocol/src/protocol/common.rs app-server-protocol/src/protocol/v2.rs app-server/src/codex_message_processor.rs core/src/windows_sandbox.rs core/src/windows_sandbox_tests.rs` - Added unit tests for the pure readiness mapping in `core/src/windows_sandbox_tests.rs` - Regenerated vendored schema fixtures with `cargo run -p codex-app-server-protocol --bin write_schema_fixtures -- --schema-root app-server-protocol/schema` - Did not run the full cargo test suite --- .../schema/json/ClientRequest.json | 23 +++++ .../codex_app_server_protocol.schemas.json | 44 ++++++++++ .../codex_app_server_protocol.v2.schemas.json | 44 ++++++++++ .../v2/WindowsSandboxReadinessResponse.json | 23 +++++ .../schema/typescript/ClientRequest.ts | 2 +- .../typescript/v2/WindowsSandboxReadiness.ts | 5 ++ .../v2/WindowsSandboxReadinessResponse.ts | 6 ++ .../schema/typescript/v2/index.ts | 2 + .../src/protocol/common.rs | 5 ++ .../app-server-protocol/src/protocol/v2.rs | 16 ++++ codex-rs/app-server/src/message_processor.rs | 5 ++ codex-rs/app-server/src/request_processors.rs | 3 + .../windows_sandbox_processor.rs | 83 +++++++++++++++++++ 13 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 codex-rs/app-server-protocol/schema/json/v2/WindowsSandboxReadinessResponse.json create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/WindowsSandboxReadiness.ts create mode 100644 codex-rs/app-server-protocol/schema/typescript/v2/WindowsSandboxReadinessResponse.ts diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 17c782ee09..9ea9893f5b 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -5914,6 +5914,29 @@ "title": "WindowsSandbox/setupStartRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "windowsSandbox/readiness" + ], + "title": "WindowsSandbox/readinessRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "WindowsSandbox/readinessRequest", + "type": "object" + }, { "properties": { "id": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index bce9dc3bd4..2eaba62ac8 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -1577,6 +1577,29 @@ "title": "WindowsSandbox/setupStartRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "windowsSandbox/readiness" + ], + "title": "WindowsSandbox/readinessRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "WindowsSandbox/readinessRequest", + "type": "object" + }, { "properties": { "id": { @@ -18355,6 +18378,27 @@ }, "type": "object" }, + "WindowsSandboxReadiness": { + "enum": [ + "ready", + "notConfigured", + "updateRequired" + ], + "type": "string" + }, + "WindowsSandboxReadinessResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "status": { + "$ref": "#/definitions/v2/WindowsSandboxReadiness" + } + }, + "required": [ + "status" + ], + "title": "WindowsSandboxReadinessResponse", + "type": "object" + }, "WindowsSandboxSetupCompletedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index a6eb5aec84..e99aa6653a 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -2336,6 +2336,29 @@ "title": "WindowsSandbox/setupStartRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "windowsSandbox/readiness" + ], + "title": "WindowsSandbox/readinessRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "WindowsSandbox/readinessRequest", + "type": "object" + }, { "properties": { "id": { @@ -16241,6 +16264,27 @@ }, "type": "object" }, + "WindowsSandboxReadiness": { + "enum": [ + "ready", + "notConfigured", + "updateRequired" + ], + "type": "string" + }, + "WindowsSandboxReadinessResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "status": { + "$ref": "#/definitions/WindowsSandboxReadiness" + } + }, + "required": [ + "status" + ], + "title": "WindowsSandboxReadinessResponse", + "type": "object" + }, "WindowsSandboxSetupCompletedNotification": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/WindowsSandboxReadinessResponse.json b/codex-rs/app-server-protocol/schema/json/v2/WindowsSandboxReadinessResponse.json new file mode 100644 index 0000000000..de5ee264cb --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/WindowsSandboxReadinessResponse.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "WindowsSandboxReadiness": { + "enum": [ + "ready", + "notConfigured", + "updateRequired" + ], + "type": "string" + } + }, + "properties": { + "status": { + "$ref": "#/definitions/WindowsSandboxReadiness" + } + }, + "required": [ + "status" + ], + "title": "WindowsSandboxReadinessResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index 989dbb6551..3484819a45 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -81,4 +81,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/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "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": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "device/key/create", id: RequestId, params: DeviceKeyCreateParams, } | { "method": "device/key/public", id: RequestId, params: DeviceKeyPublicParams, } | { "method": "device/key/sign", id: RequestId, params: DeviceKeySignParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "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": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "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": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "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/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "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": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "device/key/create", id: RequestId, params: DeviceKeyCreateParams, } | { "method": "device/key/public", id: RequestId, params: DeviceKeyPublicParams, } | { "method": "device/key/sign", id: RequestId, params: DeviceKeySignParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "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": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "windowsSandbox/readiness", id: RequestId, params: undefined, } | { "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": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "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, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/WindowsSandboxReadiness.ts b/codex-rs/app-server-protocol/schema/typescript/v2/WindowsSandboxReadiness.ts new file mode 100644 index 0000000000..41b1161acf --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/WindowsSandboxReadiness.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type WindowsSandboxReadiness = "ready" | "notConfigured" | "updateRequired"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/WindowsSandboxReadinessResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/WindowsSandboxReadinessResponse.ts new file mode 100644 index 0000000000..bc42a1d962 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/WindowsSandboxReadinessResponse.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { WindowsSandboxReadiness } from "./WindowsSandboxReadiness"; + +export type WindowsSandboxReadinessResponse = { status: WindowsSandboxReadiness, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index a949da8954..a226ebe11f 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -436,6 +436,8 @@ export type { TurnSteerResponse } from "./TurnSteerResponse"; export type { UserInput } from "./UserInput"; export type { WarningNotification } from "./WarningNotification"; export type { WebSearchAction } from "./WebSearchAction"; +export type { WindowsSandboxReadiness } from "./WindowsSandboxReadiness"; +export type { WindowsSandboxReadinessResponse } from "./WindowsSandboxReadinessResponse"; export type { WindowsSandboxSetupCompletedNotification } from "./WindowsSandboxSetupCompletedNotification"; export type { WindowsSandboxSetupMode } from "./WindowsSandboxSetupMode"; export type { WindowsSandboxSetupStartParams } from "./WindowsSandboxSetupStartParams"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 477d0427b2..5ab2e5ea01 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -843,6 +843,11 @@ client_request_definitions! { serialization: global("windows-sandbox-setup"), response: v2::WindowsSandboxSetupStartResponse, }, + WindowsSandboxReadiness => "windowsSandbox/readiness" { + params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>, + serialization: global("config"), + response: v2::WindowsSandboxReadinessResponse, + }, LoginAccount => "account/login/start" { params: v2::LoginAccountParams, diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs index 92da9551f5..223ef3091d 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2.rs @@ -7405,6 +7405,15 @@ pub enum WindowsSandboxSetupMode { Unelevated, } +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub enum WindowsSandboxReadiness { + Ready, + NotConfigured, + UpdateRequired, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -7421,6 +7430,13 @@ pub struct WindowsSandboxSetupStartResponse { pub started: bool, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct WindowsSandboxReadinessResponse { + pub status: WindowsSandboxReadiness, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 308fd8f1e6..973617a94e 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -831,6 +831,11 @@ impl MessageProcessor { .read(params) .await .map(|response| Some(response.into())), + ClientRequest::WindowsSandboxReadiness { .. } => self + .windows_sandbox_processor + .windows_sandbox_readiness() + .await + .map(|response| Some(response.into())), ClientRequest::ExternalAgentConfigDetect { params, .. } => self .external_agent_config_processor .detect(params) diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index a2b76bf0bd..0154b61734 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -230,6 +230,8 @@ use codex_app_server_protocol::TurnStatus; use codex_app_server_protocol::TurnSteerParams; use codex_app_server_protocol::TurnSteerResponse; use codex_app_server_protocol::UserInput as V2UserInput; +use codex_app_server_protocol::WindowsSandboxReadiness; +use codex_app_server_protocol::WindowsSandboxReadinessResponse; use codex_app_server_protocol::WindowsSandboxSetupCompletedNotification; use codex_app_server_protocol::WindowsSandboxSetupMode; use codex_app_server_protocol::WindowsSandboxSetupStartParams; @@ -274,6 +276,7 @@ use codex_core::sandboxing::SandboxPermissions; use codex_core::windows_sandbox::WindowsSandboxLevelExt; use codex_core::windows_sandbox::WindowsSandboxSetupMode as CoreWindowsSandboxSetupMode; use codex_core::windows_sandbox::WindowsSandboxSetupRequest; +use codex_core::windows_sandbox::sandbox_setup_is_complete; use codex_core_plugins::OPENAI_CURATED_MARKETPLACE_NAME; use codex_core_plugins::PluginInstallError as CorePluginInstallError; use codex_core_plugins::PluginInstallRequest; diff --git a/codex-rs/app-server/src/request_processors/windows_sandbox_processor.rs b/codex-rs/app-server/src/request_processors/windows_sandbox_processor.rs index f380d46653..2392cc8078 100644 --- a/codex-rs/app-server/src/request_processors/windows_sandbox_processor.rs +++ b/codex-rs/app-server/src/request_processors/windows_sandbox_processor.rs @@ -20,6 +20,12 @@ impl WindowsSandboxRequestProcessor { } } + pub(crate) async fn windows_sandbox_readiness( + &self, + ) -> Result { + Ok(determine_windows_sandbox_readiness(&self.config)) + } + pub(crate) async fn windows_sandbox_setup_start( &self, request_id: &ConnectionRequestId, @@ -101,3 +107,80 @@ impl WindowsSandboxRequestProcessor { Ok(()) } } + +fn determine_windows_sandbox_readiness(config: &Config) -> WindowsSandboxReadinessResponse { + if !cfg!(windows) { + return WindowsSandboxReadinessResponse { + status: WindowsSandboxReadiness::NotConfigured, + }; + } + + determine_windows_sandbox_readiness_from_state( + WindowsSandboxLevel::from_config(config), + sandbox_setup_is_complete(config.codex_home.as_path()), + ) +} + +fn determine_windows_sandbox_readiness_from_state( + windows_sandbox_level: WindowsSandboxLevel, + sandbox_setup_is_complete: bool, +) -> WindowsSandboxReadinessResponse { + let status = match windows_sandbox_level { + WindowsSandboxLevel::Disabled => WindowsSandboxReadiness::NotConfigured, + WindowsSandboxLevel::RestrictedToken => WindowsSandboxReadiness::Ready, + WindowsSandboxLevel::Elevated => { + if sandbox_setup_is_complete { + WindowsSandboxReadiness::Ready + } else { + WindowsSandboxReadiness::UpdateRequired + } + } + }; + + WindowsSandboxReadinessResponse { status } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn determine_windows_sandbox_readiness_reports_not_configured_when_disabled() { + let response = determine_windows_sandbox_readiness_from_state( + WindowsSandboxLevel::Disabled, + /*sandbox_setup_is_complete*/ false, + ); + + assert_eq!(response.status, WindowsSandboxReadiness::NotConfigured); + } + + #[test] + fn determine_windows_sandbox_readiness_reports_ready_for_unelevated_mode() { + let response = determine_windows_sandbox_readiness_from_state( + WindowsSandboxLevel::RestrictedToken, + /*sandbox_setup_is_complete*/ false, + ); + + assert_eq!(response.status, WindowsSandboxReadiness::Ready); + } + + #[test] + fn determine_windows_sandbox_readiness_reports_ready_for_complete_elevated_mode() { + let response = determine_windows_sandbox_readiness_from_state( + WindowsSandboxLevel::Elevated, + /*sandbox_setup_is_complete*/ true, + ); + + assert_eq!(response.status, WindowsSandboxReadiness::Ready); + } + + #[test] + fn determine_windows_sandbox_readiness_reports_update_required_when_elevated_setup_is_stale() { + let response = determine_windows_sandbox_readiness_from_state( + WindowsSandboxLevel::Elevated, + /*sandbox_setup_is_complete*/ false, + ); + + assert_eq!(response.status, WindowsSandboxReadiness::UpdateRequired); + } +}