Compare commits

...

1 Commits

Author SHA1 Message Date
iceweasel-oai
96557c4854 app server request/response structure for sandbox nux 2026-02-13 15:07:10 -08:00
31 changed files with 1502 additions and 19 deletions

View File

@@ -3268,6 +3268,25 @@
"type": "object"
}
]
},
"WindowsSandboxNuxStartParams": {
"properties": {
"surface": {
"$ref": "#/definitions/WindowsSandboxNuxSurface"
}
},
"required": [
"surface"
],
"type": "object"
},
"WindowsSandboxNuxSurface": {
"enum": [
"tui",
"codexApp",
"vscode"
],
"type": "string"
}
},
"description": "Request from the client to the server.",
@@ -4111,6 +4130,30 @@
"title": "Config/batchWriteRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"windowsSandbox/nuxStart"
],
"title": "WindowsSandbox/nuxStartRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/WindowsSandboxNuxStartParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "WindowsSandbox/nuxStartRequest",
"type": "object"
},
{
"properties": {
"id": {

View File

@@ -7699,6 +7699,61 @@
}
]
},
"WindowsSandboxNuxSurface": {
"enum": [
"tui",
"codexApp",
"vscode"
],
"type": "string"
},
"WindowsSandboxSetupStatus": {
"enum": [
"started",
"running",
"completed",
"failed"
],
"type": "string"
},
"WindowsSandboxSetupStatusNotification": {
"properties": {
"attempt": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"elapsedMs": {
"format": "int64",
"type": "integer"
},
"failureCode": {
"type": [
"string",
"null"
]
},
"failureMessage": {
"type": [
"string",
"null"
]
},
"status": {
"$ref": "#/definitions/WindowsSandboxSetupStatus"
},
"surface": {
"$ref": "#/definitions/WindowsSandboxNuxSurface"
}
},
"required": [
"attempt",
"elapsedMs",
"status",
"surface"
],
"type": "object"
},
"WindowsWorldWritableWarningNotification": {
"properties": {
"extraCount": {
@@ -8311,6 +8366,26 @@
"title": "Windows/worldWritableWarningNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"windowsSandbox/setupStatus"
],
"title": "WindowsSandbox/setupStatusNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/WindowsSandboxSetupStatusNotification"
}
},
"required": [
"method",
"params"
],
"title": "WindowsSandbox/setupStatusNotification",
"type": "object"
},
{
"properties": {
"method": {

View File

@@ -609,6 +609,48 @@
"question"
],
"type": "object"
},
"WindowsSandboxNuxPromptParams": {
"properties": {
"failureCode": {
"type": [
"string",
"null"
]
},
"failureMessage": {
"type": [
"string",
"null"
]
},
"screen": {
"$ref": "#/definitions/WindowsSandboxNuxPromptScreen"
},
"surface": {
"$ref": "#/definitions/WindowsSandboxNuxSurface"
}
},
"required": [
"screen",
"surface"
],
"type": "object"
},
"WindowsSandboxNuxPromptScreen": {
"enum": [
"enable",
"fallback"
],
"type": "string"
},
"WindowsSandboxNuxSurface": {
"enum": [
"tui",
"codexApp",
"vscode"
],
"type": "string"
}
},
"description": "Request initiated from the server and sent to the client.",
@@ -737,6 +779,30 @@
"title": "Account/chatgptAuthTokens/refreshRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"windowsSandbox/nuxPrompt"
],
"title": "WindowsSandbox/nuxPromptRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/WindowsSandboxNuxPromptParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "WindowsSandbox/nuxPromptRequest",
"type": "object"
},
{
"description": "DEPRECATED APIs below Request to approve a patch. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).",
"properties": {

View File

@@ -0,0 +1,46 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"WindowsSandboxNuxPromptScreen": {
"enum": [
"enable",
"fallback"
],
"type": "string"
},
"WindowsSandboxNuxSurface": {
"enum": [
"tui",
"codexApp",
"vscode"
],
"type": "string"
}
},
"properties": {
"failureCode": {
"type": [
"string",
"null"
]
},
"failureMessage": {
"type": [
"string",
"null"
]
},
"screen": {
"$ref": "#/definitions/WindowsSandboxNuxPromptScreen"
},
"surface": {
"$ref": "#/definitions/WindowsSandboxNuxSurface"
}
},
"required": [
"screen",
"surface"
],
"title": "WindowsSandboxNuxPromptParams",
"type": "object"
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"WindowsSandboxNuxPromptAction": {
"enum": [
"setupElevated",
"setupUnelevated",
"retryElevated",
"quit"
],
"type": "string"
}
},
"properties": {
"action": {
"$ref": "#/definitions/WindowsSandboxNuxPromptAction"
}
},
"required": [
"action"
],
"title": "WindowsSandboxNuxPromptResponse",
"type": "object"
}

View File

@@ -1274,6 +1274,30 @@
"title": "Config/batchWriteRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"windowsSandbox/nuxStart"
],
"title": "WindowsSandbox/nuxStartRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/WindowsSandboxNuxStartParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "WindowsSandbox/nuxStartRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -8524,6 +8548,26 @@
"title": "Windows/worldWritableWarningNotification",
"type": "object"
},
{
"properties": {
"method": {
"enum": [
"windowsSandbox/setupStatus"
],
"title": "WindowsSandbox/setupStatusNotificationMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/WindowsSandboxSetupStatusNotification"
}
},
"required": [
"method",
"params"
],
"title": "WindowsSandbox/setupStatusNotification",
"type": "object"
},
{
"properties": {
"method": {
@@ -8737,6 +8781,30 @@
"title": "Account/chatgptAuthTokens/refreshRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"windowsSandbox/nuxPrompt"
],
"title": "WindowsSandbox/nuxPromptRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/WindowsSandboxNuxPromptParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "WindowsSandbox/nuxPromptRequest",
"type": "object"
},
{
"description": "DEPRECATED APIs below Request to approve a patch. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).",
"properties": {
@@ -10007,6 +10075,64 @@
}
]
},
"WindowsSandboxNuxPromptAction": {
"enum": [
"setupElevated",
"setupUnelevated",
"retryElevated",
"quit"
],
"type": "string"
},
"WindowsSandboxNuxPromptParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"failureCode": {
"type": [
"string",
"null"
]
},
"failureMessage": {
"type": [
"string",
"null"
]
},
"screen": {
"$ref": "#/definitions/WindowsSandboxNuxPromptScreen"
},
"surface": {
"$ref": "#/definitions/v2/WindowsSandboxNuxSurface"
}
},
"required": [
"screen",
"surface"
],
"title": "WindowsSandboxNuxPromptParams",
"type": "object"
},
"WindowsSandboxNuxPromptResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"action": {
"$ref": "#/definitions/WindowsSandboxNuxPromptAction"
}
},
"required": [
"action"
],
"title": "WindowsSandboxNuxPromptResponse",
"type": "object"
},
"WindowsSandboxNuxPromptScreen": {
"enum": [
"enable",
"fallback"
],
"type": "string"
},
"v2": {
"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.",
@@ -16587,6 +16713,89 @@
],
"type": "string"
},
"WindowsSandboxNuxStartParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"surface": {
"$ref": "#/definitions/v2/WindowsSandboxNuxSurface"
}
},
"required": [
"surface"
],
"title": "WindowsSandboxNuxStartParams",
"type": "object"
},
"WindowsSandboxNuxStartResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"accepted": {
"type": "boolean"
}
},
"required": [
"accepted"
],
"title": "WindowsSandboxNuxStartResponse",
"type": "object"
},
"WindowsSandboxNuxSurface": {
"enum": [
"tui",
"codexApp",
"vscode"
],
"type": "string"
},
"WindowsSandboxSetupStatus": {
"enum": [
"started",
"running",
"completed",
"failed"
],
"type": "string"
},
"WindowsSandboxSetupStatusNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"attempt": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"elapsedMs": {
"format": "int64",
"type": "integer"
},
"failureCode": {
"type": [
"string",
"null"
]
},
"failureMessage": {
"type": [
"string",
"null"
]
},
"status": {
"$ref": "#/definitions/v2/WindowsSandboxSetupStatus"
},
"surface": {
"$ref": "#/definitions/v2/WindowsSandboxNuxSurface"
}
},
"required": [
"attempt",
"elapsedMs",
"status",
"surface"
],
"title": "WindowsSandboxSetupStatusNotification",
"type": "object"
},
"WindowsWorldWritableWarningNotification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {

