diff --git a/CLAUDE.md b/CLAUDE.md index e01bc8c4b8..ec7b476e9b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,7 +103,7 @@ git push origin v0.9.4 ## Harnesses -Overdeck supports three coding-agent harnesses: `claude-code` (default), `pi`/`ohmypi` (alternative, multi-provider), and `codex` (OpenAI Codex CLI — first-party agent loop for the GPT model family; work agents run as live TUI sessions via `codexMode: work-tui`, runtime adapter at `src/lib/runtimes/codex.ts`). The harness is picked per spawn at plan kickoff, role runs, work agent start, and the conversation panel; roles read harness/model defaults from Settings. Pi + Anthropic + subscription auth is the only blocked combination (ToS gate in `src/lib/harness-policy.ts`). +Overdeck supports three coding-agent harnesses: `claude-code` (default), `pi`/`ohmypi` (alternative, multi-provider), and `codex` (OpenAI Codex CLI — first-party agent loop for the GPT model family; Codex work agents use the persistent `codex app-server` transport by default, with `codex.transport: tui` as a temporary escape hatch to the legacy `codexMode: work-tui` path). The Codex runtime adapter remains `src/lib/runtimes/codex.ts`. The harness is picked per spawn at plan kickoff, role runs, work agent start, and the conversation panel; roles read harness/model defaults from Settings. Pi + Anthropic + subscription auth is the only blocked combination (ToS gate in `src/lib/harness-policy.ts`). See [configuration/harnesses.mdx](configuration/harnesses.mdx) for installation, picker locations, ToS rules, and troubleshooting. The wider field of coding-agent harnesses Overdeck could adopt is surveyed in [reference/harness-landscape.mdx](reference/harness-landscape.mdx). (`docs/HARNESSES.md` is now a redirect stub — the harness docs are published in the Mintlify site.) diff --git a/configuration/harnesses.mdx b/configuration/harnesses.mdx index 1b69bc8645..0f8f8749c1 100644 --- a/configuration/harnesses.mdx +++ b/configuration/harnesses.mdx @@ -12,7 +12,7 @@ that runs in tmux. The **model** is what the binary calls. They are picked independently per spawn. -**Supported today:** `claude-code` (default) and `ohmypi`. For the wider field of +**Supported today:** `claude-code` (default), `ohmypi`, and `codex`. For the wider field of coding-agent harnesses Overdeck could adopt next — and how each one handles skills, MCP, and AGENTS.md — see the [Harness Landscape](/reference/harness-landscape). @@ -21,6 +21,7 @@ skills, MCP, and AGENTS.md — see the [Harness Landscape](/reference/harness-la | --- | --- | --- | --- | | `claude-code` | [Anthropic Claude Code](https://github.com/anthropics/claude-code) | All workflows | Default if you don't pick. Required for Anthropic-subscription users. | | `ohmypi` | [oh-my-pi](https://github.com/oh-my-pi/pi-coding-agent) (`omp`) | Multi-provider work, large workspaces | Adds oh-my-pi RPC over a named pipe; reads `~/.claude/skills` for skill parity. | +| `codex` | [OpenAI Codex CLI](https://github.com/openai/codex) (`codex`) | OpenAI-model work | Uses persistent `codex app-server` sessions by default; requires Codex CLI `0.144.0` or newer. | ## Supported harnesses @@ -51,8 +52,15 @@ skills, MCP, and AGENTS.md — see the [Harness Landscape](/reference/harness-la - First-party OpenAI agent loop for the GPT model family (GPT-5.6 Sol/Terra/Luna, GPT-5.5, GPT-5.4, GPT-5.3-Codex, …) with ChatGPT-subscription or API-key auth. - `gpt-5.6-sol` is the default OpenAI model surfaced by this harness. -- Work agents run as live, attachable TUI sessions (`codexMode: work-tui`) - under the PTY supervisor — never one-shot `codex exec`. +- Work agents use a persistent `codex app-server` child by default. Overdeck + talks newline-delimited JSON-RPC over stdio, so message delivery, approvals, + readiness, and interruption are structured protocol events rather than TUI + keystrokes. +- Set `codex.transport: tui` as a temporary escape hatch to the legacy live, + attachable TUI path (`codexMode: work-tui`) under the PTY supervisor. + Both transports are persistent sessions — never one-shot `codex exec`. +- The app-server transport requires Codex CLI `0.144.0` or newer. Upgrade with + your normal Codex CLI install method, then verify with `codex --version`. - Runtime adapter at `src/lib/runtimes/codex.ts`; per-agent thread-id pinning enables session introspection and cost parsing (`src/lib/cost-parsers/codex-parser.ts`). @@ -220,6 +228,13 @@ If your dashboard shows a stale heartbeat, check `~/.overdeck/heartbeats/.json` — omp writes there on every `tool_execution_end`. +### Codex app-server is unavailable or too old + +Run `codex --version`. The app-server transport requires Codex CLI `0.144.0` +or newer and the `codex app-server` subcommand. If the version is too old, +upgrade Codex CLI and start a fresh session. To keep work moving during a +rollout, set `codex.transport: tui` to use the legacy TUI transport temporarily. + ## Tradeoffs ### RPC over named pipe vs tmux paste-buffer diff --git a/docs/AGENT-STATE-PLANES.md b/docs/AGENT-STATE-PLANES.md index 64c7fc23af..3989acc394 100644 --- a/docs/AGENT-STATE-PLANES.md +++ b/docs/AGENT-STATE-PLANES.md @@ -60,6 +60,12 @@ Lifecycle events project status, while the Deacon keeps a thin patrol as a dropped-event safety net. A global Deacon pause gates every patrol and recovery path. +Codex app-server sessions keep the same liveness oracle. The tmux pane hosts +Overdeck's Codex app-server host process, which owns the `codex app-server` +child over stdio and renders a readable event feed into the pane. Deacon still +patrols the tmux session; it does not treat the Codex child process as a +separate liveness source. + ## Resume classifier and intent policy `getAgentResumeGateBlockReason()` is the only classifier for `paused`, `troubled`, diff --git a/docs/CODEX-APP-SERVER-PROTOCOL.md b/docs/CODEX-APP-SERVER-PROTOCOL.md new file mode 100644 index 0000000000..5c0bff1cf3 --- /dev/null +++ b/docs/CODEX-APP-SERVER-PROTOCOL.md @@ -0,0 +1,152 @@ +# Codex App Server Protocol + +Verified on this workspace with `codex-cli 0.144.1`. + +The fixture at `src/lib/codex/__fixtures__/app-server-schema.json` was produced by: + +```bash +codex app-server generate-json-schema --out /tmp/pan-2597-codex-schema +cp /tmp/pan-2597-codex-schema/codex_app_server_protocol.schemas.json \ + src/lib/codex/__fixtures__/app-server-schema.json +``` + +The generated bundle includes the methods below. The v2-specific generated bundle +(`codex_app_server_protocol.v2.schemas.json`) uses the same active method names and +contains the resolved v2 parameter schemas. + +## Wire Envelope + +`JSONRPCRequest` is an object with required `id` and `method`, optional `params`, +and optional `trace`. `JSONRPCNotification` is an object with required `method` +and optional `params`; it has no `id`. `JSONRPCResponse` has required `id` and +`result`; `JSONRPCError` has required `id` and `error`. + +The generated schemas do not define a `jsonrpc` property on requests, +notifications, responses, or errors. Do not send `jsonrpc: "2.0"`. + +`RequestId` accepts either a string or an integer. The schema verifies that +integer IDs are valid, not that they are mandatory. Overdeck uses a monotonic +integer counter because the t3code reference manager reads `context.nextRequestId`, +increments it, and writes that value as `id`. + +The generated schema describes one JSON-RPC message object at a time; it does +not encode stream framing. The newline-delimited stdio framing is verified from +the t3code reference manager, whose `writeMessage` serializes one message and +writes `${encoded}\n` to the Codex child stdin. + +## Client Requests + +| Purpose | Generated method | t3code reference | Status | +| --- | --- | --- | --- | +| Handshake request | `initialize` | `initialize` | Match | +| Start a thread | `thread/start` | `thread/start` | Match | +| Resume a thread | `thread/resume` | `thread/resume` | Match | +| Start a turn | `turn/start` | `turn/start` | Match | +| Interrupt a turn | `turn/interrupt` | `turn/interrupt` | Match | +| Read a thread | `thread/read` | `thread/read` | Match | +| Read account state | `account/read` | `account/read` | Match | +| Roll back a thread | `thread/rollback` | `thread/rollback` | Match; present for completeness although not in the first manager slice | + +## Client Notifications + +| Purpose | Generated method | t3code reference | Status | +| --- | --- | --- | --- | +| Complete handshake | `initialized` | `initialized` | Match | + +## Server Notifications + +| Purpose | Generated method | t3code reference | Status | +| --- | --- | --- | --- | +| Thread opened | `thread/started` | `thread/started` | Match | +| Turn began | `turn/started` | `turn/started` | Match | +| Turn finished | `turn/completed` | `turn/completed` | Match | +| Structured error | `error` | `error` | Match | + +Relevant generated payload shapes: + +- `ThreadStartedNotification` requires `thread`. +- `TurnStartedNotification` requires `threadId` and `turn`. +- `TurnCompletedNotification` requires `threadId` and `turn`. +- `ErrorNotification` requires `error`, `threadId`, `turnId`, and `willRetry`. + +## Server Requests + +| Purpose | Generated method | t3code reference | Status | +| --- | --- | --- | --- | +| Command approval | `item/commandExecution/requestApproval` | `item/commandExecution/requestApproval` | Match | +| File-change approval | `item/fileChange/requestApproval` | `item/fileChange/requestApproval` | Match | +| User input for a tool | `item/tool/requestUserInput` | `item/tool/requestUserInput` | Match | +| File-read approval | Not present in generated schema | `item/fileRead/requestApproval` | Divergence: codex-cli 0.144.1 does not expose this server request in the generated schema | + +The generated schema also includes server requests outside the current Overdeck +plan: `item/permissions/requestApproval`, `item/tool/call`, +`mcpServer/elicitation/request`, `account/chatgptAuthTokens/refresh`, +`attestation/generate`, plus deprecated `applyPatchApproval` and +`execCommandApproval`. + +## Response Payloads + +Approval replies are JSON-RPC responses to the server request `id`: + +- `CommandExecutionRequestApprovalResponse` requires `decision`. +- `FileChangeRequestApprovalResponse` requires `decision`. +- `ToolRequestUserInputResponse` requires `answers`, an object whose values are + `ToolRequestUserInputAnswer` objects. +- `ToolRequestUserInputAnswer` requires `answers`, an array of strings. + +t3code matches this shape by converting provider answers into +`Record` before writing `{ id, result: { answers } }`. + +## Transcript And Cost Continuity + +Checkpoint C1 was verified with a live `codex-cli 0.144.1 app-server` session +using an isolated temporary `CODEX_HOME`. +`thread/start` returned a planned rollout path immediately, but Codex did not +create the JSONL until the first `turn/start` completed. + +After a minimal prompt (`Reply with exactly OK.`), Codex wrote: + +```text +/sessions/2026/07/12/rollout-2026-07-12T14-28-38-019f5796-a6eb-7ec0-91e6-ac452b37e193.jsonl +``` + +The existing rollout path helpers worked unchanged: + +- `findLatestRollout(codexHome)` returned the live JSONL. +- `findRolloutPath(codexHome, "019f5796-a6eb-7ec0-91e6-ac452b37e193")` + returned the same JSONL. + +The existing cost parser also worked unchanged. `parseCodexSessionSync` returned +one assistant message, model `gpt-5.6-sol`, 26,389 input tokens, 9,984 cached +input tokens, 5 output tokens, and total cost `0.087167`. +`parseCodexSessionCostEventsSync` emitted one cost event with the same usage. + +The existing conversation reader path worked unchanged. `getCachedMessages` +already treats `rollout-*.jsonl` as Codex and dispatches to +`parseCodexConversationMessages`; that parser returned two chat messages +(`user`, `assistant`), total tokens `26394`, total cost `0.087167`, and the +assistant text `OK`. + +No fallback transcript adapter is required for app-server. The durable transcript +and cost source remains the Codex rollout JSONL under the per-agent +`CODEX_HOME/sessions/YYYY/MM/DD/` tree. + +## Per-Turn Model Checkpoint + +Checkpoint C3 was verified with `codex-cli 0.144.1` using an isolated +temporary `CODEX_HOME`. A thread was started with model +`gpt-5.6-sol`, then a later `turn/start` in the same thread included an explicit +`model: "gpt-5.6-sol"` field. The app-server accepted the request without a +protocol error, so Overdeck uses the native per-turn `model` field for +app-server model overrides on this version. No restart-and-resume fallback is +needed for Codex CLI 0.144.x. + +## Divergences + +1. `item/fileRead/requestApproval` appears in t3code's pending approval union and + routing logic, but it is absent from codex-cli 0.144.1's generated + `ServerRequest` schema. Overdeck implementation beads must not assume a + file-read approval request exists unless a later schema version verifies it. +2. The plan shorthand "integer id counter" is an Overdeck/t3code implementation + choice, not a schema restriction. The generated `RequestId` allows string or + integer IDs; Overdeck uses integers. diff --git a/packages/contracts/src/harness-behavior.ts b/packages/contracts/src/harness-behavior.ts index 191eaef194..d74f37c4e1 100644 --- a/packages/contracts/src/harness-behavior.ts +++ b/packages/contracts/src/harness-behavior.ts @@ -3,9 +3,18 @@ import type { Harness } from "./types" export type RuntimeName = Harness export type HarnessName = RuntimeName | "pi" -export type HarnessLaunchCommandKind = "claude-code" | "ohmypi-rpc" | "codex-work-tui" -export type HarnessDeliveryKind = "pty-supervisor" | "rpc-fifo" | "codex-exec-resume" | "tmux-paste" -export type HarnessReadinessKind = "claude-session-signal" | "ohmypi-ready-file" | "codex-tui-prompt" +export type HarnessLaunchCommandKind = "claude-code" | "ohmypi-rpc" | "codex-work-tui" | "codex-app-server" +export type HarnessDeliveryKind = + | "pty-supervisor" + | "rpc-fifo" + | "codex-exec-resume" + | "codex-app-server-rpc" + | "tmux-paste" +export type HarnessReadinessKind = + | "claude-session-signal" + | "ohmypi-ready-file" + | "codex-tui-prompt" + | "codex-app-server-ready" export type HarnessTranscriptKind = "claude-jsonl" | "ohmypi-jsonl" | "codex-rollout-jsonl" export type HarnessSessionIdSource = "launcher-session-id" | "transcript-jsonl" | "codex-thread-id" export type HarnessContextLayerKind = "claude" | "pi" | "codex" @@ -29,7 +38,7 @@ export interface HarnessBehavior { readonly usesRpcFifo: boolean readonly usesCodexHome: boolean readonly injectsPromptTimeMemory: boolean - readonly workAgentMode: "claude-code" | "ohmypi-rpc" | "codex-work-tui" + readonly workAgentMode: "claude-code" | "ohmypi-rpc" | "codex-work-tui" | "codex-app-server" readonly readyTimeoutSeconds: number } diff --git a/scripts/circular-deps-baseline.txt b/scripts/circular-deps-baseline.txt index b499946fc8..7e1752d6f1 100644 --- a/scripts/circular-deps-baseline.txt +++ b/scripts/circular-deps-baseline.txt @@ -32,8 +32,6 @@ src/dashboard/server/services/conversation-service.ts > src/dashboard/server/ser src/dashboard/server/services/conversation-service.ts > src/dashboard/server/services/conversation/activity-summary.ts > src/dashboard/server/services/pi-conversation-parser.ts src/lib/activity-logger.ts > src/lib/agents.ts > src/lib/agents/agent-state.ts src/lib/agents.ts > src/lib/agents/activity.ts -src/lib/agents.ts > src/lib/agents/activity.ts > src/lib/runtimes/codex.ts > src/lib/tmux.ts > src/lib/config-yaml.ts > src/lib/config-yaml/defaults.ts > src/lib/config-yaml/roles.ts -src/lib/agents.ts > src/lib/agents/activity.ts > src/lib/runtimes/codex.ts > src/lib/tmux.ts > src/lib/config-yaml.ts > src/lib/config-yaml/defaults.ts > src/lib/config-yaml/roles.ts > src/lib/config-yaml/percent.ts > src/lib/config-yaml/schema.ts src/lib/agents.ts > src/lib/agents/activity.ts > src/lib/runtimes/ohmypi.ts > src/lib/launcher-generator.ts src/lib/agents.ts > src/lib/agents/agent-state.ts > src/lib/agents/identity.ts > src/lib/agents/runtime-state.ts src/lib/agents.ts > src/lib/agents/agent-state.ts > src/lib/agents/identity.ts > src/lib/overdeck/agent-rollback-state.ts diff --git a/src/cli/commands/__tests__/review-mode.test.ts b/src/cli/commands/__tests__/review-mode.test.ts index 9abce29015..e897e93d02 100644 --- a/src/cli/commands/__tests__/review-mode.test.ts +++ b/src/cli/commands/__tests__/review-mode.test.ts @@ -1,13 +1,19 @@ import { execFileSync } from 'node:child_process'; -import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { existsSync, mkdtempSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { reviewModeCommand } from '../review-mode.js'; +import { + cleanupGitRecordRoot, + initGitRecordRoot, + removeGitRecordRemote, +} from '../../../../tests/helpers/git-record-fixture.js'; describe('reviewModeCommand', () => { let workspacePath: string; + let recordRemote: string; let originalCwd: string; let exitSpy: ReturnType; let errorSpy: ReturnType; @@ -15,12 +21,7 @@ describe('reviewModeCommand', () => { beforeEach(() => { originalCwd = process.cwd(); workspacePath = mkdtempSync(join(tmpdir(), 'pan-review-mode-')); - execFileSync('git', ['init', '-q', '-b', 'main'], { cwd: workspacePath }); - execFileSync('git', ['config', 'user.email', 'test@overdeck.local'], { cwd: workspacePath }); - execFileSync('git', ['config', 'user.name', 'Overdeck Test'], { cwd: workspacePath }); - execFileSync('git', ['config', 'commit.gpgsign', 'false'], { cwd: workspacePath }); - execFileSync('git', ['commit', '--allow-empty', '-q', '-m', 'chore: seed test repository'], { cwd: workspacePath }); - execFileSync('git', ['remote', 'add', 'origin', '.'], { cwd: workspacePath }); + recordRemote = initGitRecordRoot(workspacePath); process.chdir(workspacePath); exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { throw new Error('process.exit'); @@ -29,10 +30,11 @@ describe('reviewModeCommand', () => { vi.spyOn(console, 'log').mockImplementation(() => {}); }); - afterEach(() => { + afterEach(async () => { process.chdir(originalCwd); vi.restoreAllMocks(); - rmSync(workspacePath, { recursive: true, force: true }); + await cleanupGitRecordRoot(workspacePath); + removeGitRecordRemote(recordRemote); }); it("persists reviewMode='full' through the per-issue record write door", async () => { diff --git a/src/dashboard/frontend/src/App/hooks/usePendingInputDialogs.ts b/src/dashboard/frontend/src/App/hooks/usePendingInputDialogs.ts index 9437c1b5a1..a0b5ee1cd5 100644 --- a/src/dashboard/frontend/src/App/hooks/usePendingInputDialogs.ts +++ b/src/dashboard/frontend/src/App/hooks/usePendingInputDialogs.ts @@ -382,7 +382,7 @@ export function usePendingInputDialogs({ agents, issues }: UsePendingInputDialog }); const codexApprovalMutation = useMutation({ - mutationFn: async ({ id, optionNumber }: { + mutationFn: async ({ id, optionNumber, toolUseId }: { id: string; optionNumber: number; label?: string; @@ -391,7 +391,7 @@ export function usePendingInputDialogs({ agents, issues }: UsePendingInputDialog const res = await fetchWithTimeout(`/api/conversations/${encodeURIComponent(id)}/codex-approval`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ optionNumber }), + body: JSON.stringify({ optionNumber, toolUseId }), }); if (!res.ok) { let message = `Failed to send approval (${res.status})`; diff --git a/src/dashboard/server/routes/__tests__/conversations-supervisor.test.ts b/src/dashboard/server/routes/__tests__/conversations-supervisor.test.ts index 93f12fb2c1..ff9b27b786 100644 --- a/src/dashboard/server/routes/__tests__/conversations-supervisor.test.ts +++ b/src/dashboard/server/routes/__tests__/conversations-supervisor.test.ts @@ -143,8 +143,7 @@ describe('spawnConversationSession PTY supervisor wiring', () => { expect(dismissDevChannelsDialogMock).not.toHaveBeenCalled(); }); - it('wraps Codex TUI conversations with the PTY supervisor and waits for its socket', async () => { - createSupervisorSocket = true; + it('launches Codex app-server conversations without the PTY supervisor', async () => { const { spawnConversationSession } = await import('../../../../lib/overdeck/conversation-runtime.js'); await spawnConversationSession( @@ -162,13 +161,14 @@ describe('spawnConversationSession PTY supervisor wiring', () => { expect(launcher).toContain("export OVERDECK_AGENT_ID='conv-codex-supervisor-test'"); expect(launcher).toContain(`export CODEX_HOME='${join(overdeckHome, 'agents', 'conv-codex-supervisor-test', 'codex-home')}'`); expect(launcher).toContain("node '"); - expect(launcher).toContain("/dist/pty-supervisor.js' codex"); - expect(existsSync(join(overdeckHome, 'agents', 'conv-codex-supervisor-test', 'pty-token'))).toBe(true); - expect((statSync(join(overdeckHome, 'sockets', 'pty-conv-codex-supervisor-test.sock')).mode & 0o777)).toBe(0o600); + expect(launcher).toContain("/dist/codex-app-server-host.js'"); + expect(launcher).not.toContain('pty-supervisor.js'); + expect(existsSync(join(overdeckHome, 'agents', 'conv-codex-supervisor-test', 'pty-token'))).toBe(false); + expect(existsSync(join(overdeckHome, 'sockets', 'pty-conv-codex-supervisor-test.sock'))).toBe(false); expect(dismissDevChannelsDialogMock).not.toHaveBeenCalled(); }); - it('resumes Codex TUI conversations with the persisted thread id', async () => { + it('resumes Codex app-server conversations with the persisted thread id', async () => { createSupervisorSocket = true; const session = 'conv-codex-resume-supervisor-test'; const threadId = '019eaaec-4dfa-7ab1-90ba-9104d16534d1'; @@ -191,7 +191,8 @@ describe('spawnConversationSession PTY supervisor wiring', () => { ); const launcher = launcherFor(session); - expect(launcher).toContain(`/dist/pty-supervisor.js' codex resume -c project_doc_max_bytes=0 '${threadId}'`); + expect(launcher).toContain(`/dist/codex-app-server-host.js' --model 'gpt-5.5' --resume '${threadId}'`); + expect(launcher).not.toContain('pty-supervisor.js'); expect(launcher).not.toContain('codex exec resume'); }); diff --git a/src/dashboard/server/routes/http-handler.ts b/src/dashboard/server/routes/http-handler.ts index 6da68b98a0..ea5f0061d3 100644 --- a/src/dashboard/server/routes/http-handler.ts +++ b/src/dashboard/server/routes/http-handler.ts @@ -24,6 +24,7 @@ import { IssueNotFound, PlanEmpty, RateLimited, + TasksNotInitialized, TrackerApiError, TrackerNotConfigured, WorkspaceCreateError, @@ -49,7 +50,7 @@ let opaqueLastSummaryAt = 0; * TrackerNotConfigured → 503 Service Unavailable * RateLimited → 429 Too Many Requests * AgentAlreadyRunning → 409 Conflict - * PlanEmpty → 422 Unprocessable Entity + * PlanEmpty, TasksNotInitialized → 422 Unprocessable Entity * TrackerApiError → 502 Bad Gateway * WorkspaceCreateError, AgentStartError → 500 Internal Server Error * Unknown errors → 500 Internal Server Error @@ -68,6 +69,7 @@ export function httpHandler( | RateLimited | AgentAlreadyRunning | PlanEmpty + | TasksNotInitialized | TrackerApiError | WorkspaceCreateError | AgentStartError; @@ -92,6 +94,9 @@ export function httpHandler( Effect.catchTag('PlanEmpty', (err: PlanEmpty) => Effect.succeed(jsonResponse({ error: `Plan is empty for issue: ${err.id}` }, { status: 422 })) ), + Effect.catchTag('TasksNotInitialized', (err: TasksNotInitialized) => + Effect.succeed(jsonResponse({ error: `Tasks not initialized for workspace: ${err.workspace}` }, { status: 422 })) + ), Effect.catchTag('TrackerApiError', (err: TrackerApiError) => Effect.succeed( jsonResponse({ error: `Tracker API error (${err.tracker}): ${err.message}` }, { status: 502 }) diff --git a/src/dashboard/server/services/__tests__/agent-spawner.test.ts b/src/dashboard/server/services/__tests__/agent-spawner.test.ts index c69f4b4fb7..092e2b55e9 100644 --- a/src/dashboard/server/services/__tests__/agent-spawner.test.ts +++ b/src/dashboard/server/services/__tests__/agent-spawner.test.ts @@ -46,17 +46,21 @@ vi.mock('../../../../lib/lifecycle/workflows.js', () => ({ deepWipe: mockDeepWipe, })); +// ─── Mock vBRIEF plan reader ───────────────────────────────────────────────── + +const mockReadWorkspacePlanSync = vi.fn(); +vi.mock('../../../../lib/vbrief/io.js', () => ({ + readWorkspacePlanSync: mockReadWorkspacePlanSync, +})); + // ─── Mock projects (resolveProjectFromIssue) ───────────────────────────────── const mockResolveProjectFromIssue = vi.fn().mockReturnValue({ path: '/projects/myapp', name: 'myapp' }); vi.mock('../../../../lib/projects.js', () => ({ + findProjectByPathSync: vi.fn().mockReturnValue({ path: '/projects/myapp', name: 'myapp' }), + listProjectsSync: vi.fn(() => [{ key: 'myapp', config: { path: '/projects/myapp', name: 'myapp' } }]), resolveProjectFromIssue: mockResolveProjectFromIssue, resolveProjectFromIssueSync: mockResolveProjectFromIssue, - findProjectByPathSync: vi.fn().mockReturnValue({ path: '/projects/myapp', name: 'myapp' }), -})); - -vi.mock('../../../../lib/vbrief/io.js', () => ({ - readWorkspacePlanSync: vi.fn().mockReturnValue({ plan: { items: [{ id: 'item-1' }] } }), })); // ─── Helpers ────────────────────────────────────────────────────────────────── @@ -84,6 +88,7 @@ describe('AgentSpawner Effect service', () => { // Default: workspace exists and no agent is running. mockExistsSync.mockReturnValue(true); mockGetAgentState.mockReturnValue(Effect.succeed(null)); + mockReadWorkspacePlanSync.mockReturnValue({ plan: { items: [{ id: 'wi-1' }] } }); mockSpawnAgent.mockResolvedValue({ id: 'pan-1', issueId: 'PAN-1' }); mockStopAgent.mockReturnValue(undefined); mockMessageAgent.mockResolvedValue(undefined); @@ -119,6 +124,19 @@ describe('AgentSpawner Effect service', () => { expect((err as any)._tag).toBe('WorkspaceNotFound'); }); + it('fails with PlanEmpty when the workspace plan has no items', async () => { + mockReadWorkspacePlanSync.mockReturnValue({ plan: { items: [] } }); + + const { AgentSpawner, AgentSpawnerLive } = await import('../agent-spawner.js'); + + const program = Effect.gen(function* () { + const spawner = yield* AgentSpawner; + return yield* spawner.startWork('PAN-1', { workspacePath: WORKSPACE }); + }).pipe(Effect.provide(AgentSpawnerLive)); + + const err = await runProgramFail(program); + expect((err as any)._tag).toBe('PlanEmpty'); + }); it('fails with AgentAlreadyRunning when agent status is running', async () => { mockGetAgentState.mockReturnValue(Effect.succeed({ status: 'running', issueId: 'PAN-1' })); diff --git a/src/dashboard/server/services/__tests__/agent-spawner.unit.test.ts b/src/dashboard/server/services/__tests__/agent-spawner.unit.test.ts index c42eac4451..67955a77eb 100644 --- a/src/dashboard/server/services/__tests__/agent-spawner.unit.test.ts +++ b/src/dashboard/server/services/__tests__/agent-spawner.unit.test.ts @@ -49,10 +49,19 @@ vi.mock('../../../../lib/lifecycle/workflows.js', () => ({ deepWipe: mockDeepWipe, })); +// ─── Mock vBRIEF plan reader ───────────────────────────────────────────────── + +const mockReadWorkspacePlanSync = vi.fn(); +vi.mock('../../../../lib/vbrief/io.js', () => ({ + readWorkspacePlanSync: mockReadWorkspacePlanSync, +})); + // ─── Mock projects ──────────────────────────────────────────────────────────── const mockResolveProjectFromIssue = vi.fn().mockReturnValue({ path: '/projects/myapp', name: 'myapp' }); vi.mock('../../../../lib/projects.js', () => ({ + findProjectByPathSync: vi.fn().mockReturnValue({ path: '/projects/myapp', name: 'myapp' }), + listProjectsSync: vi.fn(() => [{ key: 'myapp', config: { path: '/projects/myapp', name: 'myapp' } }]), resolveProjectFromIssue: mockResolveProjectFromIssue, resolveProjectFromIssueSync: mockResolveProjectFromIssue, findProjectByPathSync: vi.fn().mockReturnValue({ path: '/projects/myapp', name: 'myapp' }), @@ -86,6 +95,7 @@ describe('AgentSpawner — integration', () => { vi.clearAllMocks(); mockExistsSync.mockReturnValue(true); mockGetAgentState.mockReturnValue(Effect.succeed(null)); + mockReadWorkspacePlanSync.mockReturnValue({ plan: { items: [{ id: 'wi-1' }] } }); mockSpawnAgent.mockResolvedValue({ id: 'pan-1', issueId: 'PAN-1' }); mockStopAgent.mockReturnValue(undefined); mockMessageAgent.mockResolvedValue(undefined); diff --git a/src/dashboard/server/services/codex-conversation-parser.ts b/src/dashboard/server/services/codex-conversation-parser.ts index d307a08e77..704ebeb380 100644 --- a/src/dashboard/server/services/codex-conversation-parser.ts +++ b/src/dashboard/server/services/codex-conversation-parser.ts @@ -2,7 +2,7 @@ * OpenAI Codex → chat panel adapter (PAN-1520). * * Codex conversations write a "rollout" JSONL under the per-agent - * $CODEX_HOME/sessions/YYYY/MM/DD/rollout--.jsonl. The schema + * $CODEX_HOME/sessions/YYYY/MM/DD/rollout--.jsonl. The schema * is OpenAI's, not Claude Code's, so the Claude parser (and even the Pi parser, * which the path also happens to match) find zero message entries and the chat * panel renders blank. This module is the adapter — it reads a Codex rollout diff --git a/src/dashboard/server/services/typed-errors.ts b/src/dashboard/server/services/typed-errors.ts index ca7e102799..324f1994ec 100644 --- a/src/dashboard/server/services/typed-errors.ts +++ b/src/dashboard/server/services/typed-errors.ts @@ -57,6 +57,11 @@ export class PlanEmpty extends Data.TaggedError('PlanEmpty')<{ readonly id: string; }> {} +/** The workspace task store is missing or has not been initialized. */ +export class TasksNotInitialized extends Data.TaggedError('TasksNotInitialized')<{ + readonly workspace: string; +}> {} + /** The agent process failed to start. */ export class AgentStartError extends Data.TaggedError('AgentStartError')<{ readonly id: string; diff --git a/src/lib/__tests__/launcher-generator.test.ts b/src/lib/__tests__/launcher-generator.test.ts index a65def0da5..81592284ae 100644 --- a/src/lib/__tests__/launcher-generator.test.ts +++ b/src/lib/__tests__/launcher-generator.test.ts @@ -920,6 +920,56 @@ describe('generateLauncherScript — ohmypi harness (PAN-1989)', () => { expect(script).not.toMatch(/codex exec/); }); + it('codex app-server mode launches the host without the PTY supervisor', () => { + const script = generateLauncherScriptSync({ + ...DEFAULT_CONFIG, + role: 'work', + harness: 'codex', + codexMode: 'app-server', + spawnMode: 'conversation', + useSupervisor: true, + supervisorScriptPath: '/dist/pty-supervisor.js', + }); + expect(script).toMatch(/^node '.+\/dist\/codex-app-server-host\.js'$/m); + expect(script).not.toMatch(/pty-supervisor/); + expect(script).not.toMatch(/codex exec/); + expect(script).not.toMatch(/ -m /); + }); + + it('codex app-server mode resumes by thread id', () => { + const script = generateLauncherScriptSync({ + ...DEFAULT_CONFIG, + role: 'work', + harness: 'codex', + codexMode: 'app-server', + resumeSessionId: '019ee5e7-thread-abc', + }); + expect(script).toMatch(/^exec node '.+\/dist\/codex-app-server-host\.js' --resume '019ee5e7-thread-abc'$/m); + }); + + it('codex tui escape hatch preserves the previous conversation command byte-for-byte', () => { + const legacy = generateLauncherScriptSync({ + ...DEFAULT_CONFIG, + role: 'work', + harness: 'codex', + codexMode: 'tui', + spawnMode: 'conversation', + useSupervisor: true, + supervisorScriptPath: '/dist/pty-supervisor.js', + }); + const escapeHatch = generateLauncherScriptSync({ + ...DEFAULT_CONFIG, + role: 'work', + harness: 'codex', + codexMode: 'tui', + spawnMode: 'conversation', + useSupervisor: true, + supervisorScriptPath: '/dist/pty-supervisor.js', + }); + expect(escapeHatch).toBe(legacy); + expect(escapeHatch).toMatch(/^node '\/dist\/pty-supervisor\.js' codex -c project_doc_max_bytes=0$/m); + }); + it('codex plan launchers do not receive Claude-only append-system-prompt flags', () => { const script = generateLauncherScriptSync({ ...DEFAULT_CONFIG, diff --git a/src/lib/agents/__tests__/delivery.test.ts b/src/lib/agents/__tests__/delivery.test.ts new file mode 100644 index 0000000000..c160493561 --- /dev/null +++ b/src/lib/agents/__tests__/delivery.test.ts @@ -0,0 +1,189 @@ +import { Effect } from 'effect'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { createServer, type Server as NetServer } from 'node:net'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +let tmpHome: string; +let stateDir: string; +let socketDir: string; + +vi.mock('../../tmux.js', () => ({ + sendKeys: vi.fn(() => Effect.void), + sessionExists: vi.fn(() => Effect.succeed(false)), + isPaneDead: vi.fn(() => Effect.succeed(false)), +})); + +vi.mock('../../paths.js', async (importOriginal) => { + const actual = (await importOriginal()) as Record; + return { + ...actual, + get AGENTS_DIR() { + return stateDir; + }, + }; +}); + +import { deliverAgentMessage } from '../delivery.js'; +import { sendKeys } from '../../tmux.js'; +import type { AgentState } from '../agent-state.js'; + +interface FakeBridgeOptions { + status?: number; + body?: string; + delayMs?: number; + capture?: { lastBody?: string }; +} + +function writeAgentState(agentId: string, partial: Partial = {}): void { + const dir = join(stateDir, agentId); + mkdirSync(dir, { recursive: true }); + const state: AgentState = { + id: agentId, + issueId: 'PAN-TEST', + workspace: '/tmp/workspace', + harness: 'codex', + role: 'work', + model: 'gpt-5.6-sol', + status: 'running', + startedAt: new Date().toISOString(), + ...partial, + }; + writeFileSync(join(dir, 'state.json'), JSON.stringify(state)); +} + +function writeAppServerToken(agentId: string): void { + const dir = join(stateDir, agentId); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, 'appserver-token'), 'token-123\n'); +} + +function startFakeBridge(socketPath: string, opts: FakeBridgeOptions): Promise { + return new Promise((resolveServer) => { + const server = createServer((sock) => { + let buf = Buffer.alloc(0); + sock.on('data', (chunk) => { + buf = Buffer.concat([buf, chunk]); + const text = buf.toString('utf-8'); + const headerEnd = text.indexOf('\r\n\r\n'); + if (headerEnd < 0) return; + const headerBlock = text.slice(0, headerEnd); + const lengthMatch = /Content-Length:\s*(\d+)/i.exec(headerBlock); + const len = lengthMatch ? parseInt(lengthMatch[1], 10) : 0; + if (Buffer.byteLength(text.slice(headerEnd + 4)) < len) return; + const body = text.slice(headerEnd + 4, headerEnd + 4 + len); + if (opts.capture) opts.capture.lastBody = body; + const respond = () => { + const status = opts.status ?? 200; + const responseBody = opts.body ?? '{}'; + sock.end( + `HTTP/1.1 ${status} ${status === 200 ? 'OK' : 'ERR'}\r\n` + + `Content-Length: ${Buffer.byteLength(responseBody)}\r\n` + + `Connection: close\r\n\r\n${responseBody}`, + ); + }; + if (opts.delayMs) setTimeout(respond, opts.delayMs); + else respond(); + }); + }); + server.listen(socketPath, () => resolveServer(server)); + }); +} + +function readDeliveryLog(agentId: string): Array> { + return readFileSync(join(tmpHome, 'logs', `bridge-${agentId}.log`), 'utf8') + .trim() + .split('\n') + .filter(Boolean) + .map((line) => JSON.parse(line) as Record); +} + +describe('app-server delivery tier', () => { + beforeEach(() => { + tmpHome = mkdtempSync(join(tmpdir(), 'pan-appserver-delivery-')); + stateDir = join(tmpHome, 'agents'); + socketDir = join(tmpHome, 'sockets'); + mkdirSync(stateDir, { recursive: true }); + mkdirSync(socketDir, { recursive: true }); + process.env.OVERDECK_HOME = tmpHome; + vi.mocked(sendKeys).mockClear(); + }); + + afterEach(() => { + vi.useRealTimers(); + delete process.env.OVERDECK_HOME; + rmSync(tmpHome, { recursive: true, force: true }); + }); + + it('returns app-server when the host socket accepts the message op', async () => { + const agentId = 'agent-appserver-success'; + writeAgentState(agentId); + writeAppServerToken(agentId); + const capture: { lastBody?: string } = {}; + const server = await startFakeBridge(join(socketDir, `appserver-${agentId}.sock`), { capture }); + try { + const result = await deliverAgentMessage(agentId, 'hello', 'test-caller'); + expect(result).toEqual({ ok: true, path: 'app-server' }); + expect(JSON.parse(capture.lastBody!)).toEqual({ + op: 'message', + content: 'hello', + model: 'gpt-5.6-sol', + meta: { caller: 'test-caller' }, + }); + expect(vi.mocked(sendKeys)).not.toHaveBeenCalled(); + expect(readDeliveryLog(agentId).at(-1)).toMatchObject({ path: 'app-server' }); + } finally { + await new Promise((resolve) => server.close(() => resolve())); + } + }); + + it('sends a 1MB payload in one request without rewriting it', async () => { + const agentId = 'agent-appserver-large'; + const payload = 'x'.repeat(1024 * 1024); + writeAgentState(agentId); + writeAppServerToken(agentId); + const capture: { lastBody?: string } = {}; + const server = await startFakeBridge(join(socketDir, `appserver-${agentId}.sock`), { capture }); + try { + const result = await deliverAgentMessage(agentId, payload, 'large-caller'); + expect(result).toEqual({ ok: true, path: 'app-server' }); + expect(JSON.parse(capture.lastBody!).content).toBe(payload); + expect(vi.mocked(sendKeys)).not.toHaveBeenCalled(); + } finally { + await new Promise((resolve) => server.close(() => resolve())); + } + }); + + it('falls back to tmux when the app-server POST fails', async () => { + const agentId = 'agent-appserver-fallback'; + writeAgentState(agentId); + writeAppServerToken(agentId); + const server = await startFakeBridge(join(socketDir, `appserver-${agentId}.sock`), { status: 500, body: 'nope' }); + try { + const result = await deliverAgentMessage(agentId, 'fallback', 'fallback-caller'); + expect(result).toMatchObject({ ok: true, path: 'tmux' }); + expect(vi.mocked(sendKeys)).toHaveBeenCalledWith(agentId, 'fallback'); + expect(readDeliveryLog(agentId).at(-1)).toMatchObject({ path: 'tmux' }); + } finally { + await new Promise((resolve) => server.close(() => resolve())); + } + }); + + it('falls back after the app-server 8s timeout using fake timers', async () => { + vi.useFakeTimers(); + const agentId = 'agent-appserver-timeout'; + writeAgentState(agentId); + writeAppServerToken(agentId); + const server = await startFakeBridge(join(socketDir, `appserver-${agentId}.sock`), { delayMs: 20_000 }); + try { + const delivered = deliverAgentMessage(agentId, 'timeout', 'timeout-caller'); + await vi.advanceTimersByTimeAsync(8_100); + const result = await delivered; + expect(result).toMatchObject({ ok: true, path: 'tmux' }); + expect(vi.mocked(sendKeys)).toHaveBeenCalledWith(agentId, 'timeout'); + } finally { + await new Promise((resolve) => server.close(() => resolve())); + } + }); +}); diff --git a/src/lib/agents/__tests__/resume.test.ts b/src/lib/agents/__tests__/resume.test.ts new file mode 100644 index 0000000000..becc17ce42 --- /dev/null +++ b/src/lib/agents/__tests__/resume.test.ts @@ -0,0 +1,102 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { createServer, type Server } from 'node:http'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +import { deliverAgentMessage } from '../delivery.js'; +import { resolveRecoveryResumeSessionId } from '../recovery.js'; + +let tempHome: string; +let prevOverdeckHome: string | undefined; +let servers: Server[] = []; + +beforeEach(() => { + tempHome = mkdtempSync(join(tmpdir(), 'pan-resume-test-')); + prevOverdeckHome = process.env.OVERDECK_HOME; + process.env.OVERDECK_HOME = tempHome; +}); + +afterEach(() => { + for (const server of servers) { + server.close(); + } + servers = []; + if (prevOverdeckHome === undefined) delete process.env.OVERDECK_HOME; + else process.env.OVERDECK_HOME = prevOverdeckHome; + rmSync(tempHome, { recursive: true, force: true }); +}); + +function agentDir(agentId: string): string { + return join(tempHome, 'agents', agentId); +} + +describe('resolveRecoveryResumeSessionId', () => { + it('returns the persisted Codex thread id for recovery launchers', () => { + const agentId = 'agent-codex-resume'; + mkdirSync(agentDir(agentId), { recursive: true }); + writeFileSync(join(agentDir(agentId), 'codex-thread-id'), 'thread-123\n'); + + expect(resolveRecoveryResumeSessionId(agentId, 'codex')).toBe('thread-123'); + }); + + it('does not apply Codex thread ids to other harnesses', () => { + const agentId = 'agent-claude-resume'; + mkdirSync(agentDir(agentId), { recursive: true }); + writeFileSync(join(agentDir(agentId), 'codex-thread-id'), 'thread-ignored\n'); + + expect(resolveRecoveryResumeSessionId(agentId, 'claude-code')).toBeUndefined(); + }); + + it('falls back to the latest rollout thread id when the explicit file is absent', () => { + const agentId = 'agent-codex-rollout-resume'; + const sessionsDir = join(agentDir(agentId), 'codex-home', 'sessions', '2026', '07', '12'); + mkdirSync(sessionsDir, { recursive: true }); + writeFileSync( + join(sessionsDir, 'rollout-2026-07-12T14-28-38-019f5796-a6eb-7ec0-91e6-ac452b37e193.jsonl'), + '{"type":"session_meta","payload":{"id":"019f5796-a6eb-7ec0-91e6-ac452b37e193"}}\n', + ); + + expect(resolveRecoveryResumeSessionId(agentId, 'codex')).toBe('019f5796-a6eb-7ec0-91e6-ac452b37e193'); + }); +}); + +describe('resume Codex app-server delivery', () => { + it('delivers the continue prompt through the app-server socket tier', async () => { + const agentId = 'agent-codex-delivery'; + const socketDir = join(tempHome, 'sockets'); + mkdirSync(socketDir, { recursive: true }); + mkdirSync(agentDir(agentId), { recursive: true }); + writeFileSync(join(agentDir(agentId), 'appserver-token'), 'token-123\n'); + + let resolveReceived!: (body: string) => void; + const received = new Promise((resolve) => { + resolveReceived = resolve; + }); + const server = createServer((req, res) => { + let body = ''; + req.setEncoding('utf-8'); + req.on('data', (chunk) => { + body += chunk; + }); + req.on('end', () => { + res.writeHead(200, { 'content-type': 'application/json' }); + res.end('{}'); + resolveReceived(body); + }); + }); + servers.push(server); + await new Promise((resolve) => { + server.listen(join(socketDir, `appserver-${agentId}.sock`), resolve); + }); + + const result = await deliverAgentMessage(agentId, 'continue now', 'resumeAgent:codex-continue'); + + expect(result).toEqual({ ok: true, path: 'app-server' }); + await expect(received).resolves.toBe(JSON.stringify({ + op: 'message', + content: 'continue now', + meta: { caller: 'resumeAgent:codex-continue' }, + })); + }); +}); diff --git a/src/lib/agents/__tests__/runtime-command.test.ts b/src/lib/agents/__tests__/runtime-command.test.ts new file mode 100644 index 0000000000..b818a0bcd0 --- /dev/null +++ b/src/lib/agents/__tests__/runtime-command.test.ts @@ -0,0 +1,51 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { waitForCodexAppServerReady } from '../runtime-command.js'; +import { shouldUseSupervisorForConversation } from '../../overdeck/conversation-runtime.js'; + +afterEach(() => { + vi.useRealTimers(); +}); + +describe('waitForCodexAppServerReady', () => { + it('resolves when the status op reports ready', async () => { + const readStatus = vi.fn(async () => ({ state: 'ready' })); + const sessionExists = vi.fn(async () => true); + + await expect(waitForCodexAppServerReady('agent-ready', 1, { + readStatus, + sessionExists, + sleep: async () => {}, + })).resolves.toBeUndefined(); + + expect(sessionExists).toHaveBeenCalledWith('agent-ready'); + expect(readStatus).toHaveBeenCalledWith('agent-ready'); + }); + + it('rejects with an actionable timeout when readiness never arrives', async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-07-12T00:00:00Z')); + const pending = waitForCodexAppServerReady('agent-timeout', 1, { + readStatus: vi.fn(async () => ({ state: 'starting' })), + sessionExists: vi.fn(async () => true), + }); + const assertion = expect(pending).rejects.toThrow( + 'Timed out waiting for Codex app-server readiness for agent-timeout. Last state: starting.', + ); + + await vi.advanceTimersByTimeAsync(1_500); + + await assertion; + }); +}); + +describe('shouldUseSupervisorForConversation', () => { + it('disables the PTY supervisor for Codex app-server conversations', () => { + expect(shouldUseSupervisorForConversation('codex', { codexTransport: 'app-server' })).toBe(false); + }); + + it('preserves the existing Codex TUI and Claude Code supervisor behavior', () => { + expect(shouldUseSupervisorForConversation('codex', { codexTransport: 'tui' })).toBe(true); + expect(shouldUseSupervisorForConversation('claude-code')).toBe(true); + }); +}); diff --git a/src/lib/agents/delivery.ts b/src/lib/agents/delivery.ts index 39190a56d2..2d75eeae8e 100644 --- a/src/lib/agents/delivery.ts +++ b/src/lib/agents/delivery.ts @@ -1,4 +1,4 @@ -import { existsSync, mkdirSync, writeFileSync } from 'fs'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; import { request as httpRequest } from 'node:http'; import { join, dirname } from 'path'; import { homedir } from 'os'; @@ -30,7 +30,7 @@ import { export type DeliveryResult = { ok: boolean; - path: 'supervisor' | 'channels' | 'tmux' | 'pi' | 'codex'; + path: 'app-server' | 'supervisor' | 'channels' | 'tmux' | 'pi' | 'codex'; failure?: string; }; @@ -66,9 +66,10 @@ function overdeckHomeForChannels(): string { async function appendChannelDeliveryLog( agentId: string, entry: { - path: 'supervisor' | 'channel' | 'tmux'; + path: 'app-server' | 'supervisor' | 'channel' | 'tmux'; reason?: string; caller?: string; + appServer?: string; 'pty-supervisor'?: string; channels?: string; }, @@ -92,6 +93,17 @@ async function appendChannelDeliveryLog( } } +function readAppServerTokenSync(agentId: string): string | null { + try { + const tokenPath = join(overdeckHomeForSockets(), 'agents', agentId, 'appserver-token'); + if (!existsSync(tokenPath)) return null; + const token = readFileSync(tokenPath, 'utf-8').trim(); + return token || null; + } catch { + return null; + } +} + /** * POST a JSON body to a Unix-domain socket using node:net + a hand-rolled * minimal HTTP/1.1 request. Resolves on a 200-class response, rejects on any @@ -188,8 +200,9 @@ export async function deliverAgentMessage( let channelsEnabled = false; let resolvedMethod = deliveryMethod; + let state: AgentState | null = null; try { - const state = await Effect.runPromise(getAgentState(normalizedId)); + state = await Effect.runPromise(getAgentState(normalizedId)); channelsEnabled = Boolean(state?.channelsEnabled); resolvedMethod ??= state?.deliveryMethod ?? 'auto'; } catch { @@ -202,6 +215,33 @@ export async function deliverAgentMessage( return { ok: true, path: 'tmux' }; } + let appServerFailure: string | undefined; + if (resolvedMethod === 'auto') { + const appServerSocketPath = join(overdeckHomeForSockets(), 'sockets', `appserver-${normalizedId}.sock`); + if (existsSync(appServerSocketPath)) { + const appServerToken = readAppServerTokenSync(normalizedId); + if (!appServerToken) { + appServerFailure = 'appserver-token-missing'; + } else { + try { + const appServerBody: Record = { op: 'message', content: message, meta: { caller } }; + if (state?.model) appServerBody.model = state.model; + await postUnixSocketJson( + appServerSocketPath, + appServerBody, + 8_000, + appServerToken, + ); + await appendChannelDeliveryLog(normalizedId, { path: 'app-server', caller }); + return { ok: true, path: 'app-server' }; + } catch (err) { + const reason = err instanceof Error ? err.message : String(err); + appServerFailure = `socket-post-failed: ${reason}`; + } + } + } + } + let supervisorFailure: string | undefined; if (resolvedMethod === 'auto' || resolvedMethod === 'supervisor') { const supervisorSocketPath = join(overdeckHomeForSockets(), 'sockets', `pty-${normalizedId}.sock`); @@ -276,6 +316,7 @@ export async function deliverAgentMessage( path: 'tmux', reason: channelFailure, caller, + ...(appServerFailure ? { appServer: appServerFailure } : {}), ...(supervisorFailure ? { 'pty-supervisor': supervisorFailure } : {}), ...(channelFailure ? { channels: channelFailure } : {}), }); diff --git a/src/lib/agents/recovery.ts b/src/lib/agents/recovery.ts index 3927d902dd..901952d1f0 100644 --- a/src/lib/agents/recovery.ts +++ b/src/lib/agents/recovery.ts @@ -2,6 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from 'fs'; import { join } from 'path'; import { Effect } from 'effect'; import { BLANKED_PROVIDER_ENV } from '../child-env.js'; +import { getLatestSessionIdSync } from './activity.js'; import { sendGracefulRestartWarning } from '../graceful-restart.js'; import { checkHookSync, generateFixedPointPromptSync } from '../hooks.js'; import { generateLauncherScriptSync } from '../launcher-generator.js'; @@ -56,6 +57,11 @@ export interface RestartAgentOptions { message?: string; } +export function resolveRecoveryResumeSessionId(agentId: string, harness: RuntimeName): string | undefined { + if (harness !== 'codex') return undefined; + return getLatestSessionIdSync(agentId) ?? undefined; +} + export async function restartAgent( agentId: string, opts: RestartAgentOptions = {}, @@ -358,6 +364,7 @@ export async function recoverAgent( baseCommand: await getRoleRuntimeBaseCommand(state.model, normalizedId, recoveryRole, recoveryHarness), appendSystemPromptFiles: await claudeSystemPromptFiles(state.workspace, recoveryHarness), ...(recoveryHarness === 'codex' ? {} : { promptInline: recoveryPrompt }), + resumeSessionId: resolveRecoveryResumeSessionId(normalizedId, recoveryHarness), useSupervisor: recoverySupervisorLaunch.useSupervisor, supervisorScriptPath: recoverySupervisorLaunch.supervisorScriptPath, ...recoveryCodexFields, diff --git a/src/lib/agents/runtime-command.ts b/src/lib/agents/runtime-command.ts index d0a936adfc..f6fc14ba3d 100644 --- a/src/lib/agents/runtime-command.ts +++ b/src/lib/agents/runtime-command.ts @@ -1,6 +1,7 @@ import { existsSync, mkdirSync, readFileSync, statfsSync, statSync, writeFileSync } from 'fs'; import { mkdir, rename as renameAsync, stat as statAsync, writeFile } from 'fs/promises'; import { exec } from 'child_process'; +import { request as httpRequest } from 'node:http'; import { randomUUID } from 'crypto'; import { homedir } from 'os'; import { basename, dirname, join, resolve } from 'path'; @@ -176,12 +177,13 @@ export async function getOhmypiLauncherFields(agentId: string, model: string): P export function getCodexLauncherFields(agentId: string, model: string, workspacePath?: string): { harness: 'codex'; - codexMode: 'work-tui'; + codexMode: 'app-server' | 'work-tui'; codexHome: string; codexSessionDir: string; model: string; } { const codexHome = join(homedir(), '.overdeck', 'agents', agentId, 'codex-home'); + const codexConfig = loadYamlConfig().config.codex; // PAN-1803: codex work agents must inherit the user's configured codex // permission level (Settings → Permissions → Codex) and pre-trust the // workspace, EXACTLY like the conversation path @@ -189,7 +191,7 @@ export function getCodexLauncherFields(agentId: string, model: string, workspace // folder-trust / "load project-local config?" wizard and blocks the pane. // Without the permission mapping, work agents ignore the Settings choice // and run hardcoded never+workspace-write. - const codexPermMode = loadYamlConfig().config.codex?.permissionMode ?? 'workspace'; + const codexPermMode = codexConfig?.permissionMode ?? 'workspace'; const approvalPolicy = codexPermMode === 'full-access' ? 'never' : 'on-request'; const sandboxMode = codexPermMode === 'full-access' ? 'danger-full-access' @@ -204,7 +206,7 @@ export function getCodexLauncherFields(agentId: string, model: string, workspace }); return { harness: 'codex', - codexMode: 'work-tui', + codexMode: codexConfig?.transport === 'tui' ? 'work-tui' : 'app-server', codexHome, codexSessionDir: join(codexHome, 'sessions'), model, @@ -297,8 +299,131 @@ async function waitForCodexTuiReady(agentId: string, timeoutSec = 30): Promise Promise; + readStatus?: (agentId: string) => Promise; + sleep?: (ms: number) => Promise; + now?: () => number; +} + +function readAppServerToken(agentId: string): string | null { + const tokenPath = join(getAgentDir(agentId), 'appserver-token'); + if (!existsSync(tokenPath)) return null; + try { + return readFileSync(tokenPath, 'utf-8').trim() || null; + } catch { + return null; + } +} + +async function postAppServerStatus(agentId: string): Promise { + const socketPath = join(getOverdeckHome(), 'sockets', `appserver-${agentId}.sock`); + const token = readAppServerToken(agentId); + if (!existsSync(socketPath)) throw new Error(`app-server socket missing for ${agentId}`); + if (!token) throw new Error(`app-server token missing for ${agentId}`); + const payload = JSON.stringify({ op: 'status' }); + + return new Promise((resolveStatus, reject) => { + let settled = false; + const timeout = setTimeout(() => { + if (settled) return; + settled = true; + req.destroy(new Error(`app-server status op timed out after 2000ms`)); + reject(new Error(`app-server status op timed out after 2000ms`)); + }, 2_000); + const finishErr = (error: Error) => { + if (settled) return; + settled = true; + clearTimeout(timeout); + reject(error); + }; + const finishOk = (status: CodexAppServerStatus) => { + if (settled) return; + settled = true; + clearTimeout(timeout); + resolveStatus(status); + }; + const req = httpRequest( + { + socketPath, + path: '/', + method: 'POST', + agent: false, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload), + 'X-Overdeck-Bridge-Token': token, + }, + }, + (res) => { + let body = ''; + res.setEncoding('utf-8'); + res.on('data', (chunk) => { body += chunk; }); + res.on('end', () => { + const status = res.statusCode ?? 0; + if (status < 200 || status >= 300) { + finishErr(new Error(`app-server status op returned HTTP ${status}`)); + return; + } + try { + finishOk(JSON.parse(body) as CodexAppServerStatus); + } catch (error) { + finishErr(new Error(`app-server status op returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`)); + } + }); + }, + ); + req.on('error', finishErr); + req.write(payload); + req.end(); + }); +} + +export async function waitForCodexAppServerReady( + agentId: string, + timeoutSec = 30, + deps: CodexAppServerReadyDeps = {}, +): Promise { + const now = deps.now ?? (() => Date.now()); + const sleep = deps.sleep ?? ((ms: number) => new Promise((resolveSleep) => setTimeout(resolveSleep, ms))); + const sessionExistsForAgent = deps.sessionExists ?? (async (id: string) => Effect.runPromise(sessionExists(id))); + const readStatus = deps.readStatus ?? postAppServerStatus; + const deadline = now() + timeoutSec * 1000; + let lastState = 'unknown'; + let lastError = ''; + + while (now() < deadline) { + if (!(await sessionExistsForAgent(agentId))) { + throw new Error(`Codex app-server session ${agentId} exited before readiness.`); + } + try { + const status = await readStatus(agentId); + lastState = status.state ?? 'unknown'; + if (lastState === 'ready') return; + } catch (error) { + lastError = error instanceof Error ? error.message : String(error); + } + await sleep(500); + } + + const detail = lastError ? ` Last status error: ${lastError}` : ` Last state: ${lastState}.`; + throw new Error(`Timed out waiting for Codex app-server readiness for ${agentId}.${detail}`); +} + export async function waitForPromptReady(agentId: string, harness: RuntimeName | undefined, timeoutSec = 30): Promise { - if (getHarnessBehavior(harness).readinessKind === 'codex-tui-prompt') return waitForCodexTuiReady(agentId, timeoutSec); + const readinessKind = + harness === 'codex' && loadYamlConfig().config.codex?.transport !== 'tui' + ? 'codex-app-server-ready' + : getHarnessBehavior(harness).readinessKind; + if (readinessKind === 'codex-app-server-ready') { + await waitForCodexAppServerReady(agentId, timeoutSec); + return true; + } + if (readinessKind === 'codex-tui-prompt') return waitForCodexTuiReady(agentId, timeoutSec); return waitForReadySignal(agentId, timeoutSec); } diff --git a/src/lib/agents/spawn.ts b/src/lib/agents/spawn.ts index c77af64cc7..3275745d2b 100644 --- a/src/lib/agents/spawn.ts +++ b/src/lib/agents/spawn.ts @@ -8,7 +8,7 @@ import { dirname, join, resolve } from 'path'; import { Effect } from 'effect'; import { emitActivityEntrySync, emitActivityTtsSync } from '../activity-logger.js'; import { BLANKED_PROVIDER_ENV } from '../child-env.js'; -import { isTldrEnabledSync } from '../config-yaml.js'; +import { isTldrEnabledSync, loadConfigSync } from '../config-yaml.js'; import { createConversation, getConversationByName, reactivateConversationForSpawn } from '../overdeck/conversations.js'; import { startWorkSync } from '../cv.js'; import { generateFixedPointPromptSync, checkHookSync, initHookSync } from '../hooks.js'; @@ -19,6 +19,7 @@ import { resolveHarness } from '../harness-resolve.js'; import { assertCodexNativeAuthForSpawn } from '../codex-auth.js'; import type { ModelId } from '../settings.js'; import type { RuntimeName } from '../runtimes/types.js'; +import { getHarnessBehavior } from '../runtimes/behavior.js'; import { writeBridgeTokenSync } from '../bridge-token.js'; import { createSession, exactPaneTarget, sessionExists, setOption } from '../tmux.js'; import { createActiveSlice } from '../vbrief/dag.js'; @@ -787,7 +788,11 @@ export async function spawnAgent(options: SpawnOptions): Promise { // (PAN-1805). Non-blocking — codex writes its rollout only after the kickoff // prompt lands, so a blocking wait here would stall spawn. The latest-rollout // fallback covers sessions whose first turn lands after this window. - if (resolvedHarness === 'codex') { + if ( + resolvedHarness === 'codex' + && loadConfigSync().config.codex?.transport === 'tui' + && getHarnessBehavior(resolvedHarness).readinessKind === 'codex-tui-prompt' + ) { const codexHomeForAgent = join(homedir(), '.overdeck', 'agents', agentId, 'codex-home'); void (async () => { try { diff --git a/src/lib/cloister/verification-runner.ts b/src/lib/cloister/verification-runner.ts index 006f846c81..b4b3973d4f 100644 --- a/src/lib/cloister/verification-runner.ts +++ b/src/lib/cloister/verification-runner.ts @@ -20,6 +20,11 @@ import { runQualityGates, DEFAULT_GATES } from './validation.js'; import { readVerificationArtifact, writeVerificationArtifact } from './verification-artifact.js'; import { buildFinalFailureInstructions } from './verification-feedback.js'; import { isVerificationWorkerActive, runSupervisedVerification } from './verification-worker-supervisor.js'; +import type { + VerificationRunnerOptions, + VerificationRunnerOutcome, + WorkspaceInfo, +} from './verification-types.js'; import { readReviewStatusMap } from './review-status-source.js'; import { writeFeedbackFile } from './feedback-writer.js'; import { resolveIssueFeedbackTarget, surfaceIssueFeedbackNeedsYou } from './feedback-target.js'; @@ -29,20 +34,13 @@ import { getVBriefACStatusSync } from '../vbrief/acceptance-criteria.js'; import { VBriefMergeConflictError } from '../vbrief/io.js'; import { checkIncompletePlanItemsPromise } from '../work/done-preflight.js'; import type { TemplatePlaceholders } from '../workspace-config.js'; -import type { VerificationRunnerOutcome, WorkspaceInfo } from './verification-types.js'; - -export type { VerificationRunnerOutcome, WorkspaceInfo } from './verification-types.js'; const execAsync = promisify(exec); export const VERIFICATION_MAX_CYCLES = 3; const NO_PROGRESS_REPEAT_THRESHOLD = 2; -export interface VerificationRunnerOptions { - syncTargetBranch?: boolean; - /** PAN-2487: receives human-readable gate progress lines (ship-log mirror). */ - onGateLog?: (line: string) => void; -} +export type { VerificationRunnerOptions, VerificationRunnerOutcome, WorkspaceInfo } from './verification-types.js'; interface SyncResult { repoDir: string; diff --git a/src/lib/cloister/verification-types.ts b/src/lib/cloister/verification-types.ts index ddc405a9db..bc76f96f53 100644 --- a/src/lib/cloister/verification-types.ts +++ b/src/lib/cloister/verification-types.ts @@ -9,3 +9,9 @@ export interface WorkspaceInfo { isRemote: boolean; vmName?: string; } + +export interface VerificationRunnerOptions { + syncTargetBranch?: boolean; + /** PAN-2487: receives human-readable gate progress lines (ship-log mirror). */ + onGateLog?: (line: string) => void; +} diff --git a/src/lib/codex/__fixtures__/app-server-schema.json b/src/lib/codex/__fixtures__/app-server-schema.json new file mode 100644 index 0000000000..368a74af85 --- /dev/null +++ b/src/lib/codex/__fixtures__/app-server-schema.json @@ -0,0 +1,21034 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CodexAppServerProtocol", + "type": "object", + "definitions": { + "RequestId": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RequestId", + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64" + } + ] + }, + "JSONRPCError": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSONRPCError", + "description": "A response to a request that indicates an error occurred.", + "type": "object", + "required": [ + "error", + "id" + ], + "properties": { + "error": { + "$ref": "#/definitions/JSONRPCErrorError" + }, + "id": { + "$ref": "#/definitions/v2/RequestId" + } + } + }, + "JSONRPCErrorError": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSONRPCErrorError", + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int64" + }, + "data": true, + "message": { + "type": "string" + } + } + }, + "JSONRPCNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSONRPCNotification", + "description": "A notification which does not expect a response.", + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string" + }, + "params": true + } + }, + "JSONRPCRequest": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSONRPCRequest", + "description": "A request that expects a response.", + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string" + }, + "params": true, + "trace": { + "description": "Optional W3C Trace Context for distributed tracing.", + "anyOf": [ + { + "$ref": "#/definitions/W3cTraceContext" + }, + { + "type": "null" + } + ] + } + } + }, + "JSONRPCResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSONRPCResponse", + "description": "A successful (non-error) response to a request.", + "type": "object", + "required": [ + "id", + "result" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "result": true + } + }, + "W3cTraceContext": { + "type": "object", + "properties": { + "traceparent": { + "type": [ + "string", + "null" + ] + }, + "tracestate": { + "type": [ + "string", + "null" + ] + } + } + }, + "JSONRPCMessage": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSONRPCMessage", + "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.", + "anyOf": [ + { + "$ref": "#/definitions/JSONRPCRequest" + }, + { + "$ref": "#/definitions/JSONRPCNotification" + }, + { + "$ref": "#/definitions/JSONRPCResponse" + }, + { + "$ref": "#/definitions/JSONRPCError" + } + ] + }, + "ClientInfo": { + "type": "object", + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": "string" + } + } + }, + "FuzzyFileSearchParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FuzzyFileSearchParams", + "type": "object", + "required": [ + "query", + "roots" + ], + "properties": { + "cancellationToken": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": "string" + }, + "roots": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ExecCommandApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecCommandApprovalResponse", + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "$ref": "#/definitions/ReviewDecision" + } + } + }, + "ApplyPatchApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApplyPatchApprovalResponse", + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "$ref": "#/definitions/ReviewDecision" + } + } + }, + "ReviewDecision": { + "description": "User's decision in response to an ExecApprovalRequest.", + "oneOf": [ + { + "description": "User has approved this command and the agent should execute it.", + "type": "string", + "enum": [ + "approved" + ] + }, + { + "description": "User has approved this command and wants to apply the proposed execpolicy amendment so future matching commands are permitted.", + "type": "object", + "required": [ + "approved_execpolicy_amendment" + ], + "properties": { + "approved_execpolicy_amendment": { + "type": "object", + "required": [ + "proposed_execpolicy_amendment" + ], + "properties": { + "proposed_execpolicy_amendment": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false, + "title": "ApprovedExecpolicyAmendmentReviewDecision" + }, + { + "description": "User has approved this request and wants future prompts in the same session-scoped approval cache to be automatically approved for the remainder of the session.", + "type": "string", + "enum": [ + "approved_for_session" + ] + }, + { + "description": "User chose to persist a network policy rule (allow/deny) for future requests to the same host.", + "type": "object", + "required": [ + "network_policy_amendment" + ], + "properties": { + "network_policy_amendment": { + "type": "object", + "required": [ + "network_policy_amendment" + ], + "properties": { + "network_policy_amendment": { + "$ref": "#/definitions/NetworkPolicyAmendment" + } + } + } + }, + "additionalProperties": false, + "title": "NetworkPolicyAmendmentReviewDecision" + }, + { + "description": "User has denied this command and the agent should not execute it, but it should continue the session and try something else.", + "type": "string", + "enum": [ + "denied" + ] + }, + { + "description": "Automatic approval review timed out before reaching a decision.", + "type": "string", + "enum": [ + "timed_out" + ] + }, + { + "description": "User has denied this command and the agent should not do anything until the user's next command.", + "type": "string", + "enum": [ + "abort" + ] + } + ] + }, + "InitializeCapabilities": { + "description": "Client-declared capabilities negotiated during initialize.", + "type": "object", + "properties": { + "experimentalApi": { + "description": "Opt into receiving experimental API methods and fields.", + "default": false, + "type": "boolean" + }, + "mcpServerOpenaiFormElicitation": { + "description": "Allow downstream MCP servers to request OpenAI extended form elicitations.", + "type": "boolean" + }, + "optOutNotificationMethods": { + "description": "Exact notification method names that should be suppressed for this connection (for example `thread/started`).", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "requestAttestation": { + "description": "Opt into `attestation/generate` requests for upstream `x-oai-attestation`.", + "default": false, + "type": "boolean" + } + } + }, + "InitializeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InitializeParams", + "type": "object", + "required": [ + "clientInfo" + ], + "properties": { + "capabilities": { + "anyOf": [ + { + "$ref": "#/definitions/InitializeCapabilities" + }, + { + "type": "null" + } + ] + }, + "clientInfo": { + "$ref": "#/definitions/ClientInfo" + } + } + }, + "ClientRequest": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ClientRequest", + "description": "Request from the client to the server.", + "oneOf": [ + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "initialize" + ], + "title": "InitializeRequestMethod" + }, + "params": { + "$ref": "#/definitions/InitializeParams" + } + }, + "title": "InitializeRequest" + }, + { + "description": "NEW APIs", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/start" + ], + "title": "Thread/startRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadStartParams" + } + }, + "title": "Thread/startRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/resume" + ], + "title": "Thread/resumeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadResumeParams" + } + }, + "title": "Thread/resumeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/fork" + ], + "title": "Thread/forkRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadForkParams" + } + }, + "title": "Thread/forkRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/archive" + ], + "title": "Thread/archiveRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadArchiveParams" + } + }, + "title": "Thread/archiveRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/delete" + ], + "title": "Thread/deleteRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadDeleteParams" + } + }, + "title": "Thread/deleteRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/unsubscribe" + ], + "title": "Thread/unsubscribeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadUnsubscribeParams" + } + }, + "title": "Thread/unsubscribeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/name/set" + ], + "title": "Thread/name/setRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadSetNameParams" + } + }, + "title": "Thread/name/setRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/goal/set" + ], + "title": "Thread/goal/setRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadGoalSetParams" + } + }, + "title": "Thread/goal/setRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/goal/get" + ], + "title": "Thread/goal/getRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadGoalGetParams" + } + }, + "title": "Thread/goal/getRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/goal/clear" + ], + "title": "Thread/goal/clearRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadGoalClearParams" + } + }, + "title": "Thread/goal/clearRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/metadata/update" + ], + "title": "Thread/metadata/updateRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadMetadataUpdateParams" + } + }, + "title": "Thread/metadata/updateRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/unarchive" + ], + "title": "Thread/unarchiveRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadUnarchiveParams" + } + }, + "title": "Thread/unarchiveRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/compact/start" + ], + "title": "Thread/compact/startRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadCompactStartParams" + } + }, + "title": "Thread/compact/startRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/shellCommand" + ], + "title": "Thread/shellCommandRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadShellCommandParams" + } + }, + "title": "Thread/shellCommandRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/approveGuardianDeniedAction" + ], + "title": "Thread/approveGuardianDeniedActionRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadApproveGuardianDeniedActionParams" + } + }, + "title": "Thread/approveGuardianDeniedActionRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/rollback" + ], + "title": "Thread/rollbackRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRollbackParams" + } + }, + "title": "Thread/rollbackRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/list" + ], + "title": "Thread/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadListParams" + } + }, + "title": "Thread/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/loaded/list" + ], + "title": "Thread/loaded/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadLoadedListParams" + } + }, + "title": "Thread/loaded/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/read" + ], + "title": "Thread/readRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadReadParams" + } + }, + "title": "Thread/readRequest" + }, + { + "description": "Append raw Responses API items to the thread history without starting a user turn.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "thread/inject_items" + ], + "title": "Thread/injectItemsRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadInjectItemsParams" + } + }, + "title": "Thread/injectItemsRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "skills/list" + ], + "title": "Skills/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/SkillsListParams" + } + }, + "title": "Skills/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "skills/extraRoots/set" + ], + "title": "Skills/extraRoots/setRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/SkillsExtraRootsSetParams" + } + }, + "title": "Skills/extraRoots/setRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "hooks/list" + ], + "title": "Hooks/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/HooksListParams" + } + }, + "title": "Hooks/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "marketplace/add" + ], + "title": "Marketplace/addRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/MarketplaceAddParams" + } + }, + "title": "Marketplace/addRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "marketplace/remove" + ], + "title": "Marketplace/removeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/MarketplaceRemoveParams" + } + }, + "title": "Marketplace/removeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "marketplace/upgrade" + ], + "title": "Marketplace/upgradeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/MarketplaceUpgradeParams" + } + }, + "title": "Marketplace/upgradeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/list" + ], + "title": "Plugin/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginListParams" + } + }, + "title": "Plugin/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/installed" + ], + "title": "Plugin/installedRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginInstalledParams" + } + }, + "title": "Plugin/installedRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/read" + ], + "title": "Plugin/readRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginReadParams" + } + }, + "title": "Plugin/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/skill/read" + ], + "title": "Plugin/skill/readRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginSkillReadParams" + } + }, + "title": "Plugin/skill/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/share/save" + ], + "title": "Plugin/share/saveRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginShareSaveParams" + } + }, + "title": "Plugin/share/saveRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/share/updateTargets" + ], + "title": "Plugin/share/updateTargetsRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginShareUpdateTargetsParams" + } + }, + "title": "Plugin/share/updateTargetsRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/share/list" + ], + "title": "Plugin/share/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginShareListParams" + } + }, + "title": "Plugin/share/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/share/checkout" + ], + "title": "Plugin/share/checkoutRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginShareCheckoutParams" + } + }, + "title": "Plugin/share/checkoutRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/share/delete" + ], + "title": "Plugin/share/deleteRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginShareDeleteParams" + } + }, + "title": "Plugin/share/deleteRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "app/list" + ], + "title": "App/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/AppsListParams" + } + }, + "title": "App/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/readFile" + ], + "title": "Fs/readFileRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsReadFileParams" + } + }, + "title": "Fs/readFileRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/writeFile" + ], + "title": "Fs/writeFileRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsWriteFileParams" + } + }, + "title": "Fs/writeFileRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/createDirectory" + ], + "title": "Fs/createDirectoryRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsCreateDirectoryParams" + } + }, + "title": "Fs/createDirectoryRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/getMetadata" + ], + "title": "Fs/getMetadataRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsGetMetadataParams" + } + }, + "title": "Fs/getMetadataRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/readDirectory" + ], + "title": "Fs/readDirectoryRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsReadDirectoryParams" + } + }, + "title": "Fs/readDirectoryRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/remove" + ], + "title": "Fs/removeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsRemoveParams" + } + }, + "title": "Fs/removeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/copy" + ], + "title": "Fs/copyRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsCopyParams" + } + }, + "title": "Fs/copyRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/watch" + ], + "title": "Fs/watchRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsWatchParams" + } + }, + "title": "Fs/watchRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fs/unwatch" + ], + "title": "Fs/unwatchRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsUnwatchParams" + } + }, + "title": "Fs/unwatchRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "skills/config/write" + ], + "title": "Skills/config/writeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/SkillsConfigWriteParams" + } + }, + "title": "Skills/config/writeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/install" + ], + "title": "Plugin/installRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginInstallParams" + } + }, + "title": "Plugin/installRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "plugin/uninstall" + ], + "title": "Plugin/uninstallRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PluginUninstallParams" + } + }, + "title": "Plugin/uninstallRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "turn/start" + ], + "title": "Turn/startRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/TurnStartParams" + } + }, + "title": "Turn/startRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "turn/steer" + ], + "title": "Turn/steerRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/TurnSteerParams" + } + }, + "title": "Turn/steerRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "turn/interrupt" + ], + "title": "Turn/interruptRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/TurnInterruptParams" + } + }, + "title": "Turn/interruptRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "review/start" + ], + "title": "Review/startRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ReviewStartParams" + } + }, + "title": "Review/startRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "model/list" + ], + "title": "Model/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ModelListParams" + } + }, + "title": "Model/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "modelProvider/capabilities/read" + ], + "title": "ModelProvider/capabilities/readRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ModelProviderCapabilitiesReadParams" + } + }, + "title": "ModelProvider/capabilities/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "experimentalFeature/list" + ], + "title": "ExperimentalFeature/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ExperimentalFeatureListParams" + } + }, + "title": "ExperimentalFeature/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "permissionProfile/list" + ], + "title": "PermissionProfile/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/PermissionProfileListParams" + } + }, + "title": "PermissionProfile/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "experimentalFeature/enablement/set" + ], + "title": "ExperimentalFeature/enablement/setRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ExperimentalFeatureEnablementSetParams" + } + }, + "title": "ExperimentalFeature/enablement/setRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "mcpServer/oauth/login" + ], + "title": "McpServer/oauth/loginRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/McpServerOauthLoginParams" + } + }, + "title": "McpServer/oauth/loginRequest" + }, + { + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "config/mcpServer/reload" + ], + "title": "Config/mcpServer/reloadRequestMethod" + }, + "params": { + "type": "null" + } + }, + "title": "Config/mcpServer/reloadRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "mcpServerStatus/list" + ], + "title": "McpServerStatus/listRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ListMcpServerStatusParams" + } + }, + "title": "McpServerStatus/listRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "mcpServer/resource/read" + ], + "title": "McpServer/resource/readRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/McpResourceReadParams" + } + }, + "title": "McpServer/resource/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "mcpServer/tool/call" + ], + "title": "McpServer/tool/callRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/McpServerToolCallParams" + } + }, + "title": "McpServer/tool/callRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "windowsSandbox/setupStart" + ], + "title": "WindowsSandbox/setupStartRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/WindowsSandboxSetupStartParams" + } + }, + "title": "WindowsSandbox/setupStartRequest" + }, + { + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "windowsSandbox/readiness" + ], + "title": "WindowsSandbox/readinessRequestMethod" + }, + "params": { + "type": "null" + } + }, + "title": "WindowsSandbox/readinessRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/login/start" + ], + "title": "Account/login/startRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/LoginAccountParams" + } + }, + "title": "Account/login/startRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/login/cancel" + ], + "title": "Account/login/cancelRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/CancelLoginAccountParams" + } + }, + "title": "Account/login/cancelRequest" + }, + { + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/logout" + ], + "title": "Account/logoutRequestMethod" + }, + "params": { + "type": "null" + } + }, + "title": "Account/logoutRequest" + }, + { + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/rateLimits/read" + ], + "title": "Account/rateLimits/readRequestMethod" + }, + "params": { + "type": "null" + } + }, + "title": "Account/rateLimits/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/rateLimitResetCredit/consume" + ], + "title": "Account/rateLimitResetCredit/consumeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ConsumeAccountRateLimitResetCreditParams" + } + }, + "title": "Account/rateLimitResetCredit/consumeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/usage/read" + ], + "title": "Account/usage/readRequestMethod" + }, + "params": { + "type": "null" + } + }, + "title": "Account/usage/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/workspaceMessages/read" + ], + "title": "Account/workspaceMessages/readRequestMethod" + }, + "params": { + "type": "null" + } + }, + "title": "Account/workspaceMessages/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/sendAddCreditsNudgeEmail" + ], + "title": "Account/sendAddCreditsNudgeEmailRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/SendAddCreditsNudgeEmailParams" + } + }, + "title": "Account/sendAddCreditsNudgeEmailRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "feedback/upload" + ], + "title": "Feedback/uploadRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/FeedbackUploadParams" + } + }, + "title": "Feedback/uploadRequest" + }, + { + "description": "Execute a standalone command (argv vector) under the server's sandbox.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "command/exec" + ], + "title": "Command/execRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/CommandExecParams" + } + }, + "title": "Command/execRequest" + }, + { + "description": "Write stdin bytes to a running `command/exec` session or close stdin.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "command/exec/write" + ], + "title": "Command/exec/writeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/CommandExecWriteParams" + } + }, + "title": "Command/exec/writeRequest" + }, + { + "description": "Terminate a running `command/exec` session by client-supplied `processId`.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "command/exec/terminate" + ], + "title": "Command/exec/terminateRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/CommandExecTerminateParams" + } + }, + "title": "Command/exec/terminateRequest" + }, + { + "description": "Resize a running PTY-backed `command/exec` session by client-supplied `processId`.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "command/exec/resize" + ], + "title": "Command/exec/resizeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/CommandExecResizeParams" + } + }, + "title": "Command/exec/resizeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "config/read" + ], + "title": "Config/readRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ConfigReadParams" + } + }, + "title": "Config/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "externalAgentConfig/detect" + ], + "title": "ExternalAgentConfig/detectRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ExternalAgentConfigDetectParams" + } + }, + "title": "ExternalAgentConfig/detectRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "externalAgentConfig/import" + ], + "title": "ExternalAgentConfig/importRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportParams" + } + }, + "title": "ExternalAgentConfig/importRequest" + }, + { + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "externalAgentConfig/import/readHistories" + ], + "title": "ExternalAgentConfig/import/readHistoriesRequestMethod" + }, + "params": { + "type": "null" + } + }, + "title": "ExternalAgentConfig/import/readHistoriesRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "config/value/write" + ], + "title": "Config/value/writeRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ConfigValueWriteParams" + } + }, + "title": "Config/value/writeRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "config/batchWrite" + ], + "title": "Config/batchWriteRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/ConfigBatchWriteParams" + } + }, + "title": "Config/batchWriteRequest" + }, + { + "type": "object", + "required": [ + "id", + "method" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "configRequirements/read" + ], + "title": "ConfigRequirements/readRequestMethod" + }, + "params": { + "type": "null" + } + }, + "title": "ConfigRequirements/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/read" + ], + "title": "Account/readRequestMethod" + }, + "params": { + "$ref": "#/definitions/v2/GetAccountParams" + } + }, + "title": "Account/readRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "fuzzyFileSearch" + ], + "title": "FuzzyFileSearchRequestMethod" + }, + "params": { + "$ref": "#/definitions/FuzzyFileSearchParams" + } + }, + "title": "FuzzyFileSearchRequest" + } + ] + }, + "AdditionalPermissionProfile": { + "type": "object", + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AdditionalFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "description": "Partial overlay used for per-command permission requests.", + "anyOf": [ + { + "$ref": "#/definitions/v2/AdditionalNetworkPermissions" + }, + { + "type": "null" + } + ] + } + } + }, + "ApplyPatchApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ApplyPatchApprovalParams", + "type": "object", + "required": [ + "callId", + "conversationId", + "fileChanges" + ], + "properties": { + "callId": { + "description": "Use to correlate this with [codex_protocol::protocol::PatchApplyBeginEvent] and [codex_protocol::protocol::PatchApplyEndEvent].", + "type": "string" + }, + "conversationId": { + "$ref": "#/definitions/v2/ThreadId" + }, + "fileChanges": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/FileChange" + } + }, + "grantRoot": { + "description": "When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + "type": [ + "string", + "null" + ] + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + } + } + }, + "AttestationGenerateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AttestationGenerateParams", + "type": "object" + }, + "ChatgptAuthTokensRefreshParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ChatgptAuthTokensRefreshParams", + "type": "object", + "required": [ + "reason" + ], + "properties": { + "previousAccountId": { + "description": "Workspace/account identifier that Codex was previously using.\n\nClients that manage multiple accounts/workspaces can use this as a hint to refresh the token for the correct workspace.\n\nThis may be `null` when the prior auth state did not include a workspace identifier (`chatgpt_account_id`).", + "type": [ + "string", + "null" + ] + }, + "reason": { + "$ref": "#/definitions/ChatgptAuthTokensRefreshReason" + } + } + }, + "ChatgptAuthTokensRefreshReason": { + "oneOf": [ + { + "description": "Codex attempted a backend request and received `401 Unauthorized`.", + "type": "string", + "enum": [ + "unauthorized" + ] + } + ] + }, + "CommandExecutionApprovalDecision": { + "oneOf": [ + { + "description": "User approved the command.", + "type": "string", + "enum": [ + "accept" + ] + }, + { + "description": "User approved the command and future prompts in the same session-scoped approval cache should run without prompting.", + "type": "string", + "enum": [ + "acceptForSession" + ] + }, + { + "description": "User approved the command, and wants to apply the proposed execpolicy amendment so future matching commands can run without prompting.", + "type": "object", + "required": [ + "acceptWithExecpolicyAmendment" + ], + "properties": { + "acceptWithExecpolicyAmendment": { + "type": "object", + "required": [ + "execpolicy_amendment" + ], + "properties": { + "execpolicy_amendment": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "additionalProperties": false, + "title": "AcceptWithExecpolicyAmendmentCommandExecutionApprovalDecision" + }, + { + "description": "User chose a persistent network policy rule (allow/deny) for this host.", + "type": "object", + "required": [ + "applyNetworkPolicyAmendment" + ], + "properties": { + "applyNetworkPolicyAmendment": { + "type": "object", + "required": [ + "network_policy_amendment" + ], + "properties": { + "network_policy_amendment": { + "$ref": "#/definitions/NetworkPolicyAmendment" + } + } + } + }, + "additionalProperties": false, + "title": "ApplyNetworkPolicyAmendmentCommandExecutionApprovalDecision" + }, + { + "description": "User denied the command. The agent will continue the turn.", + "type": "string", + "enum": [ + "decline" + ] + }, + { + "description": "User denied the command. The turn will also be immediately interrupted.", + "type": "string", + "enum": [ + "cancel" + ] + } + ] + }, + "CommandExecutionRequestApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecutionRequestApprovalParams", + "type": "object", + "required": [ + "itemId", + "startedAtMs", + "threadId", + "turnId" + ], + "properties": { + "turnId": { + "type": "string" + }, + "approvalId": { + "description": "Unique identifier for this specific approval callback.\n\nFor regular shell/unified_exec approvals, this is null.\n\nFor zsh-exec-bridge subcommand approvals, multiple callbacks can belong to one parent `itemId`, so `approvalId` is a distinct opaque callback id (a UUID) used to disambiguate routing.", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "command": { + "description": "The command to be executed.", + "type": [ + "string", + "null" + ] + }, + "commandActions": { + "description": "Best-effort parsed command actions for friendly display.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/CommandAction" + } + }, + "cwd": { + "description": "The command's working directory.", + "anyOf": [ + { + "$ref": "#/definitions/v2/LegacyAppPathString" + }, + { + "type": "null" + } + ] + }, + "environmentId": { + "description": "Environment in which the command will run.", + "default": null, + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "networkApprovalContext": { + "description": "Optional context for a managed-network approval prompt.", + "anyOf": [ + { + "$ref": "#/definitions/NetworkApprovalContext" + }, + { + "type": "null" + } + ] + }, + "proposedExecpolicyAmendment": { + "description": "Optional proposed execpolicy amendment to allow similar commands without prompting.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "proposedNetworkPolicyAmendments": { + "description": "Optional proposed network policy amendments (allow/deny host) for future requests.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/NetworkPolicyAmendment" + } + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for network access).", + "type": [ + "string", + "null" + ] + }, + "startedAtMs": { + "description": "Unix timestamp (in milliseconds) when this approval request started.", + "type": "integer", + "format": "int64" + } + } + }, + "PermissionsRequestApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionsRequestApprovalResponse", + "type": "object", + "required": [ + "permissions" + ], + "properties": { + "permissions": { + "$ref": "#/definitions/GrantedPermissionProfile" + }, + "scope": { + "default": "turn", + "allOf": [ + { + "$ref": "#/definitions/PermissionGrantScope" + } + ] + }, + "strictAutoReview": { + "description": "Review every subsequent command in this turn before normal sandboxed execution.", + "type": [ + "boolean", + "null" + ] + } + } + }, + "DynamicToolCallParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DynamicToolCallParams", + "type": "object", + "required": [ + "arguments", + "callId", + "threadId", + "tool", + "turnId" + ], + "properties": { + "arguments": true, + "callId": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "tool": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ExecCommandApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecCommandApprovalParams", + "type": "object", + "required": [ + "callId", + "command", + "conversationId", + "cwd", + "parsedCmd" + ], + "properties": { + "approvalId": { + "description": "Identifier for this specific approval callback.", + "type": [ + "string", + "null" + ] + }, + "callId": { + "description": "Use to correlate this with [codex_protocol::protocol::ExecCommandBeginEvent] and [codex_protocol::protocol::ExecCommandEndEvent].", + "type": "string" + }, + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "conversationId": { + "$ref": "#/definitions/v2/ThreadId" + }, + "cwd": { + "type": "string" + }, + "parsedCmd": { + "type": "array", + "items": { + "$ref": "#/definitions/ParsedCommand" + } + }, + "reason": { + "type": [ + "string", + "null" + ] + } + } + }, + "FileChange": { + "oneOf": [ + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "add" + ], + "title": "AddFileChangeType" + } + }, + "title": "AddFileChange" + }, + { + "type": "object", + "required": [ + "content", + "type" + ], + "properties": { + "content": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "delete" + ], + "title": "DeleteFileChangeType" + } + }, + "title": "DeleteFileChange" + }, + { + "type": "object", + "required": [ + "type", + "unified_diff" + ], + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "update" + ], + "title": "UpdateFileChangeType" + }, + "unified_diff": { + "type": "string" + } + }, + "title": "UpdateFileChange" + } + ] + }, + "FileChangeRequestApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FileChangeRequestApprovalParams", + "type": "object", + "required": [ + "itemId", + "startedAtMs", + "threadId", + "turnId" + ], + "properties": { + "grantRoot": { + "description": "[UNSTABLE] When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "reason": { + "description": "Optional explanatory reason (e.g. request for extra write access).", + "type": [ + "string", + "null" + ] + }, + "startedAtMs": { + "description": "Unix timestamp (in milliseconds) when this approval request started.", + "type": "integer", + "format": "int64" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "McpElicitationArrayType": { + "type": "string", + "enum": [ + "array" + ] + }, + "McpElicitationBooleanSchema": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "default": { + "type": [ + "boolean", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/definitions/McpElicitationBooleanType" + } + }, + "additionalProperties": false + }, + "McpElicitationBooleanType": { + "type": "string", + "enum": [ + "boolean" + ] + }, + "McpElicitationConstOption": { + "type": "object", + "required": [ + "const", + "title" + ], + "properties": { + "const": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "McpElicitationEnumSchema": { + "anyOf": [ + { + "$ref": "#/definitions/McpElicitationSingleSelectEnumSchema" + }, + { + "$ref": "#/definitions/McpElicitationMultiSelectEnumSchema" + }, + { + "$ref": "#/definitions/McpElicitationLegacyTitledEnumSchema" + } + ] + }, + "McpElicitationLegacyTitledEnumSchema": { + "type": "object", + "required": [ + "enum", + "type" + ], + "properties": { + "default": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "enumNames": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/definitions/McpElicitationStringType" + } + }, + "additionalProperties": false + }, + "McpElicitationMultiSelectEnumSchema": { + "anyOf": [ + { + "$ref": "#/definitions/McpElicitationUntitledMultiSelectEnumSchema" + }, + { + "$ref": "#/definitions/McpElicitationTitledMultiSelectEnumSchema" + } + ] + }, + "McpElicitationNumberSchema": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "default": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "maximum": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "minimum": { + "type": [ + "number", + "null" + ], + "format": "double" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/definitions/McpElicitationNumberType" + } + }, + "additionalProperties": false + }, + "McpElicitationNumberType": { + "type": "string", + "enum": [ + "number", + "integer" + ] + }, + "McpElicitationObjectType": { + "type": "string", + "enum": [ + "object" + ] + }, + "McpElicitationPrimitiveSchema": { + "anyOf": [ + { + "$ref": "#/definitions/McpElicitationEnumSchema" + }, + { + "$ref": "#/definitions/McpElicitationStringSchema" + }, + { + "$ref": "#/definitions/McpElicitationNumberSchema" + }, + { + "$ref": "#/definitions/McpElicitationBooleanSchema" + } + ] + }, + "McpElicitationSchema": { + "description": "Typed form schema for MCP `elicitation/create` requests.\n\nThis matches the `requestedSchema` shape from the MCP 2025-11-25 `ElicitRequestFormParams` schema.", + "type": "object", + "required": [ + "properties", + "type" + ], + "properties": { + "$schema": { + "type": [ + "string", + "null" + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/McpElicitationPrimitiveSchema" + } + }, + "required": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "type": { + "$ref": "#/definitions/McpElicitationObjectType" + } + }, + "additionalProperties": false + }, + "McpElicitationSingleSelectEnumSchema": { + "anyOf": [ + { + "$ref": "#/definitions/McpElicitationUntitledSingleSelectEnumSchema" + }, + { + "$ref": "#/definitions/McpElicitationTitledSingleSelectEnumSchema" + } + ] + }, + "McpElicitationStringFormat": { + "type": "string", + "enum": [ + "email", + "uri", + "date", + "date-time" + ] + }, + "McpElicitationStringSchema": { + "type": "object", + "required": [ + "type" + ], + "properties": { + "default": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "format": { + "anyOf": [ + { + "$ref": "#/definitions/McpElicitationStringFormat" + }, + { + "type": "null" + } + ] + }, + "maxLength": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "minLength": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/definitions/McpElicitationStringType" + } + }, + "additionalProperties": false + }, + "McpElicitationStringType": { + "type": "string", + "enum": [ + "string" + ] + }, + "McpElicitationTitledEnumItems": { + "type": "object", + "required": [ + "anyOf" + ], + "properties": { + "anyOf": { + "type": "array", + "items": { + "$ref": "#/definitions/McpElicitationConstOption" + } + } + }, + "additionalProperties": false + }, + "McpElicitationTitledMultiSelectEnumSchema": { + "type": "object", + "required": [ + "items", + "type" + ], + "properties": { + "default": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "items": { + "$ref": "#/definitions/McpElicitationTitledEnumItems" + }, + "maxItems": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "minItems": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/definitions/McpElicitationArrayType" + } + }, + "additionalProperties": false + }, + "McpElicitationTitledSingleSelectEnumSchema": { + "type": "object", + "required": [ + "oneOf", + "type" + ], + "properties": { + "default": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "oneOf": { + "type": "array", + "items": { + "$ref": "#/definitions/McpElicitationConstOption" + } + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/definitions/McpElicitationStringType" + } + }, + "additionalProperties": false + }, + "McpElicitationUntitledEnumItems": { + "type": "object", + "required": [ + "enum", + "type" + ], + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "$ref": "#/definitions/McpElicitationStringType" + } + }, + "additionalProperties": false + }, + "McpElicitationUntitledMultiSelectEnumSchema": { + "type": "object", + "required": [ + "items", + "type" + ], + "properties": { + "default": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "items": { + "$ref": "#/definitions/McpElicitationUntitledEnumItems" + }, + "maxItems": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "minItems": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/definitions/McpElicitationArrayType" + } + }, + "additionalProperties": false + }, + "McpElicitationUntitledSingleSelectEnumSchema": { + "type": "object", + "required": [ + "enum", + "type" + ], + "properties": { + "default": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "type": { + "$ref": "#/definitions/McpElicitationStringType" + } + }, + "additionalProperties": false + }, + "McpServerElicitationRequestParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerElicitationRequestParams", + "type": "object", + "oneOf": [ + { + "type": "object", + "required": [ + "message", + "mode", + "requestedSchema" + ], + "properties": { + "_meta": true, + "message": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "form" + ] + }, + "requestedSchema": { + "$ref": "#/definitions/McpElicitationSchema" + } + } + }, + { + "type": "object", + "required": [ + "message", + "mode", + "requestedSchema" + ], + "properties": { + "_meta": true, + "message": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "openai/form" + ] + }, + "requestedSchema": true + } + }, + { + "type": "object", + "required": [ + "elicitationId", + "message", + "mode", + "url" + ], + "properties": { + "_meta": true, + "elicitationId": { + "type": "string" + }, + "message": { + "type": "string" + }, + "mode": { + "type": "string", + "enum": [ + "url" + ] + }, + "url": { + "type": "string" + } + } + } + ], + "required": [ + "serverName", + "threadId" + ], + "properties": { + "serverName": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "description": "Active Codex turn when this elicitation was observed, if app-server could correlate one.\n\nThis is nullable because MCP models elicitation as a standalone server-to-client request identified by the MCP server request id. It may be triggered during a turn, but turn context is app-server correlation rather than part of the protocol identity of the elicitation itself.", + "type": [ + "string", + "null" + ] + } + } + }, + "NetworkApprovalContext": { + "type": "object", + "required": [ + "host", + "protocol" + ], + "properties": { + "host": { + "type": "string" + }, + "protocol": { + "$ref": "#/definitions/v2/NetworkApprovalProtocol" + } + } + }, + "NetworkPolicyAmendment": { + "type": "object", + "required": [ + "action", + "host" + ], + "properties": { + "action": { + "$ref": "#/definitions/NetworkPolicyRuleAction" + }, + "host": { + "type": "string" + } + } + }, + "NetworkPolicyRuleAction": { + "type": "string", + "enum": [ + "allow", + "deny" + ] + }, + "ParsedCommand": { + "oneOf": [ + { + "type": "object", + "required": [ + "cmd", + "name", + "path", + "type" + ], + "properties": { + "cmd": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "description": "(Best effort) Path to the file being read by the command. When possible, this is an absolute path, though when relative, it should be resolved against the `cwd`` that will be used to run the command to derive the absolute path.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "read" + ], + "title": "ReadParsedCommandType" + } + }, + "title": "ReadParsedCommand" + }, + { + "type": "object", + "required": [ + "cmd", + "type" + ], + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "list_files" + ], + "title": "ListFilesParsedCommandType" + } + }, + "title": "ListFilesParsedCommand" + }, + { + "type": "object", + "required": [ + "cmd", + "type" + ], + "properties": { + "cmd": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "search" + ], + "title": "SearchParsedCommandType" + } + }, + "title": "SearchParsedCommand" + }, + { + "type": "object", + "required": [ + "cmd", + "type" + ], + "properties": { + "cmd": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "unknown" + ], + "title": "UnknownParsedCommandType" + } + }, + "title": "UnknownParsedCommand" + } + ] + }, + "PermissionsRequestApprovalParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionsRequestApprovalParams", + "type": "object", + "required": [ + "cwd", + "itemId", + "permissions", + "startedAtMs", + "threadId", + "turnId" + ], + "properties": { + "cwd": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "environmentId": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "itemId": { + "type": "string" + }, + "permissions": { + "$ref": "#/definitions/v2/RequestPermissionProfile" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "startedAtMs": { + "description": "Unix timestamp (in milliseconds) when this approval request started.", + "type": "integer", + "format": "int64" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ToolRequestUserInputOption": { + "description": "EXPERIMENTAL. Defines a single selectable option for request_user_input.", + "type": "object", + "required": [ + "description", + "label" + ], + "properties": { + "description": { + "type": "string" + }, + "label": { + "type": "string" + } + } + }, + "ToolRequestUserInputParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ToolRequestUserInputParams", + "description": "EXPERIMENTAL. Params sent with a request_user_input event.", + "type": "object", + "required": [ + "itemId", + "questions", + "threadId", + "turnId" + ], + "properties": { + "autoResolutionMs": { + "default": null, + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "itemId": { + "type": "string" + }, + "questions": { + "type": "array", + "items": { + "$ref": "#/definitions/ToolRequestUserInputQuestion" + } + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ToolRequestUserInputQuestion": { + "description": "EXPERIMENTAL. Represents one request_user_input question and its required options.", + "type": "object", + "required": [ + "header", + "id", + "question" + ], + "properties": { + "header": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isOther": { + "default": false, + "type": "boolean" + }, + "isSecret": { + "default": false, + "type": "boolean" + }, + "options": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/ToolRequestUserInputOption" + } + }, + "question": { + "type": "string" + } + } + }, + "ServerRequest": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ServerRequest", + "description": "Request initiated from the server and sent to the client.", + "oneOf": [ + { + "description": "NEW APIs Sent when approval is requested for a specific command execution. This request is used for Turns started via turn/start.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "item/commandExecution/requestApproval" + ], + "title": "Item/commandExecution/requestApprovalRequestMethod" + }, + "params": { + "$ref": "#/definitions/CommandExecutionRequestApprovalParams" + } + }, + "title": "Item/commandExecution/requestApprovalRequest" + }, + { + "description": "Sent when approval is requested for a specific file change. This request is used for Turns started via turn/start.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "item/fileChange/requestApproval" + ], + "title": "Item/fileChange/requestApprovalRequestMethod" + }, + "params": { + "$ref": "#/definitions/FileChangeRequestApprovalParams" + } + }, + "title": "Item/fileChange/requestApprovalRequest" + }, + { + "description": "EXPERIMENTAL - Request input from the user for a tool call.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "item/tool/requestUserInput" + ], + "title": "Item/tool/requestUserInputRequestMethod" + }, + "params": { + "$ref": "#/definitions/ToolRequestUserInputParams" + } + }, + "title": "Item/tool/requestUserInputRequest" + }, + { + "description": "Request input for an MCP server elicitation.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "mcpServer/elicitation/request" + ], + "title": "McpServer/elicitation/requestRequestMethod" + }, + "params": { + "$ref": "#/definitions/McpServerElicitationRequestParams" + } + }, + "title": "McpServer/elicitation/requestRequest" + }, + { + "description": "Request approval for additional permissions from the user.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "item/permissions/requestApproval" + ], + "title": "Item/permissions/requestApprovalRequestMethod" + }, + "params": { + "$ref": "#/definitions/PermissionsRequestApprovalParams" + } + }, + "title": "Item/permissions/requestApprovalRequest" + }, + { + "description": "Execute a dynamic tool call on the client.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "item/tool/call" + ], + "title": "Item/tool/callRequestMethod" + }, + "params": { + "$ref": "#/definitions/DynamicToolCallParams" + } + }, + "title": "Item/tool/callRequest" + }, + { + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "account/chatgptAuthTokens/refresh" + ], + "title": "Account/chatgptAuthTokens/refreshRequestMethod" + }, + "params": { + "$ref": "#/definitions/ChatgptAuthTokensRefreshParams" + } + }, + "title": "Account/chatgptAuthTokens/refreshRequest" + }, + { + "description": "Generate a fresh upstream attestation result on demand.", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "attestation/generate" + ], + "title": "Attestation/generateRequestMethod" + }, + "params": { + "$ref": "#/definitions/AttestationGenerateParams" + } + }, + "title": "Attestation/generateRequest" + }, + { + "description": "DEPRECATED APIs below Request to approve a patch. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "applyPatchApproval" + ], + "title": "ApplyPatchApprovalRequestMethod" + }, + "params": { + "$ref": "#/definitions/ApplyPatchApprovalParams" + } + }, + "title": "ApplyPatchApprovalRequest" + }, + { + "description": "Request to exec a command. This request is used for Turns started via the legacy APIs (i.e. SendUserTurn, SendUserMessage).", + "type": "object", + "required": [ + "id", + "method", + "params" + ], + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "type": "string", + "enum": [ + "execCommandApproval" + ], + "title": "ExecCommandApprovalRequestMethod" + }, + "params": { + "$ref": "#/definitions/ExecCommandApprovalParams" + } + }, + "title": "ExecCommandApprovalRequest" + } + ] + }, + "ClientNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ClientNotification", + "oneOf": [ + { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "initialized" + ], + "title": "InitializedNotificationMethod" + } + }, + "title": "InitializedNotification" + } + ] + }, + "FuzzyFileSearchMatchType": { + "type": "string", + "enum": [ + "file", + "directory" + ] + }, + "FuzzyFileSearchResult": { + "description": "Superset of [`codex_file_search::FileMatch`]", + "type": "object", + "required": [ + "file_name", + "match_type", + "path", + "root", + "score" + ], + "properties": { + "file_name": { + "type": "string" + }, + "indices": { + "type": [ + "array", + "null" + ], + "items": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "match_type": { + "$ref": "#/definitions/FuzzyFileSearchMatchType" + }, + "path": { + "type": "string" + }, + "root": { + "type": "string" + }, + "score": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + "FuzzyFileSearchSessionCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FuzzyFileSearchSessionCompletedNotification", + "type": "object", + "required": [ + "sessionId" + ], + "properties": { + "sessionId": { + "type": "string" + } + } + }, + "FuzzyFileSearchSessionUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FuzzyFileSearchSessionUpdatedNotification", + "type": "object", + "required": [ + "files", + "query", + "sessionId" + ], + "properties": { + "files": { + "type": "array", + "items": { + "$ref": "#/definitions/FuzzyFileSearchResult" + } + }, + "query": { + "type": "string" + }, + "sessionId": { + "type": "string" + } + } + }, + "ServerNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ServerNotification", + "description": "Notification sent from the server to the client.", + "oneOf": [ + { + "description": "NEW NOTIFICATIONS", + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "error" + ], + "title": "ErrorNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ErrorNotification" + } + }, + "title": "ErrorNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/started" + ], + "title": "Thread/startedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadStartedNotification" + } + }, + "title": "Thread/startedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/status/changed" + ], + "title": "Thread/status/changedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadStatusChangedNotification" + } + }, + "title": "Thread/status/changedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/archived" + ], + "title": "Thread/archivedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadArchivedNotification" + } + }, + "title": "Thread/archivedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/deleted" + ], + "title": "Thread/deletedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadDeletedNotification" + } + }, + "title": "Thread/deletedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/unarchived" + ], + "title": "Thread/unarchivedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadUnarchivedNotification" + } + }, + "title": "Thread/unarchivedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/closed" + ], + "title": "Thread/closedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadClosedNotification" + } + }, + "title": "Thread/closedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "skills/changed" + ], + "title": "Skills/changedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/SkillsChangedNotification" + } + }, + "title": "Skills/changedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/name/updated" + ], + "title": "Thread/name/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadNameUpdatedNotification" + } + }, + "title": "Thread/name/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/goal/updated" + ], + "title": "Thread/goal/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadGoalUpdatedNotification" + } + }, + "title": "Thread/goal/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/goal/cleared" + ], + "title": "Thread/goal/clearedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadGoalClearedNotification" + } + }, + "title": "Thread/goal/clearedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/settings/updated" + ], + "title": "Thread/settings/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadSettingsUpdatedNotification" + } + }, + "title": "Thread/settings/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/tokenUsage/updated" + ], + "title": "Thread/tokenUsage/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadTokenUsageUpdatedNotification" + } + }, + "title": "Thread/tokenUsage/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "turn/started" + ], + "title": "Turn/startedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/TurnStartedNotification" + } + }, + "title": "Turn/startedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "hook/started" + ], + "title": "Hook/startedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/HookStartedNotification" + } + }, + "title": "Hook/startedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "turn/completed" + ], + "title": "Turn/completedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/TurnCompletedNotification" + } + }, + "title": "Turn/completedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "hook/completed" + ], + "title": "Hook/completedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/HookCompletedNotification" + } + }, + "title": "Hook/completedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "turn/diff/updated" + ], + "title": "Turn/diff/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/TurnDiffUpdatedNotification" + } + }, + "title": "Turn/diff/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "turn/plan/updated" + ], + "title": "Turn/plan/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/TurnPlanUpdatedNotification" + } + }, + "title": "Turn/plan/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/started" + ], + "title": "Item/startedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ItemStartedNotification" + } + }, + "title": "Item/startedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/autoApprovalReview/started" + ], + "title": "Item/autoApprovalReview/startedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ItemGuardianApprovalReviewStartedNotification" + } + }, + "title": "Item/autoApprovalReview/startedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/autoApprovalReview/completed" + ], + "title": "Item/autoApprovalReview/completedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ItemGuardianApprovalReviewCompletedNotification" + } + }, + "title": "Item/autoApprovalReview/completedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/completed" + ], + "title": "Item/completedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ItemCompletedNotification" + } + }, + "title": "Item/completedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/agentMessage/delta" + ], + "title": "Item/agentMessage/deltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/AgentMessageDeltaNotification" + } + }, + "title": "Item/agentMessage/deltaNotification" + }, + { + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items.", + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/plan/delta" + ], + "title": "Item/plan/deltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/PlanDeltaNotification" + } + }, + "title": "Item/plan/deltaNotification" + }, + { + "description": "Stream base64-encoded stdout/stderr chunks for a running `command/exec` session.", + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "command/exec/outputDelta" + ], + "title": "Command/exec/outputDeltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/CommandExecOutputDeltaNotification" + } + }, + "title": "Command/exec/outputDeltaNotification" + }, + { + "description": "Stream base64-encoded stdout/stderr chunks for a running `process/spawn` session.", + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "process/outputDelta" + ], + "title": "Process/outputDeltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ProcessOutputDeltaNotification" + } + }, + "title": "Process/outputDeltaNotification" + }, + { + "description": "Final exit notification for a `process/spawn` session.", + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "process/exited" + ], + "title": "Process/exitedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ProcessExitedNotification" + } + }, + "title": "Process/exitedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/commandExecution/outputDelta" + ], + "title": "Item/commandExecution/outputDeltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/CommandExecutionOutputDeltaNotification" + } + }, + "title": "Item/commandExecution/outputDeltaNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/commandExecution/terminalInteraction" + ], + "title": "Item/commandExecution/terminalInteractionNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/TerminalInteractionNotification" + } + }, + "title": "Item/commandExecution/terminalInteractionNotification" + }, + { + "description": "Deprecated legacy apply_patch output stream notification.", + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/fileChange/outputDelta" + ], + "title": "Item/fileChange/outputDeltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/FileChangeOutputDeltaNotification" + } + }, + "title": "Item/fileChange/outputDeltaNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/fileChange/patchUpdated" + ], + "title": "Item/fileChange/patchUpdatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/FileChangePatchUpdatedNotification" + } + }, + "title": "Item/fileChange/patchUpdatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "serverRequest/resolved" + ], + "title": "ServerRequest/resolvedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ServerRequestResolvedNotification" + } + }, + "title": "ServerRequest/resolvedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/mcpToolCall/progress" + ], + "title": "Item/mcpToolCall/progressNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/McpToolCallProgressNotification" + } + }, + "title": "Item/mcpToolCall/progressNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "mcpServer/oauthLogin/completed" + ], + "title": "McpServer/oauthLogin/completedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/McpServerOauthLoginCompletedNotification" + } + }, + "title": "McpServer/oauthLogin/completedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "mcpServer/startupStatus/updated" + ], + "title": "McpServer/startupStatus/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/McpServerStatusUpdatedNotification" + } + }, + "title": "McpServer/startupStatus/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "account/updated" + ], + "title": "Account/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/AccountUpdatedNotification" + } + }, + "title": "Account/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "account/rateLimits/updated" + ], + "title": "Account/rateLimits/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/AccountRateLimitsUpdatedNotification" + } + }, + "title": "Account/rateLimits/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "app/list/updated" + ], + "title": "App/list/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/AppListUpdatedNotification" + } + }, + "title": "App/list/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "remoteControl/status/changed" + ], + "title": "RemoteControl/status/changedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/RemoteControlStatusChangedNotification" + } + }, + "title": "RemoteControl/status/changedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "externalAgentConfig/import/progress" + ], + "title": "ExternalAgentConfig/import/progressNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportProgressNotification" + } + }, + "title": "ExternalAgentConfig/import/progressNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "externalAgentConfig/import/completed" + ], + "title": "ExternalAgentConfig/import/completedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportCompletedNotification" + } + }, + "title": "ExternalAgentConfig/import/completedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "fs/changed" + ], + "title": "Fs/changedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/FsChangedNotification" + } + }, + "title": "Fs/changedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/reasoning/summaryTextDelta" + ], + "title": "Item/reasoning/summaryTextDeltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ReasoningSummaryTextDeltaNotification" + } + }, + "title": "Item/reasoning/summaryTextDeltaNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/reasoning/summaryPartAdded" + ], + "title": "Item/reasoning/summaryPartAddedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ReasoningSummaryPartAddedNotification" + } + }, + "title": "Item/reasoning/summaryPartAddedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "item/reasoning/textDelta" + ], + "title": "Item/reasoning/textDeltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ReasoningTextDeltaNotification" + } + }, + "title": "Item/reasoning/textDeltaNotification" + }, + { + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/compacted" + ], + "title": "Thread/compactedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ContextCompactedNotification" + } + }, + "title": "Thread/compactedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "model/rerouted" + ], + "title": "Model/reroutedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ModelReroutedNotification" + } + }, + "title": "Model/reroutedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "model/verification" + ], + "title": "Model/verificationNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ModelVerificationNotification" + } + }, + "title": "Model/verificationNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "turn/moderationMetadata" + ], + "title": "Turn/moderationMetadataNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/TurnModerationMetadataNotification" + } + }, + "title": "Turn/moderationMetadataNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "model/safetyBuffering/updated" + ], + "title": "Model/safetyBuffering/updatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ModelSafetyBufferingUpdatedNotification" + } + }, + "title": "Model/safetyBuffering/updatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "warning" + ], + "title": "WarningNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/WarningNotification" + } + }, + "title": "WarningNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "guardianWarning" + ], + "title": "GuardianWarningNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/GuardianWarningNotification" + } + }, + "title": "GuardianWarningNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "deprecationNotice" + ], + "title": "DeprecationNoticeNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/DeprecationNoticeNotification" + } + }, + "title": "DeprecationNoticeNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "configWarning" + ], + "title": "ConfigWarningNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ConfigWarningNotification" + } + }, + "title": "ConfigWarningNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "fuzzyFileSearch/sessionUpdated" + ], + "title": "FuzzyFileSearch/sessionUpdatedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/FuzzyFileSearchSessionUpdatedNotification" + } + }, + "title": "FuzzyFileSearch/sessionUpdatedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "fuzzyFileSearch/sessionCompleted" + ], + "title": "FuzzyFileSearch/sessionCompletedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/FuzzyFileSearchSessionCompletedNotification" + } + }, + "title": "FuzzyFileSearch/sessionCompletedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/realtime/started" + ], + "title": "Thread/realtime/startedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRealtimeStartedNotification" + } + }, + "title": "Thread/realtime/startedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/realtime/itemAdded" + ], + "title": "Thread/realtime/itemAddedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRealtimeItemAddedNotification" + } + }, + "title": "Thread/realtime/itemAddedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/realtime/transcript/delta" + ], + "title": "Thread/realtime/transcript/deltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRealtimeTranscriptDeltaNotification" + } + }, + "title": "Thread/realtime/transcript/deltaNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/realtime/transcript/done" + ], + "title": "Thread/realtime/transcript/doneNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRealtimeTranscriptDoneNotification" + } + }, + "title": "Thread/realtime/transcript/doneNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/realtime/outputAudio/delta" + ], + "title": "Thread/realtime/outputAudio/deltaNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRealtimeOutputAudioDeltaNotification" + } + }, + "title": "Thread/realtime/outputAudio/deltaNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/realtime/sdp" + ], + "title": "Thread/realtime/sdpNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRealtimeSdpNotification" + } + }, + "title": "Thread/realtime/sdpNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/realtime/error" + ], + "title": "Thread/realtime/errorNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRealtimeErrorNotification" + } + }, + "title": "Thread/realtime/errorNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "thread/realtime/closed" + ], + "title": "Thread/realtime/closedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/ThreadRealtimeClosedNotification" + } + }, + "title": "Thread/realtime/closedNotification" + }, + { + "description": "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.", + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "windows/worldWritableWarning" + ], + "title": "Windows/worldWritableWarningNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/WindowsWorldWritableWarningNotification" + } + }, + "title": "Windows/worldWritableWarningNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "windowsSandbox/setupCompleted" + ], + "title": "WindowsSandbox/setupCompletedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/WindowsSandboxSetupCompletedNotification" + } + }, + "title": "WindowsSandbox/setupCompletedNotification" + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "account/login/completed" + ], + "title": "Account/login/completedNotificationMethod" + }, + "params": { + "$ref": "#/definitions/v2/AccountLoginCompletedNotification" + } + }, + "title": "Account/login/completedNotification" + } + ] + }, + "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.", + "type": "string" + }, + "ApprovalsReviewer": { + "description": "Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + "type": "string", + "enum": [ + "user", + "auto_review", + "guardian_subagent" + ] + }, + "AskForApproval": { + "oneOf": [ + { + "type": "string", + "enum": [ + "untrusted", + "on-request", + "never" + ] + }, + { + "type": "object", + "required": [ + "granular" + ], + "properties": { + "granular": { + "type": "object", + "required": [ + "mcp_elicitations", + "rules", + "sandbox_approval" + ], + "properties": { + "mcp_elicitations": { + "type": "boolean" + }, + "request_permissions": { + "default": false, + "type": "boolean" + }, + "rules": { + "type": "boolean" + }, + "sandbox_approval": { + "type": "boolean" + }, + "skill_approval": { + "default": false, + "type": "boolean" + } + } + } + }, + "additionalProperties": false, + "title": "GranularAskForApproval" + } + ] + }, + "CapabilityRootLocation": { + "description": "Location used to resolve a selected capability root.", + "oneOf": [ + { + "description": "A path owned by an execution environment.", + "type": "object", + "required": [ + "environmentId", + "path", + "type" + ], + "properties": { + "environmentId": { + "type": "string" + }, + "path": { + "description": "Absolute path for the root in the selected environment.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "environment" + ], + "title": "EnvironmentCapabilityRootLocationType" + } + }, + "title": "EnvironmentCapabilityRootLocation" + } + ] + }, + "DynamicToolNamespaceTool": { + "oneOf": [ + { + "type": "object", + "required": [ + "description", + "inputSchema", + "name", + "type" + ], + "properties": { + "deferLoading": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "inputSchema": true, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "function" + ], + "title": "FunctionDynamicToolNamespaceToolType" + } + }, + "title": "FunctionDynamicToolNamespaceTool" + } + ] + }, + "DynamicToolSpec": { + "oneOf": [ + { + "type": "object", + "required": [ + "description", + "inputSchema", + "name", + "type" + ], + "properties": { + "deferLoading": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "inputSchema": true, + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "function" + ], + "title": "FunctionDynamicToolSpecType" + } + }, + "title": "FunctionDynamicToolSpec" + }, + { + "type": "object", + "required": [ + "description", + "name", + "tools", + "type" + ], + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tools": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/DynamicToolNamespaceTool" + } + }, + "type": { + "type": "string", + "enum": [ + "namespace" + ], + "title": "NamespaceDynamicToolSpecType" + } + }, + "title": "NamespaceDynamicToolSpec" + } + ] + }, + "LegacyAppPathString": { + "type": "string" + }, + "MultiAgentMode": { + "description": "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", + "oneOf": [ + { + "type": "string", + "enum": [ + "explicitRequestOnly", + "proactive" + ] + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false, + "title": "CustomMultiAgentMode" + } + ] + }, + "Personality": { + "type": "string", + "enum": [ + "none", + "friendly", + "pragmatic" + ] + }, + "SandboxMode": { + "type": "string", + "enum": [ + "read-only", + "workspace-write", + "danger-full-access" + ] + }, + "SelectedCapabilityRoot": { + "description": "A user-selected root that can expose one or more runtime capabilities.", + "type": "object", + "required": [ + "id", + "location" + ], + "properties": { + "id": { + "description": "Stable identifier supplied by the capability selection platform.", + "type": "string" + }, + "location": { + "description": "Where the selected root can be resolved.", + "allOf": [ + { + "$ref": "#/definitions/v2/CapabilityRootLocation" + } + ] + } + } + }, + "ThreadHistoryMode": { + "type": "string", + "enum": [ + "legacy", + "paginated" + ] + }, + "ThreadSource": { + "type": "string" + }, + "ThreadStartSource": { + "type": "string", + "enum": [ + "startup", + "clear" + ] + }, + "TurnEnvironmentParams": { + "type": "object", + "required": [ + "cwd", + "environmentId" + ], + "properties": { + "cwd": { + "$ref": "#/definitions/v2/LegacyAppPathString" + }, + "environmentId": { + "type": "string" + } + } + }, + "ThreadStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadStartParams", + "type": "object", + "properties": { + "serviceName": { + "type": [ + "string", + "null" + ] + }, + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "approvalsReviewer": { + "description": "Override where approval requests are routed for review on this thread and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "serviceTier": { + "type": [ + "string", + "null" + ] + }, + "ephemeral": { + "type": [ + "boolean", + "null" + ] + }, + "sessionStartSource": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadStartSource" + }, + { + "type": "null" + } + ] + }, + "threadSource": { + "description": "Optional client-supplied analytics source classification for this thread.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadSource" + }, + { + "type": "null" + } + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Personality" + }, + { + "type": "null" + } + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + } + } + }, + "AgentMessageInputContent": { + "oneOf": [ + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "input_text" + ], + "title": "InputTextAgentMessageInputContentType" + } + }, + "title": "InputTextAgentMessageInputContent" + }, + { + "type": "object", + "required": [ + "encrypted_content", + "type" + ], + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "encrypted_content" + ], + "title": "EncryptedContentAgentMessageInputContentType" + } + }, + "title": "EncryptedContentAgentMessageInputContent" + } + ] + }, + "ContentItem": { + "oneOf": [ + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "input_text" + ], + "title": "InputTextContentItemType" + } + }, + "title": "InputTextContentItem" + }, + { + "type": "object", + "required": [ + "image_url", + "type" + ], + "properties": { + "detail": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ImageDetail" + }, + { + "type": "null" + } + ] + }, + "image_url": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "input_image" + ], + "title": "InputImageContentItemType" + } + }, + "title": "InputImageContentItem" + }, + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "output_text" + ], + "title": "OutputTextContentItemType" + } + }, + "title": "OutputTextContentItem" + } + ] + }, + "FunctionCallOutputBody": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/v2/FunctionCallOutputContentItem" + } + } + ] + }, + "FunctionCallOutputContentItem": { + "description": "Responses API compatible content items that can be returned by a tool call. This is a subset of ContentItem with the types we support as function call outputs.", + "oneOf": [ + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "input_text" + ], + "title": "InputTextFunctionCallOutputContentItemType" + } + }, + "title": "InputTextFunctionCallOutputContentItem" + }, + { + "type": "object", + "required": [ + "image_url", + "type" + ], + "properties": { + "detail": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ImageDetail" + }, + { + "type": "null" + } + ] + }, + "image_url": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "input_image" + ], + "title": "InputImageFunctionCallOutputContentItemType" + } + }, + "title": "InputImageFunctionCallOutputContentItem" + }, + { + "type": "object", + "required": [ + "encrypted_content", + "type" + ], + "properties": { + "encrypted_content": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "encrypted_content" + ], + "title": "EncryptedContentFunctionCallOutputContentItemType" + } + }, + "title": "EncryptedContentFunctionCallOutputContentItem" + } + ] + }, + "ImageDetail": { + "type": "string", + "enum": [ + "auto", + "low", + "high", + "original" + ] + }, + "InternalChatMessageMetadataPassthrough": { + "description": "Internal Responses API passthrough metadata copied into underlying chat messages.\n\nResponses API strongly types this payload. Do not modify it without first getting API approval and making the corresponding Responses API change.", + "type": "object", + "properties": { + "turn_id": { + "type": [ + "string", + "null" + ] + } + } + }, + "LocalShellAction": { + "oneOf": [ + { + "type": "object", + "required": [ + "command", + "type" + ], + "properties": { + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "timeout_ms": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "type": { + "type": "string", + "enum": [ + "exec" + ], + "title": "ExecLocalShellActionType" + }, + "user": { + "type": [ + "string", + "null" + ] + }, + "working_directory": { + "type": [ + "string", + "null" + ] + } + }, + "title": "ExecLocalShellAction" + } + ] + }, + "LocalShellStatus": { + "type": "string", + "enum": [ + "completed", + "in_progress", + "incomplete" + ] + }, + "MessagePhase": { + "description": "Classifies an assistant message as interim commentary or final answer text.\n\nProviders do not emit this consistently, so callers must treat `None` as \"phase unknown\" and keep compatibility behavior for legacy models.", + "oneOf": [ + { + "description": "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + "type": "string", + "enum": [ + "commentary" + ] + }, + { + "description": "The assistant's terminal answer text for the current turn.", + "type": "string", + "enum": [ + "final_answer" + ] + } + ] + }, + "ReasoningItemContent": { + "oneOf": [ + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "reasoning_text" + ], + "title": "ReasoningTextReasoningItemContentType" + } + }, + "title": "ReasoningTextReasoningItemContent" + }, + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "text" + ], + "title": "TextReasoningItemContentType" + } + }, + "title": "TextReasoningItemContent" + } + ] + }, + "ReasoningItemReasoningSummary": { + "oneOf": [ + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "summary_text" + ], + "title": "SummaryTextReasoningItemReasoningSummaryType" + } + }, + "title": "SummaryTextReasoningItemReasoningSummary" + } + ] + }, + "ResponseItem": { + "oneOf": [ + { + "type": "object", + "required": [ + "content", + "role", + "type" + ], + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ContentItem" + } + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "phase": { + "anyOf": [ + { + "$ref": "#/definitions/v2/MessagePhase" + }, + { + "type": "null" + } + ] + }, + "role": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "message" + ], + "title": "MessageResponseItemType" + } + }, + "title": "MessageResponseItem" + }, + { + "type": "object", + "required": [ + "author", + "content", + "recipient", + "type" + ], + "properties": { + "author": { + "type": "string" + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AgentMessageInputContent" + } + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "recipient": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "agent_message" + ], + "title": "AgentMessageResponseItemType" + } + }, + "title": "AgentMessageResponseItem" + }, + { + "type": "object", + "required": [ + "summary", + "type" + ], + "properties": { + "content": { + "default": null, + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/ReasoningItemContent" + } + }, + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "summary": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ReasoningItemReasoningSummary" + } + }, + "type": { + "type": "string", + "enum": [ + "reasoning" + ], + "title": "ReasoningResponseItemType" + } + }, + "title": "ReasoningResponseItem" + }, + { + "type": "object", + "required": [ + "action", + "status", + "type" + ], + "properties": { + "action": { + "$ref": "#/definitions/v2/LocalShellAction" + }, + "call_id": { + "description": "Set when using the Responses API.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Legacy id field retained for compatibility with older payloads.", + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "status": { + "$ref": "#/definitions/v2/LocalShellStatus" + }, + "type": { + "type": "string", + "enum": [ + "local_shell_call" + ], + "title": "LocalShellCallResponseItemType" + } + }, + "title": "LocalShellCallResponseItem" + }, + { + "type": "object", + "required": [ + "arguments", + "call_id", + "name", + "type" + ], + "properties": { + "arguments": { + "type": "string" + }, + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "function_call" + ], + "title": "FunctionCallResponseItemType" + } + }, + "title": "FunctionCallResponseItem" + }, + { + "type": "object", + "required": [ + "arguments", + "execution", + "type" + ], + "properties": { + "arguments": true, + "call_id": { + "type": [ + "string", + "null" + ] + }, + "execution": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "tool_search_call" + ], + "title": "ToolSearchCallResponseItemType" + } + }, + "title": "ToolSearchCallResponseItem" + }, + { + "type": "object", + "required": [ + "call_id", + "output", + "type" + ], + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "output": { + "$ref": "#/definitions/v2/FunctionCallOutputBody" + }, + "type": { + "type": "string", + "enum": [ + "function_call_output" + ], + "title": "FunctionCallOutputResponseItemType" + } + }, + "title": "FunctionCallOutputResponseItem" + }, + { + "type": "object", + "required": [ + "call_id", + "input", + "name", + "type" + ], + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "input": { + "type": "string" + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "custom_tool_call" + ], + "title": "CustomToolCallResponseItemType" + } + }, + "title": "CustomToolCallResponseItem" + }, + { + "type": "object", + "required": [ + "call_id", + "output", + "type" + ], + "properties": { + "call_id": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "output": { + "$ref": "#/definitions/v2/FunctionCallOutputBody" + }, + "type": { + "type": "string", + "enum": [ + "custom_tool_call_output" + ], + "title": "CustomToolCallOutputResponseItemType" + } + }, + "title": "CustomToolCallOutputResponseItem" + }, + { + "type": "object", + "required": [ + "execution", + "status", + "tools", + "type" + ], + "properties": { + "call_id": { + "type": [ + "string", + "null" + ] + }, + "execution": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "status": { + "type": "string" + }, + "tools": { + "type": "array", + "items": true + }, + "type": { + "type": "string", + "enum": [ + "tool_search_output" + ], + "title": "ToolSearchOutputResponseItemType" + } + }, + "title": "ToolSearchOutputResponseItem" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ResponsesApiWebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "status": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "web_search_call" + ], + "title": "WebSearchCallResponseItemType" + } + }, + "title": "WebSearchCallResponseItem" + }, + { + "type": "object", + "required": [ + "result", + "status", + "type" + ], + "properties": { + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "result": { + "type": "string" + }, + "revised_prompt": { + "type": [ + "string", + "null" + ] + }, + "status": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "image_generation_call" + ], + "title": "ImageGenerationCallResponseItemType" + } + }, + "title": "ImageGenerationCallResponseItem" + }, + { + "type": "object", + "required": [ + "encrypted_content", + "type" + ], + "properties": { + "encrypted_content": { + "type": "string" + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "compaction" + ], + "title": "CompactionResponseItemType" + } + }, + "title": "CompactionResponseItem" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "compaction_trigger" + ], + "title": "CompactionTriggerResponseItemType" + } + }, + "title": "CompactionTriggerResponseItem" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "encrypted_content": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": [ + "string", + "null" + ] + }, + "internal_chat_message_metadata_passthrough": { + "anyOf": [ + { + "$ref": "#/definitions/v2/InternalChatMessageMetadataPassthrough" + }, + { + "type": "null" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "context_compaction" + ], + "title": "ContextCompactionResponseItemType" + } + }, + "title": "ContextCompactionResponseItem" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "other" + ], + "title": "OtherResponseItemType" + } + }, + "title": "OtherResponseItem" + } + ] + }, + "ResponsesApiWebSearchAction": { + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "queries": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "search" + ], + "title": "SearchResponsesApiWebSearchActionType" + } + }, + "title": "SearchResponsesApiWebSearchAction" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "open_page" + ], + "title": "OpenPageResponsesApiWebSearchActionType" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "title": "OpenPageResponsesApiWebSearchAction" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "find_in_page" + ], + "title": "FindInPageResponsesApiWebSearchActionType" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "title": "FindInPageResponsesApiWebSearchAction" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "other" + ], + "title": "OtherResponsesApiWebSearchActionType" + } + }, + "title": "OtherResponsesApiWebSearchAction" + } + ] + }, + "SortDirection": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "ThreadResumeInitialTurnsPageParams": { + "type": "object", + "properties": { + "itemsView": { + "description": "How much item detail to include for each returned turn; defaults to summary.", + "anyOf": [ + { + "$ref": "#/definitions/v2/TurnItemsView" + }, + { + "type": "null" + } + ] + }, + "limit": { + "description": "Optional turn page size.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "sortDirection": { + "description": "Optional turn pagination direction; defaults to descending.", + "anyOf": [ + { + "$ref": "#/definitions/v2/SortDirection" + }, + { + "type": "null" + } + ] + } + } + }, + "TurnItemsView": { + "oneOf": [ + { + "description": "`items` was not loaded for this turn. The field is intentionally empty.", + "type": "string", + "enum": [ + "notLoaded" + ] + }, + { + "description": "`items` contains only a display summary for this turn.", + "type": "string", + "enum": [ + "summary" + ] + }, + { + "description": "`items` contains every ThreadItem available from persisted app-server history for this turn.", + "type": "string", + "enum": [ + "full" + ] + } + ] + }, + "ThreadResumeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadResumeParams", + "description": "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "approvalsReviewer": { + "description": "Override where approval requests are routed for review on this thread and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Personality" + }, + { + "type": "null" + } + ] + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "model": { + "description": "Configuration overrides for the resumed thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "serviceTier": { + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadForkParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadForkParams", + "description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "approvalPolicy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "approvalsReviewer": { + "description": "Override where approval requests are routed for review on this thread and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + }, + { + "type": "null" + } + ] + }, + "baseInstructions": { + "type": [ + "string", + "null" + ] + }, + "config": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "cwd": { + "type": [ + "string", + "null" + ] + }, + "developerInstructions": { + "type": [ + "string", + "null" + ] + }, + "ephemeral": { + "type": "boolean" + }, + "threadSource": { + "description": "Optional client-supplied analytics source classification for this forked thread.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadSource" + }, + { + "type": "null" + } + ] + }, + "lastTurnId": { + "description": "Optional last turn id to fork through, inclusive.\n\nWhen specified, turns after `last_turn_id` are omitted from the fork. The referenced turn cannot be in progress.", + "type": [ + "string", + "null" + ] + }, + "model": { + "description": "Configuration overrides for the forked thread, if any.", + "type": [ + "string", + "null" + ] + }, + "modelProvider": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "sandbox": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "serviceTier": { + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadArchiveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadArchiveParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadDeleteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadDeleteParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadUnsubscribeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadUnsubscribeParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "AccountLoginCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AccountLoginCompletedNotification", + "type": "object", + "required": [ + "success" + ], + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "loginId": { + "type": [ + "string", + "null" + ] + }, + "success": { + "type": "boolean" + } + } + }, + "WindowsSandboxSetupCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WindowsSandboxSetupCompletedNotification", + "type": "object", + "required": [ + "mode", + "success" + ], + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "mode": { + "$ref": "#/definitions/v2/WindowsSandboxSetupMode" + }, + "success": { + "type": "boolean" + } + } + }, + "ThreadSetNameParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadSetNameParams", + "type": "object", + "required": [ + "name", + "threadId" + ], + "properties": { + "name": { + "type": "string" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadGoalStatus": { + "type": "string", + "enum": [ + "active", + "paused", + "blocked", + "usageLimited", + "budgetLimited", + "complete" + ] + }, + "ThreadGoalSetParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadGoalSetParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "objective": { + "type": [ + "string", + "null" + ] + }, + "status": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadGoalStatus" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + }, + "tokenBudget": { + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "ThreadGoalGetParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadGoalGetParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadGoalClearParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadGoalClearParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadMetadataGitInfoUpdateParams": { + "type": "object", + "properties": { + "branch": { + "description": "Omit to leave the stored branch unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "description": "Omit to leave the stored origin URL unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", + "type": [ + "string", + "null" + ] + }, + "sha": { + "description": "Omit to leave the stored commit unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadMetadataUpdateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadMetadataUpdateParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "gitInfo": { + "description": "Patch the stored Git metadata for this thread. Omit a field to leave it unchanged, set it to `null` to clear it, or provide a string to replace the stored value.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadMetadataGitInfoUpdateParams" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + } + } + }, + "CollaborationMode": { + "description": "Collaboration mode for a Codex session.", + "type": "object", + "required": [ + "mode", + "settings" + ], + "properties": { + "mode": { + "$ref": "#/definitions/v2/ModeKind" + }, + "settings": { + "$ref": "#/definitions/v2/Settings" + } + } + }, + "ModeKind": { + "description": "Initial collaboration mode to use when the TUI starts.", + "type": "string", + "enum": [ + "plan", + "default" + ] + }, + "NetworkAccess": { + "type": "string", + "enum": [ + "restricted", + "enabled" + ] + }, + "ReasoningEffort": { + "description": "A non-empty reasoning effort value advertised by the model.", + "type": "string", + "minLength": 1 + }, + "ReasoningSummary": { + "description": "A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries", + "oneOf": [ + { + "type": "string", + "enum": [ + "auto", + "concise", + "detailed" + ] + }, + { + "description": "Option to disable reasoning summaries.", + "type": "string", + "enum": [ + "none" + ] + } + ] + }, + "SandboxPolicy": { + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "dangerFullAccess" + ], + "title": "DangerFullAccessSandboxPolicyType" + } + }, + "title": "DangerFullAccessSandboxPolicy" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "readOnly" + ], + "title": "ReadOnlySandboxPolicyType" + } + }, + "title": "ReadOnlySandboxPolicy" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "networkAccess": { + "default": "restricted", + "allOf": [ + { + "$ref": "#/definitions/v2/NetworkAccess" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "externalSandbox" + ], + "title": "ExternalSandboxSandboxPolicyType" + } + }, + "title": "ExternalSandboxSandboxPolicy" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "excludeSlashTmp": { + "default": false, + "type": "boolean" + }, + "excludeTmpdirEnvVar": { + "default": false, + "type": "boolean" + }, + "networkAccess": { + "default": false, + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "workspaceWrite" + ], + "title": "WorkspaceWriteSandboxPolicyType" + }, + "writableRoots": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + } + }, + "title": "WorkspaceWriteSandboxPolicy" + } + ] + }, + "Settings": { + "description": "Settings for a collaboration mode.", + "type": "object", + "required": [ + "model" + ], + "properties": { + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + } + }, + "WindowsWorldWritableWarningNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WindowsWorldWritableWarningNotification", + "type": "object", + "required": [ + "extraCount", + "failedScan", + "samplePaths" + ], + "properties": { + "extraCount": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "failedScan": { + "type": "boolean" + }, + "samplePaths": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ThreadMemoryMode": { + "type": "string", + "enum": [ + "enabled", + "disabled" + ] + }, + "ThreadRealtimeClosedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRealtimeClosedNotification", + "description": "EXPERIMENTAL - emitted when thread realtime transport closes.", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "reason": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadUnarchiveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadUnarchiveParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadCompactStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadCompactStartParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadShellCommandParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadShellCommandParams", + "type": "object", + "required": [ + "command", + "threadId" + ], + "properties": { + "command": { + "description": "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.", + "type": "string" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadApproveGuardianDeniedActionParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadApproveGuardianDeniedActionParams", + "type": "object", + "required": [ + "event", + "threadId" + ], + "properties": { + "event": { + "description": "Serialized `codex_protocol::protocol::GuardianAssessmentEvent`." + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadRealtimeErrorNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRealtimeErrorNotification", + "description": "EXPERIMENTAL - emitted when thread realtime encounters an error.", + "type": "object", + "required": [ + "message", + "threadId" + ], + "properties": { + "message": { + "type": "string" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadRealtimeSdpNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRealtimeSdpNotification", + "description": "EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.", + "type": "object", + "required": [ + "sdp", + "threadId" + ], + "properties": { + "sdp": { + "type": "string" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadRealtimeOutputAudioDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRealtimeOutputAudioDeltaNotification", + "description": "EXPERIMENTAL - streamed output audio emitted by thread realtime.", + "type": "object", + "required": [ + "audio", + "threadId" + ], + "properties": { + "audio": { + "$ref": "#/definitions/v2/ThreadRealtimeAudioChunk" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadRollbackParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRollbackParams", + "description": "DEPRECATED: `thread/rollback` will be removed soon.", + "type": "object", + "required": [ + "numTurns", + "threadId" + ], + "properties": { + "numTurns": { + "description": "The number of turns to drop from the end of the thread. Must be >= 1.\n\nThis only modifies the thread's history and does not revert local file changes that have been made by the agent. Clients are responsible for reverting these changes.", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadListCwdFilter": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ThreadSortKey": { + "type": "string", + "enum": [ + "created_at", + "updated_at", + "recency_at" + ] + }, + "ThreadSourceKind": { + "type": "string", + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "subAgent", + "subAgentReview", + "subAgentCompact", + "subAgentThreadSpawn", + "subAgentOther", + "unknown" + ] + }, + "ThreadListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadListParams", + "type": "object", + "properties": { + "sourceKinds": { + "description": "Optional source filter; when set, only sessions from these source kinds are returned. When omitted or empty, defaults to interactive sources.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/ThreadSourceKind" + } + }, + "archived": { + "description": "Optional archived filter; when set to true, only archived threads are returned. If false or null, only non-archived threads are returned.", + "type": [ + "boolean", + "null" + ] + }, + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "cwd": { + "description": "Optional cwd filter or filters; when set, only threads whose session cwd exactly matches one of these paths are returned.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadListCwdFilter" + }, + { + "type": "null" + } + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "modelProviders": { + "description": "Optional provider filter; when set, only sessions recorded under these providers are returned. When present but empty, includes all providers.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "useStateDbOnly": { + "description": "If true, return from the state DB without scanning JSONL rollouts to repair thread metadata. Omitted or false preserves scan-and-repair behavior.", + "type": "boolean" + }, + "searchTerm": { + "description": "Optional substring filter for the extracted thread title.", + "type": [ + "string", + "null" + ] + }, + "sortDirection": { + "description": "Optional sort direction; defaults to descending (newest first).", + "anyOf": [ + { + "$ref": "#/definitions/v2/SortDirection" + }, + { + "type": "null" + } + ] + }, + "sortKey": { + "description": "Optional sort key; defaults to created_at.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadSortKey" + }, + { + "type": "null" + } + ] + } + } + }, + "ThreadRealtimeTranscriptDoneNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRealtimeTranscriptDoneNotification", + "description": "EXPERIMENTAL - final transcript text emitted when realtime completes a transcript part.", + "type": "object", + "required": [ + "role", + "text", + "threadId" + ], + "properties": { + "role": { + "type": "string" + }, + "text": { + "description": "Final complete text for the transcript part.", + "type": "string" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadLoadedListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadLoadedListParams", + "type": "object", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to no limit.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + }, + "ThreadReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadReadParams", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "includeTurns": { + "description": "When true, include turns and their items from rollout history.", + "type": "boolean" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadRealtimeTranscriptDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRealtimeTranscriptDeltaNotification", + "description": "EXPERIMENTAL - flat transcript delta emitted whenever realtime transcript text changes.", + "type": "object", + "required": [ + "delta", + "role", + "threadId" + ], + "properties": { + "delta": { + "description": "Live transcript delta from the realtime event.", + "type": "string" + }, + "role": { + "type": "string" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadRealtimeItemAddedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRealtimeItemAddedNotification", + "description": "EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend.", + "type": "object", + "required": [ + "item", + "threadId" + ], + "properties": { + "item": true, + "threadId": { + "type": "string" + } + } + }, + "ThreadInjectItemsParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadInjectItemsParams", + "type": "object", + "required": [ + "items", + "threadId" + ], + "properties": { + "items": { + "description": "Raw Responses API items to append to the thread's model-visible history.", + "type": "array", + "items": true + }, + "threadId": { + "type": "string" + } + } + }, + "SkillsListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsListParams", + "type": "object", + "properties": { + "cwds": { + "description": "When empty, defaults to the current session working directory.", + "type": "array", + "items": { + "type": "string" + } + }, + "forceReload": { + "description": "When true, bypass the skills cache and re-scan skills from disk.", + "type": "boolean" + } + } + }, + "SkillsExtraRootsSetParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsExtraRootsSetParams", + "type": "object", + "required": [ + "extraRoots" + ], + "properties": { + "extraRoots": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + } + } + }, + "HooksListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HooksListParams", + "type": "object", + "properties": { + "cwds": { + "description": "When empty, defaults to the current session working directory.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "MarketplaceAddParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MarketplaceAddParams", + "type": "object", + "required": [ + "source" + ], + "properties": { + "refName": { + "type": [ + "string", + "null" + ] + }, + "source": { + "type": "string" + }, + "sparsePaths": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + } + }, + "MarketplaceRemoveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MarketplaceRemoveParams", + "type": "object", + "required": [ + "marketplaceName" + ], + "properties": { + "marketplaceName": { + "type": "string" + } + } + }, + "MarketplaceUpgradeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MarketplaceUpgradeParams", + "type": "object", + "properties": { + "marketplaceName": { + "type": [ + "string", + "null" + ] + } + } + }, + "PluginListMarketplaceKind": { + "type": "string", + "enum": [ + "local", + "vertical", + "workspace-directory", + "shared-with-me", + "created-by-me-remote" + ] + }, + "PluginListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginListParams", + "type": "object", + "properties": { + "cwds": { + "description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces and the official curated marketplace are considered.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + }, + "marketplaceKinds": { + "description": "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/PluginListMarketplaceKind" + } + } + } + }, + "PluginInstalledParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginInstalledParams", + "type": "object", + "properties": { + "cwds": { + "description": "Optional working directories used to discover repo marketplaces.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + }, + "installSuggestionPluginNames": { + "description": "Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + } + }, + "PluginReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginReadParams", + "type": "object", + "required": [ + "pluginName" + ], + "properties": { + "marketplacePath": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "pluginName": { + "type": "string" + }, + "remoteMarketplaceName": { + "type": [ + "string", + "null" + ] + } + } + }, + "PluginSkillReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginSkillReadParams", + "type": "object", + "required": [ + "remoteMarketplaceName", + "remotePluginId", + "skillName" + ], + "properties": { + "remoteMarketplaceName": { + "type": "string" + }, + "remotePluginId": { + "type": "string" + }, + "skillName": { + "type": "string" + } + } + }, + "PluginShareDiscoverability": { + "type": "string", + "enum": [ + "LISTED", + "UNLISTED", + "PRIVATE" + ] + }, + "PluginSharePrincipalType": { + "type": "string", + "enum": [ + "user", + "group", + "workspace" + ] + }, + "PluginShareTarget": { + "type": "object", + "required": [ + "principalId", + "principalType", + "role" + ], + "properties": { + "principalId": { + "type": "string" + }, + "principalType": { + "$ref": "#/definitions/v2/PluginSharePrincipalType" + }, + "role": { + "$ref": "#/definitions/v2/PluginShareTargetRole" + } + } + }, + "PluginShareTargetRole": { + "type": "string", + "enum": [ + "reader", + "editor" + ] + }, + "PluginShareSaveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareSaveParams", + "type": "object", + "required": [ + "pluginPath" + ], + "properties": { + "discoverability": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PluginShareDiscoverability" + }, + { + "type": "null" + } + ] + }, + "pluginPath": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "remotePluginId": { + "type": [ + "string", + "null" + ] + }, + "shareTargets": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/PluginShareTarget" + } + } + } + }, + "PluginShareUpdateDiscoverability": { + "type": "string", + "enum": [ + "UNLISTED", + "PRIVATE" + ] + }, + "PluginShareUpdateTargetsParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareUpdateTargetsParams", + "type": "object", + "required": [ + "discoverability", + "remotePluginId", + "shareTargets" + ], + "properties": { + "discoverability": { + "$ref": "#/definitions/v2/PluginShareUpdateDiscoverability" + }, + "remotePluginId": { + "type": "string" + }, + "shareTargets": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/PluginShareTarget" + } + } + } + }, + "PluginShareListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareListParams", + "type": "object" + }, + "PluginShareCheckoutParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareCheckoutParams", + "type": "object", + "required": [ + "remotePluginId" + ], + "properties": { + "remotePluginId": { + "type": "string" + } + } + }, + "PluginShareDeleteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareDeleteParams", + "type": "object", + "required": [ + "remotePluginId" + ], + "properties": { + "remotePluginId": { + "type": "string" + } + } + }, + "AppsListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppsListParams", + "description": "EXPERIMENTAL - list available apps/connectors.", + "type": "object", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "forceRefetch": { + "description": "When true, bypass app caches and fetch the latest data from sources.", + "type": "boolean" + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "threadId": { + "description": "Optional thread id used to evaluate app feature gating from that thread's config.", + "type": [ + "string", + "null" + ] + } + } + }, + "FsReadFileParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsReadFileParams", + "description": "Read a file from the host filesystem.", + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "Absolute path to read.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + } + } + }, + "FsWriteFileParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsWriteFileParams", + "description": "Write a file on the host filesystem.", + "type": "object", + "required": [ + "dataBase64", + "path" + ], + "properties": { + "dataBase64": { + "description": "File contents encoded as base64.", + "type": "string" + }, + "path": { + "description": "Absolute path to write.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + } + } + }, + "FsCreateDirectoryParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsCreateDirectoryParams", + "description": "Create a directory on the host filesystem.", + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "Absolute directory path to create.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "recursive": { + "description": "Whether parent directories should also be created. Defaults to `true`.", + "type": [ + "boolean", + "null" + ] + } + } + }, + "FsGetMetadataParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsGetMetadataParams", + "description": "Request metadata for an absolute path.", + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "Absolute path to inspect.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + } + } + }, + "FsReadDirectoryParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsReadDirectoryParams", + "description": "List direct child names for a directory.", + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "Absolute directory path to read.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + } + } + }, + "FsRemoveParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsRemoveParams", + "description": "Remove a file or directory tree from the host filesystem.", + "type": "object", + "required": [ + "path" + ], + "properties": { + "force": { + "description": "Whether missing paths should be ignored. Defaults to `true`.", + "type": [ + "boolean", + "null" + ] + }, + "path": { + "description": "Absolute path to remove.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "recursive": { + "description": "Whether directory removal should recurse. Defaults to `true`.", + "type": [ + "boolean", + "null" + ] + } + } + }, + "FsCopyParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsCopyParams", + "description": "Copy a file or directory tree on the host filesystem.", + "type": "object", + "required": [ + "destinationPath", + "sourcePath" + ], + "properties": { + "destinationPath": { + "description": "Absolute destination path.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "recursive": { + "description": "Required for directory copies; ignored for file copies.", + "type": "boolean" + }, + "sourcePath": { + "description": "Absolute source path.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + } + } + }, + "FsWatchParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsWatchParams", + "description": "Start filesystem watch notifications for an absolute path.", + "type": "object", + "required": [ + "path", + "watchId" + ], + "properties": { + "path": { + "description": "Absolute file or directory path to watch.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "watchId": { + "description": "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.", + "type": "string" + } + } + }, + "FsUnwatchParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsUnwatchParams", + "description": "Stop filesystem watch notifications for a prior `fs/watch`.", + "type": "object", + "required": [ + "watchId" + ], + "properties": { + "watchId": { + "description": "Watch identifier previously provided to `fs/watch`.", + "type": "string" + } + } + }, + "SkillsConfigWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsConfigWriteParams", + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "name": { + "description": "Name-based selector.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "Path-based selector.", + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + } + } + }, + "PluginInstallParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginInstallParams", + "type": "object", + "required": [ + "pluginName" + ], + "properties": { + "marketplacePath": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "pluginName": { + "type": "string" + }, + "remoteMarketplaceName": { + "type": [ + "string", + "null" + ] + } + } + }, + "PluginUninstallParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginUninstallParams", + "type": "object", + "required": [ + "pluginId" + ], + "properties": { + "pluginId": { + "type": "string" + } + } + }, + "AdditionalContextEntry": { + "type": "object", + "required": [ + "kind", + "value" + ], + "properties": { + "kind": { + "$ref": "#/definitions/v2/AdditionalContextKind" + }, + "value": { + "type": "string" + } + } + }, + "AdditionalContextKind": { + "type": "string", + "enum": [ + "untrusted", + "application" + ] + }, + "ByteRange": { + "type": "object", + "required": [ + "end", + "start" + ], + "properties": { + "end": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "start": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + } + }, + "TextElement": { + "type": "object", + "required": [ + "byteRange" + ], + "properties": { + "byteRange": { + "description": "Byte range in the parent `text` buffer that this element occupies.", + "allOf": [ + { + "$ref": "#/definitions/v2/ByteRange" + } + ] + }, + "placeholder": { + "description": "Optional human-readable placeholder for the element, displayed in the UI.", + "type": [ + "string", + "null" + ] + } + } + }, + "UserInput": { + "oneOf": [ + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "text_elements": { + "description": "UI-defined spans within `text` used to render or persist special elements.", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/TextElement" + } + }, + "type": { + "type": "string", + "enum": [ + "text" + ], + "title": "TextUserInputType" + } + }, + "title": "TextUserInput" + }, + { + "type": "object", + "required": [ + "type", + "url" + ], + "properties": { + "detail": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/v2/ImageDetail" + }, + { + "type": "null" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "image" + ], + "title": "ImageUserInputType" + }, + "url": { + "type": "string" + } + }, + "title": "ImageUserInput" + }, + { + "type": "object", + "required": [ + "path", + "type" + ], + "properties": { + "detail": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/v2/ImageDetail" + }, + { + "type": "null" + } + ] + }, + "path": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "localImage" + ], + "title": "LocalImageUserInputType" + } + }, + "title": "LocalImageUserInput" + }, + { + "type": "object", + "required": [ + "name", + "path", + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "skill" + ], + "title": "SkillUserInputType" + } + }, + "title": "SkillUserInput" + }, + { + "type": "object", + "required": [ + "name", + "path", + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "mention" + ], + "title": "MentionUserInputType" + } + }, + "title": "MentionUserInput" + } + ] + }, + "TurnStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnStartParams", + "type": "object", + "required": [ + "input", + "threadId" + ], + "properties": { + "sandboxPolicy": { + "description": "Override the sandbox policy for this turn and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + }, + { + "type": "null" + } + ] + }, + "approvalPolicy": { + "description": "Override the approval policy for this turn and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "approvalsReviewer": { + "description": "Override where approval requests are routed for review on this turn and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + }, + { + "type": "null" + } + ] + }, + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Override the reasoning summary for this turn and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "cwd": { + "description": "Override the working directory for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "effort": { + "description": "Override the reasoning effort for this turn and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "threadId": { + "type": "string" + }, + "input": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/UserInput" + } + }, + "model": { + "description": "Override the model for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + }, + "personality": { + "description": "Override the personality for this turn and subsequent turns.", + "anyOf": [ + { + "$ref": "#/definitions/v2/Personality" + }, + { + "type": "null" + } + ] + }, + "outputSchema": { + "description": "Optional JSON Schema used to constrain the final assistant message for this turn." + }, + "serviceTier": { + "description": "Override the service tier for this turn and subsequent turns.", + "type": [ + "string", + "null" + ] + } + } + }, + "TurnSteerParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnSteerParams", + "type": "object", + "required": [ + "expectedTurnId", + "input", + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + }, + "clientUserMessageId": { + "type": [ + "string", + "null" + ] + }, + "expectedTurnId": { + "description": "Required active turn id precondition. The request fails when it does not match the currently active turn.", + "type": "string" + }, + "input": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/UserInput" + } + } + } + }, + "TurnInterruptParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnInterruptParams", + "type": "object", + "required": [ + "threadId", + "turnId" + ], + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "RealtimeConversationVersion": { + "type": "string", + "enum": [ + "v1", + "v2" + ] + }, + "RealtimeOutputModality": { + "type": "string", + "enum": [ + "text", + "audio" + ] + }, + "RealtimeVoice": { + "type": "string", + "enum": [ + "alloy", + "arbor", + "ash", + "ballad", + "breeze", + "cedar", + "coral", + "cove", + "echo", + "ember", + "juniper", + "maple", + "marin", + "sage", + "shimmer", + "sol", + "spruce", + "vale", + "verse" + ] + }, + "ThreadRealtimeStartTransport": { + "description": "EXPERIMENTAL - transport used by thread realtime.", + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "websocket" + ], + "title": "WebsocketThreadRealtimeStartTransportType" + } + }, + "title": "WebsocketThreadRealtimeStartTransport" + }, + { + "type": "object", + "required": [ + "sdp", + "type" + ], + "properties": { + "sdp": { + "description": "SDP offer generated by a WebRTC RTCPeerConnection after configuring audio and the realtime events data channel.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "webrtc" + ], + "title": "WebrtcThreadRealtimeStartTransportType" + } + }, + "title": "WebrtcThreadRealtimeStartTransport" + } + ] + }, + "ThreadRealtimeStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRealtimeStartedNotification", + "description": "EXPERIMENTAL - emitted when thread realtime startup is accepted.", + "type": "object", + "required": [ + "threadId", + "version" + ], + "properties": { + "realtimeSessionId": { + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "version": { + "$ref": "#/definitions/v2/RealtimeConversationVersion" + } + } + }, + "ThreadRealtimeAudioChunk": { + "description": "EXPERIMENTAL - thread realtime audio chunk.", + "type": "object", + "required": [ + "data", + "numChannels", + "sampleRate" + ], + "properties": { + "data": { + "type": "string" + }, + "itemId": { + "type": [ + "string", + "null" + ] + }, + "numChannels": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sampleRate": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "samplesPerChannel": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + }, + "ConfigWarningNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigWarningNotification", + "type": "object", + "required": [ + "summary" + ], + "properties": { + "details": { + "description": "Optional extra guidance or error details.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "Optional path to the config file that triggered the warning.", + "type": [ + "string", + "null" + ] + }, + "range": { + "description": "Optional range for the error location inside the config file.", + "anyOf": [ + { + "$ref": "#/definitions/v2/TextRange" + }, + { + "type": "null" + } + ] + }, + "summary": { + "description": "Concise summary of the warning.", + "type": "string" + } + } + }, + "ConversationTextRole": { + "type": "string", + "enum": [ + "user", + "developer", + "assistant" + ] + }, + "TextRange": { + "type": "object", + "required": [ + "end", + "start" + ], + "properties": { + "end": { + "$ref": "#/definitions/v2/TextPosition" + }, + "start": { + "$ref": "#/definitions/v2/TextPosition" + } + } + }, + "TextPosition": { + "type": "object", + "required": [ + "column", + "line" + ], + "properties": { + "column": { + "description": "1-based column number (in Unicode scalar values).", + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "line": { + "description": "1-based line number.", + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + } + }, + "DeprecationNoticeNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DeprecationNoticeNotification", + "type": "object", + "required": [ + "summary" + ], + "properties": { + "details": { + "description": "Optional extra guidance, such as migration steps or rationale.", + "type": [ + "string", + "null" + ] + }, + "summary": { + "description": "Concise summary of what is deprecated.", + "type": "string" + } + } + }, + "GuardianWarningNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GuardianWarningNotification", + "type": "object", + "required": [ + "message", + "threadId" + ], + "properties": { + "message": { + "description": "Concise guardian warning message for the user.", + "type": "string" + }, + "threadId": { + "description": "Thread target for the guardian warning.", + "type": "string" + } + } + }, + "ReviewDelivery": { + "type": "string", + "enum": [ + "inline", + "detached" + ] + }, + "ReviewTarget": { + "oneOf": [ + { + "description": "Review the working tree: staged, unstaged, and untracked files.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "uncommittedChanges" + ], + "title": "UncommittedChangesReviewTargetType" + } + }, + "title": "UncommittedChangesReviewTarget" + }, + { + "description": "Review changes between the current branch and the given base branch.", + "type": "object", + "required": [ + "branch", + "type" + ], + "properties": { + "branch": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "baseBranch" + ], + "title": "BaseBranchReviewTargetType" + } + }, + "title": "BaseBranchReviewTarget" + }, + { + "description": "Review the changes introduced by a specific commit.", + "type": "object", + "required": [ + "sha", + "type" + ], + "properties": { + "sha": { + "type": "string" + }, + "title": { + "description": "Optional human-readable label (e.g., commit subject) for UIs.", + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "commit" + ], + "title": "CommitReviewTargetType" + } + }, + "title": "CommitReviewTarget" + }, + { + "description": "Arbitrary instructions, equivalent to the old free-form prompt.", + "type": "object", + "required": [ + "instructions", + "type" + ], + "properties": { + "instructions": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "custom" + ], + "title": "CustomReviewTargetType" + } + }, + "title": "CustomReviewTarget" + } + ] + }, + "ReviewStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReviewStartParams", + "type": "object", + "required": [ + "target", + "threadId" + ], + "properties": { + "delivery": { + "description": "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`).", + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/v2/ReviewDelivery" + }, + { + "type": "null" + } + ] + }, + "target": { + "$ref": "#/definitions/v2/ReviewTarget" + }, + "threadId": { + "type": "string" + } + } + }, + "ModelListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModelListParams", + "type": "object", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "includeHidden": { + "description": "When true, include models that are hidden from the default picker list.", + "type": [ + "boolean", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + }, + "ModelProviderCapabilitiesReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModelProviderCapabilitiesReadParams", + "type": "object" + }, + "ExperimentalFeatureListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExperimentalFeatureListParams", + "type": "object", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to a reasonable server-side value.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "threadId": { + "description": "Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.", + "type": [ + "string", + "null" + ] + } + } + }, + "PermissionProfileListParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionProfileListParams", + "type": "object", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "cwd": { + "description": "Optional working directory to resolve project config layers.", + "type": [ + "string", + "null" + ] + }, + "limit": { + "description": "Optional page size; defaults to the full result set.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + } + } + }, + "ExperimentalFeatureEnablementSetParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExperimentalFeatureEnablementSetParams", + "type": "object", + "required": [ + "enablement" + ], + "properties": { + "enablement": { + "description": "Process-wide runtime feature enablement keyed by canonical feature name.\n\nOnly named features are updated. Omitted features are left unchanged. Send an empty map for a no-op.", + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + } + }, + "RemoteControlEnableParams": { + "type": "object", + "properties": { + "ephemeral": { + "type": "boolean" + } + } + }, + "WarningNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WarningNotification", + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "description": "Concise warning message for the user.", + "type": "string" + }, + "threadId": { + "description": "Optional thread target when the warning applies to a specific thread.", + "type": [ + "string", + "null" + ] + } + } + }, + "RemoteControlDisableParams": { + "type": "object", + "properties": { + "ephemeral": { + "type": "boolean" + } + } + }, + "ModelSafetyBufferingUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModelSafetyBufferingUpdatedNotification", + "type": "object", + "required": [ + "model", + "reasons", + "showBufferingUi", + "threadId", + "turnId", + "useCases" + ], + "properties": { + "fasterModel": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "reasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "showBufferingUi": { + "type": "boolean" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "useCases": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "TurnModerationMetadataNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnModerationMetadataNotification", + "type": "object", + "required": [ + "metadata", + "threadId", + "turnId" + ], + "properties": { + "metadata": true, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ModelVerificationNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModelVerificationNotification", + "type": "object", + "required": [ + "threadId", + "turnId", + "verifications" + ], + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "verifications": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ModelVerification" + } + } + } + }, + "ModelVerification": { + "type": "string", + "enum": [ + "trustedAccessForCyber" + ] + }, + "ModelReroutedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModelReroutedNotification", + "type": "object", + "required": [ + "fromModel", + "reason", + "threadId", + "toModel", + "turnId" + ], + "properties": { + "fromModel": { + "type": "string" + }, + "reason": { + "$ref": "#/definitions/v2/ModelRerouteReason" + }, + "threadId": { + "type": "string" + }, + "toModel": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ModelRerouteReason": { + "type": "string", + "enum": [ + "highRiskCyberActivity" + ] + }, + "ContextCompactedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ContextCompactedNotification", + "description": "Deprecated: Use `ContextCompaction` item type instead.", + "type": "object", + "required": [ + "threadId", + "turnId" + ], + "properties": { + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ReasoningTextDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReasoningTextDeltaNotification", + "type": "object", + "required": [ + "contentIndex", + "delta", + "itemId", + "threadId", + "turnId" + ], + "properties": { + "contentIndex": { + "type": "integer", + "format": "int64" + }, + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ReasoningSummaryPartAddedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReasoningSummaryPartAddedNotification", + "type": "object", + "required": [ + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "properties": { + "itemId": { + "type": "string" + }, + "summaryIndex": { + "type": "integer", + "format": "int64" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ReasoningSummaryTextDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReasoningSummaryTextDeltaNotification", + "type": "object", + "required": [ + "delta", + "itemId", + "summaryIndex", + "threadId", + "turnId" + ], + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "summaryIndex": { + "type": "integer", + "format": "int64" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "McpServerOauthLoginParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerOauthLoginParams", + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "scopes": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "threadId": { + "type": [ + "string", + "null" + ] + }, + "timeoutSecs": { + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "McpServerStatusDetail": { + "type": "string", + "enum": [ + "full", + "toolsAndAuthOnly" + ] + }, + "ListMcpServerStatusParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListMcpServerStatusParams", + "type": "object", + "properties": { + "cursor": { + "description": "Opaque pagination cursor returned by a previous call.", + "type": [ + "string", + "null" + ] + }, + "detail": { + "description": "Controls how much MCP inventory data to fetch for each server. Defaults to `Full` when omitted.", + "anyOf": [ + { + "$ref": "#/definitions/v2/McpServerStatusDetail" + }, + { + "type": "null" + } + ] + }, + "limit": { + "description": "Optional page size; defaults to a server-defined value.", + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "threadId": { + "type": [ + "string", + "null" + ] + } + } + }, + "McpResourceReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpResourceReadParams", + "type": "object", + "required": [ + "server", + "uri" + ], + "properties": { + "server": { + "type": "string" + }, + "threadId": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + } + }, + "McpServerToolCallParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerToolCallParams", + "type": "object", + "required": [ + "server", + "threadId", + "tool" + ], + "properties": { + "_meta": true, + "arguments": true, + "server": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "tool": { + "type": "string" + } + } + }, + "WindowsSandboxSetupMode": { + "type": "string", + "enum": [ + "elevated", + "unelevated" + ] + }, + "WindowsSandboxSetupStartParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WindowsSandboxSetupStartParams", + "type": "object", + "required": [ + "mode" + ], + "properties": { + "cwd": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "mode": { + "$ref": "#/definitions/v2/WindowsSandboxSetupMode" + } + } + }, + "LoginAppBrand": { + "type": "string", + "enum": [ + "codex", + "chatgpt" + ] + }, + "LoginAccountParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LoginAccountParams", + "oneOf": [ + { + "type": "object", + "required": [ + "apiKey", + "type" + ], + "properties": { + "apiKey": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "apiKey" + ], + "title": "ApiKeyv2::LoginAccountParamsType" + } + }, + "title": "ApiKeyv2::LoginAccountParams" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "appBrand": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/v2/LoginAppBrand" + }, + { + "type": "null" + } + ] + }, + "codexStreamlinedLogin": { + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "chatgpt" + ], + "title": "Chatgptv2::LoginAccountParamsType" + }, + "useHostedLoginSuccessPage": { + "type": "boolean" + } + }, + "title": "Chatgptv2::LoginAccountParams" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodev2::LoginAccountParamsType" + } + }, + "title": "ChatgptDeviceCodev2::LoginAccountParams" + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", + "type": "object", + "required": [ + "accessToken", + "chatgptAccountId", + "type" + ], + "properties": { + "accessToken": { + "description": "Access token (JWT) supplied by the client. This token is used for backend API requests and email extraction.", + "type": "string" + }, + "chatgptAccountId": { + "description": "Workspace/account identifier supplied by the client.", + "type": "string" + }, + "chatgptPlanType": { + "description": "Optional plan type supplied by the client.\n\nWhen `null`, Codex attempts to derive the plan type from access-token claims. If unavailable, the plan defaults to `unknown`.", + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensv2::LoginAccountParamsType" + } + }, + "title": "ChatgptAuthTokensv2::LoginAccountParams" + } + ] + }, + "CancelLoginAccountParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CancelLoginAccountParams", + "type": "object", + "required": [ + "loginId" + ], + "properties": { + "loginId": { + "type": "string" + } + } + }, + "ConsumeAccountRateLimitResetCreditParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConsumeAccountRateLimitResetCreditParams", + "type": "object", + "required": [ + "idempotencyKey" + ], + "properties": { + "creditId": { + "description": "Opaque reset-credit identifier to redeem. When omitted, the backend selects the next available credit.", + "type": [ + "string", + "null" + ] + }, + "idempotencyKey": { + "description": "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.", + "type": "string" + } + } + }, + "AddCreditsNudgeCreditType": { + "type": "string", + "enum": [ + "credits", + "usage_limit" + ] + }, + "SendAddCreditsNudgeEmailParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SendAddCreditsNudgeEmailParams", + "type": "object", + "required": [ + "creditType" + ], + "properties": { + "creditType": { + "$ref": "#/definitions/v2/AddCreditsNudgeCreditType" + } + } + }, + "FeedbackUploadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FeedbackUploadParams", + "type": "object", + "required": [ + "classification" + ], + "properties": { + "classification": { + "type": "string" + }, + "extraLogFiles": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "includeLogs": { + "type": "boolean" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "tags": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "threadId": { + "type": [ + "string", + "null" + ] + } + } + }, + "CommandExecTerminalSize": { + "description": "PTY size in character cells for `command/exec` PTY sessions.", + "type": "object", + "required": [ + "cols", + "rows" + ], + "properties": { + "cols": { + "description": "Terminal width in character cells.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "rows": { + "description": "Terminal height in character cells.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + } + }, + "CommandExecParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecParams", + "description": "Run a standalone command (argv vector) in the server sandbox without creating a thread or turn.\n\nThe final `command/exec` response is deferred until the process exits and is sent only after all `command/exec/outputDelta` notifications for that connection have been emitted.", + "type": "object", + "required": [ + "command" + ], + "properties": { + "command": { + "description": "Command argv vector. Empty arrays are rejected.", + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "description": "Optional working directory. Defaults to the server cwd.", + "type": [ + "string", + "null" + ] + }, + "disableOutputCap": { + "description": "Disable stdout/stderr capture truncation for this request.\n\nCannot be combined with `outputBytesCap`.", + "type": "boolean" + }, + "disableTimeout": { + "description": "Disable the timeout entirely for this request.\n\nCannot be combined with `timeoutMs`.", + "type": "boolean" + }, + "env": { + "description": "Optional environment overrides merged into the server-computed environment.\n\nMatching names override inherited values. Set a key to `null` to unset an inherited variable.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": [ + "string", + "null" + ] + } + }, + "outputBytesCap": { + "description": "Optional per-stream stdout/stderr capture cap in bytes.\n\nWhen omitted, the server default applies. Cannot be combined with `disableOutputCap`.", + "type": [ + "integer", + "null" + ], + "format": "uint", + "minimum": 0.0 + }, + "tty": { + "description": "Enable PTY mode.\n\nThis implies `streamStdin` and `streamStdoutStderr`.", + "type": "boolean" + }, + "processId": { + "description": "Optional client-supplied, connection-scoped process id.\n\nRequired for `tty`, `streamStdin`, `streamStdoutStderr`, and follow-up `command/exec/write`, `command/exec/resize`, and `command/exec/terminate` calls. When omitted, buffered execution gets an internal id that is not exposed to the client.", + "type": [ + "string", + "null" + ] + }, + "sandboxPolicy": { + "description": "Optional sandbox policy for this command.\n\nUses the same shape as thread/turn execution sandbox configuration and defaults to the user's configured policy when omitted. Cannot be combined with `permissionProfile`.", + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + }, + { + "type": "null" + } + ] + }, + "size": { + "description": "Optional initial PTY size in character cells. Only valid when `tty` is true.", + "anyOf": [ + { + "$ref": "#/definitions/v2/CommandExecTerminalSize" + }, + { + "type": "null" + } + ] + }, + "streamStdin": { + "description": "Allow follow-up `command/exec/write` requests to write stdin bytes.\n\nRequires a client-supplied `processId`.", + "type": "boolean" + }, + "streamStdoutStderr": { + "description": "Stream stdout/stderr via `command/exec/outputDelta` notifications.\n\nStreamed bytes are not duplicated into the final response and require a client-supplied `processId`.", + "type": "boolean" + }, + "timeoutMs": { + "description": "Optional timeout in milliseconds.\n\nWhen omitted, the server default applies. Cannot be combined with `disableTimeout`.", + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "CommandExecWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecWriteParams", + "description": "Write stdin bytes to a running `command/exec` session, close stdin, or both.", + "type": "object", + "required": [ + "processId" + ], + "properties": { + "closeStdin": { + "description": "Close stdin after writing `deltaBase64`, if present.", + "type": "boolean" + }, + "deltaBase64": { + "description": "Optional base64-encoded stdin bytes to write.", + "type": [ + "string", + "null" + ] + }, + "processId": { + "description": "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", + "type": "string" + } + } + }, + "CommandExecTerminateParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecTerminateParams", + "description": "Terminate a running `command/exec` session.", + "type": "object", + "required": [ + "processId" + ], + "properties": { + "processId": { + "description": "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", + "type": "string" + } + } + }, + "CommandExecResizeParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecResizeParams", + "description": "Resize a running PTY-backed `command/exec` session.", + "type": "object", + "required": [ + "processId", + "size" + ], + "properties": { + "processId": { + "description": "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", + "type": "string" + }, + "size": { + "description": "New PTY size in character cells.", + "allOf": [ + { + "$ref": "#/definitions/v2/CommandExecTerminalSize" + } + ] + } + } + }, + "ProcessTerminalSize": { + "description": "PTY size in character cells for `process/spawn` PTY sessions.", + "type": "object", + "required": [ + "cols", + "rows" + ], + "properties": { + "cols": { + "description": "Terminal width in character cells.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "rows": { + "description": "Terminal height in character cells.", + "type": "integer", + "format": "uint16", + "minimum": 0.0 + } + } + }, + "FsChangedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsChangedNotification", + "description": "Filesystem watch notification emitted for `fs/watch` subscribers.", + "type": "object", + "required": [ + "changedPaths", + "watchId" + ], + "properties": { + "changedPaths": { + "description": "File or directory paths associated with this event.", + "type": "array", + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + }, + "watchId": { + "description": "Watch identifier previously provided to `fs/watch`.", + "type": "string" + } + } + }, + "ExternalAgentConfigImportCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExternalAgentConfigImportCompletedNotification", + "type": "object", + "required": [ + "importId", + "itemTypeResults" + ], + "properties": { + "importId": { + "type": "string" + }, + "itemTypeResults": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportTypeResult" + } + } + } + }, + "ExternalAgentConfigImportProgressNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExternalAgentConfigImportProgressNotification", + "type": "object", + "required": [ + "importId", + "itemTypeResults" + ], + "properties": { + "importId": { + "type": "string" + }, + "itemTypeResults": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportTypeResult" + } + } + } + }, + "ExternalAgentConfigImportTypeResult": { + "type": "object", + "required": [ + "failures", + "itemType", + "successes" + ], + "properties": { + "failures": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportItemTypeFailure" + } + }, + "itemType": { + "$ref": "#/definitions/v2/ExternalAgentConfigMigrationItemType" + }, + "successes": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportItemTypeSuccess" + } + } + } + }, + "ConfigReadParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigReadParams", + "type": "object", + "properties": { + "cwd": { + "description": "Optional working directory to resolve project config layers. If specified, return the effective config as seen from that directory (i.e., including any project layers between `cwd` and the project/repo root).", + "type": [ + "string", + "null" + ] + }, + "includeLayers": { + "type": "boolean" + } + } + }, + "ExternalAgentConfigDetectParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExternalAgentConfigDetectParams", + "type": "object", + "properties": { + "cwds": { + "description": "Zero or more working directories to include for repo-scoped detection.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "includeHome": { + "description": "If true, include detection under the user's home directory.", + "type": "boolean" + } + } + }, + "CommandMigration": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + "ExternalAgentConfigMigrationItem": { + "type": "object", + "required": [ + "description", + "itemType" + ], + "properties": { + "cwd": { + "description": "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", + "type": [ + "string", + "null" + ] + }, + "description": { + "type": "string" + }, + "details": { + "anyOf": [ + { + "$ref": "#/definitions/v2/MigrationDetails" + }, + { + "type": "null" + } + ] + }, + "itemType": { + "$ref": "#/definitions/v2/ExternalAgentConfigMigrationItemType" + } + } + }, + "ExternalAgentConfigMigrationItemType": { + "type": "string", + "enum": [ + "AGENTS_MD", + "CONFIG", + "SKILLS", + "PLUGINS", + "MCP_SERVER_CONFIG", + "SUBAGENTS", + "HOOKS", + "COMMANDS", + "SESSIONS" + ] + }, + "HookMigration": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + "McpServerMigration": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + "MigrationDetails": { + "type": "object", + "properties": { + "commands": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/CommandMigration" + } + }, + "hooks": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/HookMigration" + } + }, + "mcpServers": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/McpServerMigration" + } + }, + "plugins": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/PluginsMigration" + } + }, + "sessions": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/SessionMigration" + } + }, + "skills": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/SkillMigration" + } + }, + "subagents": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/SubagentMigration" + } + } + } + }, + "PluginsMigration": { + "type": "object", + "required": [ + "marketplaceName", + "pluginNames" + ], + "properties": { + "marketplaceName": { + "type": "string" + }, + "pluginNames": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "SessionMigration": { + "type": "object", + "required": [ + "cwd", + "path" + ], + "properties": { + "cwd": { + "type": "string" + }, + "path": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + } + } + }, + "SkillMigration": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + "SubagentMigration": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + "ExternalAgentConfigImportParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExternalAgentConfigImportParams", + "type": "object", + "required": [ + "migrationItems" + ], + "properties": { + "migrationItems": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigMigrationItem" + } + }, + "source": { + "description": "Source product that produced the migration items. Missing means unspecified.", + "type": [ + "string", + "null" + ] + } + } + }, + "MergeStrategy": { + "type": "string", + "enum": [ + "replace", + "upsert" + ] + }, + "ConfigValueWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigValueWriteParams", + "type": "object", + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "properties": { + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + }, + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/v2/MergeStrategy" + }, + "value": true + } + }, + "ConfigEdit": { + "type": "object", + "required": [ + "keyPath", + "mergeStrategy", + "value" + ], + "properties": { + "keyPath": { + "type": "string" + }, + "mergeStrategy": { + "$ref": "#/definitions/v2/MergeStrategy" + }, + "value": true + } + }, + "ConfigBatchWriteParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigBatchWriteParams", + "type": "object", + "required": [ + "edits" + ], + "properties": { + "edits": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfigEdit" + } + }, + "expectedVersion": { + "type": [ + "string", + "null" + ] + }, + "filePath": { + "description": "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + "type": [ + "string", + "null" + ] + }, + "reloadUserConfig": { + "description": "When true, hot-reload the updated user config into all loaded threads after writing.", + "type": "boolean" + } + } + }, + "GetAccountParams": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetAccountParams", + "type": "object", + "properties": { + "refreshToken": { + "description": "When `true`, requests a proactive token refresh before returning.\n\nIn managed auth mode this triggers the normal refresh-token flow. In external auth mode this flag is ignored. Clients should refresh tokens themselves and call `account/login/start` with `chatgptAuthTokens`.", + "type": "boolean" + } + } + }, + "ActivePermissionProfile": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "extends": { + "description": "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", + "default": null, + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", + "type": "string" + } + } + }, + "AgentPath": { + "type": "string" + }, + "CodexErrorInfo": { + "description": "This translation layer make sure that we expose codex error code in camel case.\n\nWhen an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.", + "oneOf": [ + { + "type": "string", + "enum": [ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other" + ] + }, + { + "type": "object", + "required": [ + "httpConnectionFailed" + ], + "properties": { + "httpConnectionFailed": { + "type": "object", + "properties": { + "httpStatusCode": { + "type": [ + "integer", + "null" + ], + "format": "uint16", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false, + "title": "HttpConnectionFailedCodexErrorInfo" + }, + { + "description": "Failed to connect to the response SSE stream.", + "type": "object", + "required": [ + "responseStreamConnectionFailed" + ], + "properties": { + "responseStreamConnectionFailed": { + "type": "object", + "properties": { + "httpStatusCode": { + "type": [ + "integer", + "null" + ], + "format": "uint16", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false, + "title": "ResponseStreamConnectionFailedCodexErrorInfo" + }, + { + "description": "The response SSE stream disconnected in the middle of a turn before completion.", + "type": "object", + "required": [ + "responseStreamDisconnected" + ], + "properties": { + "responseStreamDisconnected": { + "type": "object", + "properties": { + "httpStatusCode": { + "type": [ + "integer", + "null" + ], + "format": "uint16", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false, + "title": "ResponseStreamDisconnectedCodexErrorInfo" + }, + { + "description": "Reached the retry limit for responses.", + "type": "object", + "required": [ + "responseTooManyFailedAttempts" + ], + "properties": { + "responseTooManyFailedAttempts": { + "type": "object", + "properties": { + "httpStatusCode": { + "type": [ + "integer", + "null" + ], + "format": "uint16", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false, + "title": "ResponseTooManyFailedAttemptsCodexErrorInfo" + }, + { + "description": "Returned when `turn/start` or `turn/steer` is submitted while the current active turn cannot accept same-turn steering, for example `/review` or manual `/compact`.", + "type": "object", + "required": [ + "activeTurnNotSteerable" + ], + "properties": { + "activeTurnNotSteerable": { + "type": "object", + "required": [ + "turnKind" + ], + "properties": { + "turnKind": { + "$ref": "#/definitions/v2/NonSteerableTurnKind" + } + } + } + }, + "additionalProperties": false, + "title": "ActiveTurnNotSteerableCodexErrorInfo" + } + ] + }, + "CollabAgentState": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "message": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/v2/CollabAgentStatus" + } + } + }, + "CollabAgentStatus": { + "type": "string", + "enum": [ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound" + ] + }, + "CollabAgentTool": { + "type": "string", + "enum": [ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent" + ] + }, + "CollabAgentToolCallStatus": { + "type": "string", + "enum": [ + "inProgress", + "completed", + "failed" + ] + }, + "CommandAction": { + "oneOf": [ + { + "type": "object", + "required": [ + "command", + "name", + "path", + "type" + ], + "properties": { + "command": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "type": { + "type": "string", + "enum": [ + "read" + ], + "title": "ReadCommandActionType" + } + }, + "title": "ReadCommandAction" + }, + { + "type": "object", + "required": [ + "command", + "type" + ], + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "listFiles" + ], + "title": "ListFilesCommandActionType" + } + }, + "title": "ListFilesCommandAction" + }, + { + "type": "object", + "required": [ + "command", + "type" + ], + "properties": { + "command": { + "type": "string" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "search" + ], + "title": "SearchCommandActionType" + } + }, + "title": "SearchCommandAction" + }, + { + "type": "object", + "required": [ + "command", + "type" + ], + "properties": { + "command": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "unknown" + ], + "title": "UnknownCommandActionType" + } + }, + "title": "UnknownCommandAction" + } + ] + }, + "CommandExecutionSource": { + "type": "string", + "enum": [ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction" + ] + }, + "CommandExecutionStatus": { + "type": "string", + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ] + }, + "DynamicToolCallOutputContentItem": { + "oneOf": [ + { + "type": "object", + "required": [ + "text", + "type" + ], + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "inputText" + ], + "title": "InputTextDynamicToolCallOutputContentItemType" + } + }, + "title": "InputTextDynamicToolCallOutputContentItem" + }, + { + "type": "object", + "required": [ + "imageUrl", + "type" + ], + "properties": { + "imageUrl": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "inputImage" + ], + "title": "InputImageDynamicToolCallOutputContentItemType" + } + }, + "title": "InputImageDynamicToolCallOutputContentItem" + } + ] + }, + "DynamicToolCallStatus": { + "type": "string", + "enum": [ + "inProgress", + "completed", + "failed" + ] + }, + "FileUpdateChange": { + "type": "object", + "required": [ + "diff", + "kind", + "path" + ], + "properties": { + "diff": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/v2/PatchChangeKind" + }, + "path": { + "type": "string" + } + } + }, + "GitInfo": { + "type": "object", + "properties": { + "branch": { + "type": [ + "string", + "null" + ] + }, + "originUrl": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + } + } + }, + "HookPromptFragment": { + "type": "object", + "required": [ + "hookRunId", + "text" + ], + "properties": { + "hookRunId": { + "type": "string" + }, + "text": { + "type": "string" + } + } + }, + "McpToolCallAppContext": { + "type": "object", + "required": [ + "connectorId" + ], + "properties": { + "actionName": { + "type": [ + "string", + "null" + ] + }, + "appName": { + "type": [ + "string", + "null" + ] + }, + "connectorId": { + "type": "string" + }, + "linkId": { + "type": [ + "string", + "null" + ] + }, + "resourceUri": { + "type": [ + "string", + "null" + ] + }, + "templateId": { + "type": [ + "string", + "null" + ] + } + } + }, + "McpToolCallError": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + }, + "McpToolCallResult": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "_meta": true, + "content": { + "type": "array", + "items": true + }, + "structuredContent": true + } + }, + "McpToolCallStatus": { + "type": "string", + "enum": [ + "inProgress", + "completed", + "failed" + ] + }, + "MemoryCitation": { + "type": "object", + "required": [ + "entries", + "threadIds" + ], + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/MemoryCitationEntry" + } + }, + "threadIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "MemoryCitationEntry": { + "type": "object", + "required": [ + "lineEnd", + "lineStart", + "note", + "path" + ], + "properties": { + "lineEnd": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "lineStart": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "note": { + "type": "string" + }, + "path": { + "type": "string" + } + } + }, + "NonSteerableTurnKind": { + "type": "string", + "enum": [ + "review", + "compact" + ] + }, + "PatchApplyStatus": { + "type": "string", + "enum": [ + "inProgress", + "completed", + "failed", + "declined" + ] + }, + "PatchChangeKind": { + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "add" + ], + "title": "AddPatchChangeKindType" + } + }, + "title": "AddPatchChangeKind" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "delete" + ], + "title": "DeletePatchChangeKindType" + } + }, + "title": "DeletePatchChangeKind" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "move_path": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "update" + ], + "title": "UpdatePatchChangeKindType" + } + }, + "title": "UpdatePatchChangeKind" + } + ] + }, + "SessionSource": { + "oneOf": [ + { + "type": "string", + "enum": [ + "cli", + "vscode", + "exec", + "appServer", + "unknown" + ] + }, + { + "type": "object", + "required": [ + "custom" + ], + "properties": { + "custom": { + "type": "string" + } + }, + "additionalProperties": false, + "title": "CustomSessionSource" + }, + { + "type": "object", + "required": [ + "subAgent" + ], + "properties": { + "subAgent": { + "$ref": "#/definitions/v2/SubAgentSource" + } + }, + "additionalProperties": false, + "title": "SubAgentSessionSource" + } + ] + }, + "SubAgentActivityKind": { + "type": "string", + "enum": [ + "started", + "interacted", + "interrupted" + ] + }, + "SubAgentSource": { + "oneOf": [ + { + "type": "string", + "enum": [ + "review", + "compact", + "memory_consolidation" + ] + }, + { + "type": "object", + "required": [ + "thread_spawn" + ], + "properties": { + "thread_spawn": { + "type": "object", + "required": [ + "depth", + "parent_thread_id" + ], + "properties": { + "agent_nickname": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "agent_path": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/v2/AgentPath" + }, + { + "type": "null" + } + ] + }, + "agent_role": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "depth": { + "type": "integer", + "format": "int32" + }, + "parent_thread_id": { + "$ref": "#/definitions/v2/ThreadId" + } + } + } + }, + "additionalProperties": false, + "title": "ThreadSpawnSubAgentSource" + }, + { + "type": "object", + "required": [ + "other" + ], + "properties": { + "other": { + "type": "string" + } + }, + "additionalProperties": false, + "title": "OtherSubAgentSource" + } + ] + }, + "Thread": { + "type": "object", + "required": [ + "cliVersion", + "createdAt", + "cwd", + "ephemeral", + "id", + "modelProvider", + "preview", + "sessionId", + "source", + "status", + "turns", + "updatedAt" + ], + "properties": { + "agentNickname": { + "description": "Optional random unique nickname assigned to an AgentControl-spawned sub-agent.", + "type": [ + "string", + "null" + ] + }, + "agentRole": { + "description": "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.", + "type": [ + "string", + "null" + ] + }, + "cliVersion": { + "description": "Version of the CLI that created the thread.", + "type": "string" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the thread was created.", + "type": "integer", + "format": "int64" + }, + "cwd": { + "description": "Working directory captured for the thread.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "ephemeral": { + "description": "Whether the thread is ephemeral and should not be materialized on disk.", + "type": "boolean" + }, + "updatedAt": { + "description": "Unix timestamp (in seconds) when the thread was last updated.", + "type": "integer", + "format": "int64" + }, + "forkedFromId": { + "description": "Source thread id when this thread was created by forking another thread.", + "type": [ + "string", + "null" + ] + }, + "gitInfo": { + "description": "Optional Git metadata captured when the thread was created.", + "anyOf": [ + { + "$ref": "#/definitions/v2/GitInfo" + }, + { + "type": "null" + } + ] + }, + "turns": { + "description": "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "type": "array", + "items": { + "$ref": "#/definitions/v2/Turn" + } + }, + "id": { + "description": "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", + "type": "string" + }, + "modelProvider": { + "description": "Model provider used for this thread (for example, 'openai').", + "type": "string" + }, + "name": { + "description": "Optional user-facing thread title.", + "type": [ + "string", + "null" + ] + }, + "parentThreadId": { + "description": "The ID of the parent thread. This will only be set if this thread is a subagent.", + "type": [ + "string", + "null" + ] + }, + "path": { + "description": "[UNSTABLE] Path to the thread on disk.", + "type": [ + "string", + "null" + ] + }, + "preview": { + "description": "Usually the first user message in the thread, if available.", + "type": "string" + }, + "recencyAt": { + "description": "Unix timestamp (in seconds) used for thread recency ordering.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "sessionId": { + "description": "Session id shared by threads that belong to the same session tree.", + "type": "string" + }, + "source": { + "description": "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", + "allOf": [ + { + "$ref": "#/definitions/v2/SessionSource" + } + ] + }, + "status": { + "description": "Current runtime status for the thread.", + "allOf": [ + { + "$ref": "#/definitions/v2/ThreadStatus" + } + ] + }, + "threadSource": { + "description": "Optional analytics source classification for this thread.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadSource" + }, + { + "type": "null" + } + ] + } + } + }, + "ThreadActiveFlag": { + "type": "string", + "enum": [ + "waitingOnApproval", + "waitingOnUserInput" + ] + }, + "ThreadExtra": { + "description": "Extra app-server data for a thread.", + "type": "object" + }, + "ThreadId": { + "type": "string" + }, + "ThreadItem": { + "oneOf": [ + { + "type": "object", + "required": [ + "content", + "id", + "type" + ], + "properties": { + "clientId": { + "type": [ + "string", + "null" + ] + }, + "content": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/UserInput" + } + }, + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "userMessage" + ], + "title": "UserMessageThreadItemType" + } + }, + "title": "UserMessageThreadItem" + }, + { + "type": "object", + "required": [ + "fragments", + "id", + "type" + ], + "properties": { + "fragments": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/HookPromptFragment" + } + }, + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "hookPrompt" + ], + "title": "HookPromptThreadItemType" + } + }, + "title": "HookPromptThreadItem" + }, + { + "type": "object", + "required": [ + "id", + "text", + "type" + ], + "properties": { + "id": { + "type": "string" + }, + "memoryCitation": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/v2/MemoryCitation" + }, + { + "type": "null" + } + ] + }, + "phase": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/v2/MessagePhase" + }, + { + "type": "null" + } + ] + }, + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "agentMessage" + ], + "title": "AgentMessageThreadItemType" + } + }, + "title": "AgentMessageThreadItem" + }, + { + "description": "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", + "type": "object", + "required": [ + "id", + "text", + "type" + ], + "properties": { + "id": { + "type": "string" + }, + "text": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "plan" + ], + "title": "PlanThreadItemType" + } + }, + "title": "PlanThreadItem" + }, + { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "content": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "summary": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "reasoning" + ], + "title": "ReasoningThreadItemType" + } + }, + "title": "ReasoningThreadItem" + }, + { + "type": "object", + "required": [ + "command", + "commandActions", + "cwd", + "id", + "status", + "type" + ], + "properties": { + "aggregatedOutput": { + "description": "The command's output, aggregated from stdout and stderr.", + "type": [ + "string", + "null" + ] + }, + "command": { + "description": "The command to be executed.", + "type": "string" + }, + "commandActions": { + "description": "A best-effort parsing of the command to understand the action(s) it will perform. This returns a list of CommandAction objects because a single shell command may be composed of many commands piped together.", + "type": "array", + "items": { + "$ref": "#/definitions/v2/CommandAction" + } + }, + "cwd": { + "description": "The command's working directory.", + "allOf": [ + { + "$ref": "#/definitions/v2/LegacyAppPathString" + } + ] + }, + "durationMs": { + "description": "The duration of the command execution in milliseconds.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "exitCode": { + "description": "The command's exit code.", + "type": [ + "integer", + "null" + ], + "format": "int32" + }, + "id": { + "type": "string" + }, + "processId": { + "description": "Identifier for the underlying PTY process (when available).", + "type": [ + "string", + "null" + ] + }, + "source": { + "default": "agent", + "allOf": [ + { + "$ref": "#/definitions/v2/CommandExecutionSource" + } + ] + }, + "status": { + "$ref": "#/definitions/v2/CommandExecutionStatus" + }, + "type": { + "type": "string", + "enum": [ + "commandExecution" + ], + "title": "CommandExecutionThreadItemType" + } + }, + "title": "CommandExecutionThreadItem" + }, + { + "type": "object", + "required": [ + "changes", + "id", + "status", + "type" + ], + "properties": { + "changes": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/FileUpdateChange" + } + }, + "id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/v2/PatchApplyStatus" + }, + "type": { + "type": "string", + "enum": [ + "fileChange" + ], + "title": "FileChangeThreadItemType" + } + }, + "title": "FileChangeThreadItem" + }, + { + "type": "object", + "required": [ + "arguments", + "id", + "server", + "status", + "tool", + "type" + ], + "properties": { + "appContext": { + "anyOf": [ + { + "$ref": "#/definitions/v2/McpToolCallAppContext" + }, + { + "type": "null" + } + ] + }, + "arguments": true, + "durationMs": { + "description": "The duration of the MCP tool call in milliseconds.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "error": { + "anyOf": [ + { + "$ref": "#/definitions/v2/McpToolCallError" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "mcpAppResourceUri": { + "description": "Deprecated: use `appContext.resourceUri` instead.", + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "result": { + "anyOf": [ + { + "$ref": "#/definitions/v2/McpToolCallResult" + }, + { + "type": "null" + } + ] + }, + "server": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/v2/McpToolCallStatus" + }, + "tool": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallThreadItemType" + } + }, + "title": "McpToolCallThreadItem" + }, + { + "type": "object", + "required": [ + "arguments", + "id", + "status", + "tool", + "type" + ], + "properties": { + "arguments": true, + "contentItems": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/DynamicToolCallOutputContentItem" + } + }, + "durationMs": { + "description": "The duration of the dynamic tool call in milliseconds.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "id": { + "type": "string" + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "status": { + "$ref": "#/definitions/v2/DynamicToolCallStatus" + }, + "success": { + "type": [ + "boolean", + "null" + ] + }, + "tool": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "dynamicToolCall" + ], + "title": "DynamicToolCallThreadItemType" + } + }, + "title": "DynamicToolCallThreadItem" + }, + { + "type": "object", + "required": [ + "agentsStates", + "id", + "receiverThreadIds", + "senderThreadId", + "status", + "tool", + "type" + ], + "properties": { + "agentsStates": { + "description": "Last known status of the target agents, when available.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/v2/CollabAgentState" + } + }, + "id": { + "description": "Unique identifier for this collab tool call.", + "type": "string" + }, + "model": { + "description": "Model requested for the spawned agent, when applicable.", + "type": [ + "string", + "null" + ] + }, + "prompt": { + "description": "Prompt text sent as part of the collab tool call, when available.", + "type": [ + "string", + "null" + ] + }, + "reasoningEffort": { + "description": "Reasoning effort requested for the spawned agent, when applicable.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "receiverThreadIds": { + "description": "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", + "type": "array", + "items": { + "type": "string" + } + }, + "senderThreadId": { + "description": "Thread ID of the agent issuing the collab request.", + "type": "string" + }, + "status": { + "description": "Current status of the collab tool call.", + "allOf": [ + { + "$ref": "#/definitions/v2/CollabAgentToolCallStatus" + } + ] + }, + "tool": { + "description": "Name of the collab tool that was invoked.", + "allOf": [ + { + "$ref": "#/definitions/v2/CollabAgentTool" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "collabAgentToolCall" + ], + "title": "CollabAgentToolCallThreadItemType" + } + }, + "title": "CollabAgentToolCallThreadItem" + }, + { + "type": "object", + "required": [ + "agentPath", + "agentThreadId", + "id", + "kind", + "type" + ], + "properties": { + "agentPath": { + "type": "string" + }, + "agentThreadId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "kind": { + "$ref": "#/definitions/v2/SubAgentActivityKind" + }, + "type": { + "type": "string", + "enum": [ + "subAgentActivity" + ], + "title": "SubAgentActivityThreadItemType" + } + }, + "title": "SubAgentActivityThreadItem" + }, + { + "type": "object", + "required": [ + "id", + "query", + "type" + ], + "properties": { + "action": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchAction" + }, + { + "type": "null" + } + ] + }, + "id": { + "type": "string" + }, + "query": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "webSearch" + ], + "title": "WebSearchThreadItemType" + } + }, + "title": "WebSearchThreadItem" + }, + { + "type": "object", + "required": [ + "id", + "path", + "type" + ], + "properties": { + "id": { + "type": "string" + }, + "path": { + "$ref": "#/definitions/v2/LegacyAppPathString" + }, + "type": { + "type": "string", + "enum": [ + "imageView" + ], + "title": "ImageViewThreadItemType" + } + }, + "title": "ImageViewThreadItem" + }, + { + "type": "object", + "required": [ + "durationMs", + "id", + "type" + ], + "properties": { + "durationMs": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "sleep" + ], + "title": "SleepThreadItemType" + } + }, + "title": "SleepThreadItem" + }, + { + "type": "object", + "required": [ + "id", + "result", + "status", + "type" + ], + "properties": { + "id": { + "type": "string" + }, + "result": { + "type": "string" + }, + "revisedPrompt": { + "type": [ + "string", + "null" + ] + }, + "savedPath": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "status": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "imageGeneration" + ], + "title": "ImageGenerationThreadItemType" + } + }, + "title": "ImageGenerationThreadItem" + }, + { + "type": "object", + "required": [ + "id", + "review", + "type" + ], + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "enteredReviewMode" + ], + "title": "EnteredReviewModeThreadItemType" + } + }, + "title": "EnteredReviewModeThreadItem" + }, + { + "type": "object", + "required": [ + "id", + "review", + "type" + ], + "properties": { + "id": { + "type": "string" + }, + "review": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "exitedReviewMode" + ], + "title": "ExitedReviewModeThreadItemType" + } + }, + "title": "ExitedReviewModeThreadItem" + }, + { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "contextCompaction" + ], + "title": "ContextCompactionThreadItemType" + } + }, + "title": "ContextCompactionThreadItem" + } + ] + }, + "ThreadStatus": { + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "notLoaded" + ], + "title": "NotLoadedThreadStatusType" + } + }, + "title": "NotLoadedThreadStatus" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "idle" + ], + "title": "IdleThreadStatusType" + } + }, + "title": "IdleThreadStatus" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "systemError" + ], + "title": "SystemErrorThreadStatusType" + } + }, + "title": "SystemErrorThreadStatus" + }, + { + "type": "object", + "required": [ + "activeFlags", + "type" + ], + "properties": { + "activeFlags": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ThreadActiveFlag" + } + }, + "type": { + "type": "string", + "enum": [ + "active" + ], + "title": "ActiveThreadStatusType" + } + }, + "title": "ActiveThreadStatus" + } + ] + }, + "Turn": { + "type": "object", + "required": [ + "id", + "items", + "status" + ], + "properties": { + "completedAt": { + "description": "Unix timestamp (in seconds) when the turn completed.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "durationMs": { + "description": "Duration between turn start and completion in milliseconds, if known.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "error": { + "description": "Only populated when the Turn's status is failed.", + "anyOf": [ + { + "$ref": "#/definitions/v2/TurnError" + }, + { + "type": "null" + } + ] + }, + "id": { + "description": "Identifier for this turn. Codex-generated turn IDs are UUIDv7.", + "type": "string" + }, + "items": { + "description": "Thread items currently included in this turn payload.", + "type": "array", + "items": { + "$ref": "#/definitions/v2/ThreadItem" + } + }, + "itemsView": { + "description": "Describes how much of `items` has been loaded for this turn.", + "default": "full", + "allOf": [ + { + "$ref": "#/definitions/v2/TurnItemsView" + } + ] + }, + "startedAt": { + "description": "Unix timestamp (in seconds) when the turn started.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "status": { + "$ref": "#/definitions/v2/TurnStatus" + } + } + }, + "TurnError": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "additionalDetails": { + "default": null, + "type": [ + "string", + "null" + ] + }, + "codexErrorInfo": { + "anyOf": [ + { + "$ref": "#/definitions/v2/CodexErrorInfo" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": "string" + } + } + }, + "TurnStatus": { + "type": "string", + "enum": [ + "completed", + "interrupted", + "failed", + "inProgress" + ] + }, + "WebSearchAction": { + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "queries": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "query": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "search" + ], + "title": "SearchWebSearchActionType" + } + }, + "title": "SearchWebSearchAction" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "openPage" + ], + "title": "OpenPageWebSearchActionType" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "title": "OpenPageWebSearchAction" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "pattern": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "findInPage" + ], + "title": "FindInPageWebSearchActionType" + }, + "url": { + "type": [ + "string", + "null" + ] + } + }, + "title": "FindInPageWebSearchAction" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "other" + ], + "title": "OtherWebSearchActionType" + } + }, + "title": "OtherWebSearchAction" + } + ] + }, + "ThreadStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadStartResponse", + "type": "object", + "required": [ + "approvalPolicy", + "approvalsReviewer", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "properties": { + "sandbox": { + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance.", + "allOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + } + ] + }, + "approvalPolicy": { + "$ref": "#/definitions/v2/AskForApproval" + }, + "approvalsReviewer": { + "description": "Reviewer currently used for approval requests on this thread.", + "allOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + } + ] + }, + "cwd": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "instructionSources": { + "description": "Environment-native paths to instruction source files currently loaded for this thread.", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/LegacyAppPathString" + } + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "serviceTier": { + "type": [ + "string", + "null" + ] + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "thread": { + "$ref": "#/definitions/v2/Thread" + } + } + }, + "TurnsPage": { + "type": "object", + "required": [ + "data" + ], + "properties": { + "backwardsCursor": { + "type": [ + "string", + "null" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/Turn" + } + }, + "nextCursor": { + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadResumeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadResumeResponse", + "type": "object", + "required": [ + "approvalPolicy", + "approvalsReviewer", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "properties": { + "sandbox": { + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance.", + "allOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + } + ] + }, + "approvalPolicy": { + "$ref": "#/definitions/v2/AskForApproval" + }, + "approvalsReviewer": { + "description": "Reviewer currently used for approval requests on this thread.", + "allOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + } + ] + }, + "cwd": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "thread": { + "$ref": "#/definitions/v2/Thread" + }, + "instructionSources": { + "description": "Environment-native paths to instruction source files currently loaded for this thread.", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/LegacyAppPathString" + } + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "serviceTier": { + "type": [ + "string", + "null" + ] + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + } + } + }, + "ThreadForkResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadForkResponse", + "type": "object", + "required": [ + "approvalPolicy", + "approvalsReviewer", + "cwd", + "model", + "modelProvider", + "sandbox", + "thread" + ], + "properties": { + "sandbox": { + "description": "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance.", + "allOf": [ + { + "$ref": "#/definitions/v2/SandboxPolicy" + } + ] + }, + "approvalPolicy": { + "$ref": "#/definitions/v2/AskForApproval" + }, + "approvalsReviewer": { + "description": "Reviewer currently used for approval requests on this thread.", + "allOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + } + ] + }, + "cwd": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "instructionSources": { + "description": "Environment-native paths to instruction source files currently loaded for this thread.", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/LegacyAppPathString" + } + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "thread": { + "$ref": "#/definitions/v2/Thread" + }, + "reasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "serviceTier": { + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadArchiveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadArchiveResponse", + "type": "object" + }, + "ThreadDeleteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadDeleteResponse", + "type": "object" + }, + "ThreadUnsubscribeStatus": { + "type": "string", + "enum": [ + "notLoaded", + "notSubscribed", + "unsubscribed" + ] + }, + "ThreadUnsubscribeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadUnsubscribeResponse", + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/v2/ThreadUnsubscribeStatus" + } + } + }, + "RemoteControlStatusChangedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RemoteControlStatusChangedNotification", + "description": "Current remote-control connection status and remote identity exposed to clients.", + "type": "object", + "required": [ + "installationId", + "serverName", + "status" + ], + "properties": { + "environmentId": { + "type": [ + "string", + "null" + ] + }, + "installationId": { + "type": "string" + }, + "serverName": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/v2/RemoteControlConnectionStatus" + } + } + }, + "AppListUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppListUpdatedNotification", + "description": "EXPERIMENTAL - notification emitted when the app list changes.", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AppInfo" + } + } + } + }, + "ThreadSetNameResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadSetNameResponse", + "type": "object" + }, + "ThreadGoal": { + "type": "object", + "required": [ + "createdAt", + "objective", + "status", + "threadId", + "timeUsedSeconds", + "tokensUsed", + "updatedAt" + ], + "properties": { + "createdAt": { + "type": "integer", + "format": "int64" + }, + "objective": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/v2/ThreadGoalStatus" + }, + "threadId": { + "type": "string" + }, + "timeUsedSeconds": { + "type": "integer", + "format": "int64" + }, + "tokenBudget": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "tokensUsed": { + "type": "integer", + "format": "int64" + }, + "updatedAt": { + "type": "integer", + "format": "int64" + } + } + }, + "ThreadGoalSetResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadGoalSetResponse", + "type": "object", + "required": [ + "goal" + ], + "properties": { + "goal": { + "$ref": "#/definitions/v2/ThreadGoal" + } + } + }, + "ThreadGoalGetResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadGoalGetResponse", + "type": "object", + "properties": { + "goal": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ThreadGoal" + }, + { + "type": "null" + } + ] + } + } + }, + "ThreadGoalClearResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadGoalClearResponse", + "type": "object", + "required": [ + "cleared" + ], + "properties": { + "cleared": { + "type": "boolean" + } + } + }, + "ThreadMetadataUpdateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadMetadataUpdateResponse", + "type": "object", + "required": [ + "thread" + ], + "properties": { + "thread": { + "$ref": "#/definitions/v2/Thread" + } + } + }, + "AccountRateLimitsUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AccountRateLimitsUpdatedNotification", + "description": "Sparse rolling rate-limit update.\n\nClients should merge available values into the most recent `account/rateLimits/read` response or refetch that snapshot. Nullable account metadata may be unavailable in a rolling update and does not clear a previously observed value.", + "type": "object", + "required": [ + "rateLimits" + ], + "properties": { + "rateLimits": { + "$ref": "#/definitions/v2/RateLimitSnapshot" + } + } + }, + "AccountUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AccountUpdatedNotification", + "type": "object", + "properties": { + "authMode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AuthMode" + }, + { + "type": "null" + } + ] + }, + "planType": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PlanType" + }, + { + "type": "null" + } + ] + } + } + }, + "AuthMode": { + "description": "Authentication mode for OpenAI-backed providers.", + "oneOf": [ + { + "description": "OpenAI API key provided by the caller and stored by Codex.", + "type": "string", + "enum": [ + "apikey" + ] + }, + { + "description": "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + "type": "string", + "enum": [ + "chatgpt" + ] + }, + { + "description": "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + "type": "string", + "enum": [ + "chatgptAuthTokens" + ] + }, + { + "description": "Backend auth supplied as request headers.", + "type": "string", + "enum": [ + "headers" + ] + }, + { + "description": "Programmatic Codex auth backed by a registered Agent Identity.", + "type": "string", + "enum": [ + "agentIdentity" + ] + }, + { + "description": "Programmatic Codex auth backed by a personal access token.", + "type": "string", + "enum": [ + "personalAccessToken" + ] + }, + { + "description": "Amazon Bedrock bearer token managed by Codex.", + "type": "string", + "enum": [ + "bedrockApiKey" + ] + } + ] + }, + "ThreadUnarchiveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadUnarchiveResponse", + "type": "object", + "required": [ + "thread" + ], + "properties": { + "thread": { + "$ref": "#/definitions/v2/Thread" + } + } + }, + "ThreadCompactStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadCompactStartResponse", + "type": "object" + }, + "ThreadShellCommandResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadShellCommandResponse", + "type": "object" + }, + "ThreadApproveGuardianDeniedActionResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadApproveGuardianDeniedActionResponse", + "type": "object" + }, + "McpServerStatusUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerStatusUpdatedNotification", + "type": "object", + "required": [ + "name", + "status" + ], + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "failureReason": { + "anyOf": [ + { + "$ref": "#/definitions/v2/McpServerStartupFailureReason" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/v2/McpServerStartupState" + }, + "threadId": { + "type": [ + "string", + "null" + ] + } + } + }, + "McpServerStartupState": { + "type": "string", + "enum": [ + "starting", + "ready", + "failed", + "cancelled" + ] + }, + "McpServerStartupFailureReason": { + "type": "string", + "enum": [ + "reauthenticationRequired" + ] + }, + "McpServerOauthLoginCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerOauthLoginCompletedNotification", + "type": "object", + "required": [ + "name", + "success" + ], + "properties": { + "error": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "success": { + "type": "boolean" + }, + "threadId": { + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadRollbackResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadRollbackResponse", + "type": "object", + "required": [ + "thread" + ], + "properties": { + "thread": { + "description": "The updated thread after applying the rollback, with `turns` populated.\n\nThe ThreadItems stored in each Turn are lossy since we explicitly do not persist all agent interactions, such as command executions. This is the same behavior as `thread/resume`.", + "allOf": [ + { + "$ref": "#/definitions/v2/Thread" + } + ] + } + } + }, + "ThreadListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadListResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "backwardsCursor": { + "description": "Opaque cursor to pass as `cursor` when reversing `sortDirection`. This is only populated when the page contains at least one thread. Use it with the opposite `sortDirection`; for timestamp sorts it anchors at the start of the page timestamp so same-second updates are not skipped.", + "type": [ + "string", + "null" + ] + }, + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/Thread" + } + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. if None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadSearchResult": { + "type": "object", + "required": [ + "snippet", + "thread" + ], + "properties": { + "snippet": { + "type": "string" + }, + "thread": { + "$ref": "#/definitions/v2/Thread" + } + } + }, + "McpToolCallProgressNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpToolCallProgressNotification", + "type": "object", + "required": [ + "itemId", + "message", + "threadId", + "turnId" + ], + "properties": { + "itemId": { + "type": "string" + }, + "message": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ThreadLoadedListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadLoadedListResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "description": "Thread ids for sessions currently loaded in memory.", + "type": "array", + "items": { + "type": "string" + } + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. if None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadReadResponse", + "type": "object", + "required": [ + "thread" + ], + "properties": { + "thread": { + "$ref": "#/definitions/v2/Thread" + } + } + }, + "ServerRequestResolvedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ServerRequestResolvedNotification", + "type": "object", + "required": [ + "requestId", + "threadId" + ], + "properties": { + "requestId": { + "$ref": "#/definitions/v2/RequestId" + }, + "threadId": { + "type": "string" + } + } + }, + "RequestId": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer", + "format": "int64" + } + ] + }, + "ThreadInjectItemsResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadInjectItemsResponse", + "type": "object" + }, + "SkillDependencies": { + "type": "object", + "required": [ + "tools" + ], + "properties": { + "tools": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/SkillToolDependency" + } + } + } + }, + "SkillErrorInfo": { + "type": "object", + "required": [ + "message", + "path" + ], + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + } + }, + "SkillInterface": { + "type": "object", + "properties": { + "brandColor": { + "type": [ + "string", + "null" + ] + }, + "defaultPrompt": { + "type": [ + "string", + "null" + ] + }, + "displayName": { + "type": [ + "string", + "null" + ] + }, + "iconLarge": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "iconSmall": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "shortDescription": { + "type": [ + "string", + "null" + ] + } + } + }, + "SkillMetadata": { + "type": "object", + "required": [ + "description", + "enabled", + "name", + "path", + "scope" + ], + "properties": { + "dependencies": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SkillDependencies" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "scope": { + "$ref": "#/definitions/v2/SkillScope" + }, + "shortDescription": { + "description": "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", + "type": [ + "string", + "null" + ] + } + } + }, + "SkillScope": { + "type": "string", + "enum": [ + "user", + "repo", + "system", + "admin" + ] + }, + "SkillToolDependency": { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "transport": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string" + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "value": { + "type": "string" + } + } + }, + "SkillsListEntry": { + "type": "object", + "required": [ + "cwd", + "errors", + "skills" + ], + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/SkillErrorInfo" + } + }, + "skills": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/SkillMetadata" + } + } + } + }, + "SkillsListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsListResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/SkillsListEntry" + } + } + } + }, + "SkillsExtraRootsSetResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsExtraRootsSetResponse", + "type": "object" + }, + "HookErrorInfo": { + "type": "object", + "required": [ + "message", + "path" + ], + "properties": { + "message": { + "type": "string" + }, + "path": { + "type": "string" + } + } + }, + "HookEventName": { + "type": "string", + "enum": [ + "preToolUse", + "permissionRequest", + "postToolUse", + "preCompact", + "postCompact", + "sessionStart", + "userPromptSubmit", + "subagentStart", + "subagentStop", + "stop" + ] + }, + "HookHandlerType": { + "type": "string", + "enum": [ + "command", + "prompt", + "agent" + ] + }, + "HookMetadata": { + "type": "object", + "required": [ + "currentHash", + "displayOrder", + "enabled", + "eventName", + "handlerType", + "isManaged", + "key", + "source", + "sourcePath", + "timeoutSec", + "trustStatus" + ], + "properties": { + "command": { + "type": [ + "string", + "null" + ] + }, + "currentHash": { + "type": "string" + }, + "displayOrder": { + "type": "integer", + "format": "int64" + }, + "enabled": { + "type": "boolean" + }, + "eventName": { + "$ref": "#/definitions/v2/HookEventName" + }, + "handlerType": { + "$ref": "#/definitions/v2/HookHandlerType" + }, + "isManaged": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "matcher": { + "type": [ + "string", + "null" + ] + }, + "pluginId": { + "type": [ + "string", + "null" + ] + }, + "source": { + "$ref": "#/definitions/v2/HookSource" + }, + "sourcePath": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "statusMessage": { + "type": [ + "string", + "null" + ] + }, + "timeoutSec": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "trustStatus": { + "$ref": "#/definitions/v2/HookTrustStatus" + } + } + }, + "HookSource": { + "type": "string", + "enum": [ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "plugin", + "cloudRequirements", + "cloudManagedConfig", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown" + ] + }, + "HookTrustStatus": { + "type": "string", + "enum": [ + "managed", + "untrusted", + "trusted", + "modified" + ] + }, + "HooksListEntry": { + "type": "object", + "required": [ + "cwd", + "errors", + "hooks", + "warnings" + ], + "properties": { + "cwd": { + "type": "string" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/HookErrorInfo" + } + }, + "hooks": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/HookMetadata" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "HooksListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HooksListResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/HooksListEntry" + } + } + } + }, + "MarketplaceAddResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MarketplaceAddResponse", + "type": "object", + "required": [ + "alreadyAdded", + "installedRoot", + "marketplaceName" + ], + "properties": { + "alreadyAdded": { + "type": "boolean" + }, + "installedRoot": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "marketplaceName": { + "type": "string" + } + } + }, + "MarketplaceRemoveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MarketplaceRemoveResponse", + "type": "object", + "required": [ + "marketplaceName" + ], + "properties": { + "installedRoot": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "marketplaceName": { + "type": "string" + } + } + }, + "MarketplaceUpgradeErrorInfo": { + "type": "object", + "required": [ + "marketplaceName", + "message" + ], + "properties": { + "marketplaceName": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "MarketplaceUpgradeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MarketplaceUpgradeResponse", + "type": "object", + "required": [ + "errors", + "selectedMarketplaces", + "upgradedRoots" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/MarketplaceUpgradeErrorInfo" + } + }, + "selectedMarketplaces": { + "type": "array", + "items": { + "type": "string" + } + }, + "upgradedRoots": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + } + } + }, + "MarketplaceInterface": { + "type": "object", + "properties": { + "displayName": { + "type": [ + "string", + "null" + ] + } + } + }, + "MarketplaceLoadErrorInfo": { + "type": "object", + "required": [ + "marketplacePath", + "message" + ], + "properties": { + "marketplacePath": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "message": { + "type": "string" + } + } + }, + "PluginAuthPolicy": { + "type": "string", + "enum": [ + "ON_INSTALL", + "ON_USE" + ] + }, + "PluginAvailability": { + "oneOf": [ + { + "type": "string", + "enum": [ + "DISABLED_BY_ADMIN" + ] + }, + { + "description": "Plugin-service currently sends `\"ENABLED\"` for available remote plugins. Codex app-server exposes `\"AVAILABLE\"` in its API; the alias keeps decoding compatible with that upstream response.", + "type": "string", + "enum": [ + "AVAILABLE" + ] + } + ] + }, + "PluginInstallPolicy": { + "type": "string", + "enum": [ + "NOT_AVAILABLE", + "AVAILABLE", + "INSTALLED_BY_DEFAULT" + ] + }, + "PluginInstallPolicySource": { + "type": "string", + "enum": [ + "WORKSPACE_SETTING", + "IMPLICIT_CANONICAL_APP" + ] + }, + "PluginInterface": { + "type": "object", + "required": [ + "capabilities", + "screenshotUrls", + "screenshots" + ], + "properties": { + "brandColor": { + "type": [ + "string", + "null" + ] + }, + "capabilities": { + "type": "array", + "items": { + "type": "string" + } + }, + "category": { + "type": [ + "string", + "null" + ] + }, + "composerIcon": { + "description": "Local composer icon path, resolved from the installed plugin package.", + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "composerIconUrl": { + "description": "Remote composer icon URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, + "defaultPrompt": { + "description": "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "developerName": { + "type": [ + "string", + "null" + ] + }, + "displayName": { + "type": [ + "string", + "null" + ] + }, + "logo": { + "description": "Local logo path, resolved from the installed plugin package.", + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "logoDark": { + "description": "Local dark-mode logo path, resolved from the installed plugin package.", + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "logoUrl": { + "description": "Remote logo URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, + "logoUrlDark": { + "description": "Remote dark-mode logo URL from the plugin catalog.", + "type": [ + "string", + "null" + ] + }, + "longDescription": { + "type": [ + "string", + "null" + ] + }, + "privacyPolicyUrl": { + "type": [ + "string", + "null" + ] + }, + "screenshotUrls": { + "description": "Remote screenshot URLs from the plugin catalog.", + "type": "array", + "items": { + "type": "string" + } + }, + "screenshots": { + "description": "Local screenshot paths, resolved from the installed plugin package.", + "type": "array", + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + }, + "shortDescription": { + "type": [ + "string", + "null" + ] + }, + "termsOfServiceUrl": { + "type": [ + "string", + "null" + ] + }, + "websiteUrl": { + "type": [ + "string", + "null" + ] + } + } + }, + "PluginMarketplaceEntry": { + "type": "object", + "required": [ + "name", + "plugins" + ], + "properties": { + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/v2/MarketplaceInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "description": "Local marketplace file path when the marketplace is backed by a local file. Remote-only catalog marketplaces do not have a local path.", + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "plugins": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/PluginSummary" + } + } + } + }, + "PluginShareContext": { + "type": "object", + "required": [ + "remotePluginId" + ], + "properties": { + "creatorAccountUserId": { + "type": [ + "string", + "null" + ] + }, + "creatorName": { + "type": [ + "string", + "null" + ] + }, + "discoverability": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PluginShareDiscoverability" + }, + { + "type": "null" + } + ] + }, + "remotePluginId": { + "type": "string" + }, + "remoteVersion": { + "description": "Version of the remote shared plugin release when available.", + "default": null, + "type": [ + "string", + "null" + ] + }, + "sharePrincipals": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/PluginSharePrincipal" + } + }, + "shareUrl": { + "type": [ + "string", + "null" + ] + } + } + }, + "PluginSharePrincipal": { + "type": "object", + "required": [ + "name", + "principalId", + "principalType", + "role" + ], + "properties": { + "name": { + "type": "string" + }, + "principalId": { + "type": "string" + }, + "principalType": { + "$ref": "#/definitions/v2/PluginSharePrincipalType" + }, + "role": { + "$ref": "#/definitions/v2/PluginSharePrincipalRole" + } + } + }, + "PluginSharePrincipalRole": { + "type": "string", + "enum": [ + "reader", + "editor", + "owner" + ] + }, + "PluginSource": { + "oneOf": [ + { + "type": "object", + "required": [ + "path", + "type" + ], + "properties": { + "path": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "type": { + "type": "string", + "enum": [ + "local" + ], + "title": "LocalPluginSourceType" + } + }, + "title": "LocalPluginSource" + }, + { + "type": "object", + "required": [ + "type", + "url" + ], + "properties": { + "path": { + "type": [ + "string", + "null" + ] + }, + "refName": { + "type": [ + "string", + "null" + ] + }, + "sha": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "git" + ], + "title": "GitPluginSourceType" + }, + "url": { + "type": "string" + } + }, + "title": "GitPluginSource" + }, + { + "type": "object", + "required": [ + "package", + "type" + ], + "properties": { + "package": { + "type": "string" + }, + "registry": { + "description": "Optional HTTPS registry URL. Authentication stays in the user's npm config.", + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "npm" + ], + "title": "NpmPluginSourceType" + }, + "version": { + "description": "Optional npm version or version range.", + "type": [ + "string", + "null" + ] + } + }, + "title": "NpmPluginSource" + }, + { + "description": "The plugin is available in the remote catalog. Download metadata is kept server-side and is not exposed through the app-server API.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "remote" + ], + "title": "RemotePluginSourceType" + } + }, + "title": "RemotePluginSource" + } + ] + }, + "PluginSummary": { + "type": "object", + "required": [ + "authPolicy", + "enabled", + "id", + "installPolicy", + "installed", + "name", + "source" + ], + "properties": { + "authPolicy": { + "$ref": "#/definitions/v2/PluginAuthPolicy" + }, + "availability": { + "description": "Availability state for installing and using the plugin.", + "default": "AVAILABLE", + "allOf": [ + { + "$ref": "#/definitions/v2/PluginAvailability" + } + ] + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "installPolicy": { + "$ref": "#/definitions/v2/PluginInstallPolicy" + }, + "installPolicySource": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PluginInstallPolicySource" + }, + { + "type": "null" + } + ] + }, + "installed": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PluginInterface" + }, + { + "type": "null" + } + ] + }, + "keywords": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "localVersion": { + "description": "Version of the locally materialized plugin package when available.", + "default": null, + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "remotePluginId": { + "description": "Backend remote plugin identifier when available.", + "type": [ + "string", + "null" + ] + }, + "shareContext": { + "description": "Remote sharing context associated with this plugin when available.", + "anyOf": [ + { + "$ref": "#/definitions/v2/PluginShareContext" + }, + { + "type": "null" + } + ] + }, + "source": { + "$ref": "#/definitions/v2/PluginSource" + }, + "version": { + "description": "Version advertised by the remote marketplace backend when available.", + "default": null, + "type": [ + "string", + "null" + ] + } + } + }, + "PluginListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginListResponse", + "type": "object", + "required": [ + "marketplaces" + ], + "properties": { + "featuredPluginIds": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "marketplaceLoadErrors": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/MarketplaceLoadErrorInfo" + } + }, + "marketplaces": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/PluginMarketplaceEntry" + } + } + } + }, + "PluginInstalledResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginInstalledResponse", + "type": "object", + "required": [ + "marketplaces" + ], + "properties": { + "marketplaceLoadErrors": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/MarketplaceLoadErrorInfo" + } + }, + "marketplaces": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/PluginMarketplaceEntry" + } + } + } + }, + "AppSummary": { + "description": "EXPERIMENTAL - app metadata summary for plugin responses.", + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "category": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "id": { + "type": "string" + }, + "installUrl": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + } + } + }, + "AppTemplateSummary": { + "type": "object", + "required": [ + "materializedAppIds", + "name", + "templateId" + ], + "properties": { + "canonicalConnectorId": { + "type": [ + "string", + "null" + ] + }, + "category": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "logoUrl": { + "type": [ + "string", + "null" + ] + }, + "logoUrlDark": { + "type": [ + "string", + "null" + ] + }, + "materializedAppIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "reason": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AppTemplateUnavailableReason" + }, + { + "type": "null" + } + ] + }, + "templateId": { + "type": "string" + } + } + }, + "AppTemplateUnavailableReason": { + "type": "string", + "enum": [ + "NOT_CONFIGURED_FOR_WORKSPACE", + "NO_ACTIVE_WORKSPACE" + ] + }, + "PluginDetail": { + "type": "object", + "required": [ + "appTemplates", + "apps", + "hooks", + "marketplaceName", + "mcpServers", + "skills", + "summary" + ], + "properties": { + "appTemplates": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AppTemplateSummary" + } + }, + "apps": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AppSummary" + } + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "hooks": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/PluginHookSummary" + } + }, + "marketplaceName": { + "type": "string" + }, + "marketplacePath": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "mcpServers": { + "type": "array", + "items": { + "type": "string" + } + }, + "shareUrl": { + "type": [ + "string", + "null" + ] + }, + "skills": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/SkillSummary" + } + }, + "summary": { + "$ref": "#/definitions/v2/PluginSummary" + } + } + }, + "PluginHookSummary": { + "type": "object", + "required": [ + "eventName", + "key" + ], + "properties": { + "eventName": { + "$ref": "#/definitions/v2/HookEventName" + }, + "key": { + "type": "string" + } + } + }, + "SkillSummary": { + "type": "object", + "required": [ + "description", + "enabled", + "name" + ], + "properties": { + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "interface": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SkillInterface" + }, + { + "type": "null" + } + ] + }, + "name": { + "type": "string" + }, + "path": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "shortDescription": { + "type": [ + "string", + "null" + ] + } + } + }, + "PluginReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginReadResponse", + "type": "object", + "required": [ + "plugin" + ], + "properties": { + "plugin": { + "$ref": "#/definitions/v2/PluginDetail" + } + } + }, + "PluginSkillReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginSkillReadResponse", + "type": "object", + "properties": { + "contents": { + "type": [ + "string", + "null" + ] + } + } + }, + "PluginShareSaveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareSaveResponse", + "type": "object", + "required": [ + "remotePluginId", + "shareUrl" + ], + "properties": { + "remotePluginId": { + "type": "string" + }, + "shareUrl": { + "type": "string" + } + } + }, + "PluginShareUpdateTargetsResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareUpdateTargetsResponse", + "type": "object", + "required": [ + "discoverability", + "principals" + ], + "properties": { + "discoverability": { + "$ref": "#/definitions/v2/PluginShareDiscoverability" + }, + "principals": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/PluginSharePrincipal" + } + } + } + }, + "PluginShareListItem": { + "type": "object", + "required": [ + "plugin" + ], + "properties": { + "localPluginPath": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + { + "type": "null" + } + ] + }, + "plugin": { + "$ref": "#/definitions/v2/PluginSummary" + } + } + }, + "PluginShareListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareListResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/PluginShareListItem" + } + } + } + }, + "PluginShareCheckoutResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareCheckoutResponse", + "type": "object", + "required": [ + "marketplaceName", + "marketplacePath", + "pluginId", + "pluginName", + "pluginPath", + "remotePluginId" + ], + "properties": { + "marketplaceName": { + "type": "string" + }, + "marketplacePath": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "pluginId": { + "type": "string" + }, + "pluginName": { + "type": "string" + }, + "pluginPath": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "remotePluginId": { + "type": "string" + }, + "remoteVersion": { + "type": [ + "string", + "null" + ] + } + } + }, + "PluginShareDeleteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginShareDeleteResponse", + "type": "object" + }, + "AppBranding": { + "description": "EXPERIMENTAL - app metadata returned by app-list APIs.", + "type": "object", + "required": [ + "isDiscoverableApp" + ], + "properties": { + "category": { + "type": [ + "string", + "null" + ] + }, + "developer": { + "type": [ + "string", + "null" + ] + }, + "isDiscoverableApp": { + "type": "boolean" + }, + "privacyPolicy": { + "type": [ + "string", + "null" + ] + }, + "termsOfService": { + "type": [ + "string", + "null" + ] + }, + "website": { + "type": [ + "string", + "null" + ] + } + } + }, + "AppInfo": { + "description": "EXPERIMENTAL - app metadata returned by app-list APIs.", + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "appMetadata": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AppMetadata" + }, + { + "type": "null" + } + ] + }, + "branding": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AppBranding" + }, + { + "type": "null" + } + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "distributionChannel": { + "type": [ + "string", + "null" + ] + }, + "iconAssets": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "iconDarkAssets": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "installUrl": { + "type": [ + "string", + "null" + ] + }, + "isAccessible": { + "default": false, + "type": "boolean" + }, + "isEnabled": { + "description": "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```", + "default": true, + "type": "boolean" + }, + "labels": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "logoUrl": { + "type": [ + "string", + "null" + ] + }, + "logoUrlDark": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "pluginDisplayNames": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "AppMetadata": { + "type": "object", + "properties": { + "categories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "developer": { + "type": [ + "string", + "null" + ] + }, + "firstPartyRequiresInstall": { + "type": [ + "boolean", + "null" + ] + }, + "firstPartyType": { + "type": [ + "string", + "null" + ] + }, + "review": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AppReview" + }, + { + "type": "null" + } + ] + }, + "screenshots": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/AppScreenshot" + } + }, + "seoDescription": { + "type": [ + "string", + "null" + ] + }, + "showInComposerWhenUnlinked": { + "type": [ + "boolean", + "null" + ] + }, + "subCategories": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "version": { + "type": [ + "string", + "null" + ] + }, + "versionId": { + "type": [ + "string", + "null" + ] + }, + "versionNotes": { + "type": [ + "string", + "null" + ] + } + } + }, + "AppReview": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "type": "string" + } + } + }, + "AppScreenshot": { + "type": "object", + "required": [ + "userPrompt" + ], + "properties": { + "fileId": { + "type": [ + "string", + "null" + ] + }, + "url": { + "type": [ + "string", + "null" + ] + }, + "userPrompt": { + "type": "string" + } + } + }, + "AppsListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppsListResponse", + "description": "EXPERIMENTAL - app list response.", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AppInfo" + } + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + } + }, + "FsReadFileResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsReadFileResponse", + "description": "Base64-encoded file contents returned by `fs/readFile`.", + "type": "object", + "required": [ + "dataBase64" + ], + "properties": { + "dataBase64": { + "description": "File contents encoded as base64.", + "type": "string" + } + } + }, + "FsWriteFileResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsWriteFileResponse", + "description": "Successful response for `fs/writeFile`.", + "type": "object" + }, + "FsCreateDirectoryResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsCreateDirectoryResponse", + "description": "Successful response for `fs/createDirectory`.", + "type": "object" + }, + "FsGetMetadataResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsGetMetadataResponse", + "description": "Metadata returned by `fs/getMetadata`.", + "type": "object", + "required": [ + "createdAtMs", + "isDirectory", + "isFile", + "isSymlink", + "modifiedAtMs" + ], + "properties": { + "createdAtMs": { + "description": "File creation time in Unix milliseconds when available, otherwise `0`.", + "type": "integer", + "format": "int64" + }, + "isDirectory": { + "description": "Whether the path resolves to a directory.", + "type": "boolean" + }, + "isFile": { + "description": "Whether the path resolves to a regular file.", + "type": "boolean" + }, + "isSymlink": { + "description": "Whether the path itself is a symbolic link.", + "type": "boolean" + }, + "modifiedAtMs": { + "description": "File modification time in Unix milliseconds when available, otherwise `0`.", + "type": "integer", + "format": "int64" + } + } + }, + "FsReadDirectoryEntry": { + "description": "A directory entry returned by `fs/readDirectory`.", + "type": "object", + "required": [ + "fileName", + "isDirectory", + "isFile" + ], + "properties": { + "fileName": { + "description": "Direct child entry name only, not an absolute or relative path.", + "type": "string" + }, + "isDirectory": { + "description": "Whether this entry resolves to a directory.", + "type": "boolean" + }, + "isFile": { + "description": "Whether this entry resolves to a regular file.", + "type": "boolean" + } + } + }, + "FsReadDirectoryResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsReadDirectoryResponse", + "description": "Directory entries returned by `fs/readDirectory`.", + "type": "object", + "required": [ + "entries" + ], + "properties": { + "entries": { + "description": "Direct child entries in the requested directory.", + "type": "array", + "items": { + "$ref": "#/definitions/v2/FsReadDirectoryEntry" + } + } + } + }, + "FsRemoveResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsRemoveResponse", + "description": "Successful response for `fs/remove`.", + "type": "object" + }, + "FsCopyResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsCopyResponse", + "description": "Successful response for `fs/copy`.", + "type": "object" + }, + "FsWatchResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsWatchResponse", + "description": "Successful response for `fs/watch`.", + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "Canonicalized path associated with the watch.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + } + } + }, + "FsUnwatchResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FsUnwatchResponse", + "description": "Successful response for `fs/unwatch`.", + "type": "object" + }, + "SkillsConfigWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsConfigWriteResponse", + "type": "object", + "required": [ + "effectiveEnabled" + ], + "properties": { + "effectiveEnabled": { + "type": "boolean" + } + } + }, + "PluginInstallResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginInstallResponse", + "type": "object", + "required": [ + "appsNeedingAuth", + "authPolicy" + ], + "properties": { + "appsNeedingAuth": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AppSummary" + } + }, + "authPolicy": { + "$ref": "#/definitions/v2/PluginAuthPolicy" + } + } + }, + "PluginUninstallResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PluginUninstallResponse", + "type": "object" + }, + "TurnStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnStartResponse", + "type": "object", + "required": [ + "turn" + ], + "properties": { + "turn": { + "$ref": "#/definitions/v2/Turn" + } + } + }, + "TurnSteerResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnSteerResponse", + "type": "object", + "required": [ + "turnId" + ], + "properties": { + "turnId": { + "type": "string" + } + } + }, + "TurnInterruptResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnInterruptResponse", + "type": "object" + }, + "FileChangePatchUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FileChangePatchUpdatedNotification", + "type": "object", + "required": [ + "changes", + "itemId", + "threadId", + "turnId" + ], + "properties": { + "changes": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/FileUpdateChange" + } + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "FileChangeOutputDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FileChangeOutputDeltaNotification", + "description": "Deprecated legacy notification for `apply_patch` textual output.\n\nThe server no longer emits this notification.", + "type": "object", + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "TerminalInteractionNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TerminalInteractionNotification", + "type": "object", + "required": [ + "itemId", + "processId", + "stdin", + "threadId", + "turnId" + ], + "properties": { + "itemId": { + "type": "string" + }, + "processId": { + "type": "string" + }, + "stdin": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "CommandExecutionOutputDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecutionOutputDeltaNotification", + "type": "object", + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ProcessExitedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProcessExitedNotification", + "description": "Final process exit notification for `process/spawn`.", + "type": "object", + "required": [ + "exitCode", + "processHandle", + "stderr", + "stderrCapReached", + "stdout", + "stdoutCapReached" + ], + "properties": { + "exitCode": { + "description": "Process exit code.", + "type": "integer", + "format": "int32" + }, + "processHandle": { + "description": "Client-supplied, connection-scoped `processHandle` from `process/spawn`.", + "type": "string" + }, + "stderr": { + "description": "Buffered stderr capture.\n\nEmpty when stderr was streamed via `process/outputDelta`.", + "type": "string" + }, + "stderrCapReached": { + "description": "Whether stderr reached `outputBytesCap`.\n\nIn streaming mode, stderr is empty and cap state is also reported on the final stderr `process/outputDelta` notification.", + "type": "boolean" + }, + "stdout": { + "description": "Buffered stdout capture.\n\nEmpty when stdout was streamed via `process/outputDelta`.", + "type": "string" + }, + "stdoutCapReached": { + "description": "Whether stdout reached `outputBytesCap`.\n\nIn streaming mode, stdout is empty and cap state is also reported on the final stdout `process/outputDelta` notification.", + "type": "boolean" + } + } + }, + "RealtimeVoicesList": { + "type": "object", + "required": [ + "defaultV1", + "defaultV2", + "v1", + "v2" + ], + "properties": { + "defaultV1": { + "$ref": "#/definitions/v2/RealtimeVoice" + }, + "defaultV2": { + "$ref": "#/definitions/v2/RealtimeVoice" + }, + "v1": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/RealtimeVoice" + } + }, + "v2": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/RealtimeVoice" + } + } + } + }, + "ProcessOutputDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ProcessOutputDeltaNotification", + "description": "Base64-encoded output chunk emitted for a streaming `process/spawn` request.", + "type": "object", + "required": [ + "capReached", + "deltaBase64", + "processHandle", + "stream" + ], + "properties": { + "capReached": { + "description": "True on the final streamed chunk for this stream when output was truncated by `outputBytesCap`.", + "type": "boolean" + }, + "deltaBase64": { + "description": "Base64-encoded output bytes.", + "type": "string" + }, + "processHandle": { + "description": "Client-supplied, connection-scoped `processHandle` from `process/spawn`.", + "type": "string" + }, + "stream": { + "description": "Output stream this chunk belongs to.", + "allOf": [ + { + "$ref": "#/definitions/v2/ProcessOutputStream" + } + ] + } + } + }, + "ReviewStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ReviewStartResponse", + "type": "object", + "required": [ + "reviewThreadId", + "turn" + ], + "properties": { + "reviewThreadId": { + "description": "Identifies the thread where the review runs.\n\nFor inline reviews, this is the original thread id. For detached reviews, this is the id of the new review thread.", + "type": "string" + }, + "turn": { + "$ref": "#/definitions/v2/Turn" + } + } + }, + "InputModality": { + "description": "Canonical user-input modality tags advertised by a model.", + "oneOf": [ + { + "description": "Plain text turns and tool payloads.", + "type": "string", + "enum": [ + "text" + ] + }, + { + "description": "Image attachments included in user turns.", + "type": "string", + "enum": [ + "image" + ] + } + ] + }, + "Model": { + "type": "object", + "required": [ + "defaultReasoningEffort", + "description", + "displayName", + "hidden", + "id", + "isDefault", + "model", + "supportedReasoningEfforts" + ], + "properties": { + "additionalSpeedTiers": { + "description": "Deprecated: use `serviceTiers` instead.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "availabilityNux": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ModelAvailabilityNux" + }, + { + "type": "null" + } + ] + }, + "defaultReasoningEffort": { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + "defaultServiceTier": { + "description": "Catalog default service tier id for this model, when one is configured.", + "default": null, + "type": [ + "string", + "null" + ] + }, + "description": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "hidden": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "inputModalities": { + "default": [ + "text", + "image" + ], + "type": "array", + "items": { + "$ref": "#/definitions/v2/InputModality" + } + }, + "isDefault": { + "type": "boolean" + }, + "model": { + "type": "string" + }, + "serviceTiers": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/v2/ModelServiceTier" + } + }, + "supportedReasoningEfforts": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ReasoningEffortOption" + } + }, + "supportsPersonality": { + "default": false, + "type": "boolean" + }, + "upgrade": { + "type": [ + "string", + "null" + ] + }, + "upgradeInfo": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ModelUpgradeInfo" + }, + { + "type": "null" + } + ] + } + } + }, + "ModelAvailabilityNux": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + }, + "ModelServiceTier": { + "type": "object", + "required": [ + "description", + "id", + "name" + ], + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "ModelUpgradeInfo": { + "type": "object", + "required": [ + "model" + ], + "properties": { + "migrationMarkdown": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": "string" + }, + "modelLink": { + "type": [ + "string", + "null" + ] + }, + "upgradeCopy": { + "type": [ + "string", + "null" + ] + } + } + }, + "ReasoningEffortOption": { + "type": "object", + "required": [ + "description", + "reasoningEffort" + ], + "properties": { + "description": { + "type": "string" + }, + "reasoningEffort": { + "$ref": "#/definitions/v2/ReasoningEffort" + } + } + }, + "ModelListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModelListResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/Model" + } + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + } + }, + "ModelProviderCapabilitiesReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ModelProviderCapabilitiesReadResponse", + "type": "object", + "required": [ + "imageGeneration", + "namespaceTools", + "webSearch" + ], + "properties": { + "imageGeneration": { + "type": "boolean" + }, + "namespaceTools": { + "type": "boolean" + }, + "webSearch": { + "type": "boolean" + } + } + }, + "ExperimentalFeature": { + "type": "object", + "required": [ + "defaultEnabled", + "enabled", + "name", + "stage" + ], + "properties": { + "announcement": { + "description": "Announcement copy shown to users when the feature is introduced. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] + }, + "defaultEnabled": { + "description": "Whether this feature is enabled by default.", + "type": "boolean" + }, + "description": { + "description": "Short summary describing what the feature does. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] + }, + "displayName": { + "description": "User-facing display name shown in the experimental features UI. Null when this feature is not in beta.", + "type": [ + "string", + "null" + ] + }, + "enabled": { + "description": "Whether this feature is currently enabled in the loaded config.", + "type": "boolean" + }, + "name": { + "description": "Stable key used in config.toml and CLI flag toggles.", + "type": "string" + }, + "stage": { + "description": "Lifecycle stage of this feature flag.", + "allOf": [ + { + "$ref": "#/definitions/v2/ExperimentalFeatureStage" + } + ] + } + } + }, + "ExperimentalFeatureStage": { + "oneOf": [ + { + "description": "Feature is available for user testing and feedback.", + "type": "string", + "enum": [ + "beta" + ] + }, + { + "description": "Feature is still being built and not ready for broad use.", + "type": "string", + "enum": [ + "underDevelopment" + ] + }, + { + "description": "Feature is production-ready.", + "type": "string", + "enum": [ + "stable" + ] + }, + { + "description": "Feature is deprecated and should be avoided.", + "type": "string", + "enum": [ + "deprecated" + ] + }, + { + "description": "Feature flag is retained only for backwards compatibility.", + "type": "string", + "enum": [ + "removed" + ] + } + ] + }, + "ExperimentalFeatureListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExperimentalFeatureListResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExperimentalFeature" + } + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + } + }, + "PermissionProfileSummary": { + "type": "object", + "required": [ + "allowed", + "id" + ], + "properties": { + "allowed": { + "description": "Whether the effective requirements allow selecting this profile.", + "type": "boolean" + }, + "description": { + "description": "Optional user-facing description for display in clients.", + "type": [ + "string", + "null" + ] + }, + "id": { + "description": "Available permission profile identifier.", + "type": "string" + } + } + }, + "PermissionProfileListResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionProfileListResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/PermissionProfileSummary" + } + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + } + }, + "ExperimentalFeatureEnablementSetResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExperimentalFeatureEnablementSetResponse", + "type": "object", + "required": [ + "enablement" + ], + "properties": { + "enablement": { + "description": "Feature enablement entries updated by this request.", + "type": "object", + "additionalProperties": { + "type": "boolean" + } + } + } + }, + "RemoteControlConnectionStatus": { + "type": "string", + "enum": [ + "disabled", + "connecting", + "connected", + "errored" + ] + }, + "ProcessOutputStream": { + "description": "Stream label for `process/outputDelta` notifications.", + "oneOf": [ + { + "description": "stdout stream. PTY mode multiplexes terminal output here.", + "type": "string", + "enum": [ + "stdout" + ] + }, + { + "description": "stderr stream.", + "type": "string", + "enum": [ + "stderr" + ] + } + ] + }, + "CommandExecOutputDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecOutputDeltaNotification", + "description": "Base64-encoded output chunk emitted for a streaming `command/exec` request.\n\nThese notifications are connection-scoped. If the originating connection closes, the server terminates the process.", + "type": "object", + "required": [ + "capReached", + "deltaBase64", + "processId", + "stream" + ], + "properties": { + "capReached": { + "description": "`true` on the final streamed chunk for a stream when `outputBytesCap` truncated later output on that stream.", + "type": "boolean" + }, + "deltaBase64": { + "description": "Base64-encoded output bytes.", + "type": "string" + }, + "processId": { + "description": "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", + "type": "string" + }, + "stream": { + "description": "Output stream for this chunk.", + "allOf": [ + { + "$ref": "#/definitions/v2/CommandExecOutputStream" + } + ] + } + } + }, + "CommandExecOutputStream": { + "description": "Stream label for `command/exec/outputDelta` notifications.", + "oneOf": [ + { + "description": "stdout stream. PTY mode multiplexes terminal output here.", + "type": "string", + "enum": [ + "stdout" + ] + }, + { + "description": "stderr stream.", + "type": "string", + "enum": [ + "stderr" + ] + } + ] + }, + "PlanDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PlanDeltaNotification", + "description": "EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.", + "type": "object", + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "AgentMessageDeltaNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AgentMessageDeltaNotification", + "type": "object", + "required": [ + "delta", + "itemId", + "threadId", + "turnId" + ], + "properties": { + "delta": { + "type": "string" + }, + "itemId": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "RawResponseItemCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RawResponseItemCompletedNotification", + "type": "object", + "required": [ + "item", + "threadId", + "turnId" + ], + "properties": { + "item": { + "$ref": "#/definitions/v2/ResponseItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ItemCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ItemCompletedNotification", + "type": "object", + "required": [ + "completedAtMs", + "item", + "threadId", + "turnId" + ], + "properties": { + "completedAtMs": { + "description": "Unix timestamp (in milliseconds) when this item lifecycle completed.", + "type": "integer", + "format": "int64" + }, + "item": { + "$ref": "#/definitions/v2/ThreadItem" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ItemGuardianApprovalReviewCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ItemGuardianApprovalReviewCompletedNotification", + "description": "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.", + "type": "object", + "required": [ + "action", + "completedAtMs", + "decisionSource", + "review", + "reviewId", + "startedAtMs", + "threadId", + "turnId" + ], + "properties": { + "action": { + "$ref": "#/definitions/v2/GuardianApprovalReviewAction" + }, + "completedAtMs": { + "description": "Unix timestamp (in milliseconds) when this review completed.", + "type": "integer", + "format": "int64" + }, + "decisionSource": { + "$ref": "#/definitions/v2/AutoReviewDecisionSource" + }, + "review": { + "$ref": "#/definitions/v2/GuardianApprovalReview" + }, + "reviewId": { + "description": "Stable identifier for this review.", + "type": "string" + }, + "startedAtMs": { + "description": "Unix timestamp (in milliseconds) when this review started.", + "type": "integer", + "format": "int64" + }, + "targetItemId": { + "description": "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "CollaborationModeMask": { + "description": "EXPERIMENTAL - collaboration mode preset metadata for clients.", + "type": "object", + "required": [ + "name" + ], + "properties": { + "mode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ModeKind" + }, + { + "type": "null" + } + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "reasoning_effort": { + "anyOf": [ + { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + { + "type": "null" + } + ] + } + } + }, + "AutoReviewDecisionSource": { + "description": "[UNSTABLE] Source that produced a terminal approval auto-review decision.", + "type": "string", + "enum": [ + "agent" + ] + }, + "ItemGuardianApprovalReviewStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ItemGuardianApprovalReviewStartedNotification", + "description": "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.", + "type": "object", + "required": [ + "action", + "review", + "reviewId", + "startedAtMs", + "threadId", + "turnId" + ], + "properties": { + "action": { + "$ref": "#/definitions/v2/GuardianApprovalReviewAction" + }, + "review": { + "$ref": "#/definitions/v2/GuardianApprovalReview" + }, + "reviewId": { + "description": "Stable identifier for this review.", + "type": "string" + }, + "startedAtMs": { + "description": "Unix timestamp (in milliseconds) when this review started.", + "type": "integer", + "format": "int64" + }, + "targetItemId": { + "description": "Identifier for the reviewed item or tool call when one exists.\n\nIn most cases, one review maps to one target item. The exceptions are - execve reviews, where a single command may contain multiple execve calls to review (only possible when using the shell_zsh_fork feature) - network policy reviews, where there is no target item\n\nA network call is triggered by a CommandExecution item, so having a target_item_id set to the CommandExecution item would be misleading because the review is about the network call, not the command execution. Therefore, target_item_id is set to None for network policy reviews.", + "type": [ + "string", + "null" + ] + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "RequestPermissionProfile": { + "type": "object", + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AdditionalFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AdditionalNetworkPermissions" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "NetworkApprovalProtocol": { + "type": "string", + "enum": [ + "http", + "https", + "socks5Tcp", + "socks5Udp" + ] + }, + "GuardianUserAuthorization": { + "description": "[UNSTABLE] Authorization level assigned by approval auto-review.", + "type": "string", + "enum": [ + "unknown", + "low", + "medium", + "high" + ] + }, + "GuardianRiskLevel": { + "description": "[UNSTABLE] Risk level assigned by approval auto-review.", + "type": "string", + "enum": [ + "low", + "medium", + "high", + "critical" + ] + }, + "McpServerOauthLoginResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerOauthLoginResponse", + "type": "object", + "required": [ + "authorizationUrl" + ], + "properties": { + "authorizationUrl": { + "type": "string" + } + } + }, + "McpServerRefreshResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerRefreshResponse", + "type": "object" + }, + "McpAuthStatus": { + "type": "string", + "enum": [ + "unsupported", + "notLoggedIn", + "bearerToken", + "oAuth" + ] + }, + "McpServerInfo": { + "description": "Presentation metadata advertised by an initialized MCP server.", + "type": "object", + "required": [ + "name", + "version" + ], + "properties": { + "description": { + "type": [ + "string", + "null" + ] + }, + "icons": { + "type": [ + "array", + "null" + ], + "items": true + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": "string" + }, + "websiteUrl": { + "type": [ + "string", + "null" + ] + } + } + }, + "McpServerStatus": { + "type": "object", + "required": [ + "authStatus", + "name", + "resourceTemplates", + "resources", + "tools" + ], + "properties": { + "authStatus": { + "$ref": "#/definitions/v2/McpAuthStatus" + }, + "name": { + "type": "string" + }, + "resourceTemplates": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ResourceTemplate" + } + }, + "resources": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/Resource" + } + }, + "serverInfo": { + "anyOf": [ + { + "$ref": "#/definitions/v2/McpServerInfo" + }, + { + "type": "null" + } + ] + }, + "tools": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/v2/Tool" + } + } + } + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "type": "object", + "required": [ + "name", + "uri" + ], + "properties": { + "_meta": true, + "annotations": true, + "description": { + "type": [ + "string", + "null" + ] + }, + "icons": { + "type": [ + "array", + "null" + ], + "items": true + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "size": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "type": "string" + } + } + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "type": "object", + "required": [ + "name", + "uriTemplate" + ], + "properties": { + "annotations": true, + "description": { + "type": [ + "string", + "null" + ] + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "uriTemplate": { + "type": "string" + } + } + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "type": "object", + "required": [ + "inputSchema", + "name" + ], + "properties": { + "_meta": true, + "annotations": true, + "description": { + "type": [ + "string", + "null" + ] + }, + "icons": { + "type": [ + "array", + "null" + ], + "items": true + }, + "inputSchema": true, + "name": { + "type": "string" + }, + "outputSchema": true, + "title": { + "type": [ + "string", + "null" + ] + } + } + }, + "ListMcpServerStatusResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ListMcpServerStatusResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/McpServerStatus" + } + }, + "nextCursor": { + "description": "Opaque cursor to pass to the next call to continue after the last item. If None, there are no more items to return.", + "type": [ + "string", + "null" + ] + } + } + }, + "ResourceContent": { + "description": "Contents returned when reading a resource from an MCP server.", + "anyOf": [ + { + "type": "object", + "required": [ + "text", + "uri" + ], + "properties": { + "_meta": true, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "text": { + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "blob", + "uri" + ], + "properties": { + "_meta": true, + "blob": { + "type": "string" + }, + "mimeType": { + "type": [ + "string", + "null" + ] + }, + "uri": { + "description": "The URI of this resource.", + "type": "string" + } + } + } + ] + }, + "McpResourceReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpResourceReadResponse", + "type": "object", + "required": [ + "contents" + ], + "properties": { + "contents": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ResourceContent" + } + } + } + }, + "McpServerToolCallResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerToolCallResponse", + "type": "object", + "required": [ + "content" + ], + "properties": { + "_meta": true, + "content": { + "type": "array", + "items": true + }, + "isError": { + "type": [ + "boolean", + "null" + ] + }, + "structuredContent": true + } + }, + "WindowsSandboxSetupStartResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WindowsSandboxSetupStartResponse", + "type": "object", + "required": [ + "started" + ], + "properties": { + "started": { + "type": "boolean" + } + } + }, + "WindowsSandboxReadiness": { + "type": "string", + "enum": [ + "ready", + "notConfigured", + "updateRequired" + ] + }, + "WindowsSandboxReadinessResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "WindowsSandboxReadinessResponse", + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/v2/WindowsSandboxReadiness" + } + } + }, + "LoginAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LoginAccountResponse", + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ], + "title": "ApiKeyv2::LoginAccountResponseType" + } + }, + "title": "ApiKeyv2::LoginAccountResponse" + }, + { + "type": "object", + "required": [ + "authUrl", + "loginId", + "type" + ], + "properties": { + "authUrl": { + "description": "URL the client should open in a browser to initiate the OAuth flow.", + "type": "string" + }, + "loginId": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "chatgpt" + ], + "title": "Chatgptv2::LoginAccountResponseType" + } + }, + "title": "Chatgptv2::LoginAccountResponse" + }, + { + "type": "object", + "required": [ + "loginId", + "type", + "userCode", + "verificationUrl" + ], + "properties": { + "loginId": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "chatgptDeviceCode" + ], + "title": "ChatgptDeviceCodev2::LoginAccountResponseType" + }, + "userCode": { + "description": "One-time code the user must enter after signing in.", + "type": "string" + }, + "verificationUrl": { + "description": "URL the client should open in a browser to complete device code authorization.", + "type": "string" + } + }, + "title": "ChatgptDeviceCodev2::LoginAccountResponse" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "chatgptAuthTokens" + ], + "title": "ChatgptAuthTokensv2::LoginAccountResponseType" + } + }, + "title": "ChatgptAuthTokensv2::LoginAccountResponse" + } + ] + }, + "CancelLoginAccountStatus": { + "type": "string", + "enum": [ + "canceled", + "notFound" + ] + }, + "CancelLoginAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CancelLoginAccountResponse", + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/v2/CancelLoginAccountStatus" + } + } + }, + "LogoutAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "LogoutAccountResponse", + "type": "object" + }, + "CreditsSnapshot": { + "type": "object", + "required": [ + "hasCredits", + "unlimited" + ], + "properties": { + "balance": { + "type": [ + "string", + "null" + ] + }, + "hasCredits": { + "type": "boolean" + }, + "unlimited": { + "type": "boolean" + } + } + }, + "PlanType": { + "type": "string", + "enum": [ + "free", + "go", + "plus", + "pro", + "prolite", + "team", + "self_serve_business_usage_based", + "business", + "enterprise_cbp_usage_based", + "enterprise", + "edu", + "unknown" + ] + }, + "RateLimitReachedType": { + "type": "string", + "enum": [ + "rate_limit_reached", + "workspace_owner_credits_depleted", + "workspace_member_credits_depleted", + "workspace_owner_usage_limit_reached", + "workspace_member_usage_limit_reached" + ] + }, + "RateLimitResetCredit": { + "type": "object", + "required": [ + "grantedAt", + "id", + "resetType", + "status" + ], + "properties": { + "description": { + "description": "Backend-provided display description for this credit, or `null` when unavailable.", + "type": [ + "string", + "null" + ] + }, + "expiresAt": { + "description": "Unix timestamp in seconds when the credit expires, or `null` if it does not expire.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "grantedAt": { + "description": "Unix timestamp in seconds when the credit was granted.", + "type": "integer", + "format": "int64" + }, + "id": { + "description": "Opaque backend identifier for this reset credit.", + "type": "string" + }, + "resetType": { + "$ref": "#/definitions/v2/RateLimitResetType" + }, + "status": { + "$ref": "#/definitions/v2/RateLimitResetCreditStatus" + }, + "title": { + "description": "Backend-provided display title for this credit, or `null` when unavailable.", + "type": [ + "string", + "null" + ] + } + } + }, + "RateLimitResetCreditStatus": { + "type": "string", + "enum": [ + "available", + "redeeming", + "redeemed", + "unknown" + ] + }, + "RateLimitResetCreditsSummary": { + "type": "object", + "required": [ + "availableCount" + ], + "properties": { + "availableCount": { + "type": "integer", + "format": "int64" + }, + "credits": { + "description": "Detail rows for available reset credits, when the backend provides them.\n\n`null` means only `availableCount` is known, while an empty array means details were fetched and no available credits were returned. The backend may cap this list, so its length can be less than `availableCount`.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/RateLimitResetCredit" + } + } + } + }, + "RateLimitResetType": { + "type": "string", + "enum": [ + "codexRateLimits", + "unknown" + ] + }, + "RateLimitSnapshot": { + "type": "object", + "properties": { + "credits": { + "anyOf": [ + { + "$ref": "#/definitions/v2/CreditsSnapshot" + }, + { + "type": "null" + } + ] + }, + "individualLimit": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SpendControlLimitSnapshot" + }, + { + "type": "null" + } + ] + }, + "limitId": { + "type": [ + "string", + "null" + ] + }, + "limitName": { + "type": [ + "string", + "null" + ] + }, + "planType": { + "anyOf": [ + { + "$ref": "#/definitions/v2/PlanType" + }, + { + "type": "null" + } + ] + }, + "primary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/RateLimitWindow" + }, + { + "type": "null" + } + ] + }, + "rateLimitReachedType": { + "anyOf": [ + { + "$ref": "#/definitions/v2/RateLimitReachedType" + }, + { + "type": "null" + } + ] + }, + "secondary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/RateLimitWindow" + }, + { + "type": "null" + } + ] + } + } + }, + "RateLimitWindow": { + "type": "object", + "required": [ + "usedPercent" + ], + "properties": { + "resetsAt": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "usedPercent": { + "type": "integer", + "format": "int32" + }, + "windowDurationMins": { + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "SpendControlLimitSnapshot": { + "type": "object", + "required": [ + "limit", + "remainingPercent", + "resetsAt", + "used" + ], + "properties": { + "limit": { + "type": "string" + }, + "remainingPercent": { + "type": "integer", + "format": "int32" + }, + "resetsAt": { + "type": "integer", + "format": "int64" + }, + "used": { + "type": "string" + } + } + }, + "GetAccountRateLimitsResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetAccountRateLimitsResponse", + "type": "object", + "required": [ + "rateLimits" + ], + "properties": { + "rateLimitResetCredits": { + "anyOf": [ + { + "$ref": "#/definitions/v2/RateLimitResetCreditsSummary" + }, + { + "type": "null" + } + ] + }, + "rateLimits": { + "description": "Backward-compatible single-bucket view; mirrors the historical payload.", + "allOf": [ + { + "$ref": "#/definitions/v2/RateLimitSnapshot" + } + ] + }, + "rateLimitsByLimitId": { + "description": "Multi-bucket view keyed by metered `limit_id` (for example, `codex`).", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/v2/RateLimitSnapshot" + } + } + } + }, + "ConsumeAccountRateLimitResetCreditOutcome": { + "oneOf": [ + { + "description": "A reset credit was consumed and the eligible rate-limit windows were reset.", + "type": "string", + "enum": [ + "reset" + ] + }, + { + "description": "No current rate-limit window is eligible for a reset.", + "type": "string", + "enum": [ + "nothingToReset" + ] + }, + { + "description": "The account has no earned reset credits available.", + "type": "string", + "enum": [ + "noCredit" + ] + }, + { + "description": "The same idempotency key already completed a reset successfully.", + "type": "string", + "enum": [ + "alreadyRedeemed" + ] + } + ] + }, + "ConsumeAccountRateLimitResetCreditResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConsumeAccountRateLimitResetCreditResponse", + "type": "object", + "required": [ + "outcome" + ], + "properties": { + "outcome": { + "$ref": "#/definitions/v2/ConsumeAccountRateLimitResetCreditOutcome" + } + } + }, + "AccountTokenUsageDailyBucket": { + "type": "object", + "required": [ + "startDate", + "tokens" + ], + "properties": { + "startDate": { + "type": "string" + }, + "tokens": { + "type": "integer", + "format": "int64" + } + } + }, + "AccountTokenUsageSummary": { + "type": "object", + "properties": { + "currentStreakDays": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "lifetimeTokens": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "longestRunningTurnSec": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "longestStreakDays": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "peakDailyTokens": { + "type": [ + "integer", + "null" + ], + "format": "int64" + } + } + }, + "GetAccountTokenUsageResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetAccountTokenUsageResponse", + "type": "object", + "required": [ + "summary" + ], + "properties": { + "dailyUsageBuckets": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/AccountTokenUsageDailyBucket" + } + }, + "summary": { + "$ref": "#/definitions/v2/AccountTokenUsageSummary" + } + } + }, + "WorkspaceMessage": { + "type": "object", + "required": [ + "messageBody", + "messageId", + "messageType" + ], + "properties": { + "archivedAt": { + "description": "Unix timestamp (in seconds) when the message was archived.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "createdAt": { + "description": "Unix timestamp (in seconds) when the message was created.", + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "messageBody": { + "type": "string" + }, + "messageId": { + "type": "string" + }, + "messageType": { + "$ref": "#/definitions/v2/WorkspaceMessageType" + } + } + }, + "WorkspaceMessageType": { + "type": "string", + "enum": [ + "headline", + "announcement", + "unknown" + ] + }, + "GetWorkspaceMessagesResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetWorkspaceMessagesResponse", + "type": "object", + "required": [ + "featureEnabled", + "messages" + ], + "properties": { + "featureEnabled": { + "description": "Whether the workspace-message backend route is available for this client.", + "type": "boolean" + }, + "messages": { + "description": "Active workspace messages returned by the backend.", + "type": "array", + "items": { + "$ref": "#/definitions/v2/WorkspaceMessage" + } + } + } + }, + "AddCreditsNudgeEmailStatus": { + "type": "string", + "enum": [ + "sent", + "cooldown_active" + ] + }, + "SendAddCreditsNudgeEmailResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SendAddCreditsNudgeEmailResponse", + "type": "object", + "required": [ + "status" + ], + "properties": { + "status": { + "$ref": "#/definitions/v2/AddCreditsNudgeEmailStatus" + } + } + }, + "FeedbackUploadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FeedbackUploadResponse", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "CommandExecResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecResponse", + "description": "Final buffered result for `command/exec`.", + "type": "object", + "required": [ + "exitCode", + "stderr", + "stdout" + ], + "properties": { + "exitCode": { + "description": "Process exit code.", + "type": "integer", + "format": "int32" + }, + "stderr": { + "description": "Buffered stderr capture.\n\nEmpty when stderr was streamed via `command/exec/outputDelta`.", + "type": "string" + }, + "stdout": { + "description": "Buffered stdout capture.\n\nEmpty when stdout was streamed via `command/exec/outputDelta`.", + "type": "string" + } + } + }, + "CommandExecWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecWriteResponse", + "description": "Empty success response for `command/exec/write`.", + "type": "object" + }, + "CommandExecTerminateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecTerminateResponse", + "description": "Empty success response for `command/exec/terminate`.", + "type": "object" + }, + "CommandExecResizeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecResizeResponse", + "description": "Empty success response for `command/exec/resize`.", + "type": "object" + }, + "GuardianCommandSource": { + "type": "string", + "enum": [ + "shell", + "unifiedExec" + ] + }, + "GuardianApprovalReviewStatus": { + "description": "[UNSTABLE] Lifecycle state for an approval auto-review.", + "type": "string", + "enum": [ + "inProgress", + "approved", + "denied", + "timedOut", + "aborted" + ] + }, + "GuardianApprovalReviewAction": { + "oneOf": [ + { + "type": "object", + "required": [ + "command", + "cwd", + "source", + "type" + ], + "properties": { + "command": { + "type": "string" + }, + "cwd": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "source": { + "$ref": "#/definitions/v2/GuardianCommandSource" + }, + "type": { + "type": "string", + "enum": [ + "command" + ], + "title": "CommandGuardianApprovalReviewActionType" + } + }, + "title": "CommandGuardianApprovalReviewAction" + }, + { + "type": "object", + "required": [ + "argv", + "cwd", + "program", + "source", + "type" + ], + "properties": { + "argv": { + "type": "array", + "items": { + "type": "string" + } + }, + "cwd": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "program": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/v2/GuardianCommandSource" + }, + "type": { + "type": "string", + "enum": [ + "execve" + ], + "title": "ExecveGuardianApprovalReviewActionType" + } + }, + "title": "ExecveGuardianApprovalReviewAction" + }, + { + "type": "object", + "required": [ + "cwd", + "files", + "type" + ], + "properties": { + "cwd": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "files": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + }, + "type": { + "type": "string", + "enum": [ + "applyPatch" + ], + "title": "ApplyPatchGuardianApprovalReviewActionType" + } + }, + "title": "ApplyPatchGuardianApprovalReviewAction" + }, + { + "type": "object", + "required": [ + "host", + "port", + "protocol", + "target", + "type" + ], + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "protocol": { + "$ref": "#/definitions/v2/NetworkApprovalProtocol" + }, + "target": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "networkAccess" + ], + "title": "NetworkAccessGuardianApprovalReviewActionType" + } + }, + "title": "NetworkAccessGuardianApprovalReviewAction" + }, + { + "type": "object", + "required": [ + "server", + "toolName", + "type" + ], + "properties": { + "connectorId": { + "type": [ + "string", + "null" + ] + }, + "connectorName": { + "type": [ + "string", + "null" + ] + }, + "server": { + "type": "string" + }, + "toolName": { + "type": "string" + }, + "toolTitle": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "mcpToolCall" + ], + "title": "McpToolCallGuardianApprovalReviewActionType" + } + }, + "title": "McpToolCallGuardianApprovalReviewAction" + }, + { + "type": "object", + "required": [ + "permissions", + "type" + ], + "properties": { + "permissions": { + "$ref": "#/definitions/v2/RequestPermissionProfile" + }, + "reason": { + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "requestPermissions" + ], + "title": "RequestPermissionsGuardianApprovalReviewActionType" + } + }, + "title": "RequestPermissionsGuardianApprovalReviewAction" + } + ] + }, + "GuardianApprovalReview": { + "description": "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", + "type": "object", + "required": [ + "status" + ], + "properties": { + "rationale": { + "type": [ + "string", + "null" + ] + }, + "riskLevel": { + "anyOf": [ + { + "$ref": "#/definitions/v2/GuardianRiskLevel" + }, + { + "type": "null" + } + ] + }, + "status": { + "$ref": "#/definitions/v2/GuardianApprovalReviewStatus" + }, + "userAuthorization": { + "anyOf": [ + { + "$ref": "#/definitions/v2/GuardianUserAuthorization" + }, + { + "type": "null" + } + ] + } + } + }, + "AnalyticsConfig": { + "type": "object", + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + }, + "additionalProperties": true + }, + "AppConfig": { + "type": "object", + "properties": { + "approvals_reviewer": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + }, + { + "type": "null" + } + ] + }, + "default_tools_approval_mode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AppToolApproval" + }, + { + "type": "null" + } + ] + }, + "default_tools_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "destructive_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "enabled": { + "default": true, + "type": "boolean" + }, + "open_world_enabled": { + "type": [ + "boolean", + "null" + ] + }, + "tools": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AppToolsConfig" + }, + { + "type": "null" + } + ] + } + } + }, + "AppToolApproval": { + "type": "string", + "enum": [ + "auto", + "prompt", + "writes", + "approve" + ] + }, + "AppToolConfig": { + "type": "object", + "properties": { + "approval_mode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AppToolApproval" + }, + { + "type": "null" + } + ] + }, + "enabled": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "AppToolsConfig": { + "type": "object" + }, + "AppsConfig": { + "type": "object", + "properties": { + "_default": { + "default": null, + "anyOf": [ + { + "$ref": "#/definitions/v2/AppsDefaultConfig" + }, + { + "type": "null" + } + ] + } + } + }, + "AppsDefaultConfig": { + "type": "object", + "properties": { + "approvals_reviewer": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + }, + { + "type": "null" + } + ] + }, + "default_tools_approval_mode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AppToolApproval" + }, + { + "type": "null" + } + ] + }, + "destructive_enabled": { + "default": true, + "type": "boolean" + }, + "enabled": { + "default": true, + "type": "boolean" + }, + "open_world_enabled": { + "default": true, + "type": "boolean" + } + } + }, + "AutoCompactTokenLimitScope": { + "description": "Selects which part of the active context is charged against `model_auto_compact_token_limit`.", + "oneOf": [ + { + "description": "Count the full active context against the limit.", + "type": "string", + "enum": [ + "total" + ] + }, + { + "description": "Count sampled output and later growth after the carried window prefix.", + "type": "string", + "enum": [ + "body_after_prefix" + ] + } + ] + }, + "Config": { + "type": "object", + "properties": { + "analytics": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AnalyticsConfig" + }, + { + "type": "null" + } + ] + }, + "approval_policy": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AskForApproval" + }, + { + "type": "null" + } + ] + }, + "approvals_reviewer": { + "description": "[UNSTABLE] Optional default for where approval requests are routed for review.", + "anyOf": [ + { + "$ref": "#/definitions/v2/ApprovalsReviewer" + }, + { + "type": "null" + } + ] + }, + "web_search": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchMode" + }, + { + "type": "null" + } + ] + }, + "compact_prompt": { + "type": [ + "string", + "null" + ] + }, + "desktop": { + "type": [ + "object", + "null" + ], + "additionalProperties": true + }, + "developer_instructions": { + "type": [ + "string", + "null" + ] + }, + "forced_chatgpt_workspace_id": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ForcedChatgptWorkspaceIds" + }, + { + "type": "null" + } + ] + }, + "forced_login_method": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ForcedLoginMethod" + }, + { + "type": "null" + } + ] + }, + "instructions": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "model_auto_compact_token_limit": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "model_auto_compact_token_limit_scope": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AutoCompactTokenLimitScope" + }, + { + "type": "null" + } + ] + }, + "model_context_window": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "model_provider": { + "type": [ + "string", + "null" + ] + }, + "model_reasoning_effort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "model_reasoning_summary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "model_verbosity": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Verbosity" + }, + { + "type": "null" + } + ] + }, + "review_model": { + "type": [ + "string", + "null" + ] + }, + "sandbox_mode": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxMode" + }, + { + "type": "null" + } + ] + }, + "sandbox_workspace_write": { + "anyOf": [ + { + "$ref": "#/definitions/v2/SandboxWorkspaceWrite" + }, + { + "type": "null" + } + ] + }, + "service_tier": { + "type": [ + "string", + "null" + ] + }, + "tools": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ToolsV2" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": true + }, + "ConfigLayer": { + "type": "object", + "required": [ + "config", + "name", + "version" + ], + "properties": { + "config": true, + "disabledReason": { + "type": [ + "string", + "null" + ] + }, + "name": { + "$ref": "#/definitions/v2/ConfigLayerSource" + }, + "version": { + "type": "string" + } + } + }, + "ConfigLayerMetadata": { + "type": "object", + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "$ref": "#/definitions/v2/ConfigLayerSource" + }, + "version": { + "type": "string" + } + } + }, + "ConfigLayerSource": { + "oneOf": [ + { + "description": "Managed preferences layer delivered by MDM (macOS only).", + "type": "object", + "required": [ + "domain", + "key", + "type" + ], + "properties": { + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "mdm" + ], + "title": "MdmConfigLayerSourceType" + } + }, + "title": "MdmConfigLayerSource" + }, + { + "description": "Managed config layer from a file (usually `managed_config.toml`).", + "type": "object", + "required": [ + "file", + "type" + ], + "properties": { + "file": { + "description": "This is the path to the system config.toml file, though it is not guaranteed to exist.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "system" + ], + "title": "SystemConfigLayerSourceType" + } + }, + "title": "SystemConfigLayerSource" + }, + { + "description": "Enterprise-managed config layer delivered by the cloud config bundle.", + "type": "object", + "required": [ + "id", + "name", + "type" + ], + "properties": { + "id": { + "description": "Stable identifier for the delivered layer.", + "type": "string" + }, + "name": { + "description": "Admin-facing name for the delivered layer. This is surfaced in diagnostics so users know which cloud layer needs administrator attention.", + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "enterpriseManaged" + ], + "title": "EnterpriseManagedConfigLayerSourceType" + } + }, + "title": "EnterpriseManagedConfigLayerSource" + }, + { + "description": "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory", + "type": "object", + "required": [ + "file", + "type" + ], + "properties": { + "file": { + "description": "This is the path to the user's config.toml file, though it is not guaranteed to exist.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "profile": { + "description": "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.", + "type": [ + "string", + "null" + ] + }, + "type": { + "type": "string", + "enum": [ + "user" + ], + "title": "UserConfigLayerSourceType" + } + }, + "title": "UserConfigLayerSource" + }, + { + "description": "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + "type": "object", + "required": [ + "dotCodexFolder", + "type" + ], + "properties": { + "dotCodexFolder": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "type": { + "type": "string", + "enum": [ + "project" + ], + "title": "ProjectConfigLayerSourceType" + } + }, + "title": "ProjectConfigLayerSource" + }, + { + "description": "Session-layer overrides supplied via `-c`/`--config`.", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "sessionFlags" + ], + "title": "SessionFlagsConfigLayerSourceType" + } + }, + "title": "SessionFlagsConfigLayerSource" + }, + { + "description": "`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a \"best effort\" while we phase out `managed_config.toml` in favor of `requirements.toml`.", + "type": "object", + "required": [ + "file", + "type" + ], + "properties": { + "file": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "type": { + "type": "string", + "enum": [ + "legacyManagedConfigTomlFromFile" + ], + "title": "LegacyManagedConfigTomlFromFileConfigLayerSourceType" + } + }, + "title": "LegacyManagedConfigTomlFromFileConfigLayerSource" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "legacyManagedConfigTomlFromMdm" + ], + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSourceType" + } + }, + "title": "LegacyManagedConfigTomlFromMdmConfigLayerSource" + } + ] + }, + "ForcedChatgptWorkspaceIds": { + "description": "Backward-compatible API shape for ChatGPT workspace login restrictions.", + "anyOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "ForcedLoginMethod": { + "type": "string", + "enum": [ + "chatgpt", + "api" + ] + }, + "SandboxWorkspaceWrite": { + "type": "object", + "properties": { + "exclude_slash_tmp": { + "default": false, + "type": "boolean" + }, + "exclude_tmpdir_env_var": { + "default": false, + "type": "boolean" + }, + "network_access": { + "default": false, + "type": "boolean" + }, + "writable_roots": { + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ToolsV2": { + "type": "object", + "properties": { + "web_search": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchToolConfig" + }, + { + "type": "null" + } + ] + } + } + }, + "Verbosity": { + "description": "Controls output length/detail on GPT-5 models via the Responses API. Serialized with lowercase values to match the OpenAI API.", + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + }, + "WebSearchContextSize": { + "type": "string", + "enum": [ + "low", + "medium", + "high" + ] + }, + "WebSearchLocation": { + "type": "object", + "properties": { + "city": { + "type": [ + "string", + "null" + ] + }, + "country": { + "type": [ + "string", + "null" + ] + }, + "region": { + "type": [ + "string", + "null" + ] + }, + "timezone": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "WebSearchMode": { + "type": "string", + "enum": [ + "disabled", + "cached", + "indexed", + "live" + ] + }, + "WebSearchToolConfig": { + "type": "object", + "properties": { + "allowed_domains": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "context_size": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchContextSize" + }, + { + "type": "null" + } + ] + }, + "location": { + "anyOf": [ + { + "$ref": "#/definitions/v2/WebSearchLocation" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, + "ConfigReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigReadResponse", + "type": "object", + "required": [ + "config", + "origins" + ], + "properties": { + "config": { + "$ref": "#/definitions/v2/Config" + }, + "layers": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/ConfigLayer" + } + }, + "origins": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/v2/ConfigLayerMetadata" + } + } + } + }, + "ExternalAgentConfigDetectResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExternalAgentConfigDetectResponse", + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigMigrationItem" + } + } + } + }, + "ExternalAgentConfigImportResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExternalAgentConfigImportResponse", + "type": "object", + "required": [ + "importId" + ], + "properties": { + "importId": { + "type": "string" + } + } + }, + "ExternalAgentConfigImportHistory": { + "type": "object", + "required": [ + "completedAtMs", + "failures", + "importId", + "successes" + ], + "properties": { + "completedAtMs": { + "type": "integer", + "format": "int64" + }, + "failures": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportItemTypeFailure" + } + }, + "importId": { + "type": "string" + }, + "successes": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportItemTypeSuccess" + } + } + } + }, + "ExternalAgentConfigImportItemTypeFailure": { + "type": "object", + "required": [ + "failureStage", + "itemType", + "message" + ], + "properties": { + "cwd": { + "type": [ + "string", + "null" + ] + }, + "errorType": { + "type": [ + "string", + "null" + ] + }, + "failureStage": { + "type": "string" + }, + "itemType": { + "$ref": "#/definitions/v2/ExternalAgentConfigMigrationItemType" + }, + "message": { + "type": "string" + }, + "source": { + "type": [ + "string", + "null" + ] + } + } + }, + "ExternalAgentConfigImportItemTypeSuccess": { + "type": "object", + "required": [ + "itemType" + ], + "properties": { + "cwd": { + "type": [ + "string", + "null" + ] + }, + "itemType": { + "$ref": "#/definitions/v2/ExternalAgentConfigMigrationItemType" + }, + "source": { + "type": [ + "string", + "null" + ] + }, + "target": { + "type": [ + "string", + "null" + ] + } + } + }, + "ExternalAgentConfigImportHistoriesReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExternalAgentConfigImportHistoriesReadResponse", + "type": "object", + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportHistory" + } + } + } + }, + "OverriddenMetadata": { + "type": "object", + "required": [ + "effectiveValue", + "message", + "overridingLayer" + ], + "properties": { + "effectiveValue": true, + "message": { + "type": "string" + }, + "overridingLayer": { + "$ref": "#/definitions/v2/ConfigLayerMetadata" + } + } + }, + "WriteStatus": { + "type": "string", + "enum": [ + "ok", + "okOverridden" + ] + }, + "ConfigWriteResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigWriteResponse", + "type": "object", + "required": [ + "filePath", + "status", + "version" + ], + "properties": { + "filePath": { + "description": "Canonical path to the config file that was written.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "overriddenMetadata": { + "anyOf": [ + { + "$ref": "#/definitions/v2/OverriddenMetadata" + }, + { + "type": "null" + } + ] + }, + "status": { + "$ref": "#/definitions/v2/WriteStatus" + }, + "version": { + "type": "string" + } + } + }, + "ComputerUseRequirements": { + "type": "object", + "properties": { + "allowLockedComputerUse": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "ConfigRequirements": { + "type": "object", + "properties": { + "allowAppshots": { + "type": [ + "boolean", + "null" + ] + }, + "allowManagedHooksOnly": { + "type": [ + "boolean", + "null" + ] + }, + "allowRemoteControl": { + "type": [ + "boolean", + "null" + ] + }, + "allowedApprovalPolicies": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/AskForApproval" + } + }, + "models": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ModelsRequirements" + }, + { + "type": "null" + } + ] + }, + "allowedPermissionProfiles": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "boolean" + } + }, + "allowedSandboxModes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/SandboxMode" + } + }, + "allowedWebSearchModes": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/WebSearchMode" + } + }, + "allowedWindowsSandboxImplementations": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/WindowsSandboxSetupMode" + } + }, + "computerUse": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ComputerUseRequirements" + }, + { + "type": "null" + } + ] + }, + "defaultPermissions": { + "type": [ + "string", + "null" + ] + }, + "enforceResidency": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ResidencyRequirement" + }, + { + "type": "null" + } + ] + }, + "featureRequirements": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "boolean" + } + } + } + }, + "ConfiguredHookHandler": { + "oneOf": [ + { + "type": "object", + "required": [ + "async", + "command", + "type" + ], + "properties": { + "async": { + "type": "boolean" + }, + "command": { + "type": "string" + }, + "commandWindows": { + "type": [ + "string", + "null" + ] + }, + "statusMessage": { + "type": [ + "string", + "null" + ] + }, + "timeoutSec": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0.0 + }, + "type": { + "type": "string", + "enum": [ + "command" + ], + "title": "CommandConfiguredHookHandlerType" + } + }, + "title": "CommandConfiguredHookHandler" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "prompt" + ], + "title": "PromptConfiguredHookHandlerType" + } + }, + "title": "PromptConfiguredHookHandler" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "agent" + ], + "title": "AgentConfiguredHookHandlerType" + } + }, + "title": "AgentConfiguredHookHandler" + } + ] + }, + "ConfiguredHookMatcherGroup": { + "type": "object", + "required": [ + "hooks" + ], + "properties": { + "hooks": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookHandler" + } + }, + "matcher": { + "type": [ + "string", + "null" + ] + } + } + }, + "ManagedHooksRequirements": { + "type": "object", + "required": [ + "PermissionRequest", + "PostCompact", + "PostToolUse", + "PreCompact", + "PreToolUse", + "SessionStart", + "Stop", + "SubagentStart", + "SubagentStop", + "UserPromptSubmit" + ], + "properties": { + "PermissionRequest": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "PostCompact": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "PostToolUse": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "PreCompact": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "PreToolUse": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "SessionStart": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "Stop": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "SubagentStart": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "SubagentStop": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "UserPromptSubmit": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/ConfiguredHookMatcherGroup" + } + }, + "managedDir": { + "type": [ + "string", + "null" + ] + }, + "windowsManagedDir": { + "type": [ + "string", + "null" + ] + } + } + }, + "ModelsRequirements": { + "type": "object", + "properties": { + "newThread": { + "anyOf": [ + { + "$ref": "#/definitions/v2/NewThreadModelDefaults" + }, + { + "type": "null" + } + ] + } + } + }, + "NetworkDomainPermission": { + "type": "string", + "enum": [ + "allow", + "deny" + ] + }, + "NetworkRequirements": { + "type": "object", + "properties": { + "allowLocalBinding": { + "type": [ + "boolean", + "null" + ] + }, + "allowUnixSockets": { + "description": "Legacy compatibility view derived from `unix_sockets`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "allowUpstreamProxy": { + "type": [ + "boolean", + "null" + ] + }, + "allowedDomains": { + "description": "Legacy compatibility view derived from `domains`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "dangerouslyAllowAllUnixSockets": { + "type": [ + "boolean", + "null" + ] + }, + "dangerouslyAllowNonLoopbackProxy": { + "type": [ + "boolean", + "null" + ] + }, + "deniedDomains": { + "description": "Legacy compatibility view derived from `domains`.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "domains": { + "description": "Canonical network permission map for `experimental_network`.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/v2/NetworkDomainPermission" + } + }, + "enabled": { + "type": [ + "boolean", + "null" + ] + }, + "httpPort": { + "type": [ + "integer", + "null" + ], + "format": "uint16", + "minimum": 0.0 + }, + "managedAllowedDomainsOnly": { + "description": "When true, only managed allowlist entries are respected while managed network enforcement is active.", + "type": [ + "boolean", + "null" + ] + }, + "socksPort": { + "type": [ + "integer", + "null" + ], + "format": "uint16", + "minimum": 0.0 + }, + "unixSockets": { + "description": "Canonical unix socket permission map for `experimental_network`.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/definitions/v2/NetworkUnixSocketPermission" + } + } + } + }, + "NetworkUnixSocketPermission": { + "type": "string", + "enum": [ + "allow", + "deny" + ] + }, + "NewThreadModelDefaults": { + "type": "object", + "properties": { + "model": { + "type": [ + "string", + "null" + ] + }, + "modelReasoningEffort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "serviceTier": { + "type": [ + "string", + "null" + ] + } + } + }, + "ResidencyRequirement": { + "type": "string", + "enum": [ + "us" + ] + }, + "ConfigRequirementsReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ConfigRequirementsReadResponse", + "type": "object", + "properties": { + "requirements": { + "description": "Null if no requirements are configured (e.g. no requirements.toml/MDM entries).", + "anyOf": [ + { + "$ref": "#/definitions/v2/ConfigRequirements" + }, + { + "type": "null" + } + ] + } + } + }, + "Account": { + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ], + "title": "ApiKeyAccountType" + } + }, + "title": "ApiKeyAccount" + }, + { + "type": "object", + "required": [ + "email", + "planType", + "type" + ], + "properties": { + "email": { + "type": [ + "string", + "null" + ] + }, + "planType": { + "$ref": "#/definitions/v2/PlanType" + }, + "type": { + "type": "string", + "enum": [ + "chatgpt" + ], + "title": "ChatgptAccountType" + } + }, + "title": "ChatgptAccount" + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "credentialSource": { + "default": "awsManaged", + "allOf": [ + { + "$ref": "#/definitions/v2/AmazonBedrockCredentialSource" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "amazonBedrock" + ], + "title": "AmazonBedrockAccountType" + } + }, + "title": "AmazonBedrockAccount" + } + ] + }, + "AmazonBedrockCredentialSource": { + "type": "string", + "enum": [ + "codexManaged", + "awsManaged" + ] + }, + "GetAccountResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "GetAccountResponse", + "type": "object", + "required": [ + "requiresOpenaiAuth" + ], + "properties": { + "account": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Account" + }, + { + "type": "null" + } + ] + }, + "requiresOpenaiAuth": { + "type": "boolean" + } + } + }, + "ErrorNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ErrorNotification", + "type": "object", + "required": [ + "error", + "threadId", + "turnId", + "willRetry" + ], + "properties": { + "error": { + "$ref": "#/definitions/v2/TurnError" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + }, + "willRetry": { + "type": "boolean" + } + } + }, + "ThreadStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadStartedNotification", + "type": "object", + "required": [ + "thread" + ], + "properties": { + "thread": { + "$ref": "#/definitions/v2/Thread" + } + } + }, + "ThreadStatusChangedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadStatusChangedNotification", + "type": "object", + "required": [ + "status", + "threadId" + ], + "properties": { + "status": { + "$ref": "#/definitions/v2/ThreadStatus" + }, + "threadId": { + "type": "string" + } + } + }, + "ThreadArchivedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadArchivedNotification", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadDeletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadDeletedNotification", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadUnarchivedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadUnarchivedNotification", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadClosedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadClosedNotification", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "SkillsChangedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SkillsChangedNotification", + "description": "Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.", + "type": "object" + }, + "ThreadNameUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadNameUpdatedNotification", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + }, + "threadName": { + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadGoalUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadGoalUpdatedNotification", + "type": "object", + "required": [ + "goal", + "threadId" + ], + "properties": { + "goal": { + "$ref": "#/definitions/v2/ThreadGoal" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadGoalClearedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadGoalClearedNotification", + "type": "object", + "required": [ + "threadId" + ], + "properties": { + "threadId": { + "type": "string" + } + } + }, + "ThreadSettings": { + "type": "object", + "required": [ + "approvalPolicy", + "approvalsReviewer", + "collaborationMode", + "cwd", + "model", + "modelProvider", + "sandboxPolicy" + ], + "properties": { + "activePermissionProfile": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ActivePermissionProfile" + }, + { + "type": "null" + } + ] + }, + "approvalPolicy": { + "$ref": "#/definitions/v2/AskForApproval" + }, + "approvalsReviewer": { + "$ref": "#/definitions/v2/ApprovalsReviewer" + }, + "collaborationMode": { + "$ref": "#/definitions/v2/CollaborationMode" + }, + "cwd": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "effort": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningEffort" + }, + { + "type": "null" + } + ] + }, + "model": { + "type": "string" + }, + "modelProvider": { + "type": "string" + }, + "summary": { + "anyOf": [ + { + "$ref": "#/definitions/v2/ReasoningSummary" + }, + { + "type": "null" + } + ] + }, + "personality": { + "anyOf": [ + { + "$ref": "#/definitions/v2/Personality" + }, + { + "type": "null" + } + ] + }, + "sandboxPolicy": { + "$ref": "#/definitions/v2/SandboxPolicy" + }, + "serviceTier": { + "type": [ + "string", + "null" + ] + } + } + }, + "ThreadSettingsUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadSettingsUpdatedNotification", + "type": "object", + "required": [ + "threadId", + "threadSettings" + ], + "properties": { + "threadId": { + "type": "string" + }, + "threadSettings": { + "$ref": "#/definitions/v2/ThreadSettings" + } + } + }, + "ThreadTokenUsage": { + "type": "object", + "required": [ + "last", + "total" + ], + "properties": { + "last": { + "$ref": "#/definitions/v2/TokenUsageBreakdown" + }, + "modelContextWindow": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "total": { + "$ref": "#/definitions/v2/TokenUsageBreakdown" + } + } + }, + "TokenUsageBreakdown": { + "type": "object", + "required": [ + "cachedInputTokens", + "inputTokens", + "outputTokens", + "reasoningOutputTokens", + "totalTokens" + ], + "properties": { + "cachedInputTokens": { + "type": "integer", + "format": "int64" + }, + "inputTokens": { + "type": "integer", + "format": "int64" + }, + "outputTokens": { + "type": "integer", + "format": "int64" + }, + "reasoningOutputTokens": { + "type": "integer", + "format": "int64" + }, + "totalTokens": { + "type": "integer", + "format": "int64" + } + } + }, + "ThreadTokenUsageUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ThreadTokenUsageUpdatedNotification", + "type": "object", + "required": [ + "threadId", + "tokenUsage", + "turnId" + ], + "properties": { + "threadId": { + "type": "string" + }, + "tokenUsage": { + "$ref": "#/definitions/v2/ThreadTokenUsage" + }, + "turnId": { + "type": "string" + } + } + }, + "TurnStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnStartedNotification", + "type": "object", + "required": [ + "threadId", + "turn" + ], + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/v2/Turn" + } + } + }, + "HookExecutionMode": { + "type": "string", + "enum": [ + "sync", + "async" + ] + }, + "HookOutputEntry": { + "type": "object", + "required": [ + "kind", + "text" + ], + "properties": { + "kind": { + "$ref": "#/definitions/v2/HookOutputEntryKind" + }, + "text": { + "type": "string" + } + } + }, + "HookOutputEntryKind": { + "type": "string", + "enum": [ + "warning", + "stop", + "feedback", + "context", + "error" + ] + }, + "HookRunStatus": { + "type": "string", + "enum": [ + "running", + "completed", + "failed", + "blocked", + "stopped" + ] + }, + "HookRunSummary": { + "type": "object", + "required": [ + "displayOrder", + "entries", + "eventName", + "executionMode", + "handlerType", + "id", + "scope", + "sourcePath", + "startedAt", + "status" + ], + "properties": { + "completedAt": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "displayOrder": { + "type": "integer", + "format": "int64" + }, + "durationMs": { + "type": [ + "integer", + "null" + ], + "format": "int64" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/HookOutputEntry" + } + }, + "eventName": { + "$ref": "#/definitions/v2/HookEventName" + }, + "executionMode": { + "$ref": "#/definitions/v2/HookExecutionMode" + }, + "handlerType": { + "$ref": "#/definitions/v2/HookHandlerType" + }, + "id": { + "type": "string" + }, + "scope": { + "$ref": "#/definitions/v2/HookScope" + }, + "source": { + "default": "unknown", + "allOf": [ + { + "$ref": "#/definitions/v2/HookSource" + } + ] + }, + "sourcePath": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "startedAt": { + "type": "integer", + "format": "int64" + }, + "status": { + "$ref": "#/definitions/v2/HookRunStatus" + }, + "statusMessage": { + "type": [ + "string", + "null" + ] + } + } + }, + "HookScope": { + "type": "string", + "enum": [ + "thread", + "turn" + ] + }, + "HookStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HookStartedNotification", + "type": "object", + "required": [ + "run", + "threadId" + ], + "properties": { + "run": { + "$ref": "#/definitions/v2/HookRunSummary" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": [ + "string", + "null" + ] + } + } + }, + "TurnCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnCompletedNotification", + "type": "object", + "required": [ + "threadId", + "turn" + ], + "properties": { + "threadId": { + "type": "string" + }, + "turn": { + "$ref": "#/definitions/v2/Turn" + } + } + }, + "HookCompletedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HookCompletedNotification", + "type": "object", + "required": [ + "run", + "threadId" + ], + "properties": { + "run": { + "$ref": "#/definitions/v2/HookRunSummary" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": [ + "string", + "null" + ] + } + } + }, + "TurnDiffUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnDiffUpdatedNotification", + "description": "Notification that the turn-level unified diff has changed. Contains the latest aggregated diff across all file changes in the turn.", + "type": "object", + "required": [ + "diff", + "threadId", + "turnId" + ], + "properties": { + "diff": { + "type": "string" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "TurnPlanStep": { + "type": "object", + "required": [ + "status", + "step" + ], + "properties": { + "status": { + "$ref": "#/definitions/v2/TurnPlanStepStatus" + }, + "step": { + "type": "string" + } + } + }, + "TurnPlanStepStatus": { + "type": "string", + "enum": [ + "pending", + "inProgress", + "completed" + ] + }, + "TurnPlanUpdatedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TurnPlanUpdatedNotification", + "type": "object", + "required": [ + "plan", + "threadId", + "turnId" + ], + "properties": { + "explanation": { + "type": [ + "string", + "null" + ] + }, + "plan": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/TurnPlanStep" + } + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "ItemStartedNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ItemStartedNotification", + "type": "object", + "required": [ + "item", + "startedAtMs", + "threadId", + "turnId" + ], + "properties": { + "item": { + "$ref": "#/definitions/v2/ThreadItem" + }, + "startedAtMs": { + "description": "Unix timestamp (in milliseconds) when this item lifecycle started.", + "type": "integer", + "format": "int64" + }, + "threadId": { + "type": "string" + }, + "turnId": { + "type": "string" + } + } + }, + "AdditionalFileSystemPermissions": { + "type": "object", + "properties": { + "entries": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/FileSystemSandboxEntry" + } + }, + "globScanMaxDepth": { + "type": [ + "integer", + "null" + ], + "format": "uint", + "minimum": 1.0 + }, + "read": { + "description": "This will be removed in favor of `entries`.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/LegacyAppPathString" + } + }, + "write": { + "description": "This will be removed in favor of `entries`.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/v2/LegacyAppPathString" + } + } + } + }, + "AdditionalNetworkPermissions": { + "type": "object", + "properties": { + "enabled": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "FileSystemAccessMode": { + "type": "string", + "enum": [ + "read", + "write", + "deny" + ] + }, + "FileSystemPath": { + "oneOf": [ + { + "type": "object", + "required": [ + "path", + "type" + ], + "properties": { + "path": { + "$ref": "#/definitions/v2/LegacyAppPathString" + }, + "type": { + "type": "string", + "enum": [ + "path" + ], + "title": "PathFileSystemPathType" + } + }, + "title": "PathFileSystemPath" + }, + { + "type": "object", + "required": [ + "pattern", + "type" + ], + "properties": { + "pattern": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "glob_pattern" + ], + "title": "GlobPatternFileSystemPathType" + } + }, + "title": "GlobPatternFileSystemPath" + }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "special" + ], + "title": "SpecialFileSystemPathType" + }, + "value": { + "$ref": "#/definitions/v2/FileSystemSpecialPath" + } + }, + "title": "SpecialFileSystemPath" + } + ] + }, + "FileSystemSandboxEntry": { + "type": "object", + "required": [ + "access", + "path" + ], + "properties": { + "access": { + "$ref": "#/definitions/v2/FileSystemAccessMode" + }, + "path": { + "$ref": "#/definitions/v2/FileSystemPath" + } + } + }, + "FileSystemSpecialPath": { + "oneOf": [ + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "root" + ] + } + }, + "title": "RootFileSystemSpecialPath" + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "minimal" + ] + } + }, + "title": "MinimalFileSystemSpecialPath" + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "project_roots" + ] + }, + "subpath": { + "type": [ + "string", + "null" + ] + } + }, + "title": "KindFileSystemSpecialPath" + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "tmpdir" + ] + } + }, + "title": "TmpdirFileSystemSpecialPath" + }, + { + "type": "object", + "required": [ + "kind" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "slash_tmp" + ] + } + }, + "title": "SlashTmpFileSystemSpecialPath" + }, + { + "type": "object", + "required": [ + "kind", + "path" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "unknown" + ] + }, + "path": { + "type": "string" + }, + "subpath": { + "type": [ + "string", + "null" + ] + } + } + } + ] + } + }, + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, + "InitializeResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InitializeResponse", + "type": "object", + "required": [ + "codexHome", + "platformFamily", + "platformOs", + "userAgent" + ], + "properties": { + "codexHome": { + "description": "Absolute path to the server's $CODEX_HOME directory.", + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ] + }, + "platformFamily": { + "description": "Platform family for the running app-server target, for example `\"unix\"` or `\"windows\"`.", + "type": "string" + }, + "platformOs": { + "description": "Operating system for the running app-server target, for example `\"macos\"`, `\"linux\"`, or `\"windows\"`.", + "type": "string" + }, + "userAgent": { + "type": "string" + } + } + }, + "FuzzyFileSearchResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FuzzyFileSearchResponse", + "type": "object", + "required": [ + "files" + ], + "properties": { + "files": { + "type": "array", + "items": { + "$ref": "#/definitions/FuzzyFileSearchResult" + } + } + } + }, + "AttestationGenerateResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AttestationGenerateResponse", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "description": "Opaque client attestation token.", + "type": "string" + } + } + }, + "ChatgptAuthTokensRefreshResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ChatgptAuthTokensRefreshResponse", + "type": "object", + "required": [ + "accessToken", + "chatgptAccountId" + ], + "properties": { + "accessToken": { + "type": "string" + }, + "chatgptAccountId": { + "type": "string" + }, + "chatgptPlanType": { + "type": [ + "string", + "null" + ] + } + } + }, + "DynamicToolCallResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "DynamicToolCallResponse", + "type": "object", + "required": [ + "contentItems", + "success" + ], + "properties": { + "contentItems": { + "type": "array", + "items": { + "$ref": "#/definitions/v2/DynamicToolCallOutputContentItem" + } + }, + "success": { + "type": "boolean" + } + } + }, + "CommandExecutionRequestApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CommandExecutionRequestApprovalResponse", + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "$ref": "#/definitions/CommandExecutionApprovalDecision" + } + } + }, + "FileChangeApprovalDecision": { + "oneOf": [ + { + "description": "User approved the file changes.", + "type": "string", + "enum": [ + "accept" + ] + }, + { + "description": "User approved the file changes and future changes to the same files should run without prompting.", + "type": "string", + "enum": [ + "acceptForSession" + ] + }, + { + "description": "User denied the file changes. The agent will continue the turn.", + "type": "string", + "enum": [ + "decline" + ] + }, + { + "description": "User denied the file changes. The turn will also be immediately interrupted.", + "type": "string", + "enum": [ + "cancel" + ] + } + ] + }, + "FileChangeRequestApprovalResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "FileChangeRequestApprovalResponse", + "type": "object", + "required": [ + "decision" + ], + "properties": { + "decision": { + "$ref": "#/definitions/FileChangeApprovalDecision" + } + } + }, + "ToolRequestUserInputAnswer": { + "description": "EXPERIMENTAL. Captures a user's answer to a request_user_input question.", + "type": "object", + "required": [ + "answers" + ], + "properties": { + "answers": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "ToolRequestUserInputResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ToolRequestUserInputResponse", + "description": "EXPERIMENTAL. Response payload mapping question ids to answers.", + "type": "object", + "required": [ + "answers" + ], + "properties": { + "answers": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ToolRequestUserInputAnswer" + } + } + } + }, + "McpServerElicitationAction": { + "type": "string", + "enum": [ + "accept", + "decline", + "cancel" + ] + }, + "McpServerElicitationRequestResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "McpServerElicitationRequestResponse", + "type": "object", + "required": [ + "action" + ], + "properties": { + "_meta": { + "description": "Optional client metadata for form-mode action handling." + }, + "action": { + "$ref": "#/definitions/McpServerElicitationAction" + }, + "content": { + "description": "Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`.\n\nThis is nullable because decline/cancel responses have no content." + } + } + }, + "GrantedPermissionProfile": { + "type": "object", + "properties": { + "fileSystem": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AdditionalFileSystemPermissions" + }, + { + "type": "null" + } + ] + }, + "network": { + "anyOf": [ + { + "$ref": "#/definitions/v2/AdditionalNetworkPermissions" + }, + { + "type": "null" + } + ] + } + } + }, + "PermissionGrantScope": { + "type": "string", + "enum": [ + "turn", + "session" + ] + } + } +} \ No newline at end of file diff --git a/src/lib/codex/__tests__/app-server-host.test.ts b/src/lib/codex/__tests__/app-server-host.test.ts new file mode 100644 index 0000000000..5305998f72 --- /dev/null +++ b/src/lib/codex/__tests__/app-server-host.test.ts @@ -0,0 +1,342 @@ +import { EventEmitter } from 'node:events'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { request as httpRequest } from 'node:http'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { PassThrough, Writable } from 'node:stream'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { CodexAppServerHost } from '../app-server-host.js'; +import type { CodexAppServerState, ThreadOptions, TurnOptions } from '../app-server-manager.js'; + +class FakeManager extends EventEmitter { + readonly startThreadCalls: ThreadOptions[] = []; + readonly resumeThreadCalls: Array<{ threadId: string; options: ThreadOptions }> = []; + readonly startTurnCalls: Array<{ text: string; options?: TurnOptions }> = []; + readonly approvals: Array<{ id: string | number; decision: string }> = []; + readonly userInputs: Array<{ id: string | number; answers: Record }> = []; + interruptCalls = 0; + stopCalls = 0; + private state: CodexAppServerState = { state: 'ready' }; + + async start(): Promise {} + + stop(): void { + this.stopCalls += 1; + this.state = { ...this.state, state: 'closed' }; + } + + getState(): Readonly { + return { ...this.state }; + } + + async startThread(options: ThreadOptions): Promise { + this.startThreadCalls.push(options); + this.state = { state: 'idle', threadId: 'thread-started' }; + this.emit('notification', { method: 'thread/started', params: { thread: { id: 'thread-started' } } }); + } + + async resumeThread(threadId: string, options: ThreadOptions): Promise { + this.resumeThreadCalls.push({ threadId, options }); + this.state = { state: 'idle', threadId }; + this.emit('notification', { method: 'thread/started', params: { thread: { id: threadId } } }); + } + + async startTurn(text: string, options?: TurnOptions): Promise { + this.startTurnCalls.push({ text, options }); + this.state = { ...this.state, state: 'running', activeTurnId: 'turn-1' }; + this.emit('notification', { method: 'turn/started', params: { turn: { id: 'turn-1' } } }); + this.emit('notification', { method: 'item/completed', params: { text: 'assistant delta' } }); + } + + async interruptTurn(): Promise { + this.interruptCalls += 1; + } + + setState(state: CodexAppServerState): void { + this.state = state; + } + + answerApproval(id: string | number, decision: string): void { + this.approvals.push({ id, decision }); + } + + answerUserInput(id: string | number, answers: Record): void { + this.userInputs.push({ id, answers }); + } +} + +let tmpHome: string; +let overdeckHome: string; +let originalHome: string | undefined; +let startedHosts: CodexAppServerHost[] = []; + +function makeHost(manager: FakeManager, opts: Partial[0]> = {}): CodexAppServerHost { + return new CodexAppServerHost({ + agentId: 'agent-host-test', + cwd: '/tmp/workspace', + overdeckHome, + manager, + ...opts, + }); +} + +function captureStdout(): { stdout: Writable; lines: string[] } { + const lines: string[] = []; + let buffer = ''; + const stdout = new Writable({ + write(chunk, _encoding, callback) { + buffer += String(chunk); + for (;;) { + const newline = buffer.indexOf('\n'); + if (newline < 0) break; + lines.push(buffer.slice(0, newline)); + buffer = buffer.slice(newline + 1); + } + callback(); + }, + }); + return { stdout, lines }; +} + +function readEventLog(): Array> { + return readFileSync(join(overdeckHome, 'agents', 'agent-host-test', 'appserver-events.jsonl'), 'utf8') + .trim() + .split('\n') + .filter(Boolean) + .map((line) => JSON.parse(line) as Record); +} + +async function postHost(agentId: string, token: string | undefined, body: unknown): Promise<{ status: number; body: string }> { + const payload = JSON.stringify(body); + return new Promise((resolve, reject) => { + const req = httpRequest( + { + socketPath: join(overdeckHome, 'sockets', `appserver-${agentId}.sock`), + path: '/', + method: 'POST', + agent: false, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload), + ...(token ? { 'x-overdeck-bridge-token': token } : {}), + }, + }, + (res) => { + let responseBody = ''; + res.setEncoding('utf-8'); + res.on('data', chunk => { responseBody += chunk; }); + res.on('end', () => resolve({ status: res.statusCode ?? 0, body: responseBody })); + }, + ); + req.on('error', reject); + req.write(payload); + req.end(); + }); +} + +describe('CodexAppServerHost', () => { + beforeEach(() => { + tmpHome = mkdtempSync(join(tmpdir(), 'pan-appserver-host-')); + overdeckHome = join(tmpHome, '.overdeck'); + mkdirSync(join(overdeckHome, 'agents', 'agent-host-test'), { recursive: true }); + originalHome = process.env.HOME; + process.env.HOME = tmpHome; + startedHosts = []; + }); + + afterEach(async () => { + await Promise.all(startedHosts.map(host => host.stop())); + if (originalHome === undefined) delete process.env.HOME; + else process.env.HOME = originalHome; + rmSync(tmpHome, { recursive: true, force: true }); + vi.restoreAllMocks(); + }); + + it('rejects a first message without a model before starting a thread', async () => { + const manager = new FakeManager(); + const host = makeHost(manager); + + const result = await host.handleOp({ op: 'message', content: 'hello' }); + + expect(result).toEqual({ + status: 400, + body: { error: 'model is required before starting a Codex app-server turn' }, + }); + expect(manager.startThreadCalls).toHaveLength(0); + expect(manager.startTurnCalls).toHaveLength(0); + }); + + it('returns 403 for missing or wrong socket tokens without executing an op', async () => { + const manager = new FakeManager(); + const host = makeHost(manager); + startedHosts.push(host); + await host.start(); + + expect(await postHost('agent-host-test', undefined, { op: 'interrupt' })).toMatchObject({ status: 403 }); + expect(await postHost('agent-host-test', 'wrong-token', { op: 'interrupt' })).toMatchObject({ status: 403 }); + expect(manager.interruptCalls).toBe(0); + }); + + it('status op reflects manager state and pending requests', async () => { + const manager = new FakeManager(); + manager.setState({ state: 'running', threadId: 'thread-1', activeTurnId: 'turn-1' }); + const host = makeHost(manager); + manager.emit('request', { id: 71, method: 'item/commandExecution/requestApproval', params: { command: 'git status' } }); + + expect(host.status()).toMatchObject({ + state: 'awaiting-approval', + managerState: 'running', + threadId: 'thread-1', + activeTurnId: 'turn-1', + pendingRequests: [{ id: 71, method: 'item/commandExecution/requestApproval', params: { command: 'git status' } }], + }); + }); + + it('starts a thread, persists threadId, starts a turn, and logs manager notifications', async () => { + const manager = new FakeManager(); + const host = makeHost(manager); + + const result = await host.handleOp({ op: 'message', content: 'build it', model: 'gpt-5.6-sol', effort: 'high' }); + + expect(result.status).toBe(200); + expect(manager.startThreadCalls).toEqual([{ model: 'gpt-5.6-sol', cwd: '/tmp/workspace', runtimeMode: 'default' }]); + expect(manager.startTurnCalls).toEqual([{ text: 'build it', options: { model: 'gpt-5.6-sol', effort: 'high' } }]); + expect(readFileSync(join(overdeckHome, 'agents', 'agent-host-test', 'codex-thread-id'), 'utf8')).toBe('thread-started'); + await vi.waitFor(() => { + expect(readEventLog()).toEqual(expect.arrayContaining([ + expect.objectContaining({ type: 'notification', method: 'item/completed' }), + ])); + }); + expect(readEventLog()).toEqual(expect.arrayContaining([ + expect.objectContaining({ type: 'notification', method: 'thread/started' }), + expect.objectContaining({ type: 'notification', method: 'item/completed' }), + ])); + }); + + it('passes per-turn model changes through natively on turn/start', async () => { + const manager = new FakeManager(); + const host = makeHost(manager); + + await host.handleOp({ op: 'message', content: 'first', model: 'gpt-5.6-sol' }); + await host.handleOp({ op: 'message', content: 'second', model: 'gpt-5.6-codex' }); + + expect(manager.startThreadCalls).toHaveLength(1); + expect(manager.startTurnCalls.at(-1)).toEqual({ text: 'second', options: { model: 'gpt-5.6-codex' } }); + }); + + it('resumes an existing thread on the first message when resumeThreadId is supplied', async () => { + const manager = new FakeManager(); + const host = makeHost(manager, { resumeThreadId: 'thread-existing' }); + + await host.handleOp({ op: 'message', content: 'resume work', model: 'gpt-5.6-sol' }); + + expect(manager.resumeThreadCalls).toEqual([ + { threadId: 'thread-existing', options: { model: 'gpt-5.6-sol', cwd: '/tmp/workspace', runtimeMode: 'default' } }, + ]); + expect(manager.startThreadCalls).toHaveLength(0); + }); + + it('resolves approval and user-input request ids through the manager', async () => { + const manager = new FakeManager(); + const host = makeHost(manager); + manager.emit('request', { id: 71, method: 'item/commandExecution/requestApproval', params: { command: 'git status' } }); + manager.emit('request', { id: 'prompt-1', method: 'user/input', params: { prompt: 'choose' } }); + + expect(host.status().pendingRequests).toHaveLength(2); + expect(await host.handleOp({ op: 'approval', requestId: 71, decision: 'accept' })).toEqual({ status: 200, body: { ok: true } }); + expect(await host.handleOp({ op: 'user-input', requestId: 'prompt-1', answers: { choice: ['a'] } })).toEqual({ status: 200, body: { ok: true } }); + + expect(manager.approvals).toEqual([{ id: 71, decision: 'accept' }]); + expect(manager.userInputs).toEqual([{ id: 'prompt-1', answers: { choice: ['a'] } }]); + expect(host.status().pendingRequests).toHaveLength(0); + }); + + it('rejects approval responses for requests that are no longer pending', async () => { + const manager = new FakeManager(); + const host = makeHost(manager); + manager.emit('request', { id: 72, method: 'item/commandExecution/requestApproval', params: { command: 'git status' } }); + + expect(await host.handleOp({ op: 'approval', requestId: 71, decision: 'accept' })).toEqual({ + status: 409, + body: { error: 'approval request 71 is not pending' }, + }); + expect(manager.approvals).toEqual([]); + expect(host.status().pendingRequests).toHaveLength(1); + }); + + it('forwards interrupt ops to the manager', async () => { + const manager = new FakeManager(); + const host = makeHost(manager); + + expect(await host.handleOp({ op: 'interrupt' })).toEqual({ status: 200, body: { ok: true, state: { state: 'ready' } } }); + expect(manager.interruptCalls).toBe(1); + }); + + it('interrupts a running turn before stopping on SIGTERM using fake timers', async () => { + vi.useFakeTimers(); + const manager = new FakeManager(); + manager.setState({ state: 'running', threadId: 'thread-1', activeTurnId: 'turn-1' }); + const host = makeHost(manager); + + const shutdown = host.shutdownForSignal('SIGTERM'); + await vi.waitFor(() => expect(manager.interruptCalls).toBe(1)); + await vi.runAllTimersAsync(); + await shutdown; + + expect(manager.interruptCalls).toBe(1); + expect(manager.stopCalls).toBe(1); + expect(readEventLog().map(entry => entry.type)).toEqual([ + 'lifecycle/signal', + 'op/interrupt', + 'lifecycle/child-sigterm', + ]); + }); + + it('renders user turns and assistant text to stdout as sanitized plain text lines', async () => { + const manager = new FakeManager(); + const { stdout, lines } = captureStdout(); + const host = makeHost(manager, { stdout }); + + await host.handleOp({ op: 'message', content: 'hello\u001b[31m', model: 'gpt-5.6-sol' }); + await vi.waitFor(() => expect(lines).toEqual(expect.arrayContaining([ + '[user] hello', + '[assistant] assistant delta', + ]))); + + expect(lines.join('\n')).not.toMatch(/\x1B\[/); + }); + + it('renders approval prompts with the command and y/n affordance', () => { + const manager = new FakeManager(); + const { stdout, lines } = captureStdout(); + makeHost(manager, { stdout }); + + manager.emit('request', { id: 71, method: 'item/commandExecution/requestApproval', params: { command: 'git status' } }); + + expect(lines).toContain('[approval #71] command: git status - reply via dashboard or type y/n'); + }); + + it('starts a turn from a non-approval stdin line', async () => { + const manager = new FakeManager(); + const stdin = new PassThrough(); + const host = makeHost(manager, { stdin, model: 'gpt-5.6-sol' }); + host.startPaneInput(); + + stdin.write('typed turn\n'); + + await vi.waitFor(() => expect(manager.startTurnCalls.at(-1)).toEqual({ text: 'typed turn', options: {} })); + }); + + it('resolves a pending approval from y on stdin without starting a turn', async () => { + const manager = new FakeManager(); + const stdin = new PassThrough(); + const host = makeHost(manager, { stdin, model: 'gpt-5.6-sol' }); + manager.emit('request', { id: 71, method: 'item/commandExecution/requestApproval', params: { command: 'git status' } }); + host.startPaneInput(); + + stdin.write('y\n'); + + await vi.waitFor(() => expect(manager.approvals).toEqual([{ id: 71, decision: 'accept' }])); + expect(manager.startTurnCalls).toHaveLength(0); + }); +}); diff --git a/src/lib/codex/__tests__/app-server-manager.test.ts b/src/lib/codex/__tests__/app-server-manager.test.ts new file mode 100644 index 0000000000..519f53d15a --- /dev/null +++ b/src/lib/codex/__tests__/app-server-manager.test.ts @@ -0,0 +1,148 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { CodexAppServerManager } from '../app-server-manager.js'; +import { createFakeAppServer } from './fake-app-server.js'; + +afterEach(() => vi.useRealTimers()); + +describe('CodexAppServerManager', () => { + it('performs initialize then initialized', async () => { + const fake = createFakeAppServer((message, server) => { + if (message.method === 'initialize') server.send({ id: message.id, result: {} }); + }); + const manager = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => 'codex-cli 0.144.1', spawnProcess: () => fake.child }); + await manager.start(); + expect(fake.messages.map(message => message.method)).toEqual(['initialize', 'initialized']); + manager.stop(); + }); + + it('times out unanswered requests with fake timers', async () => { + vi.useFakeTimers(); + const fake = createFakeAppServer(); + const manager = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => '0.144.1', spawnProcess: () => fake.child }); + const started = manager.start(); + const rejection = expect(started).rejects.toThrow('Timed out waiting for initialize'); + await vi.advanceTimersByTimeAsync(20_000); + await rejection; + manager.stop(); + }); + + it('warns on invalid JSON and continues with valid responses', async () => { + const fake = createFakeAppServer((message, server) => { + if (message.method === 'initialize') { + server.child.stdout.write('not-json\n'); + server.send({ id: message.id, result: { ready: true } }); + } + }); + const manager = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => '0.144.1', spawnProcess: () => fake.child }); + const warnings: string[] = []; + manager.on('warning', warning => warnings.push(String(warning))); + await manager.start(); + expect(warnings).toHaveLength(1); + expect(fake.messages.at(-1)?.method).toBe('initialized'); + manager.stop(); + }); + + it('rejects unsupported Codex versions before spawning', async () => { + const spawnProcess = vi.fn(() => createFakeAppServer().child); + const manager = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => 'codex-cli 0.143.9', spawnProcess }); + await expect(manager.start()).rejects.toThrow('Codex CLI 0.143.9 is unsupported; upgrade to 0.144.0'); + expect(spawnProcess).not.toHaveBeenCalled(); + }); + + it('emits stripped ERROR stderr and drops benign rollout messages', async () => { + const fake = createFakeAppServer((message, server) => { + if (message.method === 'initialize') server.send({ id: message.id, result: {} }); + }); + const manager = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => '0.144.1', spawnProcess: () => fake.child }); + const errors: string[] = []; + manager.on('stderr', error => errors.push(String(error))); + await manager.start(); + fake.child.stderr.write('\u001b[31mERROR failed\u001b[0m\n'); + fake.child.stderr.write('ERROR state db missing rollout path for thread abc\n'); + fake.child.stderr.write('informational message\n'); + expect(errors).toEqual(['ERROR failed']); + manager.stop(); + }); + + it('sends a 1MB turn as one request and tracks thread and turn state', async () => { + const fake = createFakeAppServer((message, server) => { + if (message.method === 'initialize') server.send({ id: message.id, result: {} }); + if (message.method === 'thread/start') { + server.send({ id: message.id, result: {} }); + server.send({ method: 'thread/started', params: { thread: { id: 'thread-1' } } }); + } + if (message.method === 'turn/start') { + server.send({ id: message.id, result: {} }); + server.send({ method: 'turn/started', params: { turn: { id: 'turn-1' } } }); + } + }); + const manager = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => '0.144.1', spawnProcess: () => fake.child }); + await manager.start(); + await manager.startThread({ model: 'caller-model' }); + const text = 'x'.repeat(1024 * 1024); + await manager.startTurn(text, { effort: 'high' }); + expect(manager.getState()).toEqual({ state: 'running', threadId: 'thread-1', activeTurnId: 'turn-1' }); + const turns = fake.messages.filter(message => message.method === 'turn/start'); + expect(turns).toHaveLength(1); + expect(((turns[0]?.params as { input: Array<{ text: string }> }).input[0]?.text)).toHaveLength(text.length); + fake.send({ method: 'turn/completed', params: { turn: { id: 'turn-1' } } }); + expect(manager.getState()).toEqual({ state: 'idle', threadId: 'thread-1', activeTurnId: undefined }); + manager.stop(); + }); + + it('falls back only for missing-thread resume errors', async () => { + const warnings: string[] = []; + const fake = createFakeAppServer((message, server) => { + if (message.method === 'initialize') server.send({ id: message.id, result: {} }); + if (message.method === 'thread/resume') server.send({ id: message.id, error: { message: 'thread/resume: thread not found' } }); + if (message.method === 'thread/start') server.send({ id: message.id, result: {} }); + }); + const manager = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => '0.144.1', spawnProcess: () => fake.child }); + manager.on('warning', warning => warnings.push(String(warning))); + await manager.start(); + await manager.resumeThread('missing', { model: 'caller-model' }); + expect(fake.messages.map(message => message.method)).toContain('thread/start'); + expect(warnings[0]).toContain('starting a fresh thread'); + manager.stop(); + + const rejected = createFakeAppServer((message, server) => { + if (message.method === 'initialize') server.send({ id: message.id, result: {} }); + if (message.method === 'thread/resume') server.send({ id: message.id, error: { message: 'permission denied' } }); + }); + const strict = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => '0.144.1', spawnProcess: () => rejected.child }); + await strict.start(); + await expect(strict.resumeThread('denied', { model: 'caller-model' })).rejects.toThrow('permission denied'); + expect(rejected.messages.some(message => message.method === 'thread/start')).toBe(false); + strict.stop(); + }); + + it('round-trips approval request ids and interrupts the active turn', async () => { + const requests: Array> = []; + const fake = createFakeAppServer((message, server) => { + if (message.method === 'initialize') server.send({ id: message.id, result: {} }); + if (message.method === 'thread/start') { + server.send({ id: message.id, result: {} }); + server.send({ method: 'thread/started', params: { thread: { id: 'thread-2' } } }); + } + if (message.method === 'turn/start') { + server.send({ id: message.id, result: {} }); + server.send({ method: 'turn/started', params: { turn: { id: 'turn-2' } } }); + server.send({ id: 71, method: 'item/commandExecution/requestApproval', params: { command: 'git status' } }); + } + if (message.method === 'turn/interrupt') server.send({ id: message.id, result: {} }); + }); + const manager = new CodexAppServerManager({ cwd: '/tmp', readVersion: async () => '0.144.1', spawnProcess: () => fake.child }); + manager.on('request', request => requests.push(request as Record)); + await manager.start(); + await manager.startThread({ model: 'caller-model' }); + await manager.startTurn('work'); + expect(requests[0]?.id).toBe(71); + manager.answerApproval(71, 'accept'); + await manager.interruptTurn(); + expect(fake.messages).toContainEqual({ id: 71, result: { decision: 'accept' } }); + expect(fake.messages).toContainEqual(expect.objectContaining({ method: 'turn/interrupt', params: { threadId: 'thread-2', turnId: 'turn-2' } })); + fake.send({ method: 'turn/completed', params: { turn: { id: 'turn-2' } } }); + expect(manager.getState().state).toBe('idle'); + manager.stop(); + }); +}); diff --git a/src/lib/codex/__tests__/fake-app-server.ts b/src/lib/codex/__tests__/fake-app-server.ts new file mode 100644 index 0000000000..b930b57b8c --- /dev/null +++ b/src/lib/codex/__tests__/fake-app-server.ts @@ -0,0 +1,38 @@ +import { EventEmitter } from 'node:events'; +import { PassThrough } from 'node:stream'; +import type { ChildProcessWithoutNullStreams } from 'node:child_process'; + +export interface FakeAppServer { + child: ChildProcessWithoutNullStreams; + messages: Array>; + send(message: unknown): void; +} + +export function createFakeAppServer(onMessage?: (message: Record, fake: FakeAppServer) => void): FakeAppServer { + const stdin = new PassThrough(); + const stdout = new PassThrough(); + const stderr = new PassThrough(); + const emitter = new EventEmitter(); + const messages: Array> = []; + let buffered = ''; + const fake = { + child: Object.assign(emitter, { stdin, stdout, stderr, kill: () => true }) as unknown as ChildProcessWithoutNullStreams, + messages, + send(message: unknown) { + stdout.write(`${JSON.stringify(message)}\n`); + }, + }; + stdin.on('data', chunk => { + buffered += String(chunk); + for (;;) { + const newline = buffered.indexOf('\n'); + if (newline < 0) break; + const line = buffered.slice(0, newline); + buffered = buffered.slice(newline + 1); + const message = JSON.parse(line) as Record; + messages.push(message); + onMessage?.(message, fake); + } + }); + return fake; +} diff --git a/src/lib/codex/app-server-host.ts b/src/lib/codex/app-server-host.ts new file mode 100644 index 0000000000..ff42097642 --- /dev/null +++ b/src/lib/codex/app-server-host.ts @@ -0,0 +1,476 @@ +import { randomUUID } from 'node:crypto'; +import { existsSync } from 'node:fs'; +import { appendFile, chmod, mkdir, rm, writeFile } from 'node:fs/promises'; +import { createServer, type Server, type ServerResponse } from 'node:http'; +import { homedir } from 'node:os'; +import { basename, join } from 'node:path'; +import { createInterface, type Interface } from 'node:readline'; +import type { Readable, Writable } from 'node:stream'; +import { fileURLToPath } from 'node:url'; +import { EventEmitter } from 'node:events'; +import { + CodexAppServerManager, + type AppServerMessage, + type CodexAppServerState, + type ThreadOptions, + type TurnOptions, +} from './app-server-manager.js'; +import { BRIDGE_TOKEN_HEADER } from '../bridge-token.js'; +import { codexHome, writeThreadId } from '../runtimes/codex.js'; + +type JsonRecord = Record; + +export interface PendingAppServerRequest { + id: string | number; + method: string; + params?: unknown; +} + +interface AppServerHostManager extends EventEmitter { + start(): Promise; + stop(): void; + getState(): Readonly; + startThread(options: ThreadOptions): Promise; + resumeThread(threadId: string, options: ThreadOptions): Promise; + startTurn(text: string, options?: TurnOptions): Promise; + interruptTurn(): Promise; + answerApproval(id: string | number, decision: string): void; + answerUserInput(id: string | number, answers: Record): void; +} + +export interface CodexAppServerHostOptions { + agentId: string; + cwd: string; + model?: string; + effort?: string; + resumeThreadId?: string; + overdeckHome?: string; + codexHome?: string; + manager?: AppServerHostManager; + stdin?: Readable; + stdout?: Writable; +} + +interface HostOpResult { + status: number; + body: JsonRecord; +} + +export class CodexAppServerHost { + private readonly overdeckHome: string; + private readonly manager: AppServerHostManager; + private readonly pendingRequests = new Map(); + private server: Server | undefined; + private input: Interface | undefined; + private token: string | undefined; + private threadModel: string | undefined; + private state: 'starting' | 'ready' | 'closed' = 'starting'; + + constructor(private readonly options: CodexAppServerHostOptions) { + this.overdeckHome = options.overdeckHome ?? process.env.OVERDECK_HOME ?? join(homedir(), '.overdeck'); + this.manager = options.manager ?? new CodexAppServerManager({ + cwd: options.cwd, + codexHome: options.codexHome ?? codexHome(), + }); + this.threadModel = options.model; + this.attachManagerEvents(); + } + + async start(): Promise { + await mkdir(this.agentDir(), { recursive: true }); + await mkdir(this.socketDir(), { recursive: true }); + this.token = randomUUID(); + await writeFile(this.tokenPath(), `${this.token}\n`, { mode: 0o600 }); + await this.manager.start(); + await this.listen(); + this.startPaneInput(); + this.state = 'ready'; + } + + async stop(): Promise { + this.state = 'closed'; + this.input?.close(); + this.input = undefined; + this.manager.stop(); + await this.closeServer(); + } + + async shutdownForSignal(signal: 'SIGTERM' | 'SIGINT', graceMs = 5_000): Promise { + await this.appendEvent('lifecycle/signal', { signal }); + const state = this.manager.getState(); + if (state.threadId && state.activeTurnId) { + await this.appendEvent('op/interrupt', { reason: signal }); + await this.manager.interruptTurn(); + } + await this.appendEvent('lifecycle/child-sigterm', { signal }); + this.manager.stop(); + await new Promise((resolve) => { + const timer = setTimeout(resolve, graceMs); + timer.unref?.(); + }); + await this.closeServer(); + this.state = 'closed'; + } + + private async closeServer(): Promise { + if (!this.server) return; + const server = this.server; + this.server = undefined; + await new Promise((resolve) => server.close(() => resolve())); + } + + status(): JsonRecord { + const managerState = this.manager.getState(); + return { + state: this.publicState(managerState), + managerState: managerState.state, + threadId: managerState.threadId, + activeTurnId: managerState.activeTurnId, + pendingRequests: [...this.pendingRequests.values()], + }; + } + + async handleOp(op: unknown): Promise { + const body = asRecord(op); + const name = typeof body.op === 'string' ? body.op : ''; + try { + if (name === 'status') return { status: 200, body: this.status() }; + if (name === 'message') return await this.handleMessageOp(body); + if (name === 'interrupt') return await this.handleInterruptOp(); + if (name === 'approval') return this.handleApprovalOp(body); + if (name === 'user-input') return this.handleUserInputOp(body); + return { status: 400, body: { error: `unsupported app-server op: ${name || ''}` } }; + } catch (error) { + return { + status: 500, + body: { error: error instanceof Error ? error.message : String(error) }, + }; + } + } + + private async handleMessageOp(op: JsonRecord): Promise { + const content = typeof op.content === 'string' ? op.content : ''; + if (!content) return { status: 400, body: { error: 'message content is required' } }; + const requestedModel = typeof op.model === 'string' ? op.model : undefined; + const requestedEffort = typeof op.effort === 'string' ? op.effort : this.options.effort; + const model = requestedModel ?? this.threadModel; + if (!model) { + return { + status: 400, + body: { error: 'model is required before starting a Codex app-server turn' }, + }; + } + + const state = this.manager.getState(); + if (!state.threadId) { + const threadOptions: ThreadOptions = { model, cwd: this.options.cwd, runtimeMode: 'default' }; + if (this.options.resumeThreadId) await this.manager.resumeThread(this.options.resumeThreadId, threadOptions); + else await this.manager.startThread(threadOptions); + this.threadModel = model; + } + + await this.appendEvent('op/message', { + contentLength: content.length, + model: requestedModel, + effort: requestedEffort, + hasExistingThread: Boolean(state.threadId), + }); + this.writePaneLine(`[user] ${content}`); + await this.manager.startTurn(content, { + ...(requestedModel ? { model: requestedModel } : {}), + ...(requestedEffort ? { effort: requestedEffort } : {}), + }); + return { status: 200, body: { ok: true, state: this.manager.getState() as JsonRecord } }; + } + + private async handleInterruptOp(): Promise { + await this.appendEvent('op/interrupt', {}); + await this.manager.interruptTurn(); + return { status: 200, body: { ok: true, state: this.manager.getState() as JsonRecord } }; + } + + private handleApprovalOp(op: JsonRecord): HostOpResult { + const requestId = parseRequestId(op.requestId); + const decision = typeof op.decision === 'string' ? op.decision : ''; + if (requestId === undefined || !decision) { + return { status: 400, body: { error: 'approval requires requestId and decision' } }; + } + if (!this.pendingRequests.has(String(requestId))) { + return { status: 409, body: { error: `approval request ${String(requestId)} is not pending` } }; + } + this.manager.answerApproval(requestId, decision); + this.pendingRequests.delete(String(requestId)); + this.writePaneLine(`[approval #${requestId}] ${decision}`); + void this.appendEvent('op/approval', { requestId, decision }); + return { status: 200, body: { ok: true } }; + } + + private handleUserInputOp(op: JsonRecord): HostOpResult { + const requestId = parseRequestId(op.requestId); + const answers = parseAnswers(op.answers); + if (requestId === undefined || !answers) { + return { status: 400, body: { error: 'user-input requires requestId and answers' } }; + } + this.manager.answerUserInput(requestId, answers); + this.pendingRequests.delete(String(requestId)); + this.writePaneLine(`[input #${requestId}] answered`); + void this.appendEvent('op/user-input', { requestId }); + return { status: 200, body: { ok: true } }; + } + + private attachManagerEvents(): void { + this.manager.on('notification', (message: AppServerMessage) => { + const threadId = extractThreadId(message); + if (message.method === 'thread/started' && threadId) writeThreadId(this.options.agentId, threadId); + this.renderNotification(message); + void this.appendEvent('notification', message as JsonRecord); + }); + this.manager.on('request', (message: AppServerMessage) => { + if (message.id === undefined || !message.method) return; + this.pendingRequests.set(String(message.id), { id: message.id, method: message.method, params: message.params }); + this.renderRequest(message); + void this.appendEvent('request', message as JsonRecord); + }); + this.manager.on('warning', (warning: unknown) => { + this.writePaneLine(`[warning] ${String(warning)}`); + void this.appendEvent('warning', { message: String(warning) }); + }); + this.manager.on('stderr', (stderr: unknown) => { + this.writePaneLine(`[stderr] ${String(stderr)}`); + void this.appendEvent('stderr', { message: String(stderr) }); + }); + this.manager.on('exit', (exit: unknown) => { + this.state = 'closed'; + this.writePaneLine('[exit] codex app-server stopped'); + void this.appendEvent('exit', asRecord(exit)); + void this.closeServer(); + }); + } + + startPaneInput(): void { + if (!this.options.stdin || this.input) return; + this.input = createInterface({ input: this.options.stdin, crlfDelay: Infinity }); + this.input.on('line', (line) => { + void this.handlePaneLine(line); + }); + } + + private async handlePaneLine(line: string): Promise { + const trimmed = line.trim(); + const approval = this.firstPendingApproval(); + if (approval && isApprovalShortcut(trimmed)) { + const decision = approvalDecision(trimmed); + this.manager.answerApproval(approval.id, decision); + this.pendingRequests.delete(String(approval.id)); + this.writePaneLine(`[approval #${approval.id}] ${decision}`); + await this.appendEvent('stdin/approval', { requestId: approval.id, decision }); + return; + } + if (!trimmed) return; + const result = await this.handleMessageOp({ op: 'message', content: line }); + if (result.status >= 400) this.writePaneLine(`[error] ${String(result.body.error ?? 'message failed')}`); + } + + private firstPendingApproval(): PendingAppServerRequest | undefined { + return [...this.pendingRequests.values()].find(request => /requestApproval/i.test(request.method)); + } + + private renderNotification(message: AppServerMessage): void { + const params = asRecord(message.params); + if (message.method === 'turn/started') { + this.writePaneLine('[turn] started'); + return; + } + if (message.method === 'turn/completed') { + this.writePaneLine('[turn] completed'); + return; + } + if (message.method === 'error') { + this.writePaneLine(`[error] ${formatPaneValue(params.error ?? params.message ?? message.params)}`); + return; + } + const text = typeof params.text === 'string' ? params.text : undefined; + if (text) this.writePaneLine(`[assistant] ${text}`); + } + + private renderRequest(message: AppServerMessage): void { + if (!message.method || message.id === undefined) return; + const params = asRecord(message.params); + if (/requestApproval/i.test(message.method)) { + const command = formatPaneValue(params.command ?? params.path ?? params); + this.writePaneLine(`[approval #${message.id}] command: ${command} - reply via dashboard or type y/n`); + return; + } + if (/requestUserInput|elicitation/i.test(message.method)) { + this.writePaneLine(`[input #${message.id}] reply via dashboard`); + } + } + + private writePaneLine(line: string): void { + this.options.stdout?.write(`${stripControl(line)}\n`); + } + + private async listen(): Promise { + const socketPath = this.socketPath(); + if (existsSync(socketPath)) await rm(socketPath, { force: true }); + this.server = createServer(async (req, res) => { + if (req.method !== 'POST') { + sendJson(res, 405, { error: 'method not allowed' }); + return; + } + if (req.headers[BRIDGE_TOKEN_HEADER] !== this.token) { + sendJson(res, 403, { error: 'forbidden' }); + return; + } + const chunks: Buffer[] = []; + req.on('data', (chunk: Buffer) => chunks.push(chunk)); + req.on('end', async () => { + let payload: unknown; + try { + payload = JSON.parse(Buffer.concat(chunks).toString('utf-8')); + } catch { + sendJson(res, 400, { error: 'invalid JSON body' }); + return; + } + const result = await this.handleOp(payload); + sendJson(res, result.status, result.body); + }); + }); + await new Promise((resolve, reject) => { + this.server!.once('error', reject); + this.server!.listen(socketPath, () => { + this.server!.off('error', reject); + resolve(); + }); + }); + await chmod(socketPath, 0o600); + } + + private async appendEvent(type: string, data: JsonRecord): Promise { + const line = JSON.stringify({ ts: new Date().toISOString(), type, ...data }); + try { + await mkdir(this.agentDir(), { recursive: true }); + await appendFile(join(this.agentDir(), 'appserver-events.jsonl'), `${line}\n`, 'utf-8'); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; + } + } + + private agentDir(): string { + return join(this.overdeckHome, 'agents', this.options.agentId); + } + + private socketDir(): string { + return join(this.overdeckHome, 'sockets'); + } + + private socketPath(): string { + return join(this.socketDir(), `appserver-${this.options.agentId}.sock`); + } + + private tokenPath(): string { + return join(this.agentDir(), 'appserver-token'); + } + + private publicState(managerState: Readonly): string { + if (this.state === 'closed') return 'closed'; + if (this.pendingRequests.size > 0) return 'awaiting-approval'; + if (managerState.activeTurnId || managerState.state === 'running') return 'running'; + if (managerState.state === 'error') return 'error'; + if (this.state === 'starting') return 'starting'; + return 'ready'; + } +} + +function sendJson(res: ServerResponse, status: number, body: JsonRecord): void { + const payload = JSON.stringify(body); + res.writeHead(status, { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload), + }); + res.end(payload); +} + +function asRecord(value: unknown): JsonRecord { + return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as JsonRecord : {}; +} + +function parseRequestId(value: unknown): string | number | undefined { + if (typeof value === 'string' || typeof value === 'number') return value; + return undefined; +} + +function parseAnswers(value: unknown): Record | undefined { + const record = asRecord(value); + const answers: Record = {}; + for (const [key, raw] of Object.entries(record)) { + if (!Array.isArray(raw) || !raw.every(item => typeof item === 'string')) return undefined; + answers[key] = raw; + } + return answers; +} + +function extractThreadId(message: AppServerMessage): string | undefined { + const params = asRecord(message.params); + const thread = asRecord(params.thread); + return typeof thread.id === 'string' ? thread.id : typeof params.threadId === 'string' ? params.threadId : undefined; +} + +function parseArgs(argv: string[]): { resumeThreadId?: string; model?: string } { + const parsed: { resumeThreadId?: string; model?: string } = {}; + for (let index = 0; index < argv.length; index += 1) { + const arg = argv[index]; + if (arg === '--resume') parsed.resumeThreadId = argv[++index]; + else if (arg === '--model') parsed.model = argv[++index]; + } + return parsed; +} + +async function main(): Promise { + const agentId = process.env.OVERDECK_AGENT_ID; + if (!agentId) throw new Error('OVERDECK_AGENT_ID is required for codex app-server host.'); + const args = parseArgs(process.argv.slice(2)); + const host = new CodexAppServerHost({ + agentId, + cwd: process.cwd(), + model: args.model, + resumeThreadId: args.resumeThreadId, + codexHome: process.env.CODEX_HOME, + stdin: process.stdin, + stdout: process.stdout, + }); + process.once('SIGTERM', () => void host.shutdownForSignal('SIGTERM').finally(() => process.exit(0))); + process.once('SIGINT', () => void host.shutdownForSignal('SIGINT', 0).finally(() => process.exit(130))); + await host.start(); +} + +function formatPaneValue(value: unknown): string { + if (typeof value === 'string') return value; + try { + return JSON.stringify(value); + } catch { + return String(value); + } +} + +function stripControl(value: string): string { + return value + .replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '') + .replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '') + .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, ''); +} + +function isApprovalShortcut(value: string): boolean { + return /^(?:y|yes|1|n|no|0)$/i.test(value); +} + +function approvalDecision(value: string): string { + return /^(?:y|yes|1)$/i.test(value) ? 'accept' : 'reject'; +} + +if (basename(fileURLToPath(import.meta.url)) === basename(process.argv[1] ?? '')) { + main().catch((error) => { + console.error(error instanceof Error ? error.stack ?? error.message : String(error)); + process.exitCode = 1; + }); +} diff --git a/src/lib/codex/app-server-manager.ts b/src/lib/codex/app-server-manager.ts new file mode 100644 index 0000000000..c66adca804 --- /dev/null +++ b/src/lib/codex/app-server-manager.ts @@ -0,0 +1,292 @@ +import { execFile } from 'node:child_process'; +import { EventEmitter } from 'node:events'; +import { createInterface, type Interface } from 'node:readline'; +import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); +const MINIMUM_CODEX_VERSION = '0.144.0'; +const DEFAULT_REQUEST_TIMEOUT_MS = 20_000; +const VERSION_TIMEOUT_MS = 4_000; + +export interface AppServerMessage { + id?: number | string; + method?: string; + params?: unknown; + result?: unknown; + error?: { message?: unknown }; +} + +export interface CodexAppServerManagerOptions { + codexBinary?: string; + cwd: string; + codexHome?: string; + requestTimeoutMs?: number; + clientVersion?: string; + spawnProcess?: () => ChildProcessWithoutNullStreams; + readVersion?: () => Promise; +} + +export type CodexRuntimeMode = 'full-access' | 'read-only' | 'default'; + +export interface ThreadOptions { + model: string; + cwd?: string; + runtimeMode?: CodexRuntimeMode; +} + +export interface TurnOptions { + model?: string; + effort?: string; +} + +export interface CodexAppServerState { + state: 'starting' | 'ready' | 'idle' | 'running' | 'error' | 'closed'; + threadId?: string; + activeTurnId?: string; +} + +interface PendingRequest { + method: string; + timeout: ReturnType; + resolve: (value: unknown) => void; + reject: (error: Error) => void; +} + +export class CodexAppServerManager extends EventEmitter { + private child: ChildProcessWithoutNullStreams | undefined; + private output: Interface | undefined; + private nextRequestId = 1; + private readonly pending = new Map(); + private sessionState: CodexAppServerState = { state: 'starting' }; + + constructor(private readonly options: CodexAppServerManagerOptions) { + super(); + } + + async start(): Promise { + if (this.child) return; + await this.assertSupportedVersion(); + + const binary = this.options.codexBinary ?? 'codex'; + const child = this.options.spawnProcess?.() ?? spawn(binary, ['app-server'], { + cwd: this.options.cwd, + env: { ...process.env, ...(this.options.codexHome ? { CODEX_HOME: this.options.codexHome } : {}) }, + stdio: ['pipe', 'pipe', 'pipe'], + }); + this.child = child; + this.output = createInterface({ input: child.stdout, crlfDelay: Infinity }); + this.output.on('line', line => this.handleLine(line)); + child.stderr.on('data', chunk => this.handleStderr(String(chunk))); + child.once('exit', (code, signal) => this.handleExit(code, signal)); + + await this.request('initialize', { + clientInfo: { + name: 'overdeck', + title: 'Overdeck', + version: this.options.clientVersion ?? '0.0.0', + }, + capabilities: { experimentalApi: true }, + }); + this.notify('initialized'); + this.sessionState = { ...this.sessionState, state: 'ready' }; + } + + getState(): Readonly { + return { ...this.sessionState }; + } + + async startThread(options: ThreadOptions): Promise { + return this.request('thread/start', this.buildThreadParams(options)); + } + + async resumeThread(threadId: string, options: ThreadOptions): Promise { + try { + return await this.request('thread/resume', { ...this.buildThreadParams(options), threadId }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + if (!isMissingThreadResumeError(message)) throw error; + this.emit('warning', `thread/resume could not find ${threadId}; starting a fresh thread.`); + return this.startThread(options); + } + } + + startTurn(text: string, options: TurnOptions = {}): Promise { + if (!this.sessionState.threadId) throw new Error('Cannot start a turn before a thread is active.'); + return this.request('turn/start', { + threadId: this.sessionState.threadId, + input: [{ type: 'text', text, text_elements: [] }], + ...(options.model ? { model: options.model } : {}), + ...(options.effort ? { effort: options.effort } : {}), + }); + } + + interruptTurn(): Promise { + if (!this.sessionState.threadId || !this.sessionState.activeTurnId) { + throw new Error('Cannot interrupt without an active turn.'); + } + return this.request('turn/interrupt', { + threadId: this.sessionState.threadId, + turnId: this.sessionState.activeTurnId, + }); + } + + readThread(threadId = this.sessionState.threadId): Promise { + if (!threadId) throw new Error('Cannot read a thread before a thread is active.'); + return this.request('thread/read', { threadId, includeTurns: true }); + } + + readAccount(): Promise { + return this.request('account/read', {}); + } + + answerApproval(id: number | string, decision: string): void { + this.write({ id, result: { decision } }); + } + + answerUserInput(id: number | string, answers: Record): void { + this.write({ id, result: { answers } }); + } + + request(method: string, params: unknown, timeoutMs = this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS): Promise { + const id = this.nextRequestId++; + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + this.pending.delete(String(id)); + reject(new Error(`Timed out waiting for ${method}.`)); + }, timeoutMs); + this.pending.set(String(id), { method, timeout, resolve: value => resolve(value as T), reject }); + this.write({ id, method, params }); + }); + } + + notify(method: string, params?: unknown): void { + this.write(params === undefined ? { method } : { method, params }); + } + + stop(): void { + this.output?.close(); + this.child?.kill('SIGTERM'); + this.child = undefined; + this.sessionState = { ...this.sessionState, state: 'closed', activeTurnId: undefined }; + for (const pending of this.pending.values()) { + clearTimeout(pending.timeout); + pending.reject(new Error('codex app-server stopped.')); + } + this.pending.clear(); + } + + private async assertSupportedVersion(): Promise { + const raw = await (this.options.readVersion?.() ?? execFileAsync(this.options.codexBinary ?? 'codex', ['--version'], { + cwd: this.options.cwd, + timeout: VERSION_TIMEOUT_MS, + }).then(result => result.stdout)); + const installed = raw.match(/\d+\.\d+\.\d+/)?.[0]; + if (!installed) throw new Error(`Could not parse Codex CLI version from: ${raw.trim()}`); + if (compareVersions(installed, MINIMUM_CODEX_VERSION) < 0) { + throw new Error(`Codex CLI ${installed} is unsupported; upgrade to ${MINIMUM_CODEX_VERSION} or newer.`); + } + } + + private handleLine(line: string): void { + let message: AppServerMessage; + try { + message = JSON.parse(line) as AppServerMessage; + } catch { + this.emit('warning', `Ignoring invalid codex app-server JSON: ${line}`); + return; + } + if (message.method && message.id !== undefined) { + this.emit('request', message); + } else if (message.method) { + this.applyNotification(message); + this.emit('notification', message); + } else if (message.id !== undefined) { + const pending = this.pending.get(String(message.id)); + if (!pending) return; + clearTimeout(pending.timeout); + this.pending.delete(String(message.id)); + if (message.error?.message) pending.reject(new Error(`${pending.method} failed: ${String(message.error.message)}`)); + else pending.resolve(message.result); + } + } + + private applyNotification(message: AppServerMessage): void { + const params = asRecord(message.params); + if (message.method === 'thread/started') { + const thread = asRecord(params.thread); + const threadId = typeof thread.id === 'string' ? thread.id : typeof params.threadId === 'string' ? params.threadId : undefined; + if (threadId) this.sessionState = { ...this.sessionState, state: 'idle', threadId }; + return; + } + if (message.method === 'turn/started') { + const turn = asRecord(params.turn); + const activeTurnId = typeof turn.id === 'string' ? turn.id : typeof params.turnId === 'string' ? params.turnId : undefined; + this.sessionState = { ...this.sessionState, state: 'running', ...(activeTurnId ? { activeTurnId } : {}) }; + return; + } + if (message.method === 'turn/completed') { + this.sessionState = { ...this.sessionState, state: 'idle', activeTurnId: undefined }; + return; + } + if (message.method === 'error' && params.willRetry !== true) { + this.sessionState = { ...this.sessionState, state: 'error', activeTurnId: undefined }; + } + } + + private buildThreadParams(options: ThreadOptions): Record { + const runtime = mapRuntimeMode(options.runtimeMode ?? 'default'); + return { + model: options.model, + cwd: options.cwd ?? this.options.cwd, + ...runtime, + experimentalRawEvents: false, + }; + } + + private handleStderr(value: string): void { + const clean = stripAnsi(value).trim(); + if (!clean || /state db missing rollout path for thread|find_thread_path_by_id_str_in_subdir/i.test(clean)) return; + if (/\bERROR\b/i.test(clean)) this.emit('stderr', clean); + } + + private handleExit(code: number | null, signal: NodeJS.Signals | null): void { + this.child = undefined; + this.emit('exit', { code, signal }); + } + + private write(message: AppServerMessage): void { + if (!this.child?.stdin.writable) throw new Error('Cannot write to codex app-server stdin.'); + this.child.stdin.write(`${JSON.stringify(message)}\n`); + } +} + +function compareVersions(left: string, right: string): number { + const a = left.split('.').map(Number); + const b = right.split('.').map(Number); + for (let index = 0; index < 3; index += 1) { + if (a[index] !== b[index]) return (a[index] ?? 0) - (b[index] ?? 0); + } + return 0; +} + +function stripAnsi(value: string): string { + return value + .replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '') + .replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '') + .replace(/\x1B[@-_]/g, ''); +} + +function asRecord(value: unknown): Record { + return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record : {}; +} + +function isMissingThreadResumeError(message: string): boolean { + return /thread\/resume/i.test(message) && /not found|missing thread|no such thread|unknown thread|does not exist/i.test(message); +} + +function mapRuntimeMode(mode: CodexRuntimeMode): { approvalPolicy: string; sandbox: string } { + if (mode === 'full-access') return { approvalPolicy: 'never', sandbox: 'danger-full-access' }; + if (mode === 'read-only') return { approvalPolicy: 'never', sandbox: 'read-only' }; + return { approvalPolicy: 'on-request', sandbox: 'workspace-write' }; +} diff --git a/src/lib/config-yaml/defaults.ts b/src/lib/config-yaml/defaults.ts index c45fdfc6d2..3b812fc935 100644 --- a/src/lib/config-yaml/defaults.ts +++ b/src/lib/config-yaml/defaults.ts @@ -228,6 +228,7 @@ export const DEFAULT_CONFIG: NormalizedConfig = { }, codex: { permissionMode: 'auto-review', + transport: 'app-server', }, }; diff --git a/src/lib/config-yaml/merge.ts b/src/lib/config-yaml/merge.ts index 73fee425c7..dcca4f48f7 100644 --- a/src/lib/config-yaml/merge.ts +++ b/src/lib/config-yaml/merge.ts @@ -204,6 +204,7 @@ export function mergeConfigs(...configs: (YamlConfig | null)[]): { config: Norma }, codex: { permissionMode: DEFAULT_CONFIG.codex.permissionMode, + transport: DEFAULT_CONFIG.codex.transport, }, }; @@ -698,6 +699,9 @@ export function mergeConfigs(...configs: (YamlConfig | null)[]): { config: Norma if (config.codex && (config.codex.permissionMode === 'read-only' || config.codex.permissionMode === 'workspace' || config.codex.permissionMode === 'auto-review' || config.codex.permissionMode === 'full-access')) { result.codex.permissionMode = config.codex.permissionMode; } + if (config.codex && (config.codex.transport === 'app-server' || config.codex.transport === 'tui')) { + result.codex.transport = config.codex.transport; + } // Merge remote work-agent provisioning settings mergeRemoteConfig(result, config); diff --git a/src/lib/config-yaml/roles.ts b/src/lib/config-yaml/roles.ts index e46590ce7b..53a8a9c8d6 100644 --- a/src/lib/config-yaml/roles.ts +++ b/src/lib/config-yaml/roles.ts @@ -1,5 +1,4 @@ import type { ModelId } from '../settings.js'; -import type { Role } from '../agents.js'; import { getModelEffortLevelsSync, resolveModelIdSync } from '../model-capabilities.js'; import { derivePercentPick, pickPercentModelRef, representativeModelRef } from './percent.js'; import { @@ -8,6 +7,7 @@ import { WORKHORSE_SLOTS, type ModelRef, type NormalizedConfig, + type Role, type RoleConfig, type RolesConfig, type WorkhorseSlot, diff --git a/src/lib/config-yaml/schema.ts b/src/lib/config-yaml/schema.ts index 627a2ec9d9..8d1cb9b712 100644 --- a/src/lib/config-yaml/schema.ts +++ b/src/lib/config-yaml/schema.ts @@ -2,7 +2,6 @@ import type { ModelId } from '../settings.js'; import type { ModelProvider } from '../model-fallback.js'; import type { EffortLevel } from '../model-capabilities.js'; import type { SubscriptionPlan, AuthMode } from '../subscription-types.js'; -import type { Role } from '../agents.js'; import type { RuntimeName } from '../runtimes/types.js'; import type { BackgroundAiFeature } from '../background-ai/registry.js'; import type { TieredExecutionConfig, ValidatedTieredExecutionConfig } from '../agents/tier-table.js'; @@ -342,6 +341,7 @@ export interface WeightedModelRef { /** Top-level role model: either a scalar model string or a weighted distribution list. */ export type RoleModelRef = ModelRef | WeightedModelRef[]; +export type Role = 'plan' | 'work' | 'review' | 'test' | 'ship' | 'flywheel' | 'strike' | 'sequencer' | 'knowledge'; /** @@ -586,6 +586,7 @@ export interface YamlConfig { */ codex?: { permissionMode?: 'read-only' | 'workspace' | 'auto-review' | 'full-access'; + transport?: 'app-server' | 'tui'; }; /** Remote work-agent provisioning settings (dashboard-editable subset). */ @@ -880,9 +881,10 @@ export interface NormalizedConfig { permissionMode: 'auto' | 'bypass'; }; - /** Permission-mode for Codex TUI conversation sessions. Always defined; defaults to 'workspace'. */ + /** Permission-mode and transport for Codex sessions. Always defined. */ codex: { permissionMode: 'read-only' | 'workspace' | 'auto-review' | 'full-access'; + transport: 'app-server' | 'tui'; }; /** Remote work-agent provisioning settings surfaced by the dashboard. */ diff --git a/src/lib/cost-parsers/codex-parser.ts b/src/lib/cost-parsers/codex-parser.ts index 793ccbd0dd..585c243d09 100644 --- a/src/lib/cost-parsers/codex-parser.ts +++ b/src/lib/cost-parsers/codex-parser.ts @@ -2,7 +2,7 @@ * Codex CLI rollout JSONL parser (PAN-1574). * * Codex sessions (D-7 external store) live at: - * $CODEX_HOME/sessions/YYYY/MM/DD/rollout--.jsonl + * $CODEX_HOME/sessions/YYYY/MM/DD/rollout--.jsonl * * Two JSONL schemas exist — rollout-file and --json stdout — with different * event names. This parser handles the ROLLOUT-FILE schema: diff --git a/src/lib/launcher-generator.ts b/src/lib/launcher-generator.ts index e26f2f8c4e..05eaa6e6a6 100644 --- a/src/lib/launcher-generator.ts +++ b/src/lib/launcher-generator.ts @@ -6,6 +6,7 @@ import { getHarnessBehavior } from './runtimes/behavior.js'; import { qualifyPiModel } from './providers.js'; import { shellQuoteModelIdSync } from './model-validation.js'; import { colorFgBgForTheme, getUiThemeSync } from './ui-theme.js'; +import { packageRoot } from './paths.js'; export type LauncherSpawnMode = 'conversation' | 'remote' | 'resume'; @@ -51,8 +52,9 @@ export interface LauncherConfig { * - 'exec': non-interactive `codex exec` with approval_policy=never * - 'tui': bare `codex` interactive TUI (conversation panels) * - 'work-tui': interactive work-agent TUI with sandbox/approval flags + * - 'app-server': persistent Codex app-server host process */ - codexMode?: 'exec' | 'tui' | 'work-tui'; + codexMode?: 'exec' | 'tui' | 'work-tui' | 'app-server'; /** * Per-agent CODEX_HOME directory path (e.g. ~/.overdeck/agents//codex-home). * When set, exported as CODEX_HOME before launching codex. @@ -753,6 +755,19 @@ function computeCodexCommandTokens(config: LauncherConfig, useExec: boolean): st return [useExec ? `exec ${cmd}` : cmd]; } + if (codexMode === 'app-server') { + const hostPath = join(packageRoot, 'dist', 'codex-app-server-host.js'); + const tokens: string[] = ['node', shellQuote(hostPath)]; + if (config.model) { + tokens.push('--model', shellQuoteModelIdSync(config.model)); + } + if (config.resumeSessionId) { + tokens.push('--resume', shellQuote(config.resumeSessionId)); + } + const cmd = tokens.join(' '); + return [useExec ? `exec ${cmd}` : cmd]; + } + const isResume = Boolean(config.resumeSessionId); // Headless exec mode — fresh spawn or resume. diff --git a/src/lib/overdeck/__tests__/conversation-delivery.test.ts b/src/lib/overdeck/__tests__/conversation-delivery.test.ts new file mode 100644 index 0000000000..f4b75d6cdd --- /dev/null +++ b/src/lib/overdeck/__tests__/conversation-delivery.test.ts @@ -0,0 +1,179 @@ +import { EventEmitter } from 'node:events'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { Effect } from 'effect'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +let transport: 'app-server' | 'tui' = 'app-server'; +let tmpHome: string; +let nextResponses: unknown[] = []; +const httpBodies: unknown[] = []; + +vi.mock('../../config-yaml.js', () => ({ + loadConfigSync: () => ({ config: { codex: { transport } } }), +})); + +vi.mock('../conversations.js', () => ({ + getConversationById: vi.fn(() => null), + getConversationByName: vi.fn(() => ({ + id: 1, + name: 'conv-test', + tmuxSession: 'conv-test', + harness: 'codex', + })), + setConversationEffort: vi.fn(), + updateConversationDeliveryMethod: vi.fn(), +})); + +vi.mock('../conversation-runtime.js', () => ({ + tmuxSessionExists: vi.fn(async () => true), +})); + +vi.mock('../../agent-input-detection.js', async (importOriginal) => { + const actual = await importOriginal() as Record; + return { + ...actual, + detectAwaitingInputForAgent: vi.fn(() => Effect.succeed({ + reason: 'other', + prompt: 'Would you like to run the following command?\n\nls\n\n1. Yes\n2. No', + })), + }; +}); + +vi.mock('../../tmux.js', () => ({ + sendRawKeystroke: vi.fn(() => Effect.void), + sendKeysAsync: vi.fn(async () => {}), +})); + +vi.mock('node:http', () => ({ + request: vi.fn((_options, callback) => { + const req = new EventEmitter() as EventEmitter & { write: (chunk: string) => void; end: () => void }; + let body = ''; + req.write = (chunk: string) => { + body += chunk; + }; + req.end = () => { + httpBodies.push(JSON.parse(body)); + const response = new EventEmitter() as EventEmitter & { statusCode: number; setEncoding: (encoding: string) => void }; + response.statusCode = 200; + response.setEncoding = vi.fn(); + callback(response); + queueMicrotask(() => { + response.emit('data', JSON.stringify(nextResponses.shift() ?? { ok: true })); + response.emit('end'); + }); + }; + return req; + }), +})); + +import { detectAwaitingInputForAgent } from '../../agent-input-detection.js'; +import { sendRawKeystroke } from '../../tmux.js'; +import { codexConversationPendingInput, handleConversationCodexApproval } from '../conversation-delivery.js'; + +function seedAppServerFiles(session = 'conv-test'): void { + mkdirSync(join(tmpHome, 'agents', session), { recursive: true }); + mkdirSync(join(tmpHome, 'sockets'), { recursive: true }); + writeFileSync(join(tmpHome, 'agents', session, 'appserver-token'), 'token-123\n'); + writeFileSync(join(tmpHome, 'sockets', `appserver-${session}.sock`), ''); +} + +describe('conversation codex approvals', () => { + beforeEach(() => { + tmpHome = mkdtempSync(join(tmpdir(), 'pan-conv-delivery-')); + process.env.OVERDECK_HOME = tmpHome; + transport = 'app-server'; + nextResponses = []; + httpBodies.length = 0; + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.useRealTimers(); + delete process.env.OVERDECK_HOME; + rmSync(tmpHome, { recursive: true, force: true }); + }); + + it('sources app-server pending approval input from status without pane capture', async () => { + seedAppServerFiles(); + nextResponses.push({ + pendingRequests: [{ + id: 71, + method: 'item/commandExecution/requestApproval', + params: { command: 'git status' }, + }], + }); + + const result = await codexConversationPendingInput({ + id: 1, + name: 'conv-test', + tmuxSession: 'conv-test', + harness: 'codex', + } as never, true, '2026-07-12T00:00:00.000Z'); + + expect(result.kinds).toEqual(['permissionRequest']); + expect(result.approval?.toolUseId).toBe('codex-approval:conv-test:71'); + expect(result.approval?.questions[0]?.question).toContain('git status'); + expect(detectAwaitingInputForAgent).not.toHaveBeenCalled(); + expect(httpBodies).toEqual([{ op: 'status' }]); + }); + + it('posts app-server approval decisions instead of sending digit keystrokes', async () => { + seedAppServerFiles(); + nextResponses.push( + { + pendingRequests: [{ + id: 71, + method: 'item/commandExecution/requestApproval', + params: { command: 'git status' }, + }], + }, + { ok: true }, + ); + + await handleConversationCodexApproval('conv-test', { + optionNumber: 1, + toolUseId: 'codex-approval:conv-test:71', + }); + + expect(httpBodies).toEqual([ + { op: 'status' }, + { op: 'approval', requestId: 71, decision: 'accept' }, + ]); + expect(sendRawKeystroke).not.toHaveBeenCalled(); + }); + + it('rejects app-server approval decisions for stale request ids', async () => { + seedAppServerFiles(); + nextResponses.push({ + pendingRequests: [{ + id: 72, + method: 'item/commandExecution/requestApproval', + params: { command: 'git status' }, + }], + }); + + const response = await handleConversationCodexApproval('conv-test', { + optionNumber: 1, + toolUseId: 'codex-approval:conv-test:71', + }); + + expect(response.status).toBe(409); + expect(httpBodies).toEqual([{ op: 'status' }]); + expect(sendRawKeystroke).not.toHaveBeenCalled(); + }); + + it('preserves the digit-keystroke approval path for tui transport', async () => { + vi.useFakeTimers(); + transport = 'tui'; + + const handled = handleConversationCodexApproval('conv-test', { optionNumber: 2 }); + await vi.advanceTimersByTimeAsync(70); + await handled; + + expect(httpBodies).toHaveLength(0); + expect(sendRawKeystroke).toHaveBeenNthCalledWith(1, 'conv-test', 'Down', 'codex-approval'); + expect(sendRawKeystroke).toHaveBeenNthCalledWith(2, 'conv-test', 'Enter', 'codex-approval'); + }); +}); diff --git a/src/lib/overdeck/conversation-delivery.ts b/src/lib/overdeck/conversation-delivery.ts index 32cb73631d..7285402716 100644 --- a/src/lib/overdeck/conversation-delivery.ts +++ b/src/lib/overdeck/conversation-delivery.ts @@ -1,4 +1,7 @@ import { randomUUID } from 'node:crypto'; +import { existsSync, readFileSync } from 'node:fs'; +import { request as httpRequest } from 'node:http'; +import { join } from 'node:path'; import { Effect } from 'effect'; @@ -14,6 +17,7 @@ import { } from './conversations.js'; import { getHarnessBehavior } from '../runtimes/behavior.js'; import type { RuntimeName } from '../runtimes/types.js'; +import { loadConfigSync } from '../config-yaml.js'; import { writeConversationControlCommand, type ControlCommand, @@ -23,6 +27,8 @@ import { sendRawKeystroke, sendKeysAsync } from '../tmux.js'; import { deliverAgentMessage } from '../agents.js'; import { tmuxSessionExists } from './conversation-runtime.js'; import type { PendingAskUserQuestionSnapshot, PendingInputKind } from '../agent-enrichment.js'; +import { getOverdeckHome } from '../paths.js'; +import { BRIDGE_TOKEN_HEADER } from '../bridge-token.js'; export const CONTROL_ACK_TIMEOUT_MS = 10_000; @@ -46,6 +52,16 @@ const PLAN_ACTION_KEYSTROKES: Record = { 'reject-ultraplan': '3', }; +interface CodexAppServerPendingRequest { + id: string | number; + method: string; + params?: unknown; +} + +interface CodexAppServerStatus { + pendingRequests?: CodexAppServerPendingRequest[]; +} + /** * PAN-1520 (FR-4) — deliver a native plan-menu action to any tmux session * (conversation or agent). The plan menu is answered with raw keystrokes @@ -154,6 +170,7 @@ export async function handleConversationCodexApproval( ): Promise> { try { const optionNumber = Number((body as { optionNumber?: unknown }).optionNumber); + const requestedToolUseId = typeof body.toolUseId === 'string' ? body.toolUseId : undefined; if (!Number.isInteger(optionNumber) || optionNumber < 1 || optionNumber > 9) { return jsonResponse({ error: 'optionNumber must be an integer 1-9' }, { status: 400 }); } @@ -170,6 +187,25 @@ export async function handleConversationCodexApproval( if (!(await tmuxSessionExists(conv.tmuxSession))) { return jsonResponse({ error: 'Conversation session is not running' }, { status: 409 }); } + if (codexUsesAppServerTransport()) { + const pending = await readFirstAppServerApproval(conv.tmuxSession); + if (!pending) { + return jsonResponse({ error: 'No Codex approval prompt is currently pending' }, { status: 409 }); + } + const expectedToolUseId = `${CODEX_APPROVAL_TOOL_PREFIX}${conv.tmuxSession}:${pending.id}`; + if (!requestedToolUseId) { + return jsonResponse({ error: 'Codex approval request id is required' }, { status: 400 }); + } + if (requestedToolUseId !== expectedToolUseId) { + return jsonResponse({ error: 'Codex approval request changed; refresh pending input before responding' }, { status: 409 }); + } + const decision = codexAppServerDecisionForOption(optionNumber); + if (!decision) { + return jsonResponse({ error: 'optionNumber out of range (1-2)' }, { status: 400 }); + } + await postCodexAppServerOp(conv.tmuxSession, { op: 'approval', requestId: pending.id, decision }); + return jsonResponse({ ok: true, optionNumber, decision }); + } // Re-detect uncached so we only send keystrokes when the menu is still // up, and so we can bound optionNumber to the options actually shown. const detection = await Effect.runPromise( @@ -247,6 +283,7 @@ export function pickDeliverAs(bodyDeliverAs: unknown): ConversationControlDelive export function resolveConversationDeliveryMethod(conv: Pick): 'auto' | 'channels' | 'tmux' { const harness = conv.harness ?? 'claude-code'; if (isPiControlChannelHarness(harness)) return 'auto'; + if (harness === 'codex' && loadConfigSync().config.codex?.transport !== 'tui') return 'auto'; return conv.deliveryMethod ?? (getHarnessBehavior(harness).deliveryKind === 'rpc-fifo' ? 'tmux' : 'auto'); } @@ -364,6 +401,26 @@ export async function codexConversationPendingInput( ): Promise<{ kinds: PendingInputKind[]; approval?: PendingAskUserQuestionSnapshot }> { if (!sessionAlive || getHarnessBehavior(conv.harness).transcriptKind !== 'codex-rollout-jsonl') return { kinds: [] }; try { + if (codexUsesAppServerTransport()) { + const pending = await readFirstAppServerApproval(conv.tmuxSession); + if (!pending) return { kinds: [] }; + return { + kinds: ['permissionRequest'], + approval: { + toolUseId: `${CODEX_APPROVAL_TOOL_PREFIX}${conv.tmuxSession}:${pending.id}`, + askedAt, + questions: [{ + question: formatAppServerApprovalQuestion(pending), + header: 'Codex approval', + multiSelect: false, + options: [ + { label: '1. accept' }, + { label: '2. reject' }, + ], + }], + }, + }; + } const detection = await Effect.runPromise(detectAwaitingInputForAgent(conv.tmuxSession, { isPlanning: false })); if (!detection) return { kinds: [] }; if (detection.reason === 'session_resume') return { kinds: ['sessionResume'] }; @@ -397,3 +454,95 @@ export async function deliverCodexApprovalChoice(tmuxSession: string, optionNumb } await Effect.runPromise(sendRawKeystroke(tmuxSession, 'Enter', 'codex-approval')); } + +function codexUsesAppServerTransport(): boolean { + return loadConfigSync().config.codex?.transport !== 'tui'; +} + +function codexAppServerDecisionForOption(optionNumber: number): 'accept' | 'reject' | null { + if (optionNumber === 1) return 'accept'; + if (optionNumber === 2) return 'reject'; + return null; +} + +async function readFirstAppServerApproval(tmuxSession: string): Promise { + const status = await postCodexAppServerOp(tmuxSession, { op: 'status' }); + return status.pendingRequests?.find(request => /requestApproval/i.test(request.method)) ?? null; +} + +function formatAppServerApprovalQuestion(request: CodexAppServerPendingRequest): string { + const params = asRecord(request.params); + const command = typeof params.command === 'string' ? params.command : undefined; + const path = typeof params.path === 'string' ? params.path : undefined; + if (command) return `Codex requests approval to run:\n\n${command}`; + if (path) return `Codex requests approval for:\n\n${path}`; + return `Codex requests approval for ${request.method}`; +} + +async function postCodexAppServerOp>(tmuxSession: string, body: Record): Promise { + const socketPath = join(getOverdeckHome(), 'sockets', `appserver-${tmuxSession}.sock`); + const tokenPath = join(getOverdeckHome(), 'agents', tmuxSession, 'appserver-token'); + if (!existsSync(socketPath)) throw new Error(`app-server socket missing for ${tmuxSession}`); + if (!existsSync(tokenPath)) throw new Error(`app-server token missing for ${tmuxSession}`); + const token = readFileSync(tokenPath, 'utf-8').trim(); + if (!token) throw new Error(`app-server token missing for ${tmuxSession}`); + const payload = JSON.stringify(body); + return new Promise((resolve, reject) => { + let settled = false; + const timeout = setTimeout(() => { + if (settled) return; + settled = true; + req.destroy(new Error(`app-server op timed out after 2000ms`)); + reject(new Error(`app-server op timed out after 2000ms`)); + }, 2_000); + const finishErr = (error: Error) => { + if (settled) return; + settled = true; + clearTimeout(timeout); + reject(error); + }; + const finishOk = (value: T) => { + if (settled) return; + settled = true; + clearTimeout(timeout); + resolve(value); + }; + const req = httpRequest( + { + socketPath, + path: '/', + method: 'POST', + agent: false, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload), + [BRIDGE_TOKEN_HEADER]: token, + }, + }, + (res) => { + let responseBody = ''; + res.setEncoding('utf-8'); + res.on('data', chunk => { responseBody += chunk; }); + res.on('end', () => { + const status = res.statusCode ?? 0; + if (status < 200 || status >= 300) { + finishErr(new Error(`app-server op returned HTTP ${status}`)); + return; + } + try { + finishOk(JSON.parse(responseBody) as T); + } catch (error) { + finishErr(new Error(`app-server op returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`)); + } + }); + }, + ); + req.on('error', finishErr); + req.write(payload); + req.end(); + }); +} + +function asRecord(value: unknown): Record { + return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record : {}; +} diff --git a/src/lib/overdeck/conversation-runtime.ts b/src/lib/overdeck/conversation-runtime.ts index 5362e1cf2f..5a31cd206d 100644 --- a/src/lib/overdeck/conversation-runtime.ts +++ b/src/lib/overdeck/conversation-runtime.ts @@ -289,7 +289,11 @@ export async function waitForTmuxSession(sessionName: string, timeoutMs = 30000) } throw new Error(`Timed out waiting for tmux session ${sessionName}`); } -function shouldUseSupervisorForConversation(harness: RuntimeName): boolean { +export function shouldUseSupervisorForConversation( + harness: RuntimeName, + options: { codexTransport?: 'app-server' | 'tui' } = {}, +): boolean { + if (harness === 'codex' && options.codexTransport === 'app-server') return false; return getHarnessBehavior(harness).supportsPtySupervisor && process.env.OVERDECK_DOCKER_WORKSPACE !== '1' && process.env.PAN_DOCKER !== '1'; } export async function waitForConversationRuntimeReady(tmuxSession: string, harness: RuntimeName, mode: 'spawn' | 'respawn'): Promise { @@ -522,11 +526,12 @@ export async function spawnConversationSession( } | undefined; let codexFields: { harness: 'codex'; - codexMode: 'tui'; + codexMode: 'app-server' | 'tui'; codexHome: string; codexSessionDir: string; resumeSessionId?: string; } | undefined; + let codexTransport: 'app-server' | 'tui' | undefined; if (model) { if (!SAFE_MODEL_PATTERN.test(model)) { throw new Error('Invalid model name'); @@ -564,7 +569,9 @@ export async function spawnConversationSession( }; } else if (behavior.usesCodexHome) { const codexHome = join(getOverdeckHome(), 'agents', tmuxSession, 'codex-home'); - const codexPermMode = loadConfigSync().config.codex?.permissionMode ?? 'workspace'; + const codexConfig = loadConfigSync().config.codex; + const codexPermMode = codexConfig?.permissionMode ?? 'workspace'; + codexTransport = codexConfig?.transport ?? 'app-server'; const codexApprovalPolicy = codexPermMode === 'full-access' ? 'never' : 'on-request'; const codexSandboxMode = codexPermMode === 'full-access' ? 'danger-full-access' @@ -584,7 +591,7 @@ export async function spawnConversationSession( : undefined; codexFields = { harness: 'codex', - codexMode: 'tui', + codexMode: codexTransport, codexHome, codexSessionDir: join(codexHome, 'sessions'), resumeSessionId, @@ -599,7 +606,7 @@ export async function spawnConversationSession( if (effort && !SAFE_EFFORT_PATTERN.test(effort)) { throw new Error('Invalid effort level'); } - const useSupervisor = shouldUseSupervisorForConversation(harness); + const useSupervisor = shouldUseSupervisorForConversation(harness, { codexTransport }); let supervisorScriptPath: string | undefined; if (useSupervisor) { supervisorScriptPath = resolvePtySupervisorScriptPath(); @@ -685,7 +692,7 @@ export async function spawnConversationSession( if (useSupervisor) { await waitForPtySupervisorSocket(tmuxSession); } - if (behavior.usesCodexHome && codexFields?.codexHome) { + if (behavior.usesCodexHome && codexFields?.codexHome && codexTransport === 'tui') { const codexHomeDir = codexFields.codexHome; void (async () => { try { diff --git a/src/lib/runtimes/behavior.ts b/src/lib/runtimes/behavior.ts index 17c61864a6..c868f47c6c 100644 --- a/src/lib/runtimes/behavior.ts +++ b/src/lib/runtimes/behavior.ts @@ -5,4 +5,9 @@ export { getHarnessBehavior, getRuntimeBehavior, } from '@overdeck/contracts'; -export type { HarnessBehavior } from '@overdeck/contracts'; +export type { + HarnessBehavior, + HarnessDeliveryKind, + HarnessLaunchCommandKind, + HarnessReadinessKind, +} from '@overdeck/contracts'; diff --git a/src/lib/runtimes/codex.ts b/src/lib/runtimes/codex.ts index b0e12d9807..21250a4c08 100644 --- a/src/lib/runtimes/codex.ts +++ b/src/lib/runtimes/codex.ts @@ -13,12 +13,14 @@ * kill-agent, cost-parser, notify-heartbeat). */ -import { existsSync, readFileSync, statSync, writeFileSync, readdirSync, mkdirSync, copyFileSync, openSync, readSync, closeSync, lstatSync, readlinkSync, symlinkSync, unlinkSync } from 'node:fs' -import { join, basename } from 'node:path' +import { existsSync, readFileSync, statSync, writeFileSync, readdirSync, mkdirSync, copyFileSync, chmodSync, openSync, readSync, closeSync, lstatSync, readlinkSync, symlinkSync, unlinkSync } from 'node:fs' +import { dirname, join, basename } from 'node:path' import { homedir } from 'node:os' import { promisify } from 'node:util' import { exec } from 'node:child_process' +import { request as httpRequest } from 'node:http' import { Effect } from 'effect' +import yaml from 'js-yaml' import type { AgentRuntime, AgentRuntimeSync, @@ -32,7 +34,7 @@ import type { Agent, } from './types.js' import { CODEX_BEHAVIOR } from './behavior.js' -import { sessionExists, killSession, listSessionsSync, createSession } from '../tmux.js' +import { tmuxCreateSession, tmuxKillSession, tmuxSessionExists } from './tmux-cli.js' import { TmuxError, ProcessSpawnError, ProcessTimeoutError } from '../errors.js' import { parseCodexSessionSync } from '../cost-parsers/codex-parser.js' @@ -54,10 +56,118 @@ function agentDirFor(agentId: string): string { return join(agentsDir(), agentId) } +function appServerSocketPathFor(agentId: string): string { + return join(overdeckDir(), 'sockets', `appserver-${agentId}.sock`) +} + +function appServerTokenPathFor(agentId: string): string { + return join(agentDirFor(agentId), 'appserver-token') +} + function threadIdPathFor(agentId: string): string { return join(agentDirFor(agentId), 'codex-thread-id') } +function readAppServerToken(agentId: string): string | null { + const tokenPath = appServerTokenPathFor(agentId) + if (!existsSync(tokenPath)) return null + try { + return readFileSync(tokenPath, 'utf-8').trim() || null + } catch { + return null + } +} + +async function postAppServerInterrupt(agentId: string): Promise { + const socketPath = appServerSocketPathFor(agentId) + const token = readAppServerToken(agentId) + if (!existsSync(socketPath) || !token) return + const payload = JSON.stringify({ op: 'interrupt' }) + await new Promise((resolve, reject) => { + let settled = false + const timeout = setTimeout(() => { + if (settled) return + settled = true + req.destroy(new Error(`app-server interrupt timed out after 2000ms`)) + reject(new Error(`app-server interrupt timed out after 2000ms`)) + }, 2_000) + const finishErr = (error: Error) => { + if (settled) return + settled = true + clearTimeout(timeout) + reject(error) + } + const finishOk = () => { + if (settled) return + settled = true + clearTimeout(timeout) + resolve() + } + const req = httpRequest( + { + socketPath, + path: '/', + method: 'POST', + agent: false, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(payload), + 'X-Overdeck-Bridge-Token': token, + }, + }, + (res) => { + res.resume() + res.on('end', () => { + const status = res.statusCode ?? 0 + if (status >= 200 && status < 300) finishOk() + else finishErr(new Error(`app-server interrupt returned HTTP ${status}`)) + }) + }, + ) + req.on('error', finishErr) + req.write(payload) + req.end() + }) +} + +function codexTransport(): 'app-server' | 'tui' { + return readCodexTransportFromConfig() === 'tui' ? 'tui' : 'app-server' +} + +function readCodexTransportFromConfig(): unknown { + const configs = [ + join(homedir(), '.overdeck', 'config.yaml'), + ...projectConfigPaths(), + ] + for (let index = configs.length - 1; index >= 0; index -= 1) { + const transport = readCodexTransportFromYaml(configs[index]!) + if (transport === 'app-server' || transport === 'tui') return transport + } + return undefined +} + +function projectConfigPaths(): string[] { + let currentDir = process.cwd() + while (true) { + if (existsSync(join(currentDir, '.git'))) { + return [join(currentDir, '.overdeck.yaml'), join(currentDir, '.pan.yaml')] + } + const parent = dirname(currentDir) + if (parent === currentDir) return [] + currentDir = parent + } +} + +function readCodexTransportFromYaml(filePath: string): unknown { + if (!existsSync(filePath)) return undefined + try { + const parsed = yaml.load(readFileSync(filePath, 'utf-8')) as { codex?: { transport?: unknown } } | null + return parsed?.codex?.transport + } catch { + return undefined + } +} + /** Resolve $CODEX_HOME: env var → ~/.codex fallback. */ export function codexHome(): string { return process.env.CODEX_HOME ?? join(homedir(), '.codex') @@ -500,20 +610,6 @@ export class CodexRuntimeSync implements AgentRuntimeSync { } } - // Tier 3: tmux session creation time. - try { - const sess = listSessionsSync().find(s => s.name === agentId) - if (sess) { - return { - timestamp: sess.created, - agentId, - source: 'tmux', - confidence: 'low', - } - } - } catch { - // ignore - } return null } @@ -558,7 +654,8 @@ export class CodexRuntimeSync implements AgentRuntimeSync { /** * Kill a Codex agent via a SIGTERM→SIGKILL escalation ladder. * - * 1. Send Ctrl-C to the tmux pane (interrupt running task). + * 1. App-server transport: POST interrupt to the host socket. TUI transport: + * send Ctrl-C to the tmux pane. * 2. Wait up to 2s for the session to disappear. * 3. SIGTERM the codex process group via pkill. * 4. Wait up to 5s for the session to disappear. @@ -568,11 +665,20 @@ export class CodexRuntimeSync implements AgentRuntimeSync { * JSONL-is-sacred rule. */ async killAgent(agentId: string): Promise { - // Step 1: interrupt the running task. - try { - await execAsync(`tmux -L overdeck send-keys -t ${shellQuote(agentId)} C-c 2>/dev/null || true`) - } catch { - // ignore + // Step 1: interrupt the running task. App-server has a structured + // interrupt op; the C-c keystroke is only for the legacy TUI escape hatch. + if (codexTransport() === 'app-server') { + try { + await postAppServerInterrupt(agentId) + } catch { + // Best effort: the SIGTERM ladder below still tears down the session. + } + } else { + try { + await execAsync(`tmux -L overdeck send-keys -t ${shellQuote(agentId)} C-c 2>/dev/null || true`) + } catch { + // ignore + } } // Step 2: poll up to 2s. @@ -596,8 +702,8 @@ export class CodexRuntimeSync implements AgentRuntimeSync { if (await pollUntilSessionGone(agentId, 5_000)) return // Step 5: SIGKILL via kill-session. - if (await Effect.runPromise(sessionExists(agentId))) { - await Effect.runPromise(killSession(agentId)) + if (await tmuxSessionExists(agentId)) { + await tmuxKillSession(agentId) } } @@ -622,12 +728,10 @@ export class CodexRuntimeSync implements AgentRuntimeSync { const fullCmd = `CODEX_HOME=${shellQuote(codexHomeDir)} ${tokens.join(' ')}` // 3. Launch the tmux session on the overdeck socket. - await Effect.runPromise(createSession(agentId, config.workspace, fullCmd, { - env: { - OVERDECK_AGENT_ID: agentId, - CODEX_HOME: codexHomeDir, - }, - })) + await tmuxCreateSession(agentId, config.workspace, fullCmd, { + OVERDECK_AGENT_ID: agentId, + CODEX_HOME: codexHomeDir, + }) // 4. Wait for the rollout JSONL to appear (readiness signal). const rolloutPath = await waitForCodexRollout(codexHomeDir, SPAWN_READY_TIMEOUT_MS) @@ -660,14 +764,14 @@ export class CodexRuntimeSync implements AgentRuntimeSync { } async isRunning(agentId: string): Promise { - return await Effect.runPromise(sessionExists(agentId)) + return await tmuxSessionExists(agentId) } } async function pollUntilSessionGone(agentId: string, timeoutMs: number): Promise { const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { - if (!(await Effect.runPromise(sessionExists(agentId)))) return true + if (!(await tmuxSessionExists(agentId))) return true await new Promise(r => setTimeout(r, 100)) } return false diff --git a/src/lib/runtimes/tmux-cli.ts b/src/lib/runtimes/tmux-cli.ts new file mode 100644 index 0000000000..bfcf949025 --- /dev/null +++ b/src/lib/runtimes/tmux-cli.ts @@ -0,0 +1,29 @@ +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; + +const execAsync = promisify(exec); + +function shellQuote(value: string): string { + return `'${value.replace(/'/g, `'\\''`)}'`; +} + +export async function tmuxSessionExists(agentId: string): Promise { + try { + await execAsync(`tmux -L overdeck has-session -t ${shellQuote(agentId)} 2>/dev/null`); + return true; + } catch { + return false; + } +} + +export async function tmuxKillSession(agentId: string): Promise { + await execAsync(`tmux -L overdeck kill-session -t ${shellQuote(agentId)} 2>/dev/null || true`); +} + +export async function tmuxCreateSession(agentId: string, workspace: string, command: string, env: Record = {}): Promise { + const envPrefix = Object.entries(env) + .map(([key, value]) => `${key}=${shellQuote(value)}`) + .join(' '); + const wrapped = `${envPrefix ? `env ${envPrefix} ` : ''}bash -lc ${shellQuote(command)}`; + await execAsync(`tmux -L overdeck new-session -d -s ${shellQuote(agentId)} -c ${shellQuote(workspace)} ${shellQuote(wrapped)}`); +} diff --git a/src/lib/state-plane.ts b/src/lib/state-plane.ts index 70872fb324..b60ae4abc2 100644 --- a/src/lib/state-plane.ts +++ b/src/lib/state-plane.ts @@ -32,6 +32,7 @@ export const STATE_PLANE_PATHS = [ '.pan/test/', '.pan/review/', '.pan/feedback/', + '.tasks/', ] as const; export type StatePlanePath = typeof STATE_PLANE_PATHS[number]; diff --git a/sync-sources/hooks/record-cost-event.js b/sync-sources/hooks/record-cost-event.js index 31d122c7e0..ba8cd1bfd3 100755 --- a/sync-sources/hooks/record-cost-event.js +++ b/sync-sources/hooks/record-cost-event.js @@ -25980,7 +25980,8 @@ const STATE_PLANE_PATHS = [ ".pan/specs/", ".pan/test/", ".pan/review/", - ".pan/feedback/" + ".pan/feedback/", + ".tasks/" ]; function isStatePlanePath(relativePath) { const normalized = relativePath.trim().replace(/\\/g, "/"); @@ -29011,7 +29012,10 @@ const DEFAULT_CONFIG = { showHarnessModelPermutations: false }, claude: { permissionMode: "bypass" }, - codex: { permissionMode: "auto-review" } + codex: { + permissionMode: "auto-review", + transport: "app-server" + } }; /** * Path to global config file @@ -29304,7 +29308,10 @@ function mergeConfigs(...configs) { showHarnessModelPermutations: DEFAULT_CONFIG.experimental.showHarnessModelPermutations }, claude: { permissionMode: DEFAULT_CONFIG.claude.permissionMode }, - codex: { permissionMode: DEFAULT_CONFIG.codex.permissionMode } + codex: { + permissionMode: DEFAULT_CONFIG.codex.permissionMode, + transport: DEFAULT_CONFIG.codex.transport + } }; const explicitlyDisabled = /* @__PURE__ */ new Set(); const validConfigs = configs.filter((c) => c !== null); @@ -29542,6 +29549,7 @@ function mergeConfigs(...configs) { } if (config.claude && (config.claude.permissionMode === "auto" || config.claude.permissionMode === "bypass")) result.claude.permissionMode = config.claude.permissionMode; if (config.codex && (config.codex.permissionMode === "read-only" || config.codex.permissionMode === "workspace" || config.codex.permissionMode === "auto-review" || config.codex.permissionMode === "full-access")) result.codex.permissionMode = config.codex.permissionMode; + if (config.codex && (config.codex.transport === "app-server" || config.codex.transport === "tui")) result.codex.transport = config.codex.transport; mergeRemoteConfig(result, config); if (config.conversationSearch) { const cs = config.conversationSearch; diff --git a/tests/playwright/conversation-supervisor-uat.test.ts b/tests/playwright/conversation-supervisor-uat.test.ts index 2b2a56a4d8..101bca1f92 100644 --- a/tests/playwright/conversation-supervisor-uat.test.ts +++ b/tests/playwright/conversation-supervisor-uat.test.ts @@ -203,6 +203,35 @@ async function createSupervisorBackedTmuxSession(agentId: string): Promise await startFakeSupervisor(agentId, fifoPath); } +async function createPlainTmuxSession(agentId: string): Promise { + if (!actualTmux) throw new Error('tmux module not initialized'); + const script = [ + `for i in $(seq 1 160); do printf 'scrollback-line-%03d\\n' "$i"; done`, + `printf 'Codex app-server ready\\n'`, + `while true; do sleep 60; done`, + ].join('; '); + await Effect.runPromise(actualTmux.createSession(agentId, workspace, `bash -lc ${shellQuote(script)}`, { + env: { TERM: 'xterm-256color' }, + width: 80, + height: 24, + })); + tmuxSessions.add(agentId); +} + +async function killFakeTmuxSession(session: string): Promise { + if (actualTmux) { + try { + await Effect.runPromise(actualTmux.killSession(session)); + } catch { + // cleanup is idempotent when the session is already gone + } + } + const existing = sessions.get(session); + if (existing) await closeBridge(existing.bridge); + sessions.delete(session); + tmuxSessions.delete(session); +} + async function startFakeSupervisor(agentId: string, fifoPath: string): Promise { const tokenPath = join(tmpHome, 'agents', agentId, 'pty-token'); const expectedToken = readFileSync(tokenPath, 'utf8').trim(); @@ -378,24 +407,32 @@ beforeEach(async () => { ...actual, createSessionSync: vi.fn(), createSession: vi.fn((session: string) => Effect.promise(async () => { - await createSupervisorBackedTmuxSession(session); + if (existsSync(join(tmpHome, 'agents', session, 'pty-token'))) { + await createSupervisorBackedTmuxSession(session); + } else { + await createPlainTmuxSession(session); + } })), killSessionSync: vi.fn(), killSession: vi.fn((session: string) => Effect.promise(async () => { - try { - await Effect.runPromise(actual.killSession(session)); - } catch { - // cleanup is idempotent when the session is already gone - } - const existing = sessions.get(session); - if (existing) await closeBridge(existing.bridge); - sessions.delete(session); - tmuxSessions.delete(session); + await killFakeTmuxSession(session); })), waitForClaudePrompt: vi.fn(() => Effect.succeed(Promise.resolve(true))), }; }); + vi.doMock('../../src/lib/runtimes/tmux-cli.js', () => ({ + tmuxCreateSession: vi.fn(async (session: string) => { + await createPlainTmuxSession(session); + }), + tmuxKillSession: vi.fn(async (session: string) => { + await killFakeTmuxSession(session); + }), + tmuxSessionExists: vi.fn(async (session: string) => { + return tmuxSessionExists(session); + }), + })); + const { resetDatabase } = await import('../../src/lib/database/index.js'); resetDatabase(); await startRealConversationRoutes(); @@ -434,10 +471,11 @@ afterEach(async () => { vi.doUnmock('../../src/lib/agents.js'); vi.doUnmock('../../src/lib/harness-resolve.js'); vi.doUnmock('../../src/lib/tmux.js'); + vi.doUnmock('../../src/lib/runtimes/tmux-cli.js'); }); describe('conversation supervisor Playwright UAT', () => { - it('resumes Codex conversations through the real route with codex resume', async () => { + it('resumes Codex app-server conversations through the real route', async () => { const response = await page.goto(baseUrl, { waitUntil: 'domcontentloaded' }); if (!response?.ok()) throw new Error('page.goto returned ' + response?.status() + ' for ' + baseUrl); const conversation = await page.evaluate(async () => { @@ -447,7 +485,7 @@ describe('conversation supervisor Playwright UAT', () => { body: JSON.stringify({ model: 'gpt-5.5', harness: 'codex' }), }) as { name: string; tmuxSession: string }; }); - await expectSupervisorBackedSession(conversation.tmuxSession); + await expect.poll(() => tmuxSessionExists(conversation.tmuxSession), { timeout: 30_000 }).toBe(true); const threadId = '019eaaec-4dfa-7ab1-90ba-9104d16534d1'; writeCodexRollout(conversation.tmuxSession, threadId); @@ -461,11 +499,12 @@ describe('conversation supervisor Playwright UAT', () => { await page.evaluate(async (conv) => { await (window as any).api('/api/conversations/' + conv.name + '/resume', { method: 'POST' }); }, conversation); - await expectSupervisorBackedSession(conversation.tmuxSession); + await expect.poll(() => tmuxSessionExists(conversation.tmuxSession), { timeout: 30_000 }).toBe(true); const launcher = launcherFor(conversation.tmuxSession); - expect(launcher).toContain(`codex resume -c project_doc_max_bytes=0 '${threadId}'`); - expect(launcher).not.toContain('codex -c project_doc_max_bytes=0\n'); + expect(launcher).toContain(`/dist/codex-app-server-host.js' --model 'gpt-5.5' --resume '${threadId}'`); + expect(launcher).not.toContain('pty-supervisor.js'); + expect(launcher).not.toContain('codex exec resume'); await cleanupConversationThroughApi(conversation); }, 45_000); diff --git a/tests/unit/dashboard/server/routes/specialists-reviewed-at-commit.test.ts b/tests/unit/dashboard/server/routes/specialists-reviewed-at-commit.test.ts index 6c936975ac..63c28b37b8 100644 --- a/tests/unit/dashboard/server/routes/specialists-reviewed-at-commit.test.ts +++ b/tests/unit/dashboard/server/routes/specialists-reviewed-at-commit.test.ts @@ -92,6 +92,10 @@ let mockDiffNameOnlyStdout = ''; const mockResolveProject = vi.fn(); vi.mock('../../../../../src/lib/projects.js', () => ({ + getProjectSync: vi.fn(() => ({ name: 'overdeck', path: '/fake/project' })), + listProjectsSync: vi.fn(() => [ + { key: 'overdeck', config: { name: 'overdeck', path: '/fake/project', issue_prefix: 'PAN' } }, + ]), resolveProjectFromIssue: (...args: unknown[]) => mockResolveProject(...args), resolveProjectFromIssueSync: (...args: unknown[]) => mockResolveProject(...args), })); @@ -152,7 +156,7 @@ vi.mock('../../../../../src/lib/agents.js', () => ({ saveAgentRuntimeState: vi.fn(), saveSessionId: vi.fn(), listRunningAgents: vi.fn().mockResolvedValue([]), - listRunningAgentsSync: vi.fn().mockResolvedValue([]), + listRunningAgentsSync: vi.fn().mockReturnValue([]), getAgentDir: vi.fn().mockReturnValue('/tmp'), getAgentState: vi.fn().mockReturnValue(null), getAgentStateSync: vi.fn().mockReturnValue(null), diff --git a/tests/unit/lib/pan-dir/cutover-behavioral.test.ts b/tests/unit/lib/pan-dir/cutover-behavioral.test.ts index 3f4f0e36a8..0bf9c5e68c 100644 --- a/tests/unit/lib/pan-dir/cutover-behavioral.test.ts +++ b/tests/unit/lib/pan-dir/cutover-behavioral.test.ts @@ -50,7 +50,6 @@ describe('PAN-1919: behavioral no-loss — all fields land in record, not contin decisions: [], hazards: [], resumePoint: null, - tasksMapping: {}, statusOverrides: {}, sessionHistory: [], feedback: [], @@ -78,7 +77,7 @@ describe('PAN-1919: behavioral no-loss — all fields land in record, not contin const now = new Date().toISOString(); writeIssueRecordSync(project, ISSUE_ID, { issueId: ISSUE_ID, schemaVersion: 2, created: now, updated: now, - decisions: [], hazards: [], resumePoint: null, tasksMapping: {}, + decisions: [], hazards: [], resumePoint: null, statusOverrides: {}, sessionHistory: [], feedback: [], pipeline: null, closeOut: null, }); @@ -99,7 +98,7 @@ describe('PAN-1919: behavioral no-loss — all fields land in record, not contin const now = new Date().toISOString(); writeIssueRecordSync(project, ISSUE_ID, { issueId: ISSUE_ID, schemaVersion: 2, created: now, updated: now, - decisions: [], hazards: [], resumePoint: null, tasksMapping: {}, + decisions: [], hazards: [], resumePoint: null, statusOverrides: {}, sessionHistory: [], feedback: [], pipeline: null, closeOut: null, }); @@ -120,7 +119,7 @@ describe('PAN-1919: behavioral no-loss — all fields land in record, not contin const now = new Date().toISOString(); writeIssueRecordSync(project, ISSUE_ID, { issueId: ISSUE_ID, schemaVersion: 2, created: now, updated: now, - decisions: [], hazards: [], resumePoint: null, tasksMapping: {}, + decisions: [], hazards: [], resumePoint: null, statusOverrides: {}, sessionHistory: [], feedback: [], pipeline: null, closeOut: null, }); @@ -141,7 +140,7 @@ describe('PAN-1919: behavioral no-loss — all fields land in record, not contin const now = new Date().toISOString(); writeIssueRecordSync(project, ISSUE_ID, { issueId: ISSUE_ID, schemaVersion: 2, created: now, updated: now, - decisions: [], hazards: [], resumePoint: null, tasksMapping: {}, + decisions: [], hazards: [], resumePoint: null, statusOverrides: {}, sessionHistory: [], feedback: [], pipeline: null, closeOut: null, }); @@ -163,7 +162,7 @@ describe('PAN-1919: behavioral no-loss — all fields land in record, not contin const now = new Date().toISOString(); writeIssueRecordSync(project, ISSUE_ID, { issueId: ISSUE_ID, schemaVersion: 2, created: now, updated: now, - decisions: [], hazards: [], resumePoint: null, tasksMapping: {}, + decisions: [], hazards: [], resumePoint: null, statusOverrides: {}, sessionHistory: [], feedback: [], pipeline: null, closeOut: null, }); @@ -187,7 +186,7 @@ describe('PAN-1919: behavioral no-loss — all fields land in record, not contin const now = new Date().toISOString(); writeIssueRecordSync(project, ISSUE_ID, { issueId: ISSUE_ID, schemaVersion: 2, created: now, updated: now, - decisions: [], hazards: [], resumePoint: null, tasksMapping: {}, + decisions: [], hazards: [], resumePoint: null, statusOverrides: {}, sessionHistory: [], feedback: [], pipeline: null, closeOut: null, }); @@ -221,7 +220,6 @@ describe('PAN-1919: cross-machine resume — record is the sole source of truth' decisions: [{ id: 'D1', summary: 'Use record writer', recordedAt: now }], hazards: [], resumePoint: { description: 'Resume at task-3', taskId: 'task-3' }, - tasksMapping: {}, statusOverrides: { 'item-1': 'completed', 'item-2': 'in-progress' }, sessionHistory: [], feedback: [], @@ -257,7 +255,7 @@ describe('PAN-1919: cross-machine resume — record is the sole source of truth' writeIssueRecordSync(project, ISSUE_ID, { issueId: ISSUE_ID, schemaVersion: 2, created: now, updated: now, harness: 'pi', model: 'kimi-k2.7-code', - decisions: [], hazards: [], resumePoint: null, tasksMapping: {}, + decisions: [], hazards: [], resumePoint: null, statusOverrides: {}, sessionHistory: [], feedback: [], pipeline: null, closeOut: null, }); diff --git a/tsdown.config.ts b/tsdown.config.ts index f800db1bed..4bcbf6c121 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ 'index': 'src/index.ts', 'supervisor/server': 'src/supervisor/server.ts', 'pty-supervisor': 'src/lib/channels/pty-supervisor.ts', + 'codex-app-server-host': 'src/lib/codex/app-server-host.ts', 'verification-worker': 'src/lib/cloister/verification-worker.ts', 'lib/memory/fts-worker': 'src/lib/memory/fts-worker.ts', },