View File

@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"WindowsSandboxNuxSurface": {
"enum": [
"tui",
"codexApp",
"vscode"
],
"type": "string"
}
},
"properties": {
"surface": {
"$ref": "#/definitions/WindowsSandboxNuxSurface"
}
},
"required": [
"surface"
],
"title": "WindowsSandboxNuxStartParams",
"type": "object"
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"accepted": {
"type": "boolean"
}
},
"required": [
"accepted"
],
"title": "WindowsSandboxNuxStartResponse",
"type": "object"
}

View File

@@ -0,0 +1,59 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"WindowsSandboxNuxSurface": {
"enum": [
"tui",
"codexApp",
"vscode"
],
"type": "string"
},
"WindowsSandboxSetupStatus": {
"enum": [
"started",
"running",
"completed",
"failed"
],
"type": "string"
}
},
"properties": {
"attempt": {
"format": "uint32",
"minimum": 0.0,
"type": "integer"
},
"elapsedMs": {
"format": "int64",
"type": "integer"
},
"failureCode": {
"type": [
"string",
"null"
]
},
"failureMessage": {
"type": [
"string",
"null"
]
},
"status": {
"$ref": "#/definitions/WindowsSandboxSetupStatus"
},
"surface": {
"$ref": "#/definitions/WindowsSandboxNuxSurface"
}
},
"required": [
"attempt",
"elapsedMs",
"status",
"surface"
],
"title": "WindowsSandboxSetupStatusNotification",
"type": "object"
}

View File

@@ -53,8 +53,9 @@ import type { ThreadUnarchiveParams } from "./v2/ThreadUnarchiveParams";
import type { TurnInterruptParams } from "./v2/TurnInterruptParams";
import type { TurnStartParams } from "./v2/TurnStartParams";
import type { TurnSteerParams } from "./v2/TurnSteerParams";
import type { WindowsSandboxNuxStartParams } from "./v2/WindowsSandboxNuxStartParams";
/**
* 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/name/set", id: RequestId, params: ThreadSetNameParams, } | { "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/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "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": "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": "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": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, };
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/name/set", id: RequestId, params: ThreadSetNameParams, } | { "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/read", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/write", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "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": "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": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "windowsSandbox/nuxStart", id: RequestId, params: WindowsSandboxNuxStartParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "newConversation", id: RequestId, params: NewConversationParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "listConversations", id: RequestId, params: ListConversationsParams, } | { "method": "resumeConversation", id: RequestId, params: ResumeConversationParams, } | { "method": "forkConversation", id: RequestId, params: ForkConversationParams, } | { "method": "archiveConversation", id: RequestId, params: ArchiveConversationParams, } | { "method": "sendUserMessage", id: RequestId, params: SendUserMessageParams, } | { "method": "sendUserTurn", id: RequestId, params: SendUserTurnParams, } | { "method": "interruptConversation", id: RequestId, params: InterruptConversationParams, } | { "method": "addConversationListener", id: RequestId, params: AddConversationListenerParams, } | { "method": "removeConversationListener", id: RequestId, params: RemoveConversationListenerParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "loginApiKey", id: RequestId, params: LoginApiKeyParams, } | { "method": "loginChatGpt", id: RequestId, params: undefined, } | { "method": "cancelLoginChatGpt", id: RequestId, params: CancelLoginChatGptParams, } | { "method": "logoutChatGpt", id: RequestId, params: undefined, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "getUserSavedConfig", id: RequestId, params: undefined, } | { "method": "setDefaultModel", id: RequestId, params: SetDefaultModelParams, } | { "method": "getUserAgent", id: RequestId, params: undefined, } | { "method": "userInfo", id: RequestId, params: undefined, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, } | { "method": "execOneOffCommand", id: RequestId, params: ExecOneOffCommandParams, };

View File

@@ -33,9 +33,10 @@ import type { TurnCompletedNotification } from "./v2/TurnCompletedNotification";
import type { TurnDiffUpdatedNotification } from "./v2/TurnDiffUpdatedNotification";
import type { TurnPlanUpdatedNotification } from "./v2/TurnPlanUpdatedNotification";
import type { TurnStartedNotification } from "./v2/TurnStartedNotification";
import type { WindowsSandboxSetupStatusNotification } from "./v2/WindowsSandboxSetupStatusNotification";
import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldWritableWarningNotification";
/**
* Notification sent from the server to the client.
*/
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification } | { "method": "authStatusChange", "params": AuthStatusChangeNotification } | { "method": "loginChatGptComplete", "params": LoginChatGptCompleteNotification } | { "method": "sessionConfigured", "params": SessionConfiguredNotification };
export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupStatus", "params": WindowsSandboxSetupStatusNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification } | { "method": "authStatusChange", "params": AuthStatusChangeNotification } | { "method": "loginChatGptComplete", "params": LoginChatGptCompleteNotification } | { "method": "sessionConfigured", "params": SessionConfiguredNotification };

View File

@@ -9,8 +9,9 @@ import type { CommandExecutionRequestApprovalParams } from "./v2/CommandExecutio
import type { DynamicToolCallParams } from "./v2/DynamicToolCallParams";
import type { FileChangeRequestApprovalParams } from "./v2/FileChangeRequestApprovalParams";
import type { ToolRequestUserInputParams } from "./v2/ToolRequestUserInputParams";
import type { WindowsSandboxNuxPromptParams } from "./v2/WindowsSandboxNuxPromptParams";
/**
* Request initiated from the server and sent to the client.
*/
export type ServerRequest = { "method": "item/commandExecution/requestApproval", id: RequestId, params: CommandExecutionRequestApprovalParams, } | { "method": "item/fileChange/requestApproval", id: RequestId, params: FileChangeRequestApprovalParams, } | { "method": "item/tool/requestUserInput", id: RequestId, params: ToolRequestUserInputParams, } | { "method": "item/tool/call", id: RequestId, params: DynamicToolCallParams, } | { "method": "account/chatgptAuthTokens/refresh", id: RequestId, params: ChatgptAuthTokensRefreshParams, } | { "method": "applyPatchApproval", id: RequestId, params: ApplyPatchApprovalParams, } | { "method": "execCommandApproval", id: RequestId, params: ExecCommandApprovalParams, };
export type ServerRequest = { "method": "item/commandExecution/requestApproval", id: RequestId, params: CommandExecutionRequestApprovalParams, } | { "method": "item/fileChange/requestApproval", id: RequestId, params: FileChangeRequestApprovalParams, } | { "method": "item/tool/requestUserInput", id: RequestId, params: ToolRequestUserInputParams, } | { "method": "item/tool/call", id: RequestId, params: DynamicToolCallParams, } | { "method": "account/chatgptAuthTokens/refresh", id: RequestId, params: ChatgptAuthTokensRefreshParams, } | { "method": "windowsSandbox/nuxPrompt", id: RequestId, params: WindowsSandboxNuxPromptParams, } | { "method": "applyPatchApproval", id: RequestId, params: ApplyPatchApprovalParams, } | { "method": "execCommandApproval", id: RequestId, params: ExecCommandApprovalParams, };

View File

@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type WindowsSandboxNuxPromptAction = "setupElevated" | "setupUnelevated" | "retryElevated" | "quit";

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 { WindowsSandboxNuxPromptScreen } from "./WindowsSandboxNuxPromptScreen";
import type { WindowsSandboxNuxSurface } from "./WindowsSandboxNuxSurface";
export type WindowsSandboxNuxPromptParams = { surface: WindowsSandboxNuxSurface, screen: WindowsSandboxNuxPromptScreen, failureCode: string | null, failureMessage: string | null, };

View File

@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { WindowsSandboxNuxPromptAction } from "./WindowsSandboxNuxPromptAction";
export type WindowsSandboxNuxPromptResponse = { action: WindowsSandboxNuxPromptAction, };

View File

@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type WindowsSandboxNuxPromptScreen = "enable" | "fallback";

View File

@@ -0,0 +1,6 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { WindowsSandboxNuxSurface } from "./WindowsSandboxNuxSurface";
export type WindowsSandboxNuxStartParams = { surface: WindowsSandboxNuxSurface, };

View File

@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type WindowsSandboxNuxStartResponse = { accepted: boolean, };

View File

@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type WindowsSandboxNuxSurface = "tui" | "codexApp" | "vscode";

View File

@@ -0,0 +1,5 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type WindowsSandboxSetupStatus = "started" | "running" | "completed" | "failed";

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 { WindowsSandboxNuxSurface } from "./WindowsSandboxNuxSurface";
import type { WindowsSandboxSetupStatus } from "./WindowsSandboxSetupStatus";
export type WindowsSandboxSetupStatusNotification = { surface: WindowsSandboxNuxSurface, status: WindowsSandboxSetupStatus, attempt: number, elapsedMs: bigint, failureCode: string | null, failureMessage: string | null, };

View File

@@ -190,5 +190,14 @@ export type { TurnSteerParams } from "./TurnSteerParams";
export type { TurnSteerResponse } from "./TurnSteerResponse";
export type { UserInput } from "./UserInput";
export type { WebSearchAction } from "./WebSearchAction";
export type { WindowsSandboxNuxPromptAction } from "./WindowsSandboxNuxPromptAction";
export type { WindowsSandboxNuxPromptParams } from "./WindowsSandboxNuxPromptParams";
export type { WindowsSandboxNuxPromptResponse } from "./WindowsSandboxNuxPromptResponse";
export type { WindowsSandboxNuxPromptScreen } from "./WindowsSandboxNuxPromptScreen";
export type { WindowsSandboxNuxStartParams } from "./WindowsSandboxNuxStartParams";
export type { WindowsSandboxNuxStartResponse } from "./WindowsSandboxNuxStartResponse";
export type { WindowsSandboxNuxSurface } from "./WindowsSandboxNuxSurface";
export type { WindowsSandboxSetupStatus } from "./WindowsSandboxSetupStatus";
export type { WindowsSandboxSetupStatusNotification } from "./WindowsSandboxSetupStatusNotification";
export type { WindowsWorldWritableWarningNotification } from "./WindowsWorldWritableWarningNotification";
export type { WriteStatus } from "./WriteStatus";

View File

@@ -353,6 +353,10 @@ client_request_definitions! {
params: v2::ConfigBatchWriteParams,
response: v2::ConfigWriteResponse,
},
WindowsSandboxNuxStart => "windowsSandbox/nuxStart" {
params: v2::WindowsSandboxNuxStartParams,
response: v2::WindowsSandboxNuxStartResponse,
},
ConfigRequirementsRead => "configRequirements/read" {
params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>,
@@ -676,6 +680,10 @@ server_request_definitions! {
params: v2::ChatgptAuthTokensRefreshParams,
response: v2::ChatgptAuthTokensRefreshResponse,
},
WindowsSandboxNuxPrompt => "windowsSandbox/nuxPrompt" {
params: v2::WindowsSandboxNuxPromptParams,
response: v2::WindowsSandboxNuxPromptResponse,
},
/// DEPRECATED APIs below
/// Request to approve a patch.
@@ -794,6 +802,7 @@ server_notification_definitions! {
/// Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.
WindowsWorldWritableWarning => "windows/worldWritableWarning" (v2::WindowsWorldWritableWarningNotification),
WindowsSandboxSetupStatus => "windowsSandbox/setupStatus" (v2::WindowsSandboxSetupStatusNotification),
#[serde(rename = "account/login/completed")]
#[ts(rename = "account/login/completed")]

View File

@@ -594,6 +594,64 @@ pub struct ConfigBatchWriteParams {
pub expected_version: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum WindowsSandboxNuxSurface {
Tui,
CodexApp,
Vscode,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxNuxStartParams {
pub surface: WindowsSandboxNuxSurface,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxNuxStartResponse {
pub accepted: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum WindowsSandboxNuxPromptScreen {
Enable,
Fallback,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxNuxPromptParams {
pub surface: WindowsSandboxNuxSurface,
pub screen: WindowsSandboxNuxPromptScreen,
pub failure_code: Option<String>,
pub failure_message: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum WindowsSandboxNuxPromptAction {
SetupElevated,
SetupUnelevated,
RetryElevated,
Quit,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxNuxPromptResponse {
pub action: WindowsSandboxNuxPromptAction,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -3059,6 +3117,28 @@ pub struct WindowsWorldWritableWarningNotification {
pub failed_scan: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum WindowsSandboxSetupStatus {
Started,
Running,
Completed,
Failed,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct WindowsSandboxSetupStatusNotification {
pub surface: WindowsSandboxNuxSurface,
pub status: WindowsSandboxSetupStatus,
pub attempt: u32,
pub elapsed_ms: i64,
pub failure_code: Option<String>,
pub failure_message: Option<String>,
}
/// Deprecated: Use `ContextCompaction` item type instead.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]

View File

@@ -149,6 +149,7 @@ Example with notification opt-out:
- `config/value/write` — write a single config key/value to the user's config.toml on disk.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), `enforceResidency`, and `network` constraints.
- `windowsSandbox/nuxStart` — start the Windows sandbox NUX flow for a specific surface (`codexApp` or `vscode`). The server drives the prompts via server-initiated requests and emits async setup status notifications while elevated setup is running.
### Example: Start or resume a thread
@@ -642,6 +643,18 @@ Order of messages:
UI guidance for IDEs: surface an approval dialog as soon as the request arrives. The turn will proceed after the server receives a response to the approval request. The terminal `item/completed` notification will be sent with the appropriate status.
## Windows Sandbox NUX (v2)
On Windows, UIs that use CLI app-server mode can delegate sandbox onboarding to app-server.
1. Client calls `windowsSandbox/nuxStart` with `{ "surface": "codexApp" | "vscode" }`.
2. If `{ "accepted": true }`, app-server sends `windowsSandbox/nuxPrompt` requests.
3. Client responds with one action: `setupElevated`, `setupUnelevated`, `retryElevated`, or `quit`.
4. While elevated setup is running, app-server emits `windowsSandbox/setupStatus` notifications with:
`status: "started" | "running" | "completed" | "failed"`, `attempt`, `elapsedMs`, and optional failure fields.
This flow lets UI surfaces render the two existing NUX screens while app-server owns setup/preflight execution, config persistence, and metrics (`codex.windows_sandbox.*` with `surface` tag).
### Dynamic tool calls (experimental)
`dynamicTools` on `thread/start` and the corresponding `item/tool/call` request/response flow are experimental APIs. To enable them, set `initialize.params.capabilities.experimentalApi = true`.

View File

@@ -106,6 +106,7 @@ use codex_app_server_protocol::SendUserMessageResponse;
use codex_app_server_protocol::SendUserTurnParams;
use codex_app_server_protocol::SendUserTurnResponse;
use codex_app_server_protocol::ServerNotification;
use codex_app_server_protocol::ServerRequestPayload;
use codex_app_server_protocol::SessionConfiguredNotification;
use codex_app_server_protocol::SetDefaultModelParams;
use codex_app_server_protocol::SetDefaultModelResponse;
@@ -156,6 +157,15 @@ use codex_app_server_protocol::TurnSteerResponse;
use codex_app_server_protocol::UserInfoResponse;
use codex_app_server_protocol::UserInput as V2UserInput;
use codex_app_server_protocol::UserSavedConfig;
use codex_app_server_protocol::WindowsSandboxNuxPromptAction;
use codex_app_server_protocol::WindowsSandboxNuxPromptParams;
use codex_app_server_protocol::WindowsSandboxNuxPromptResponse;
use codex_app_server_protocol::WindowsSandboxNuxPromptScreen;
use codex_app_server_protocol::WindowsSandboxNuxStartParams;
use codex_app_server_protocol::WindowsSandboxNuxStartResponse;
use codex_app_server_protocol::WindowsSandboxNuxSurface;
use codex_app_server_protocol::WindowsSandboxSetupStatus;
use codex_app_server_protocol::WindowsSandboxSetupStatusNotification;
use codex_app_server_protocol::build_turns_from_rollout_items;
use codex_backend_client::Client as BackendClient;
use codex_chatgpt::connectors;
@@ -212,6 +222,15 @@ use codex_core::skills::remote::list_remote_skills;
use codex_core::state_db::StateDbHandle;
use codex_core::state_db::get_state_db;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_core::windows_sandbox_nux::WindowsSandboxNuxHost;
use codex_core::windows_sandbox_nux::WindowsSandboxNuxOutcome;
use codex_core::windows_sandbox_nux::WindowsSandboxNuxPromptAction as CoreWindowsSandboxNuxPromptAction;
use codex_core::windows_sandbox_nux::WindowsSandboxNuxPromptRequest as CoreWindowsSandboxNuxPromptRequest;
use codex_core::windows_sandbox_nux::WindowsSandboxNuxPromptScreen as CoreWindowsSandboxNuxPromptScreen;
use codex_core::windows_sandbox_nux::WindowsSandboxNuxRunParams;
use codex_core::windows_sandbox_nux::WindowsSandboxNuxSurface as CoreWindowsSandboxNuxSurface;
use codex_core::windows_sandbox_nux::WindowsSandboxSetupStatus as CoreWindowsSandboxSetupStatus;
use codex_core::windows_sandbox_nux::WindowsSandboxSetupStatusUpdate as CoreWindowsSandboxSetupStatusUpdate;
use codex_feedback::CodexFeedback;
use codex_login::ServerOptions as LoginServerOptions;
use codex_login::ShutdownHandle;
@@ -281,6 +300,108 @@ struct ActiveLogin {
login_id: Uuid,
}
struct AppServerWindowsSandboxNuxHost {
outgoing: ThreadScopedOutgoingMessageSender,
surface: WindowsSandboxNuxSurface,
}
impl AppServerWindowsSandboxNuxHost {
fn prompt_screen_to_protocol(
screen: CoreWindowsSandboxNuxPromptScreen,
) -> WindowsSandboxNuxPromptScreen {
match screen {
CoreWindowsSandboxNuxPromptScreen::Enable => WindowsSandboxNuxPromptScreen::Enable,
CoreWindowsSandboxNuxPromptScreen::Fallback => WindowsSandboxNuxPromptScreen::Fallback,
}
}
fn prompt_action_from_protocol(
action: WindowsSandboxNuxPromptAction,
) -> CoreWindowsSandboxNuxPromptAction {
match action {
WindowsSandboxNuxPromptAction::SetupElevated => {
CoreWindowsSandboxNuxPromptAction::SetupElevated
}
WindowsSandboxNuxPromptAction::SetupUnelevated => {
CoreWindowsSandboxNuxPromptAction::SetupUnelevated
}
WindowsSandboxNuxPromptAction::RetryElevated => {
CoreWindowsSandboxNuxPromptAction::RetryElevated
}
WindowsSandboxNuxPromptAction::Quit => CoreWindowsSandboxNuxPromptAction::Quit,
}
}
fn setup_status_to_protocol(
status: CoreWindowsSandboxSetupStatus,
) -> WindowsSandboxSetupStatus {
match status {
CoreWindowsSandboxSetupStatus::Started => WindowsSandboxSetupStatus::Started,
CoreWindowsSandboxSetupStatus::Running => WindowsSandboxSetupStatus::Running,
CoreWindowsSandboxSetupStatus::Completed => WindowsSandboxSetupStatus::Completed,
CoreWindowsSandboxSetupStatus::Failed => WindowsSandboxSetupStatus::Failed,
}
}
}
#[async_trait::async_trait]
impl WindowsSandboxNuxHost for AppServerWindowsSandboxNuxHost {
async fn request_prompt(
&mut self,
request: CoreWindowsSandboxNuxPromptRequest,
) -> Option<CoreWindowsSandboxNuxPromptAction> {
let response_rx = self
.outgoing
.send_request(ServerRequestPayload::WindowsSandboxNuxPrompt(
WindowsSandboxNuxPromptParams {
surface: self.surface.clone(),
screen: Self::prompt_screen_to_protocol(request.screen),
failure_code: request.failure_code,
failure_message: request.failure_message,
},
))
.await;
let response = tokio::time::timeout(Duration::from_secs(10 * 60), response_rx).await;
let value = match response {
Ok(Ok(Ok(value))) => value,
Ok(Ok(Err(err))) => {
warn!("windows sandbox nux prompt failed with client error: {err:?}");
return None;
}
Ok(Err(err)) => {
warn!("windows sandbox nux prompt failed: {err:?}");
return None;
}
Err(err) => {
warn!("windows sandbox nux prompt timed out: {err}");
return None;
}
};
serde_json::from_value::<WindowsSandboxNuxPromptResponse>(value)
.map(|response| Self::prompt_action_from_protocol(response.action))
.map_err(|err| {
warn!("failed to deserialize WindowsSandboxNuxPromptResponse: {err}");
})
.ok()
}
async fn on_setup_status(&mut self, update: CoreWindowsSandboxSetupStatusUpdate) {
self.outgoing
.send_server_notification(ServerNotification::WindowsSandboxSetupStatus(
WindowsSandboxSetupStatusNotification {
surface: self.surface.clone(),
status: Self::setup_status_to_protocol(update.status),
attempt: update.attempt,
elapsed_ms: update.elapsed_ms,
failure_code: update.failure_code,
failure_message: update.failure_message,
},
))
.await;
}
}
#[derive(Clone, Copy, Debug)]
enum CancelLoginError {
NotFound(Uuid),
@@ -403,6 +524,96 @@ impl CodexMessageProcessor {
.unwrap_or_default()
}
fn windows_sandbox_surface_to_core(
surface: WindowsSandboxNuxSurface,
) -> CoreWindowsSandboxNuxSurface {
match surface {
WindowsSandboxNuxSurface::Tui => CoreWindowsSandboxNuxSurface::Tui,
WindowsSandboxNuxSurface::CodexApp => CoreWindowsSandboxNuxSurface::CodexApp,
WindowsSandboxNuxSurface::Vscode => CoreWindowsSandboxNuxSurface::Vscode,
}
}
async fn windows_sandbox_nux_start(
&self,
request_id: ConnectionRequestId,
params: WindowsSandboxNuxStartParams,
) {
#[cfg(not(target_os = "windows"))]
{
let _ = params;
self.outgoing
.send_response(
request_id,
WindowsSandboxNuxStartResponse { accepted: false },
)
.await;
return;
}
#[cfg(target_os = "windows")]
{
let config = match self.load_latest_config().await {
Ok(config) => config,
Err(error) => {
self.outgoing.send_error(request_id, error).await;
return;
}
};
if WindowsSandboxLevel::from_config(&config) == WindowsSandboxLevel::Elevated {
self.outgoing
.send_response(
request_id,
WindowsSandboxNuxStartResponse { accepted: false },
)
.await;
return;
}
self.outgoing
.send_response(
request_id.clone(),
WindowsSandboxNuxStartResponse { accepted: true },
)
.await;
let protocol_surface = params.surface;
let run_params = WindowsSandboxNuxRunParams {
surface: Self::windows_sandbox_surface_to_core(protocol_surface.clone()),
codex_home: config.codex_home.clone(),
active_profile: config.active_profile.clone(),
policy: config.permissions.sandbox_policy.get().clone(),
policy_cwd: config.cwd.clone(),
command_cwd: config.cwd.clone(),
env_map: std::env::vars().collect(),
};
let outgoing = ThreadScopedOutgoingMessageSender::new(
Arc::clone(&self.outgoing),
vec![request_id.connection_id],
);
tokio::spawn(async move {
let mut host = AppServerWindowsSandboxNuxHost {
outgoing,
surface: protocol_surface,
};
let outcome =
codex_core::windows_sandbox_nux::run_windows_sandbox_nux(&mut host, run_params)
.await;
match outcome {
Ok(WindowsSandboxNuxOutcome::EnabledElevated)
| Ok(WindowsSandboxNuxOutcome::EnabledUnelevated)
| Ok(WindowsSandboxNuxOutcome::Quit)
| Ok(WindowsSandboxNuxOutcome::PromptUnavailable) => {}
Err(err) => {
warn!(error = %err, "windows sandbox nux run failed");
}
}
});
}
}
/// If a client sends `developer_instructions: null` during a mode switch,
/// use the built-in instructions for that mode.
fn normalize_turn_start_collaboration_mode(
@@ -772,6 +983,10 @@ impl CodexMessageProcessor {
ClientRequest::ConfigRequirementsRead { .. } => {
warn!("ConfigRequirementsRead request reached CodexMessageProcessor unexpectedly");
}
ClientRequest::WindowsSandboxNuxStart { request_id, params } => {
self.windows_sandbox_nux_start(to_connection_request_id(request_id), params)
.await;
}
ClientRequest::GetAccountRateLimits {
request_id,
params: _,

View File

@@ -65,6 +65,7 @@ pub mod token_data;
mod truncate;
mod unified_exec;
pub mod windows_sandbox;
pub mod windows_sandbox_nux;
pub use client::X_RESPONSESAPI_INCLUDE_TIMING_METRICS_HEADER;
pub use model_provider_info::DEFAULT_LMSTUDIO_PORT;
pub use model_provider_info::DEFAULT_OLLAMA_PORT;

View File

@@ -52,6 +52,33 @@ pub fn windows_sandbox_level_from_features(features: &Features) -> WindowsSandbo
WindowsSandboxLevel::from_features(features)
}
pub fn record_windows_sandbox_counter(
metric_name: &str,
surface: &str,
extra_tags: &[(&str, &str)],
) {
if let Some(metrics) = codex_otel::metrics::global() {
let mut tags = Vec::with_capacity(1 + extra_tags.len());
tags.push(("surface", surface));
tags.extend_from_slice(extra_tags);
let _ = metrics.counter(metric_name, 1, &tags);
}
}
pub fn record_windows_sandbox_histogram(
metric_name: &str,
value: i64,
surface: &str,
extra_tags: &[(&str, &str)],
) {
if let Some(metrics) = codex_otel::metrics::global() {
let mut tags = Vec::with_capacity(1 + extra_tags.len());
tags.push(("surface", surface));
tags.extend_from_slice(extra_tags);
let _ = metrics.histogram(metric_name, value, &tags);
}
}
pub fn resolve_windows_sandbox_mode(
cfg: &ConfigToml,
profile: &ConfigProfile,

View File

@@ -0,0 +1,487 @@
//! Shared Windows sandbox NUX orchestration.
//!
//! This module owns the cross-surface workflow for the two-screen Windows
//! sandbox NUX:
//! - an initial "enable sandbox" screen
//! - a fallback screen shown after elevated setup failure
//!
//! The orchestration is transport-agnostic. Core does not know about app-server
//! JSON-RPC, TUI widgets, or any specific UI runtime. Instead, callers inject a
//! [`WindowsSandboxNuxHost`] implementation that provides two callbacks:
//! - request a user decision for the current prompt/screen
//! - receive async setup status updates
//!
//! This keeps workflow logic centralized in core while letting each surface map
//! host callbacks to its own IO model (for example, app-server server-requests/
//! notifications today, and a direct TUI adapter in the future).
//!
//! Responsibilities in this module:
//! - run the prompt/action loop
//! - execute elevated setup attempts (including retry path)
//! - execute unelevated legacy preflight path
//! - persist resulting sandbox config mode
//! - emit `codex.windows_sandbox.*` metrics with a surface tag
//! - emit setup status heartbeats through the injected host
//!
//! Non-goals:
//! - defining UI layout/copy for screens
//! - implementing transport-level protocols
//! - owning app-server request parsing/response shaping
//!
//! Current behavior note: async setup status updates are emitted for elevated
//! setup attempts. Unelevated legacy preflight currently runs as a single
//! operation without intermediate progress updates.
use crate::config::edit::ConfigEditsBuilder;
use crate::protocol::SandboxPolicy;
use crate::windows_sandbox;
use async_trait::async_trait;
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::Duration;
use std::time::Instant;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WindowsSandboxNuxSurface {
Tui,
CodexApp,
Vscode,
}
impl WindowsSandboxNuxSurface {
fn metric_tag(self) -> &'static str {
match self {
WindowsSandboxNuxSurface::Tui => "tui",
WindowsSandboxNuxSurface::CodexApp => "codex_app",
WindowsSandboxNuxSurface::Vscode => "vscode",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WindowsSandboxNuxPromptScreen {
Enable,
Fallback,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WindowsSandboxNuxPromptRequest {
pub screen: WindowsSandboxNuxPromptScreen,
pub failure_code: Option<String>,
pub failure_message: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WindowsSandboxNuxPromptAction {
SetupElevated,
SetupUnelevated,
RetryElevated,
Quit,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WindowsSandboxSetupStatus {
Started,
Running,
Completed,
Failed,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WindowsSandboxSetupStatusUpdate {
pub status: WindowsSandboxSetupStatus,
pub attempt: u32,
pub elapsed_ms: i64,
pub failure_code: Option<String>,
pub failure_message: Option<String>,
}
#[derive(Clone, Debug)]
pub struct WindowsSandboxNuxRunParams {
pub surface: WindowsSandboxNuxSurface,
pub codex_home: PathBuf,
pub active_profile: Option<String>,
pub policy: SandboxPolicy,
pub policy_cwd: PathBuf,
pub command_cwd: PathBuf,
pub env_map: HashMap<String, String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum WindowsSandboxNuxOutcome {
EnabledElevated,
EnabledUnelevated,
Quit,
PromptUnavailable,
}
#[async_trait]
pub trait WindowsSandboxNuxHost: Send {
async fn request_prompt(
&mut self,
request: WindowsSandboxNuxPromptRequest,
) -> Option<WindowsSandboxNuxPromptAction>;
async fn on_setup_status(&mut self, update: WindowsSandboxSetupStatusUpdate);
}
async fn persist_windows_sandbox_mode(
codex_home: &PathBuf,
profile: Option<&str>,
elevated_enabled: bool,
) -> anyhow::Result<()> {
ConfigEditsBuilder::new(codex_home)
.with_profile(profile)
.set_windows_sandbox_mode(if elevated_enabled {
"elevated"
} else {
"unelevated"
})
.clear_legacy_windows_sandbox_keys()
.apply()
.await
}
async fn emit_setup_status(
host: &mut dyn WindowsSandboxNuxHost,
status: WindowsSandboxSetupStatus,
attempt: u32,
started_at: Instant,
failure_code: Option<String>,
failure_message: Option<String>,
) {
let elapsed_ms = i64::try_from(started_at.elapsed().as_millis()).unwrap_or(i64::MAX);
host.on_setup_status(WindowsSandboxSetupStatusUpdate {
status,
attempt,
elapsed_ms,
failure_code,
failure_message,
})
.await;
}
fn screen_metric_name(
screen: &WindowsSandboxNuxPromptScreen,
enable_metric: &'static str,
fallback_metric: &'static str,
) -> &'static str {
if *screen == WindowsSandboxNuxPromptScreen::Enable {
enable_metric
} else {
fallback_metric
}
}
async fn run_unelevated_setup(
surface_tag: &str,
codex_home: &PathBuf,
active_profile: Option<&str>,
policy: &SandboxPolicy,
policy_cwd: &PathBuf,
command_cwd: &PathBuf,
env_map: &HashMap<String, String>,
) -> anyhow::Result<()> {
let policy_for_preflight = policy.clone();
let policy_cwd_for_preflight = policy_cwd.clone();
let command_cwd_for_preflight = command_cwd.clone();
let env_for_preflight = env_map.clone();
let codex_home_for_preflight = codex_home.clone();
let preflight_result = tokio::task::spawn_blocking(move || {
windows_sandbox::run_legacy_setup_preflight(
&policy_for_preflight,
policy_cwd_for_preflight.as_path(),
command_cwd_for_preflight.as_path(),
&env_for_preflight,
codex_home_for_preflight.as_path(),
)
})
.await;
if let Ok(Ok(())) = preflight_result {
} else {
windows_sandbox::record_windows_sandbox_counter(
"codex.windows_sandbox.legacy_setup_preflight_failed",
surface_tag,
&[],
);
}
persist_windows_sandbox_mode(codex_home, active_profile, false).await
}
enum ElevatedAttemptOutcome {
Success,
Failed {
fallback_code: Option<String>,
fallback_message: Option<String>,
},
}
async fn run_elevated_setup_attempt(
host: &mut dyn WindowsSandboxNuxHost,
surface_tag: &str,
codex_home: &PathBuf,
active_profile: Option<&str>,
policy: &SandboxPolicy,
policy_cwd: &PathBuf,
command_cwd: &PathBuf,
env_map: &HashMap<String, String>,
attempt: u32,
) -> anyhow::Result<ElevatedAttemptOutcome> {
let started_at = Instant::now();
emit_setup_status(
host,
WindowsSandboxSetupStatus::Started,
attempt,
started_at,
None,
None,
)
.await;
let policy_for_setup = policy.clone();
let policy_cwd_for_setup = policy_cwd.clone();
let command_cwd_for_setup = command_cwd.clone();
let env_for_setup = env_map.clone();
let codex_home_for_setup = codex_home.clone();
let mut setup_task = tokio::task::spawn_blocking(move || {
windows_sandbox::run_elevated_setup(
&policy_for_setup,
policy_cwd_for_setup.as_path(),
command_cwd_for_setup.as_path(),
&env_for_setup,
codex_home_for_setup.as_path(),
)
});
let mut progress = tokio::time::interval(Duration::from_secs(5));
let setup_result = loop {
tokio::select! {
_ = progress.tick() => {
emit_setup_status(
host,
WindowsSandboxSetupStatus::Running,
attempt,
started_at,
None,
None,
).await;
}
join_result = &mut setup_task => {
break match join_result {
Ok(Ok(())) => Ok(()),
Ok(Err(err)) => Err(err),
Err(err) => Err(anyhow::Error::msg(format!("setup task failed: {err}"))),
};
}
}
};
match setup_result {
Ok(()) => {
if let Err(err) = persist_windows_sandbox_mode(codex_home, active_profile, true).await {
emit_setup_status(
host,
WindowsSandboxSetupStatus::Failed,
attempt,
started_at,
Some("configPersistFailed".to_string()),
Some(err.to_string()),
)
.await;
return Err(err);
}
windows_sandbox::record_windows_sandbox_counter(
"codex.windows_sandbox.elevated_setup_success",
surface_tag,
&[],
);
windows_sandbox::record_windows_sandbox_histogram(
"codex.windows_sandbox.elevated_setup_duration_ms",
i64::try_from(started_at.elapsed().as_millis()).unwrap_or(i64::MAX),
surface_tag,
&[("result", "success")],
);
emit_setup_status(
host,
WindowsSandboxSetupStatus::Completed,
attempt,
started_at,
None,
None,
)
.await;
Ok(ElevatedAttemptOutcome::Success)
}
Err(err) => {
let (fallback_code, fallback_message) =
windows_sandbox::elevated_setup_failure_details(&err)
.map_or((None, None), |(code, message)| (Some(code), Some(message)));
let mut tags: Vec<(&str, &str)> = Vec::new();
if let Some(code) = fallback_code.as_deref() {
tags.push(("code", code));
}
if let Some(message) = fallback_message.as_deref() {
tags.push(("message", message));
}
windows_sandbox::record_windows_sandbox_counter(
windows_sandbox::elevated_setup_failure_metric_name(&err),
surface_tag,
&tags,
);
windows_sandbox::record_windows_sandbox_histogram(
"codex.windows_sandbox.elevated_setup_duration_ms",
i64::try_from(started_at.elapsed().as_millis()).unwrap_or(i64::MAX),
surface_tag,
&[("result", "failure")],
);
emit_setup_status(
host,
WindowsSandboxSetupStatus::Failed,
attempt,
started_at,
fallback_code.clone(),
fallback_message.clone(),
)
.await;
Ok(ElevatedAttemptOutcome::Failed {
fallback_code,
fallback_message,
})
}
}
}
#[cfg(not(target_os = "windows"))]
pub async fn run_windows_sandbox_nux(
_host: &mut dyn WindowsSandboxNuxHost,
_params: WindowsSandboxNuxRunParams,
) -> anyhow::Result<WindowsSandboxNuxOutcome> {
anyhow::bail!("windows sandbox nux is only supported on Windows")
}
#[cfg(target_os = "windows")]
pub async fn run_windows_sandbox_nux(
host: &mut dyn WindowsSandboxNuxHost,
params: WindowsSandboxNuxRunParams,
) -> anyhow::Result<WindowsSandboxNuxOutcome> {
let WindowsSandboxNuxRunParams {
surface,
codex_home,
active_profile,
policy,
policy_cwd,
command_cwd,
env_map,
} = params;
let surface_tag = surface.metric_tag();
let mut prompt_screen = WindowsSandboxNuxPromptScreen::Enable;
let mut fallback_code: Option<String> = None;
let mut fallback_message: Option<String> = None;
let mut attempt: u32 = 0;
loop {
let shown_metric = screen_metric_name(
&prompt_screen,
"codex.windows_sandbox.elevated_prompt_shown",
"codex.windows_sandbox.fallback_prompt_shown",
);
windows_sandbox::record_windows_sandbox_counter(shown_metric, surface_tag, &[]);
let action = host
.request_prompt(WindowsSandboxNuxPromptRequest {
screen: prompt_screen.clone(),
failure_code: fallback_code.clone(),
failure_message: fallback_message.clone(),
})
.await;
let Some(action) = action else {
return Ok(WindowsSandboxNuxOutcome::PromptUnavailable);
};
match action {
WindowsSandboxNuxPromptAction::Quit => {
let quit_metric = screen_metric_name(
&prompt_screen,
"codex.windows_sandbox.elevated_prompt_quit",
"codex.windows_sandbox.fallback_prompt_quit",
);
windows_sandbox::record_windows_sandbox_counter(quit_metric, surface_tag, &[]);
return Ok(WindowsSandboxNuxOutcome::Quit);
}
WindowsSandboxNuxPromptAction::SetupUnelevated => {
let use_legacy_metric = screen_metric_name(
&prompt_screen,
"codex.windows_sandbox.elevated_prompt_use_legacy",
"codex.windows_sandbox.fallback_use_legacy",
);
windows_sandbox::record_windows_sandbox_counter(
use_legacy_metric,
surface_tag,
&[],
);
run_unelevated_setup(
surface_tag,
&codex_home,
active_profile.as_deref(),
&policy,
&policy_cwd,
&command_cwd,
&env_map,
)
.await?;
return Ok(WindowsSandboxNuxOutcome::EnabledUnelevated);
}
WindowsSandboxNuxPromptAction::SetupElevated
| WindowsSandboxNuxPromptAction::RetryElevated => {
let action_metric = if action == WindowsSandboxNuxPromptAction::SetupElevated {
"codex.windows_sandbox.elevated_prompt_accept"
} else {
"codex.windows_sandbox.fallback_retry_elevated"
};
windows_sandbox::record_windows_sandbox_counter(action_metric, surface_tag, &[]);
if windows_sandbox::sandbox_setup_is_complete(codex_home.as_path()) {
persist_windows_sandbox_mode(&codex_home, active_profile.as_deref(), true)
.await?;
windows_sandbox::record_windows_sandbox_counter(
"codex.windows_sandbox.elevated_setup_success",
surface_tag,
&[],
);
return Ok(WindowsSandboxNuxOutcome::EnabledElevated);
}
attempt = attempt.saturating_add(1);
let outcome = run_elevated_setup_attempt(
host,
surface_tag,
&codex_home,
active_profile.as_deref(),
&policy,
&policy_cwd,
&command_cwd,
&env_map,
attempt,
)
.await;
match outcome? {
ElevatedAttemptOutcome::Success => {
return Ok(WindowsSandboxNuxOutcome::EnabledElevated);
}
ElevatedAttemptOutcome::Failed {
fallback_code: new_fallback_code,
fallback_message: new_fallback_message,
} => {
fallback_code = new_fallback_code;
fallback_message = new_fallback_message;
prompt_screen = WindowsSandboxNuxPromptScreen::Fallback;
}
}
}
}
}
}

View File

@@ -1706,14 +1706,17 @@ impl App {
self.chat_widget.open_windows_sandbox_enable_prompt(preset);
}
AppEvent::OpenWindowsSandboxFallbackPrompt { preset } => {
self.otel_manager
.counter("codex.windows_sandbox.fallback_prompt_shown", 1, &[]);
self.otel_manager.counter(
"codex.windows_sandbox.fallback_prompt_shown",
1,
&[("surface", "tui")],
);
self.chat_widget.clear_windows_sandbox_setup_status();
if let Some(started_at) = self.windows_sandbox.setup_started_at.take() {
self.otel_manager.record_duration(
"codex.windows_sandbox.elevated_setup_duration_ms",
started_at.elapsed(),
&[("result", "failure")],
&[("result", "failure"), ("surface", "tui")],
);
}
self.chat_widget
@@ -1757,7 +1760,7 @@ impl App {
otel_manager.counter(
"codex.windows_sandbox.elevated_setup_success",
1,
&[],
&[("surface", "tui")],
);
AppEvent::EnableWindowsSandboxForAgentMode {
preset: preset.clone(),
@@ -1775,7 +1778,7 @@ impl App {
code_tag = Some(code);
message_tag = Some(message);
}
let mut tags: Vec<(&str, &str)> = Vec::new();
let mut tags: Vec<(&str, &str)> = vec![("surface", "tui")];
if let Some(code) = code_tag.as_deref() {
tags.push(("code", code));
}
@@ -1828,7 +1831,7 @@ impl App {
otel_manager.counter(
"codex.windows_sandbox.legacy_setup_preflight_failed",
1,
&[],
&[("surface", "tui")],
);
tracing::warn!(
error = %err,
@@ -1911,7 +1914,7 @@ impl App {
self.otel_manager.record_duration(
"codex.windows_sandbox.elevated_setup_duration_ms",
started_at.elapsed(),
&[("result", "success")],
&[("result", "success"), ("surface", "tui")],
);
}
let profile = self.active_profile.as_deref();

View File

@@ -3321,7 +3321,7 @@ impl ChatWidget {
self.otel_manager.counter(
"codex.windows_sandbox.setup_elevated_sandbox_command",
1,
&[],
&[("surface", "tui")],
);
self.app_event_tx
.send(AppEvent::BeginWindowsSandboxElevatedSetup { preset });
@@ -5705,8 +5705,11 @@ impl ChatWidget {
return;
}
self.otel_manager
.counter("codex.windows_sandbox.elevated_prompt_shown", 1, &[]);
self.otel_manager.counter(
"codex.windows_sandbox.elevated_prompt_shown",
1,
&[("surface", "tui")],
);
let mut header = ColumnRenderable::new();
header.push(*Box::new(
@@ -5725,7 +5728,11 @@ impl ChatWidget {
name: "Set up default sandbox (requires Administrator permissions)".to_string(),
description: None,
actions: vec![Box::new(move |tx| {
accept_otel.counter("codex.windows_sandbox.elevated_prompt_accept", 1, &[]);
accept_otel.counter(
"codex.windows_sandbox.elevated_prompt_accept",
1,
&[("surface", "tui")],
);
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
preset: preset.clone(),
});
@@ -5737,7 +5744,11 @@ impl ChatWidget {
name: "Use non-admin sandbox (higher risk if prompt injected)".to_string(),
description: None,
actions: vec![Box::new(move |tx| {
legacy_otel.counter("codex.windows_sandbox.elevated_prompt_use_legacy", 1, &[]);
legacy_otel.counter(
"codex.windows_sandbox.elevated_prompt_use_legacy",
1,
&[("surface", "tui")],
);
tx.send(AppEvent::BeginWindowsSandboxLegacySetup {
preset: legacy_preset.clone(),
});
@@ -5749,7 +5760,11 @@ impl ChatWidget {
name: "Quit".to_string(),
description: None,
actions: vec![Box::new(move |tx| {
quit_otel.counter("codex.windows_sandbox.elevated_prompt_quit", 1, &[]);
quit_otel.counter(
"codex.windows_sandbox.elevated_prompt_quit",
1,
&[("surface", "tui")],
);
tx.send(AppEvent::Exit(ExitMode::ShutdownFirst));
})],
dismiss_on_select: true,
@@ -5799,7 +5814,11 @@ impl ChatWidget {
let otel = self.otel_manager.clone();
let preset = elevated_preset;
move |tx| {
otel.counter("codex.windows_sandbox.fallback_retry_elevated", 1, &[]);
otel.counter(
"codex.windows_sandbox.fallback_retry_elevated",
1,
&[("surface", "tui")],
);
tx.send(AppEvent::BeginWindowsSandboxElevatedSetup {
preset: preset.clone(),
});
@@ -5815,7 +5834,11 @@ impl ChatWidget {
let otel = self.otel_manager.clone();
let preset = legacy_preset;
move |tx| {
otel.counter("codex.windows_sandbox.fallback_use_legacy", 1, &[]);
otel.counter(
"codex.windows_sandbox.fallback_use_legacy",
1,
&[("surface", "tui")],
);
tx.send(AppEvent::BeginWindowsSandboxLegacySetup {
preset: preset.clone(),
});
@@ -5828,7 +5851,11 @@ impl ChatWidget {
name: "Quit".to_string(),
description: None,
actions: vec![Box::new(move |tx| {
quit_otel.counter("codex.windows_sandbox.fallback_prompt_quit", 1, &[]);
quit_otel.counter(
"codex.windows_sandbox.fallback_prompt_quit",
1,
&[("surface", "tui")],
);
tx.send(AppEvent::Exit(ExitMode::ShutdownFirst));
})],
dismiss_on_select: true,