diff --git a/.copilot-schema-version b/.copilot-schema-version index 1dc08bd..8e17bf1 100644 --- a/.copilot-schema-version +++ b/.copilot-schema-version @@ -1 +1 @@ -1.0.55-1 +1.0.56-1 diff --git a/CHANGELOG.md b/CHANGELOG.md index be64f34..5edcd97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,107 @@ All notable changes to this project will be documented in this file. This change ## [Unreleased] +### Added (post-v1.0.0-beta.4 sync, round 6) +- **`:agent-mode` and `:display-prompt` send options** — `session/send!` + (and async/streaming variants) now accept: + - `:agent-mode` — keyword in `#{:interactive :plan :autopilot :shell}`, + wire-encoded as `agentMode`. Lets the model run with different agent + behaviours per message. (upstream PR #1438) + - `:display-prompt` — string shown in the timeline UI instead of the + model-facing `:prompt`. Useful when the model prompt contains + machinery or context that should not be surfaced to the end user. + Wire-encoded as `displayPrompt`. (upstream PR #1470) +- **`:mcp-oauth-token-storage` config option** — Controls where MCP OAuth + tokens are persisted. Enum `#{:persistent :in-memory}`, defaulting to + the server's default (persistent disk-backed). Set to `:in-memory` in + multi-tenant hosts that must not leak tokens to disk. Wire-encoded as + `mcpOAuthTokenStorage` (the wire key is set directly to bypass the + default kebab-camel converter which would mangle `OAuth`). Accepted on + both create and resume. (upstream PR #1326) +- **Multitenancy per-session granular flags** — All optional, accepted on + both `create-session` and `resume-session`: + - `:embedding-cache-storage` (`#{:persistent :in-memory}`, wire + `embeddingCacheStorage`) + - `:skip-embedding-retrieval` (boolean) + - `:organization-custom-instructions` (string) + - `:enable-on-demand-instruction-discovery` (boolean) + - `:enable-file-hooks` (boolean) + - `:enable-host-git-operations` (boolean) + - `:enable-session-store` (boolean) + - `:enable-skills` (boolean) + + Lets multi-tenant hosts opt individual sessions out of disk-backed + caches, host git, hooks, sessions store, and skills discovery without + switching to a separate client. (upstream PR #1474) +- **`:plugin-directories` config option** — `[string]` of extra plugin + directories. Wire-encoded as `pluginDirectories`. Loaded even when + `:enable-config-discovery` is `false`, so multi-tenant hosts can + inject a curated plugin set without enabling general discovery. + Accepted on both create and resume. (upstream PR #1482) +- **Cloud sessions can defer `sessionId` to the server** — When + `:cloud` is set and `:session-id` is omitted from `create-session` / + ` :interval-ms :prompt "..."}` (upstream schema 1.0.42) | | `:copilot/session.schedule_cancelled` | Scheduled prompt cancelled from the schedule manager dialog; data: `{:id }` (upstream schema 1.0.42) | +| `:copilot/session.autopilot_objective_changed` | Autopilot objective lifecycle events; data: `{:operation #{"create" "update" "delete"}}` (required) with optional `:id` (integer) and `:status` (upstream schema 1.0.56). The `:status` enum is widened to include `"active"`, `"paused"`, `"cap_reached"`, `"completed"`. | +| `:copilot/session.permissions_changed` | Per-session permission flags changed; data: `{:allow-all-permissions boolean :previous-allow-all-permissions boolean}` (upstream schema 1.0.56). | | `:copilot/skill.invoked` | Skill invocation triggered; data includes :name, :path, :content, optional :description, :plugin-name, :plugin-version | | `:copilot/user.message` | User message added | | `:copilot/pending_messages.modified` | Pending message queue updated | @@ -1404,6 +1421,7 @@ Convert an unqualified event keyword to a namespace-qualified `:copilot/` keywor | `:copilot/subagent.selected` | Subagent selected | | `:copilot/subagent.deselected` | Subagent deselected | | `:copilot/hook.start` | Hook invocation started | +| `:copilot/hook.progress` | Ephemeral progress update from a long-running hook; data: `{:message "..."}` (upstream schema 1.0.56). | | `:copilot/hook.end` | Hook invocation finished | | `:copilot/system.message` | System message emitted | | `:copilot/system.notification` | System notification with structured `:kind` discriminator (e.g. `agent_completed`, `shell_completed`, `shell_detached_completed`) | diff --git a/schemas/README.md b/schemas/README.md index 29a817c..f904ea6 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -4,4 +4,4 @@ These files are fetched verbatim from the `@github/copilot` npm package at the v **Do not edit by hand.** To update, run `bb schemas:fetch` after bumping `.copilot-schema-version`. -Currently pinned version: `1.0.55-1` +Currently pinned version: `1.0.56-1` diff --git a/schemas/api.schema.json b/schemas/api.schema.json index 314b857..d4d59e2 100644 --- a/schemas/api.schema.json +++ b/schemas/api.schema.json @@ -149,6 +149,14 @@ "result": { "type": "null" } + }, + "reload": { + "rpcMethod": "mcp.config.reload", + "description": "Drops this runtime process's in-memory MCP server-definition cache so the next MCP config read observes disk.", + "params": null, + "result": { + "type": "null" + } } }, "discover": { @@ -191,6 +199,18 @@ } } }, + "user": { + "settings": { + "reload": { + "rpcMethod": "user.settings.reload", + "description": "Drops this runtime process's in-memory user settings cache so the next settings read observes disk.", + "params": null, + "result": { + "type": "null" + } + } + } + }, "sessionFs": { "setProvider": { "rpcMethod": "sessionFs.setProvider", @@ -237,7 +257,7 @@ "description": "Lists persisted sessions, optionally filtered by working-directory context.", "params": { "$ref": "#/definitions/SessionsListRequest", - "description": "Optional metadata-load limit and context filter applied to the returned sessions." + "description": "Optional metadata-load limit and filters applied to the returned sessions." }, "result": { "$ref": "#/definitions/SessionList", @@ -450,6 +470,21 @@ }, "stability": "experimental" } + }, + "agentRegistry": { + "spawn": { + "rpcMethod": "agentRegistry.spawn", + "description": "Spawns a managed-server child with the supplied configuration and returns a discriminated-union result. The caller (typically the CLI controller) is responsible for attaching to the spawned child and sending any follow-up prompt. When the controller-local spawn gate is closed the server returns JSON-RPC MethodNotFound.", + "params": { + "$ref": "#/definitions/AgentRegistrySpawnRequest", + "description": "Inputs to spawn a managed-server child via the controller's spawn delegate." + }, + "result": { + "$ref": "#/definitions/AgentRegistrySpawnResult", + "description": "Outcome of an agentRegistry.spawn call." + }, + "stability": "experimental" + } } }, "session": { @@ -789,43 +824,45 @@ }, "stability": "experimental" }, - "invokeAction": { - "rpcMethod": "session.canvas.invokeAction", - "description": "Invokes an action on an open canvas instance.", - "params": { - "type": "object", - "properties": { - "sessionId": { - "type": "string", - "description": "Target session identifier" - }, - "instanceId": { - "type": "string", - "description": "Open canvas instance identifier" - }, - "actionName": { - "type": "string", - "description": "Action name to invoke" + "action": { + "invoke": { + "rpcMethod": "session.canvas.action.invoke", + "description": "Invokes an action on an open canvas instance.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "instanceId": { + "type": "string", + "description": "Open canvas instance identifier" + }, + "actionName": { + "type": "string", + "description": "Action name to invoke" + }, + "input": { + "description": "Action input", + "x-opaque-json": true + } }, - "input": { - "description": "Action input", - "x-opaque-json": true - } + "required": [ + "sessionId", + "instanceId", + "actionName" + ], + "additionalProperties": false, + "description": "Canvas action invocation parameters.", + "title": "CanvasActionInvokeRequest" }, - "required": [ - "sessionId", - "instanceId", - "actionName" - ], - "additionalProperties": false, - "description": "Canvas action invocation parameters.", - "title": "CanvasInvokeActionRequest" - }, - "result": { - "$ref": "#/definitions/CanvasInvokeActionResult", - "description": "Canvas action invocation result." - }, - "stability": "experimental" + "result": { + "$ref": "#/definitions/CanvasActionInvokeResult", + "description": "Canvas action invocation result." + }, + "stability": "experimental" + } } }, "model": { @@ -877,6 +914,25 @@ "modelCapabilities": { "$ref": "#/definitions/ModelCapabilitiesOverride", "description": "Override individual model capabilities resolved by the runtime" + }, + "contextTier": { + "anyOf": [ + { + "type": "string", + "enum": [ + "default", + "long_context" + ], + "x-enumDescriptions": { + "default": "Use the model's default context window.", + "long_context": "Pin the session to the long-context tier when supported." + } + }, + { + "type": "null" + } + ], + "description": "Explicit context tier for the selected model. `\"default\"` / `\"long_context\"` pin the tier; `null` clears any previous explicit choice; `undefined` leaves the existing tier untouched." } }, "required": [ @@ -884,7 +940,7 @@ "modelId" ], "additionalProperties": false, - "description": "Target model identifier and optional reasoning effort, summary, and capability overrides.", + "description": "Target model identifier and optional reasoning effort, summary, capability overrides, and context tier.", "title": "ModelSwitchToRequest" }, "result": { @@ -921,6 +977,43 @@ "description": "Update the session's reasoning effort without changing the selected model. Use `switchTo` instead when you also need to change the model. The runtime stores the effort on the session and applies it to subsequent turns." }, "stability": "experimental" + }, + "list": { + "rpcMethod": "session.model.list", + "description": "Lists models available to this session using its own auth and integration context. Connected hosts (CLI TUI, GitHub App) should call this through the session client so remote sessions return the remote CLI's available models rather than the caller's.", + "params": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "skipCache": { + "type": "boolean", + "description": "If true, bypasses the per-session model list cache and re-fetches from CAPI." + } + }, + "additionalProperties": false + } + ], + "description": "Optional listing options.", + "title": "ModelListRequest", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + } + }, + "required": [ + "sessionId" + ] + }, + "result": { + "$ref": "#/definitions/SessionModelList", + "description": "The list of models available to this session." + }, + "stability": "experimental" } }, "mode": { @@ -2582,6 +2675,10 @@ }, "description": "Denylist of tool names for this session." }, + "toolFilterPrecedence": { + "$ref": "#/definitions/OptionsUpdateToolFilterPrecedence", + "description": "Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set." + }, "enableScriptSafety": { "type": "boolean", "description": "Whether shell-script safety heuristics are enabled." @@ -2701,6 +2798,30 @@ "manageScheduleEnabled": { "type": "boolean", "description": "Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users)." + }, + "skipEmbeddingRetrieval": { + "type": "boolean", + "description": "Whether to skip embedding retrieval pipeline initialization and execution." + }, + "organizationCustomInstructions": { + "type": "string", + "description": "Organization-level custom instructions to inject into the system prompt." + }, + "enableFileHooks": { + "type": "boolean", + "description": "Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism." + }, + "enableHostGitOperations": { + "type": "boolean", + "description": "Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt)." + }, + "enableSessionStore": { + "type": "boolean", + "description": "Whether to enable cross-session store writes and reads." + }, + "enableSkills": { + "type": "boolean", + "description": "Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset." } }, "additionalProperties": false, @@ -2917,6 +3038,29 @@ "description": "Resolve, build, and validate the runtime tool list for this session. Subagent sessions and consumer flows that need an initialized tool set before `send` invoke this. Default base-class implementation is a no-op for sessions that don't support tool validation." }, "stability": "experimental" + }, + "getCurrentMetadata": { + "rpcMethod": "session.tools.getCurrentMetadata", + "description": "Returns lightweight metadata for the session's currently initialized tools.", + "params": { + "type": "object", + "description": "Identifies the target session.", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + } + }, + "required": [ + "sessionId" + ], + "additionalProperties": false + }, + "result": { + "$ref": "#/definitions/ToolsGetCurrentMetadataResult", + "description": "Current lightweight tool metadata snapshot for the session." + }, + "stability": "experimental" } }, "commands": { @@ -3562,6 +3706,63 @@ }, "stability": "experimental" }, + "setAllowAll": { + "rpcMethod": "session.permissions.setAllowAll", + "description": "Enables or disables full allow-all permissions (tools, paths, and URLs) for the session. Used by attach-mode clients (e.g. LocalRpcSession's `/allow-all` forwarder) to flip the target session's permission state. Unlike `setApproveAll`, this swaps in the unrestricted path and URL managers and emits `session.permissions_changed` on transition. The result returns the authoritative post-mutation state so callers can update their local mirrors without racing the `session.permissions_changed` notification on the same wire.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "enabled": { + "type": "boolean", + "description": "Whether to enable full allow-all permissions" + }, + "source": { + "$ref": "#/definitions/PermissionsSetAllowAllSource", + "description": "Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers." + } + }, + "required": [ + "sessionId", + "enabled" + ], + "additionalProperties": false, + "description": "Whether to enable full allow-all permissions for the session.", + "title": "PermissionsSetAllowAllRequest" + }, + "result": { + "$ref": "#/definitions/AllowAllPermissionSetResult", + "description": "Indicates whether the operation succeeded and reports the post-mutation state." + }, + "stability": "experimental" + }, + "getAllowAll": { + "rpcMethod": "session.permissions.getAllowAll", + "description": "Returns whether full allow-all permissions are currently active for the session.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + } + }, + "additionalProperties": false, + "description": "No parameters.", + "title": "PermissionsGetAllowAllRequest", + "required": [ + "sessionId" + ] + }, + "result": { + "$ref": "#/definitions/AllowAllPermissionState", + "description": "Current full allow-all permission state." + }, + "stability": "experimental" + }, "modifyRules": { "rpcMethod": "session.permissions.modifyRules", "description": "Adds or removes session-scoped or location-scoped permission rules.", @@ -5296,6 +5497,10 @@ "host": { "$ref": "#/definitions/CanvasHostContext", "description": "Host context supplied by the runtime." + }, + "session": { + "$ref": "#/definitions/CanvasSessionContext", + "description": "Session context supplied by the runtime." } }, "required": [ @@ -5339,6 +5544,10 @@ "host": { "$ref": "#/definitions/CanvasHostContext", "description": "Host context supplied by the runtime." + }, + "session": { + "$ref": "#/definitions/CanvasSessionContext", + "description": "Session context supplied by the runtime." } }, "required": [ @@ -5356,57 +5565,63 @@ }, "stability": "experimental" }, - "invokeAction": { - "rpcMethod": "canvas.invokeAction", - "description": "Invokes an action on an open canvas instance via the provider.", - "params": { - "type": "object", - "properties": { - "sessionId": { - "type": "string", - "description": "Target session identifier" - }, - "extensionId": { - "type": "string", - "description": "Owning provider identifier" - }, - "canvasId": { - "type": "string", - "description": "Provider-local canvas identifier" - }, - "instanceId": { - "type": "string", - "description": "Canvas instance identifier" - }, - "actionName": { - "type": "string", - "description": "Action name to invoke" - }, - "input": { - "description": "Action input", - "x-opaque-json": true + "action": { + "invoke": { + "rpcMethod": "canvas.action.invoke", + "description": "Invokes an action on an open canvas instance via the provider.", + "params": { + "type": "object", + "properties": { + "sessionId": { + "type": "string", + "description": "Target session identifier" + }, + "extensionId": { + "type": "string", + "description": "Owning provider identifier" + }, + "canvasId": { + "type": "string", + "description": "Provider-local canvas identifier" + }, + "instanceId": { + "type": "string", + "description": "Canvas instance identifier" + }, + "actionName": { + "type": "string", + "description": "Action name to invoke" + }, + "input": { + "description": "Action input", + "x-opaque-json": true + }, + "host": { + "$ref": "#/definitions/CanvasHostContext", + "description": "Host context supplied by the runtime." + }, + "session": { + "$ref": "#/definitions/CanvasSessionContext", + "description": "Session context supplied by the runtime." + } }, - "host": { - "$ref": "#/definitions/CanvasHostContext", - "description": "Host context supplied by the runtime." - } + "required": [ + "sessionId", + "extensionId", + "canvasId", + "instanceId", + "actionName" + ], + "additionalProperties": false, + "description": "Canvas action invocation parameters sent to the provider.", + "title": "CanvasProviderInvokeActionRequest" }, - "required": [ - "sessionId", - "extensionId", - "canvasId", - "instanceId", - "actionName" - ], - "additionalProperties": false, - "description": "Canvas action invocation parameters sent to the provider.", - "title": "CanvasProviderInvokeActionRequest" - }, - "result": { - "description": "Provider-supplied action result.", - "x-opaque-json": true - }, - "stability": "experimental" + "result": { + "description": "Provider-supplied action result.", + "x-opaque-json": true + }, + "stability": "experimental" + } } } }, @@ -5669,23 +5884,445 @@ "description": "Custom agents available to the session.", "title": "AgentList" }, - "AgentReloadResult": { + "AgentRegistryLiveTargetEntry": { "type": "object", "properties": { - "agents": { - "type": "array", - "items": { - "$ref": "#/definitions/AgentInfo" - }, - "description": "Reloaded custom agents" - } - }, - "required": [ - "agents" - ], - "additionalProperties": false, - "description": "Custom agents available to the session after reloading definitions from disk.", - "title": "AgentReloadResult" + "schemaVersion": { + "type": "integer", + "description": "Registry entry schema version (1 = ui-server, 2 = managed-server)" + }, + "kind": { + "$ref": "#/definitions/AgentRegistryLiveTargetEntryKind", + "description": "Process kind tag for the registry entry" + }, + "pid": { + "type": "integer", + "description": "Operating-system pid of the process owning this entry" + }, + "host": { + "type": "string", + "description": "Bind host for the entry's JSON-RPC server" + }, + "port": { + "type": "integer", + "description": "TCP port the entry's JSON-RPC server is listening on" + }, + "token": { + "type": [ + "string", + "null" + ], + "description": "Connection token (null when the target is unauthenticated)", + "visibility": "internal" + }, + "sessionId": { + "type": "string", + "description": "Session ID of the foreground session for this entry" + }, + "sessionName": { + "type": "string", + "description": "Friendly session name (when set)" + }, + "cwd": { + "type": "string", + "description": "Working directory of the session (when known)" + }, + "branch": { + "type": "string", + "description": "Git branch of the session (when known)" + }, + "model": { + "type": "string", + "description": "Model identifier currently selected for the session" + }, + "status": { + "$ref": "#/definitions/AgentRegistryLiveTargetEntryStatus", + "description": "Coarse lifecycle status of the foreground session" + }, + "attentionKind": { + "$ref": "#/definitions/AgentRegistryLiveTargetEntryAttentionKind", + "description": "Kind of attention required when status === \"attention\". Meaningful only when status === \"attention\"." + }, + "statusRevision": { + "type": "integer", + "minimum": 0, + "description": "Monotonic per-publisher revision counter incremented on every status update. Lets watchers detect transient flips." + }, + "lastTerminalEvent": { + "$ref": "#/definitions/AgentRegistryLiveTargetEntryLastTerminalEvent", + "description": "How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled." + }, + "startedAt": { + "type": "string", + "description": "ISO 8601 timestamp captured at registration" + }, + "copilotVersion": { + "type": "string", + "description": "Copilot CLI version that wrote the entry" + }, + "lastSeenMs": { + "type": "integer", + "description": "Wall-clock milliseconds since the watcher last observed this entry (heartbeat freshness)" + } + }, + "required": [ + "schemaVersion", + "kind", + "pid", + "host", + "port", + "startedAt", + "copilotVersion", + "lastSeenMs" + ], + "additionalProperties": false, + "description": "Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window).", + "title": "AgentRegistryLiveTargetEntry" + }, + "AgentRegistryLiveTargetEntryAttentionKind": { + "type": "string", + "enum": [ + "error", + "permission", + "exit_plan", + "elicitation", + "user_input" + ], + "description": "Kind of attention required when status === \"attention\". Meaningful only when status === \"attention\".", + "title": "AgentRegistryLiveTargetEntryAttentionKind", + "x-enumDescriptions": { + "error": "Session is blocked on an unrecoverable error", + "permission": "Session is waiting for a tool-permission decision", + "exit_plan": "Session is waiting for the user to approve or reject a plan", + "elicitation": "Session is waiting on an elicitation prompt", + "user_input": "Session is waiting for free-form user input" + } + }, + "AgentRegistryLiveTargetEntryKind": { + "type": "string", + "enum": [ + "ui-server", + "managed-server" + ], + "description": "Process kind tag for the registry entry", + "title": "AgentRegistryLiveTargetEntryKind", + "x-enumDescriptions": { + "ui-server": "Interactive Copilot CLI exposing a UI server (legacy/normal CLI process)", + "managed-server": "Headless `--server --managed-server` child spawned by a controller" + } + }, + "AgentRegistryLiveTargetEntryLastTerminalEvent": { + "type": "string", + "enum": [ + "turn_end", + "abort" + ], + "description": "How the most recent turn ended (clean vs aborted). Lets the renderer distinguish done from done_cancelled.", + "title": "AgentRegistryLiveTargetEntryLastTerminalEvent", + "x-enumDescriptions": { + "turn_end": "Last turn ended cleanly (model returned a final assistant message)", + "abort": "Last turn was aborted (e.g. user interrupted)" + } + }, + "AgentRegistryLiveTargetEntryStatus": { + "type": "string", + "enum": [ + "working", + "waiting", + "done", + "attention" + ], + "description": "Coarse lifecycle status of the foreground session", + "title": "AgentRegistryLiveTargetEntryStatus", + "x-enumDescriptions": { + "working": "Session is actively processing a turn", + "waiting": "Session is idle, waiting for input", + "done": "Last turn completed successfully", + "attention": "Session needs user attention (see attentionKind for the specific reason)" + } + }, + "AgentRegistryLogCapture": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether per-spawn log capture is on (false when env-disabled or open failed)" + }, + "path": { + "type": "string", + "description": "Absolute path to the per-spawn log file (only set when enabled)" + }, + "openError": { + "type": "string", + "description": "Human-readable open failure message (only set when enabled === false AND the env-disable opt-out was NOT used)" + }, + "openErrorReason": { + "$ref": "#/definitions/AgentRegistryLogCaptureOpenErrorReason", + "description": "Categorized reason for log-open failure" + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false, + "description": "Per-spawn log-capture outcome; populated from spawnLiveTarget.", + "title": "AgentRegistryLogCapture" + }, + "AgentRegistryLogCaptureOpenErrorReason": { + "type": "string", + "enum": [ + "permission", + "disk_full", + "other" + ], + "description": "Categorized reason for log-open failure", + "title": "AgentRegistryLogCaptureOpenErrorReason", + "x-enumDescriptions": { + "permission": "Filesystem permission denied opening the log file", + "disk_full": "No space left on device", + "other": "Other / uncategorized open failure" + } + }, + "AgentRegistrySpawnError": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "spawn-error", + "description": "Discriminator: child_process.spawn itself failed" + }, + "message": { + "type": "string", + "description": "Human-readable error message" + }, + "code": { + "type": "string", + "description": "Underlying errno code (e.g. ENOENT, EACCES) when available" + } + }, + "required": [ + "kind", + "message" + ], + "additionalProperties": false, + "description": "`child_process.spawn` itself failed before the child entered the registry.", + "title": "AgentRegistrySpawnError" + }, + "AgentRegistrySpawnPermissionMode": { + "type": "string", + "enum": [ + "default", + "yolo" + ], + "description": "Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode.", + "title": "AgentRegistrySpawnPermissionMode", + "x-enumDescriptions": { + "default": "Standard permission posture (prompts for each request)", + "yolo": "Full allow-all (requires the controller-local session to currently be in allow-all mode)" + } + }, + "AgentRegistrySpawnRegistryTimeout": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "registry-timeout", + "description": "Discriminator: spawn succeeded but child never registered" + }, + "childPid": { + "type": "integer", + "description": "Process ID of the orphaned child (so the caller can offer 'kill the pid' guidance)" + }, + "logCapture": { + "$ref": "#/definitions/AgentRegistryLogCapture", + "description": "Per-spawn log-capture outcome; populated from spawnLiveTarget." + } + }, + "required": [ + "kind", + "childPid" + ], + "additionalProperties": false, + "description": "Spawn succeeded but the child did not publish a matching managed-server entry within the timeout.", + "title": "AgentRegistrySpawnRegistryTimeout" + }, + "AgentRegistrySpawnRequest": { + "type": "object", + "properties": { + "cwd": { + "type": "string", + "description": "Working directory for the spawned child (must be an existing directory)" + }, + "agentName": { + "type": "string", + "description": "Custom or built-in agent name (e.g. 'explore'). When omitted, the child uses its own default." + }, + "model": { + "type": "string", + "description": "Model identifier to apply to the new session" + }, + "name": { + "type": "string", + "description": "Friendly session name. Must satisfy validateSessionName: non-empty, no leading/trailing whitespace, <=100 chars, no control chars, no double quotes." + }, + "permissionMode": { + "$ref": "#/definitions/AgentRegistrySpawnPermissionMode", + "description": "Permission posture for the new session. 'yolo' requires the controller-local session to currently be in allow-all mode." + }, + "initialPrompt": { + "type": "string", + "description": "Optional first user message. Forwarded to the caller (the CLI's spawn wrapper sends it post-attach via the standard LocalRpcSession.send path)." + } + }, + "required": [ + "cwd" + ], + "additionalProperties": false, + "description": "Inputs to spawn a managed-server child via the controller's spawn delegate.", + "title": "AgentRegistrySpawnRequest" + }, + "AgentRegistrySpawnResult": { + "anyOf": [ + { + "$ref": "#/definitions/AgentRegistrySpawnSpawned", + "description": "Managed-server child was spawned and registered successfully." + }, + { + "$ref": "#/definitions/AgentRegistrySpawnError", + "description": "`child_process.spawn` itself failed before the child entered the registry." + }, + { + "$ref": "#/definitions/AgentRegistrySpawnRegistryTimeout", + "description": "Spawn succeeded but the child did not publish a matching managed-server entry within the timeout." + }, + { + "$ref": "#/definitions/AgentRegistrySpawnValidationError", + "description": "Synchronous pre-validation rejected the spawn request." + } + ], + "description": "Outcome of an agentRegistry.spawn call.", + "title": "AgentRegistrySpawnResult" + }, + "AgentRegistrySpawnSpawned": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "spawned", + "description": "Discriminator: managed-server child spawned successfully" + }, + "entry": { + "$ref": "#/definitions/AgentRegistryLiveTargetEntry", + "description": "Full registry entry for the spawned child. Lets the controller call `handleLiveTargetSelected(entry)` directly without re-reading the registry (avoids a TOCTOU window)." + }, + "initialPromptSent": { + "type": "boolean", + "description": "Whether the delegate already sent the initial prompt. Always omitted in the current wiring: the controller sends the prompt post-attach via the standard LocalRpcSession.send path." + }, + "initialPromptError": { + "type": "string", + "description": "If the delegate attempted to send the initial prompt and failed, the categorized error message." + }, + "logCapture": { + "$ref": "#/definitions/AgentRegistryLogCapture", + "description": "Per-spawn log-capture outcome; populated from spawnLiveTarget." + } + }, + "required": [ + "kind", + "entry" + ], + "additionalProperties": false, + "description": "Managed-server child was spawned and registered successfully.", + "title": "AgentRegistrySpawnSpawned" + }, + "AgentRegistrySpawnValidationError": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "const": "validation-error", + "description": "Discriminator: synchronous pre-validation rejected the request" + }, + "reason": { + "$ref": "#/definitions/AgentRegistrySpawnValidationErrorReason", + "description": "Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names." + }, + "field": { + "$ref": "#/definitions/AgentRegistrySpawnValidationErrorField", + "description": "Which parameter field was invalid. Omitted when the rejection is not field-specific." + }, + "message": { + "type": "string", + "description": "Human-readable explanation; safe to surface in the UI banner. Never logged to unrestricted telemetry." + } + }, + "required": [ + "kind", + "reason", + "message" + ], + "additionalProperties": false, + "description": "Synchronous pre-validation rejected the spawn request.", + "title": "AgentRegistrySpawnValidationError" + }, + "AgentRegistrySpawnValidationErrorField": { + "type": "string", + "enum": [ + "cwd", + "name", + "agentName", + "model", + "permissionMode" + ], + "description": "Which parameter field was invalid. Omitted when the rejection is not field-specific.", + "title": "AgentRegistrySpawnValidationErrorField", + "x-enumDescriptions": { + "cwd": "The cwd parameter", + "name": "The session name parameter", + "agentName": "The agentName parameter", + "model": "The model parameter", + "permissionMode": "The permissionMode parameter" + } + }, + "AgentRegistrySpawnValidationErrorReason": { + "type": "string", + "enum": [ + "cwd-not-found", + "cwd-not-directory", + "invalid-name", + "unknown-agent", + "unknown-model", + "yolo-not-allowed" + ], + "description": "Categorized reason for the rejection. Low-cardinality enum so telemetry can aggregate by reason without leaking raw paths or agent/model names.", + "title": "AgentRegistrySpawnValidationErrorReason", + "x-enumDescriptions": { + "cwd-not-found": "Provided cwd does not exist on disk", + "cwd-not-directory": "Provided cwd exists but is not a directory", + "invalid-name": "Session name failed validateSessionName", + "unknown-agent": "Requested agent name was not found in builtin or custom agents", + "unknown-model": "Requested model is not available to this session", + "yolo-not-allowed": "Caller asked for permissionMode='yolo' but the controller is not currently in allow-all mode" + } + }, + "AgentReloadResult": { + "type": "object", + "properties": { + "agents": { + "type": "array", + "items": { + "$ref": "#/definitions/AgentInfo" + }, + "description": "Reloaded custom agents" + } + }, + "required": [ + "agents" + ], + "additionalProperties": false, + "description": "Custom agents available to the session after reloading definitions from disk.", + "title": "AgentReloadResult" }, "AgentSelectRequest": { "type": "object", @@ -5717,6 +6354,41 @@ "description": "The newly selected custom agent.", "title": "AgentSelectResult" }, + "AllowAllPermissionSetResult": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "description": "Whether the operation succeeded" + }, + "enabled": { + "type": "boolean", + "description": "Authoritative allow-all state after the mutation" + } + }, + "required": [ + "success", + "enabled" + ], + "additionalProperties": false, + "description": "Indicates whether the operation succeeded and reports the post-mutation state.", + "title": "AllowAllPermissionSetResult" + }, + "AllowAllPermissionState": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether full allow-all permissions are currently active" + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false, + "description": "Current full allow-all permission state.", + "title": "AllowAllPermissionState" + }, "ApiKeyAuthInfo": { "type": "object", "properties": { @@ -5820,93 +6492,93 @@ "description": "Canvas action that the agent or host can invoke. To discover the input schema for a particular action, call the list_canvas_capabilities tool.", "title": "CanvasAction" }, - "CanvasCloseRequest": { + "CanvasActionInvokeRequest": { "type": "object", "properties": { "instanceId": { "type": "string", "description": "Open canvas instance identifier" + }, + "actionName": { + "type": "string", + "description": "Action name to invoke" + }, + "input": { + "description": "Action input", + "x-opaque-json": true } }, "required": [ - "instanceId" + "instanceId", + "actionName" ], "additionalProperties": false, - "description": "Canvas close parameters.", - "title": "CanvasCloseRequest" + "description": "Canvas action invocation parameters.", + "title": "CanvasActionInvokeRequest" }, - "CanvasHostContext": { + "CanvasActionInvokeResult": { "type": "object", "properties": { - "capabilities": { - "$ref": "#/definitions/CanvasHostContextCapabilities", - "description": "Host capabilities" + "result": { + "description": "Provider-supplied action result", + "x-opaque-json": true } }, "additionalProperties": false, - "description": "Host context supplied by the runtime.", - "title": "CanvasHostContext" + "description": "Canvas action invocation result.", + "title": "CanvasActionInvokeResult" }, - "CanvasHostContextCapabilities": { + "CanvasCloseRequest": { "type": "object", "properties": { - "canvases": { - "type": "boolean", - "description": "Whether canvas rendering is supported" + "instanceId": { + "type": "string", + "description": "Open canvas instance identifier" } }, - "additionalProperties": false, - "description": "Host capabilities", - "title": "CanvasHostContextCapabilities" - }, - "CanvasInstanceAvailability": { - "type": "string", - "enum": [ - "ready", - "stale" + "required": [ + "instanceId" ], - "description": "Runtime-controlled routing state for an open canvas instance.", - "title": "CanvasInstanceAvailability", - "x-enumDescriptions": { - "ready": "The owning provider is currently connected and routing calls will be dispatched normally.", - "stale": "The owning provider is not currently connected. Routing calls fail with canvas_provider_unavailable until the agent re-issues open_canvas (which rehydrates via a fresh canvas.open) or the provider reconnects." - } + "additionalProperties": false, + "description": "Canvas close parameters.", + "title": "CanvasCloseRequest" }, - "CanvasInvokeActionRequest": { + "CanvasHostContext": { "type": "object", "properties": { - "instanceId": { - "type": "string", - "description": "Open canvas instance identifier" - }, - "actionName": { - "type": "string", - "description": "Action name to invoke" - }, - "input": { - "description": "Action input", - "x-opaque-json": true + "capabilities": { + "$ref": "#/definitions/CanvasHostContextCapabilities", + "description": "Host capabilities" } }, - "required": [ - "instanceId", - "actionName" - ], "additionalProperties": false, - "description": "Canvas action invocation parameters.", - "title": "CanvasInvokeActionRequest" + "description": "Host context supplied by the runtime.", + "title": "CanvasHostContext" }, - "CanvasInvokeActionResult": { + "CanvasHostContextCapabilities": { "type": "object", "properties": { - "result": { - "description": "Provider-supplied action result", - "x-opaque-json": true + "canvases": { + "type": "boolean", + "description": "Whether canvas rendering is supported" } }, "additionalProperties": false, - "description": "Canvas action invocation result.", - "title": "CanvasInvokeActionResult" + "description": "Host capabilities", + "title": "CanvasHostContextCapabilities" + }, + "CanvasInstanceAvailability": { + "type": "string", + "enum": [ + "ready", + "stale" + ], + "description": "Runtime-controlled routing state for an open canvas instance.", + "title": "CanvasInstanceAvailability", + "x-enumDescriptions": { + "ready": "The owning provider is currently connected and routing calls will be dispatched normally.", + "stale": "The owning provider is not currently connected. Routing calls fail with canvas_provider_unavailable until the agent re-issues open_canvas (which rehydrates via a fresh canvas.open) or the provider reconnects." + } }, "CanvasJsonSchema": { "description": "JSON Schema for canvas open input", @@ -5997,6 +6669,10 @@ "host": { "$ref": "#/definitions/CanvasHostContext", "description": "Host context supplied by the runtime." + }, + "session": { + "$ref": "#/definitions/CanvasSessionContext", + "description": "Session context supplied by the runtime." } }, "required": [ @@ -6034,6 +6710,10 @@ "host": { "$ref": "#/definitions/CanvasHostContext", "description": "Host context supplied by the runtime." + }, + "session": { + "$ref": "#/definitions/CanvasSessionContext", + "description": "Session context supplied by the runtime." } }, "required": [ @@ -6068,6 +6748,10 @@ "host": { "$ref": "#/definitions/CanvasHostContext", "description": "Host context supplied by the runtime." + }, + "session": { + "$ref": "#/definitions/CanvasSessionContext", + "description": "Session context supplied by the runtime." } }, "required": [ @@ -6099,6 +6783,18 @@ "description": "Canvas open result returned by the provider.", "title": "CanvasProviderOpenResult" }, + "CanvasSessionContext": { + "type": "object", + "properties": { + "workingDirectory": { + "type": "string", + "description": "Active session working directory, when known." + } + }, + "additionalProperties": false, + "description": "Session context supplied by the runtime.", + "title": "CanvasSessionContext" + }, "CommandList": { "type": "object", "properties": { @@ -6846,6 +7542,49 @@ "description": "The currently selected model and reasoning effort for the session.", "title": "CurrentModel" }, + "CurrentToolMetadata": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Model-facing tool name" + }, + "namespacedName": { + "type": "string", + "description": "Optional MCP/config namespaced tool name" + }, + "mcpServerName": { + "type": "string", + "description": "MCP server name for MCP-backed tools" + }, + "mcpToolName": { + "type": "string", + "description": "Raw MCP tool name for MCP-backed tools" + }, + "description": { + "type": "string", + "description": "Tool description" + }, + "input_schema": { + "type": "object", + "additionalProperties": { + "x-opaque-json": true + }, + "description": "JSON Schema for tool input" + }, + "deferLoading": { + "type": "boolean", + "description": "Whether the tool is loaded on demand via tool search" + } + }, + "required": [ + "name", + "description" + ], + "additionalProperties": false, + "description": "Lightweight metadata for a currently initialized session tool", + "title": "CurrentToolMetadata" + }, "DiscoveredCanvas": { "type": "object", "properties": { @@ -9283,6 +10022,34 @@ "title": "McpServer", "description": "Schema for the `McpServer` type." }, + "McpServerAuthConfig": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#/definitions/McpServerAuthConfigRedirectPort", + "description": "Authentication settings with optional redirect port configuration." + } + ], + "description": "Set to `true` to use defaults, or provide an object with additional auth or OIDC settings.", + "title": "McpServerAuthConfig", + "x-opaque-json": true + }, + "McpServerAuthConfigRedirectPort": { + "type": "object", + "properties": { + "redirectPort": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "description": "Fixed port for the OAuth redirect callback server." + } + }, + "additionalProperties": true, + "description": "Authentication settings with optional redirect port configuration.", + "title": "McpServerAuthConfigRedirectPort" + }, "McpServerConfig": { "anyOf": [ { @@ -9328,12 +10095,12 @@ "format": "duration" }, "oidc": { - "$ref": "#/definitions/McpServerConfigHttpOidc", - "description": "OIDC token configuration. When truthy, a token is automatically gathered." + "$ref": "#/definitions/McpServerAuthConfig", + "description": "Set to `true` to use defaults, or provide an object with additional auth or OIDC settings." }, "auth": { - "$ref": "#/definitions/McpServerConfigHttpAuth", - "description": "Additional authentication configuration for this server." + "$ref": "#/definitions/McpServerAuthConfig", + "description": "Set to `true` to use defaults, or provide an object with additional auth or OIDC settings." }, "url": { "type": "string", @@ -9367,20 +10134,6 @@ "description": "Remote MCP server configuration accessed over HTTP or SSE.", "title": "McpServerConfigHttp" }, - "McpServerConfigHttpAuth": { - "type": "object", - "properties": { - "redirectPort": { - "type": "integer", - "minimum": 1, - "maximum": 65535, - "description": "Fixed port for the OAuth redirect callback server." - } - }, - "additionalProperties": true, - "description": "Additional authentication configuration for this server.", - "title": "McpServerConfigHttpAuth" - }, "McpServerConfigHttpOauthGrantType": { "type": "string", "enum": [ @@ -9394,20 +10147,6 @@ "client_credentials": "Headless client credentials flow using the configured OAuth client." } }, - "McpServerConfigHttpOidc": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "object", - "additionalProperties": {} - } - ], - "description": "OIDC token configuration. When truthy, a token is automatically gathered.", - "title": "McpServerConfigHttpOidc", - "x-opaque-json": true - }, "McpServerConfigHttpType": { "type": "string", "enum": [ @@ -9448,12 +10187,12 @@ "format": "duration" }, "oidc": { - "$ref": "#/definitions/McpServerConfigStdioOidc", - "description": "OIDC token configuration. When truthy, a token is automatically gathered." + "$ref": "#/definitions/McpServerAuthConfig", + "description": "Set to `true` to use defaults, or provide an object with additional auth or OIDC settings." }, "auth": { - "$ref": "#/definitions/McpServerConfigStdioAuth", - "description": "Authentication configuration for this server." + "$ref": "#/definitions/McpServerAuthConfig", + "description": "Set to `true` to use defaults, or provide an object with additional auth or OIDC settings." }, "command": { "type": "string", @@ -9486,34 +10225,6 @@ "description": "Stdio MCP server configuration launched as a child process.", "title": "McpServerConfigStdio" }, - "McpServerConfigStdioAuth": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "object", - "additionalProperties": {} - } - ], - "description": "Authentication configuration for this server.", - "title": "McpServerConfigStdioAuth", - "x-opaque-json": true - }, - "McpServerConfigStdioOidc": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "object", - "additionalProperties": {} - } - ], - "description": "OIDC token configuration. When truthy, a token is automatically gathered.", - "title": "McpServerConfigStdioOidc", - "x-opaque-json": true - }, "McpServerList": { "type": "object", "properties": { @@ -10169,6 +10880,25 @@ "description": "List of Copilot models available to the resolved user, including capabilities and billing metadata.", "title": "ModelList" }, + "ModelListRequest": { + "anyOf": [ + { + "not": {} + }, + { + "type": "object", + "properties": { + "skipCache": { + "type": "boolean", + "description": "If true, bypasses the per-session model list cache and re-fetches from CAPI." + } + }, + "additionalProperties": false + } + ], + "description": "Optional listing options.", + "title": "ModelListRequest" + }, "ModelPickerCategory": { "type": "string", "enum": [ @@ -10302,13 +11032,32 @@ "modelCapabilities": { "$ref": "#/definitions/ModelCapabilitiesOverride", "description": "Override individual model capabilities resolved by the runtime" + }, + "contextTier": { + "anyOf": [ + { + "type": "string", + "enum": [ + "default", + "long_context" + ], + "x-enumDescriptions": { + "default": "Use the model's default context window.", + "long_context": "Pin the session to the long-context tier when supported." + } + }, + { + "type": "null" + } + ], + "description": "Explicit context tier for the selected model. `\"default\"` / `\"long_context\"` pin the tier; `null` clears any previous explicit choice; `undefined` leaves the existing tier untouched." } }, "required": [ "modelId" ], "additionalProperties": false, - "description": "Target model identifier and optional reasoning effort, summary, and capability overrides.", + "description": "Target model identifier and optional reasoning effort, summary, capability overrides, and context tier.", "title": "ModelSwitchToRequest" }, "ModelSwitchToResult": { @@ -10471,6 +11220,19 @@ "indirect": "Resolve MCP server environment values from host-side references." } }, + "OptionsUpdateToolFilterPrecedence": { + "type": "string", + "enum": [ + "available", + "excluded" + ], + "description": "Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set.", + "title": "OptionsUpdateToolFilterPrecedence", + "x-enumDescriptions": { + "available": "If availableTools is set, it is the only constraint that applies (excludedTools is ignored). Preserves CLI / pre-existing client behavior. Default.", + "excluded": "A tool is enabled if and only if it matches the allowlist (or the allowlist is unset) AND it does not match the denylist. Makes 'all except X' expressible by combining the two lists." + } + }, "PendingPermissionRequest": { "type": "object", "properties": { @@ -11841,6 +12603,13 @@ "description": "Indicates whether the operation succeeded.", "title": "PermissionsFolderTrustAddTrustedResult" }, + "PermissionsGetAllowAllRequest": { + "type": "object", + "properties": {}, + "additionalProperties": false, + "description": "No parameters.", + "title": "PermissionsGetAllowAllRequest" + }, "PermissionsLocationsAddToolApprovalDetails": { "anyOf": [ { @@ -12215,6 +12984,42 @@ "description": "Indicates whether the operation succeeded.", "title": "PermissionsResetSessionApprovalsResult" }, + "PermissionsSetAllowAllRequest": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether to enable full allow-all permissions" + }, + "source": { + "$ref": "#/definitions/PermissionsSetAllowAllSource", + "description": "Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers." + } + }, + "required": [ + "enabled" + ], + "additionalProperties": false, + "description": "Whether to enable full allow-all permissions for the session.", + "title": "PermissionsSetAllowAllRequest" + }, + "PermissionsSetAllowAllSource": { + "type": "string", + "enum": [ + "cli_flag", + "slash_command", + "autopilot_confirmation", + "rpc" + ], + "description": "Optional source for allow-all telemetry. Defaults to `rpc` when omitted for SDK callers.", + "title": "PermissionsSetAllowAllSource", + "x-enumDescriptions": { + "cli_flag": "Allow-all was enabled from a CLI command-line flag.", + "slash_command": "Allow-all was enabled by a slash command.", + "autopilot_confirmation": "Allow-all was enabled by confirming autopilot behavior.", + "rpc": "Allow-all was enabled through an RPC caller." + } + }, "PermissionsSetApproveAllRequest": { "type": "object", "properties": { @@ -13470,6 +14275,11 @@ "minimum": 0, "description": "Tokens consumed by tool definitions sent to the model (excludes deferred tools)" }, + "mcpToolsTokens": { + "type": "integer", + "minimum": 0, + "description": "Tokens consumed by MCP tool definitions (subset of toolDefinitionsTokens, excludes deferred tools)" + }, "totalTokens": { "type": "integer", "minimum": 0, @@ -13501,6 +14311,7 @@ "systemTokens", "conversationTokens", "toolDefinitionsTokens", + "mcpToolsTokens", "totalTokens", "promptTokenLimit", "compactionThreshold", @@ -14327,10 +15138,18 @@ "type": "string", "description": "Optional human-friendly name set via /rename" }, + "clientName": { + "type": "string", + "description": "Runtime client name that created/last resumed this session" + }, "isRemote": { "type": "boolean", "description": "True for remote (GitHub) sessions; false for local" }, + "isDetached": { + "type": "boolean", + "description": "True for detached maintenance sessions that should be hidden from normal resume lists." + }, "context": { "$ref": "#/definitions/SessionContext", "description": "Schema for the `SessionContext` type." @@ -14386,6 +15205,10 @@ "type": "string", "description": "User-provided name supplied at session construction (via `--name`), if any. Immutable after construction." }, + "clientName": { + "type": "string", + "description": "Runtime client name associated with the session (telemetry identifier)." + }, "remoteMetadata": { "$ref": "#/definitions/MetadataSnapshotRemoteMetadata", "description": "Remote-session-specific metadata. Populated only when `isRemote` is true. Fields are immutable for the lifetime of the session." @@ -14441,6 +15264,31 @@ "autopilot": "The agent is working autonomously toward task completion." } }, + "SessionModelList": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "x-opaque-json": true + }, + "description": "Available models, ordered with the most preferred default first." + }, + "quotaSnapshots": { + "type": "object", + "additionalProperties": { + "x-opaque-json": true + }, + "description": "Per-quota snapshots returned alongside the model list, keyed by quota type." + } + }, + "required": [ + "list" + ], + "additionalProperties": false, + "description": "The list of models available to this session.", + "title": "SessionModelList" + }, "SessionPruneResult": { "type": "object", "properties": { @@ -14819,13 +15667,17 @@ "filter": { "$ref": "#/definitions/SessionListFilter", "description": "Optional filter applied to the returned sessions" + }, + "includeDetached": { + "type": "boolean", + "description": "When true, include detached maintenance sessions. Defaults to false for user-facing session lists." } }, "additionalProperties": false, - "description": "Optional metadata-load limit and context filter applied to the returned sessions." + "description": "Optional metadata-load limit and filters applied to the returned sessions." } ], - "description": "Optional metadata-load limit and context filter applied to the returned sessions.", + "description": "Optional metadata-load limit and filters applied to the returned sessions.", "title": "SessionsListRequest" }, "SessionsLoadDeferredRepoHooksRequest": { @@ -15026,6 +15878,10 @@ }, "description": "Denylist of tool names for this session." }, + "toolFilterPrecedence": { + "$ref": "#/definitions/OptionsUpdateToolFilterPrecedence", + "description": "Controls how availableTools (allowlist) and excludedTools (denylist) combine when both are set." + }, "enableScriptSafety": { "type": "boolean", "description": "Whether shell-script safety heuristics are enabled." @@ -15145,6 +16001,30 @@ "manageScheduleEnabled": { "type": "boolean", "description": "Whether to expose the `manage_schedule` tool to the agent. The runtime always owns the per-session schedule registry; this flag only controls tool exposure (typically gated to staff users)." + }, + "skipEmbeddingRetrieval": { + "type": "boolean", + "description": "Whether to skip embedding retrieval pipeline initialization and execution." + }, + "organizationCustomInstructions": { + "type": "string", + "description": "Organization-level custom instructions to inject into the system prompt." + }, + "enableFileHooks": { + "type": "boolean", + "description": "Whether to enable loading of `.github/hooks/` filesystem hooks. Separate from the SDK callback hook mechanism." + }, + "enableHostGitOperations": { + "type": "boolean", + "description": "Whether to enable host git operations (context resolution, child repo scanning, git info in system prompt)." + }, + "enableSessionStore": { + "type": "boolean", + "description": "Whether to enable cross-session store writes and reads." + }, + "enableSkills": { + "type": "boolean", + "description": "Whether to enable skill directory scanning and loading. Falls back to enableConfigDiscovery when unset." } }, "additionalProperties": false, @@ -16534,6 +17414,32 @@ "description": "Built-in tools available for the requested model, with their parameters and instructions.", "title": "ToolList" }, + "ToolsGetCurrentMetadataResult": { + "type": "object", + "properties": { + "tools": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/definitions/CurrentToolMetadata", + "description": "Lightweight metadata for a currently initialized session tool" + } + }, + { + "type": "null" + } + ], + "description": "Current tool metadata, or null when tools have not been initialized yet" + } + }, + "required": [ + "tools" + ], + "additionalProperties": false, + "description": "Current lightweight tool metadata snapshot for the session.", + "title": "ToolsGetCurrentMetadataResult" + }, "ToolsInitializeAndValidateResult": { "type": "object", "properties": {}, @@ -18004,6 +18910,9 @@ "name": { "type": "string" }, + "client_name": { + "type": "string" + }, "user_named": { "type": "boolean" }, diff --git a/schemas/session-events.schema.json b/schemas/session-events.schema.json index 90977be..7e74166 100644 --- a/schemas/session-events.schema.json +++ b/schemas/session-events.schema.json @@ -925,7 +925,7 @@ "properties": { "batchSize": { "type": "integer", - "exclusiveMinimum": 0, + "minimum": 0, "description": "Number of tokens in this billing batch" }, "costPerBatch": { @@ -1349,6 +1349,116 @@ "no": "Do not switch models." } }, + "AutopilotObjectiveChangedData": { + "type": "object", + "properties": { + "operation": { + "$ref": "#/definitions/AutopilotObjectiveChangedOperation", + "description": "The type of operation performed on the autopilot objective state file" + }, + "id": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Current autopilot objective id, if one exists" + }, + "status": { + "$ref": "#/definitions/AutopilotObjectiveChangedStatus", + "description": "Current autopilot objective status, if one exists" + } + }, + "required": [ + "operation" + ], + "additionalProperties": false, + "description": "Autopilot objective state file operation details indicating what changed", + "title": "AutopilotObjectiveChangedData" + }, + "AutopilotObjectiveChangedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique event identifier (UUID v4), generated when the event is emitted" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when the event was created" + }, + "parentId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "description": "ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event." + }, + "ephemeral": { + "type": "boolean", + "description": "When true, the event is transient and not persisted to the session event log on disk" + }, + "agentId": { + "type": "string", + "description": "Sub-agent instance identifier. Absent for events from the root/main agent and session-level events." + }, + "type": { + "type": "string", + "const": "session.autopilot_objective_changed", + "description": "Type discriminator. Always \"session.autopilot_objective_changed\"." + }, + "data": { + "$ref": "#/definitions/AutopilotObjectiveChangedData", + "description": "Autopilot objective state file operation details indicating what changed" + } + }, + "required": [ + "id", + "timestamp", + "parentId", + "type", + "data" + ], + "additionalProperties": false, + "description": "Session event \"session.autopilot_objective_changed\". Autopilot objective state file operation details indicating what changed", + "title": "AutopilotObjectiveChangedEvent" + }, + "AutopilotObjectiveChangedOperation": { + "type": "string", + "enum": [ + "create", + "update", + "delete" + ], + "description": "The type of operation performed on the autopilot objective state file", + "title": "AutopilotObjectiveChangedOperation", + "x-enumDescriptions": { + "create": "Autopilot objective state file was created for a new objective.", + "update": "Autopilot objective state file was updated for an existing objective.", + "delete": "Autopilot objective state file was deleted or cleared." + } + }, + "AutopilotObjectiveChangedStatus": { + "type": "string", + "enum": [ + "active", + "paused", + "cap_reached", + "completed" + ], + "description": "Current autopilot objective status, if one exists", + "title": "AutopilotObjectiveChangedStatus", + "x-enumDescriptions": { + "active": "Objective is active and can drive autopilot continuations.", + "paused": "Objective is paused and will not drive autopilot continuations.", + "cap_reached": "Legacy objective state indicating the previous continuation cap was reached.", + "completed": "Objective was completed by the agent." + } + }, "BackgroundTasksChangedData": { "type": "object", "properties": {}, @@ -2170,7 +2280,7 @@ "properties": { "batchSize": { "type": "integer", - "exclusiveMinimum": 0, + "minimum": 0, "description": "Number of tokens in this billing batch" }, "costPerBatch": { @@ -3585,6 +3695,10 @@ "description": "Arguments to pass to the external tool", "x-opaque-json": true }, + "workingDirectory": { + "type": "string", + "description": "Active session working directory, when known." + }, "traceparent": { "type": "string", "description": "W3C Trace Context traceparent header for the execute_tool span" @@ -3896,6 +4010,77 @@ "description": "Session event \"hook.end\". Hook invocation completion details including output, success status, and error information", "title": "HookEndEvent" }, + "HookProgressData": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Human-readable progress message from the hook process" + } + }, + "required": [ + "message" + ], + "additionalProperties": false, + "description": "Ephemeral progress update from a running hook process", + "title": "HookProgressData" + }, + "HookProgressEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique event identifier (UUID v4), generated when the event is emitted" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when the event was created" + }, + "parentId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "description": "ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event." + }, + "ephemeral": { + "type": "boolean", + "const": true, + "description": "Always true for events that are transient and not persisted to the session event log on disk." + }, + "agentId": { + "type": "string", + "description": "Sub-agent instance identifier. Absent for events from the root/main agent and session-level events." + }, + "type": { + "type": "string", + "const": "hook.progress", + "description": "Type discriminator. Always \"hook.progress\"." + }, + "data": { + "$ref": "#/definitions/HookProgressData", + "description": "Ephemeral progress update from a running hook process" + } + }, + "required": [ + "id", + "timestamp", + "parentId", + "ephemeral", + "type", + "data" + ], + "additionalProperties": false, + "description": "Session event \"hook.progress\". Ephemeral progress update from a running hook process", + "title": "HookProgressEvent" + }, "HookStartData": { "type": "object", "properties": { @@ -6460,6 +6645,80 @@ "title": "PermissionRule", "description": "Schema for the `PermissionRule` type." }, + "PermissionsChangedData": { + "type": "object", + "properties": { + "previousAllowAllPermissions": { + "type": "boolean", + "description": "Aggregate allow-all flag before the change" + }, + "allowAllPermissions": { + "type": "boolean", + "description": "Aggregate allow-all flag after the change" + } + }, + "required": [ + "previousAllowAllPermissions", + "allowAllPermissions" + ], + "additionalProperties": false, + "description": "Permissions change details carrying the aggregate allow-all boolean transition.", + "title": "PermissionsChangedData" + }, + "PermissionsChangedEvent": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid", + "description": "Unique event identifier (UUID v4), generated when the event is emitted" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when the event was created" + }, + "parentId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ], + "description": "ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event." + }, + "ephemeral": { + "type": "boolean", + "description": "When true, the event is transient and not persisted to the session event log on disk" + }, + "agentId": { + "type": "string", + "description": "Sub-agent instance identifier. Absent for events from the root/main agent and session-level events." + }, + "type": { + "type": "string", + "const": "session.permissions_changed", + "description": "Type discriminator. Always \"session.permissions_changed\"." + }, + "data": { + "$ref": "#/definitions/PermissionsChangedData", + "description": "Permissions change details carrying the aggregate allow-all boolean transition." + } + }, + "required": [ + "id", + "timestamp", + "parentId", + "type", + "data" + ], + "additionalProperties": false, + "description": "Session event \"session.permissions_changed\". Permissions change details carrying the aggregate allow-all boolean transition.", + "title": "PermissionsChangedEvent" + }, "PlanChangedData": { "type": "object", "properties": { @@ -6653,6 +6912,25 @@ "$ref": "#/definitions/ReasoningSummary", "description": "Reasoning summary mode used for model calls, if applicable (e.g. \"none\", \"concise\", \"detailed\")" }, + "contextTier": { + "anyOf": [ + { + "type": "string", + "enum": [ + "default", + "long_context" + ], + "x-enumDescriptions": { + "default": "Default context tier with standard context window size.", + "long_context": "Extended context tier with a larger context window." + } + }, + { + "type": "null" + } + ], + "description": "Context tier currently selected at resume time; null when no tier is active" + }, "context": { "$ref": "#/definitions/WorkingDirectoryContext", "description": "Updated working directory and git context at resume time" @@ -7086,6 +7364,10 @@ "$ref": "#/definitions/ScheduleCancelledEvent", "description": "Session event \"session.schedule_cancelled\". Scheduled prompt cancelled from the schedule manager dialog" }, + { + "$ref": "#/definitions/AutopilotObjectiveChangedEvent", + "description": "Session event \"session.autopilot_objective_changed\". Autopilot objective state file operation details indicating what changed" + }, { "$ref": "#/definitions/InfoEvent", "description": "Session event \"session.info\". Informational message for timeline display with categorization" @@ -7102,6 +7384,10 @@ "$ref": "#/definitions/ModeChangedEvent", "description": "Session event \"session.mode_changed\". Agent mode change details including previous and new modes" }, + { + "$ref": "#/definitions/PermissionsChangedEvent", + "description": "Session event \"session.permissions_changed\". Permissions change details carrying the aggregate allow-all boolean transition." + }, { "$ref": "#/definitions/PlanChangedEvent", "description": "Session event \"session.plan_changed\". Plan file operation details indicating what changed" @@ -7254,6 +7540,10 @@ "$ref": "#/definitions/HookEndEvent", "description": "Session event \"hook.end\". Hook invocation completion details including output, success status, and error information" }, + { + "$ref": "#/definitions/HookProgressEvent", + "description": "Session event \"hook.progress\". Ephemeral progress update from a running hook process" + }, { "$ref": "#/definitions/SystemMessageEvent", "description": "Session event \"system.message\". System/developer instruction content with role and optional template metadata" @@ -8087,6 +8377,25 @@ "$ref": "#/definitions/ReasoningSummary", "description": "Reasoning summary mode used for model calls, if applicable (e.g. \"none\", \"concise\", \"detailed\")" }, + "contextTier": { + "anyOf": [ + { + "type": "string", + "enum": [ + "default", + "long_context" + ], + "x-enumDescriptions": { + "default": "Default context tier with standard context window size.", + "long_context": "Extended context tier with a larger context window." + } + }, + { + "type": "null" + } + ], + "description": "Context tier selected at session creation time for models with tiered context pricing; null when no tier is selected (e.g., non-tiered model)" + }, "context": { "$ref": "#/definitions/WorkingDirectoryContext", "description": "Working directory and git context at session start" @@ -9996,6 +10305,10 @@ "type": "string", "description": "Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event" }, + "displayVerbatim": { + "type": "boolean", + "description": "When true, the tool output should be displayed expanded (verbatim) in the CLI timeline" + }, "parentToolCallId": { "type": "string", "description": "Tool call ID of the parent tool invocation when this event originates from a sub-agent", diff --git a/src/github/copilot_sdk.clj b/src/github/copilot_sdk.clj index 990241e..8838977 100644 --- a/src/github/copilot_sdk.clj +++ b/src/github/copilot_sdk.clj @@ -122,7 +122,13 @@ :copilot/session.remote_steerable_changed :copilot/capabilities.changed ;; MCP Apps tool-call complete (upstream schema 1.0.52-4, SEP-1865) - :copilot/mcp_app.tool_call_complete}) + :copilot/mcp_app.tool_call_complete + ;; Round 6 (upstream schema 1.0.56-1, post-v1.0.0-beta.4): autopilot + ;; objective lifecycle, allow-all-permissions mode toggles, and an + ;; ephemeral hook-progress event. + :copilot/session.autopilot_objective_changed + :copilot/session.permissions_changed + :copilot/hook.progress}) (def session-events "Session lifecycle and state management events." @@ -157,7 +163,10 @@ :copilot/session.custom_agents_updated :copilot/session.custom_notification :copilot/session.remote_steerable_changed - :copilot/capabilities.changed}) + :copilot/capabilities.changed + ;; Round 6 additions (upstream schema 1.0.56-1). + :copilot/session.autopilot_objective_changed + :copilot/session.permissions_changed}) (def assistant-events "Assistant response events." diff --git a/src/github/copilot_sdk/client.clj b/src/github/copilot_sdk/client.clj index 6b317b3..9ec42d6 100644 --- a/src/github/copilot_sdk/client.clj +++ b/src/github/copilot_sdk/client.clj @@ -1436,6 +1436,27 @@ (assoc :maxPromptTokens (:maxInputTokens wire))) wire))) +(defn- context-tier->wire + "Convert a Clojure :context-tier keyword to the wire string value. + The CLI expects \"default\" / \"long_context\" (underscore), so csk + camelCasing would produce the wrong value — we map explicitly." + [tier] + (case tier + :default "default" + :long-context "long_context" + nil nil + (throw (ex-info "Invalid :context-tier value (expected :default or :long-context)" + {:context-tier tier})))) + +(defn- large-output->wire + "Convert a :large-output config map to wire shape, accepting both the + pre-existing `:output-dir` key and the upstream-aligned alias + `:output-directory` (the wire field stays `outputDir`)." + [lo] + (let [dir (or (:output-directory lo) (:output-dir lo))] + (cond-> (dissoc lo :output-directory) + dir (assoc :output-dir dir)))) + (defn- build-create-session-params "Build wire params for session.create from config." [config] @@ -1468,7 +1489,9 @@ (cond-> {:name (:name c)} (some? (:description c)) (assoc :description (:description c)))) - cmds))] + cmds)) + config-dir (or (:config-directory config) (:config-dir config)) + wire-large-output (some-> (:large-output config) large-output->wire)] (cond-> {} (:session-id config) (assoc :session-id (:session-id config)) (:client-name config) (assoc :client-name (:client-name config)) @@ -1485,14 +1508,18 @@ wire-mcp-servers (assoc :mcp-servers wire-mcp-servers) wire-custom-agents (assoc :custom-agents wire-custom-agents) wire-default-agent (assoc :default-agent wire-default-agent) - (:config-dir config) (assoc :config-dir (:config-dir config)) + config-dir (assoc :config-dir config-dir) (:skill-directories config) (assoc :skill-directories (:skill-directories config)) (:instruction-directories config) (assoc :instruction-directories (:instruction-directories config)) (:disabled-skills config) (assoc :disabled-skills (:disabled-skills config)) - (:large-output config) (assoc :large-output (:large-output config)) + (:plugin-directories config) (assoc :plugin-directories (:plugin-directories config)) + wire-large-output (assoc :large-output wire-large-output) (:working-directory config) (assoc :working-directory (:working-directory config)) wire-infinite-sessions (assoc :infinite-sessions wire-infinite-sessions) (:reasoning-effort config) (assoc :reasoning-effort (:reasoning-effort config)) + (:reasoning-summary config) (assoc :reasoning-summary (:reasoning-summary config)) + (contains? config :context-tier) + (assoc :context-tier (context-tier->wire (:context-tier config))) (:agent config) (assoc :agent (:agent config)) true (assoc :request-user-input (boolean (:on-user-input-request config))) true (assoc :request-elicitation (boolean (:on-elicitation-request config))) @@ -1509,6 +1536,32 @@ (assoc :cloud (util/clj->wire (:cloud config))) (:model-capabilities config) (assoc :model-capabilities (util/clj->wire (:model-capabilities config))) + ;; Round 6 (upstream schema 1.0.56-1): MCP OAuth token storage mode and + ;; embedding cache storage mode are hyphen-preserving string enums + ;; (`"persistent"` / `"in-memory"`). The wire key for the first is + ;; `mcpOAuthTokenStorage` (capital `OA` in `OAuth`) — csk on + ;; `:mcp-oauth-token-storage` would emit `mcpOauthTokenStorage` instead, + ;; so we forward via a string key to bypass key conversion. The value + ;; uses `(name kw)` so `:in-memory` survives as `"in-memory"` + ;; (csk would mangle it to `"inMemory"`). + (:mcp-oauth-token-storage config) + (assoc "mcpOAuthTokenStorage" (name (:mcp-oauth-token-storage config))) + (:embedding-cache-storage config) + (assoc :embedding-cache-storage (name (:embedding-cache-storage config))) + (some? (:skip-embedding-retrieval config)) + (assoc :skip-embedding-retrieval (:skip-embedding-retrieval config)) + (:organization-custom-instructions config) + (assoc :organization-custom-instructions (:organization-custom-instructions config)) + (some? (:enable-on-demand-instruction-discovery config)) + (assoc :enable-on-demand-instruction-discovery (:enable-on-demand-instruction-discovery config)) + (some? (:enable-file-hooks config)) + (assoc :enable-file-hooks (:enable-file-hooks config)) + (some? (:enable-host-git-operations config)) + (assoc :enable-host-git-operations (:enable-host-git-operations config)) + (some? (:enable-session-store config)) + (assoc :enable-session-store (:enable-session-store config)) + (some? (:enable-skills config)) + (assoc :enable-skills (:enable-skills config)) true (assoc :include-sub-agent-streaming-events (if (some? (:include-sub-agent-streaming-events? config)) (:include-sub-agent-streaming-events? config) @@ -1547,7 +1600,9 @@ (cond-> {:name (:name c)} (some? (:description c)) (assoc :description (:description c)))) - cmds))] + cmds)) + config-dir (or (:config-directory config) (:config-dir config)) + wire-large-output (some-> (:large-output config) large-output->wire)] (cond-> {:session-id session-id} (:client-name config) (assoc :client-name (:client-name config)) (:model config) (assoc :model (:model config)) @@ -1565,12 +1620,17 @@ wire-mcp-servers (assoc :mcp-servers wire-mcp-servers) wire-custom-agents (assoc :custom-agents wire-custom-agents) wire-default-agent (assoc :default-agent wire-default-agent) - (:config-dir config) (assoc :config-dir (:config-dir config)) + config-dir (assoc :config-dir config-dir) (:skill-directories config) (assoc :skill-directories (:skill-directories config)) (:instruction-directories config) (assoc :instruction-directories (:instruction-directories config)) (:disabled-skills config) (assoc :disabled-skills (:disabled-skills config)) + (:plugin-directories config) (assoc :plugin-directories (:plugin-directories config)) + wire-large-output (assoc :large-output wire-large-output) wire-infinite-sessions (assoc :infinite-sessions wire-infinite-sessions) (:reasoning-effort config) (assoc :reasoning-effort (:reasoning-effort config)) + (:reasoning-summary config) (assoc :reasoning-summary (:reasoning-summary config)) + (contains? config :context-tier) + (assoc :context-tier (context-tier->wire (:context-tier config))) (:agent config) (assoc :agent (:agent config)) true (assoc :request-user-input (boolean (:on-user-input-request config))) true (assoc :request-elicitation (boolean (:on-elicitation-request config))) @@ -1589,6 +1649,28 @@ (assoc :remote-session (name (:remote-session config))) (:model-capabilities config) (assoc :model-capabilities (util/clj->wire (:model-capabilities config))) + ;; Round 6 (upstream schema 1.0.56-1) per-session multitenancy / + ;; storage knobs — mirror the set forwarded on session.create. The + ;; `mcpOAuthTokenStorage` wire key (capital `OA`) bypasses csk via + ;; a string key (see `build-create-session-params` for rationale). + (:mcp-oauth-token-storage config) + (assoc "mcpOAuthTokenStorage" (name (:mcp-oauth-token-storage config))) + (:embedding-cache-storage config) + (assoc :embedding-cache-storage (name (:embedding-cache-storage config))) + (some? (:skip-embedding-retrieval config)) + (assoc :skip-embedding-retrieval (:skip-embedding-retrieval config)) + (:organization-custom-instructions config) + (assoc :organization-custom-instructions (:organization-custom-instructions config)) + (some? (:enable-on-demand-instruction-discovery config)) + (assoc :enable-on-demand-instruction-discovery (:enable-on-demand-instruction-discovery config)) + (some? (:enable-file-hooks config)) + (assoc :enable-file-hooks (:enable-file-hooks config)) + (some? (:enable-host-git-operations config)) + (assoc :enable-host-git-operations (:enable-host-git-operations config)) + (some? (:enable-session-store config)) + (assoc :enable-session-store (:enable-session-store config)) + (some? (:enable-skills config)) + (assoc :enable-skills (:enable-skills config)) true (assoc :include-sub-agent-streaming-events (if (some? (:include-sub-agent-streaming-events? config)) (:include-sub-agent-streaming-events? config) @@ -1612,6 +1694,67 @@ :on-event (:on-event config) :config config})) +(defn- ensure-session-fs-handler-factory! + "When the client has sessionFs enabled, validate that the config provides + `:create-session-fs-handler` (which builds the per-session handler). + Throws fail-fast with a clear message; allows the cloud-no-id flow to + bail out BEFORE issuing the RPC instead of inside the reader-thread + callback." + [client config] + (when (:session-fs client) + (when-not (:create-session-fs-handler config) + (throw (ex-info (str ":create-session-fs-handler is required in session config " + "when :session-fs is enabled in client options.") + {:config config}))))) + +(defn- install-session-fs-handler! + "Construct and install the per-session sessionFs handler. Caller is + responsible for sessionFs validation (use `ensure-session-fs-handler-factory!` + before calling). No-op when sessionFs is disabled on the client." + [client session-id session config] + (when (:session-fs client) + (when-let [factory (:create-session-fs-handler config)] + (session/set-session-fs-handler! client session-id + (session/adapt-session-fs-handler (factory session)))))) + +(defn- make-create-session-inline-callback + "Build the inline-response callback used by the cloud-no-id session + creation path. The callback runs in the JSON-RPC reader thread; keep + it minimal and non-blocking. On any failure it removes any partially + registered session and delivers the error to `result-promise`; on + success it delivers the live session. + + Note: `install-session-fs-handler!` invokes a user-supplied + `:create-session-fs-handler` factory. Document and rely on the + convention that the factory is a fast, non-blocking constructor — + if it issues another RPC it will deadlock the reader thread. + `ensure-session-fs-handler-factory!` is called BEFORE the RPC so a + missing factory cannot reach the callback." + [client config transform-callbacks result-promise] + (let [registered-id (atom nil) + deliver-error! + (fn [ex] + (when-let [sid @registered-id] + (try + (session/remove-session! client sid) + (catch Throwable cleanup-t + (log/warn cleanup-t "Failed to remove partially registered session " sid)))) + (deliver result-promise ex))] + (fn [result] + (try + (let [assigned-id (:session-id result)] + (if (or (not (string? assigned-id)) (str/blank? assigned-id)) + (deliver-error! + (ex-info "session.create response did not include a sessionId for cloud session" + {:result result})) + (let [session (pre-register-session client assigned-id config)] + (reset! registered-id assigned-id) + (session/register-transform-callbacks! client assigned-id transform-callbacks) + (install-session-fs-handler! client assigned-id session config) + (deliver result-promise session)))) + (catch Throwable t + (deliver-error! t)))))) + (defn create-session "Create a new conversation session. @@ -1688,38 +1831,75 @@ repository: `{:repository {:owner \"...\" :name \"...\" :branch \"...\"}}`. `:branch` is optional. Forwarded as `cloud` on session.create. (upstream PR #1306) + + When `:cloud` is set and no `:session-id` is provided, the server + assigns a sessionId and the SDK registers the session under that + id; the session is registered synchronously before any subsequent + server-initiated requests can be dispatched (upstream PR #1479). Returns a CopilotSession." [client config] (log/debug "Creating session with config: " (select-keys config [:model :session-id])) (validate-session-config! config) (ensure-connected! client) + (ensure-session-fs-handler-factory! client config) (let [{:keys [connection-io]} @(:state client) - session-id (or (:session-id config) (str (java.util.UUID/randomUUID))) trace-ctx (get-trace-context (:on-get-trace-context client)) {:keys [transform-callbacks]} (extract-transform-callbacks (:system-message config)) - params (merge trace-ctx (assoc (build-create-session-params config) :session-id session-id)) - ;; Pre-register session before RPC so early events are captured - session (pre-register-session client session-id config)] - ;; Register transform callbacks on session before RPC - (session/register-transform-callbacks! client session-id transform-callbacks) - (try - ;; Attach sessionFs handler if sessionFs is configured - (when (:session-fs client) - (if-let [factory (:create-session-fs-handler config)] - (session/set-session-fs-handler! client session-id - (session/adapt-session-fs-handler (factory session))) - (throw (ex-info (str ":create-session-fs-handler is required in session config " - "when :session-fs is enabled in client options.") - {:config config})))) - (let [result (proto/send-request! connection-io "session.create" params)] - (session/set-workspace-path! client session-id (:workspace-path result)) - (session/set-capabilities! client session-id (:capabilities result)) - (log/info "Session created: " session-id) - session) - (catch Throwable t - (session/remove-session! client session-id) - (throw t))))) + cloud? (some? (:cloud config)) + caller-session-id (:session-id config) + defer-session-id? (and cloud? (not caller-session-id))] + (if defer-session-id? + ;; Cloud session without a caller-supplied id (upstream PR #1479): omit + ;; `sessionId` from the wire params and let the server assign one. The + ;; SDK registers the session under the server-returned id inside an + ;; inline-response callback so any session-scoped notifications that + ;; arrive after the response are routed to the correct session. + (let [params (merge trace-ctx (build-create-session-params config)) + session-promise (promise) + on-inline (make-create-session-inline-callback + client config transform-callbacks session-promise) + result (proto/send-request! connection-io "session.create" params 60000 + {:on-response-inline on-inline}) + registered (deref session-promise 0 :not-delivered)] + (cond + (= registered :not-delivered) + (throw (ex-info "Internal error: inline-response callback did not run for session.create" + {:result result})) + + (instance? Throwable registered) + (throw registered) + + :else + (let [session registered + session-id (:session-id session)] + (session/set-workspace-path! client session-id (:workspace-path result)) + (session/set-capabilities! client session-id (:capabilities result)) + (log/info "Session created (cloud, server-assigned id): " session-id) + session))) + ;; Standard path: client supplies (or generates) the sessionId up front. + (let [session-id (or caller-session-id (str (java.util.UUID/randomUUID))) + params (merge trace-ctx (assoc (build-create-session-params config) :session-id session-id)) + ;; Pre-register session before RPC so early events are captured + session (pre-register-session client session-id config)] + ;; Register transform callbacks on session before RPC + (session/register-transform-callbacks! client session-id transform-callbacks) + (try + (install-session-fs-handler! client session-id session config) + (let [result (proto/send-request! connection-io "session.create" params) + returned-id (:session-id result)] + (when (and (string? returned-id) + (not (str/blank? returned-id)) + (not= returned-id session-id)) + (throw (ex-info "session.create returned a sessionId that differs from the requested id" + {:requested session-id :returned returned-id}))) + (session/set-workspace-path! client session-id (:workspace-path result)) + (session/set-capabilities! client session-id (:capabilities result)) + (log/info "Session created: " session-id) + session) + (catch Throwable t + (session/remove-session! client session-id) + (throw t))))))) (defn resume-session "Resume an existing session by ID. @@ -1777,6 +1957,7 @@ (throw (ex-info "Invalid session config: :model is required when :provider (BYOK) is specified" {:config config}))) (ensure-connected! client) + (ensure-session-fs-handler-factory! client config) (let [{:keys [connection-io]} @(:state client) trace-ctx (get-trace-context (:on-get-trace-context client)) {:keys [transform-callbacks]} (extract-transform-callbacks (:system-message config)) @@ -1786,14 +1967,7 @@ ;; Register transform callbacks on session before RPC (session/register-transform-callbacks! client session-id transform-callbacks) (try - ;; Attach sessionFs handler if sessionFs is configured - (when (:session-fs client) - (if-let [factory (:create-session-fs-handler config)] - (session/set-session-fs-handler! client session-id - (session/adapt-session-fs-handler (factory session))) - (throw (ex-info (str ":create-session-fs-handler is required in session config " - "when :session-fs is enabled in client options.") - {:config config})))) + (install-session-fs-handler! client session-id session config) (let [result (proto/send-request! connection-io "session.resume" params)] (session/set-workspace-path! client session-id (:workspace-path result)) (session/set-capabilities! client session-id (:capabilities result)) @@ -1825,43 +1999,93 @@ (log/debug "Creating session (async) with config: " (select-keys config [:model :session-id])) (validate-session-config! config) (ensure-connected! client) + (ensure-session-fs-handler-factory! client config) (let [{:keys [connection-io]} @(:state client) - session-id (or (:session-id config) (str (java.util.UUID/randomUUID))) trace-ctx (get-trace-context (:on-get-trace-context client)) {:keys [transform-callbacks]} (extract-transform-callbacks (:system-message config)) - params (merge trace-ctx (assoc (build-create-session-params config) :session-id session-id)) - ;; Pre-register session before RPC so early events are captured - session (pre-register-session client session-id config) - _ (session/register-transform-callbacks! client session-id transform-callbacks) - ;; Attach sessionFs handler if sessionFs is configured (synchronously, before RPC) - _ (try - (when (:session-fs client) - (if-let [factory (:create-session-fs-handler config)] - (session/set-session-fs-handler! client session-id - (session/adapt-session-fs-handler (factory session))) - (throw (ex-info (str ":create-session-fs-handler is required in session config " - "when :session-fs is enabled in client options.") - {:config config})))) - (catch Throwable t - (session/remove-session! client session-id) - (throw t))) - rpc-ch (proto/send-request connection-io "session.create" params)] - (go - (let [response (wire params)) - msg {:jsonrpc "2.0" - :id id - :method method - :params wire-params}] - (log/debug "Sending request: method=" method " id=" id) - (swap! state-atom assoc-in [:connection :pending-requests id] {:ch ch :method method}) - (put! (:outgoing-ch conn) msg) - ch)) + The channel delivers a single {:result ...} or {:error ...} map, then closes. + + Optional `opts` map: + - `:on-response-inline` — 1-arg fn `(fn [result])` invoked synchronously + in the read thread, **before** the result is delivered to the response + channel, on success only. Use this when you need to mutate shared + state (e.g. register a session under a server-assigned id) before + any later inbound message can be dispatched. See upstream PR #1479." + ([conn method params] + (send-request conn method params {})) + ([conn method params {:keys [on-response-inline] :as _opts}] + (let [state-atom (:state-atom conn) + id (str (java.util.UUID/randomUUID)) + ch (chan 1) + wire-params (when params (util/clj->wire params)) + msg {:jsonrpc "2.0" + :id id + :method method + :params wire-params} + entry (cond-> {:ch ch :method method} + on-response-inline (assoc :on-response-inline on-response-inline))] + (log/debug "Sending request: method=" method " id=" id) + (swap! state-atom assoc-in [:connection :pending-requests id] entry) + (put! (:outgoing-ch conn) msg) + ch))) (defn- remove-pending-by-chan! "Remove a pending request entry by channel identity." @@ -559,12 +583,17 @@ (defn send-request! "Send a JSON-RPC request and block for the response. - Returns result or throws on error." + Returns result or throws on error. + + The 4- and 5-arity forms accept an `opts` map forwarded to + `send-request` (see its docstring for supported keys)." ([conn method params] - (send-request! conn method params 60000)) + (send-request! conn method params 60000 {})) ([conn method params timeout-ms] + (send-request! conn method params timeout-ms {})) + ([conn method params timeout-ms opts] (let [state-atom (:state-atom conn) - response-ch (send-request conn method params) + response-ch (send-request conn method params opts) timeout-ch (async/timeout timeout-ms) [result port] (async/alts!! [response-ch timeout-ch])] (cond diff --git a/src/github/copilot_sdk/session.clj b/src/github/copilot_sdk/session.clj index 207c0ee..c350529 100644 --- a/src/github/copilot_sdk/session.clj +++ b/src/github/copilot_sdk/session.clj @@ -908,6 +908,13 @@ - :prompt - The message text (required) - :attachments - Vector of attachments (file/directory/selection) - :mode - :enqueue (default) or :immediate + - :agent-mode - **Optional**. One of :interactive (default), :plan, + :autopilot, or :shell. Selects the agent mode for + this turn (upstream PR #1438). + - :display-prompt - **Optional**. String shown in the session timeline + instead of the model `:prompt` (e.g., when the model + prompt is augmented with internal context that should + not be shown to end users). (upstream PR #1470) - :request-headers - Optional map of HTTP headers forwarded to the upstream LLM on this send (upstream PR #1094). Keys and values must both be strings (do not use @@ -935,6 +942,8 @@ trace-ctx (merge trace-ctx) wire-attachments (assoc :attachments wire-attachments) (:mode opts) (assoc :mode (name (:mode opts))) + (:agent-mode opts) (assoc :agent-mode (name (:agent-mode opts))) + (some? (:display-prompt opts)) (assoc :display-prompt (:display-prompt opts)) (:request-headers opts) (assoc :request-headers (:request-headers opts))) result (proto/send-request! conn "session.send" params) msg-id (:message-id result)] @@ -1142,6 +1151,8 @@ trace-ctx (merge trace-ctx) wire-attachments (assoc :attachments wire-attachments) (:mode opts) (assoc :mode (name (:mode opts))) + (:agent-mode opts) (assoc :agent-mode (name (:agent-mode opts))) + (some? (:display-prompt opts)) (assoc :display-prompt (:display-prompt opts)) (:request-headers opts) (assoc :request-headers (:request-headers opts))) response-ch (proto/send-request conn "session.send" params) [result port] (if deadline-ch diff --git a/src/github/copilot_sdk/specs.clj b/src/github/copilot_sdk/specs.clj index 1c63450..fe24244 100644 --- a/src/github/copilot_sdk/specs.clj +++ b/src/github/copilot_sdk/specs.clj @@ -425,16 +425,28 @@ (s/def ::streaming? boolean?) (s/def ::on-permission-request fn?) (s/def ::config-dir ::non-blank-string) +;; Upstream PR #1482 (post-v1.0.0-beta.4): `configDir` was renamed to +;; `configDirectory` in the official TypeScript SDK API. The wire stays +;; `configDir`; only the idiom-layer key changed. `:config-directory` is the +;; new spelling; `:config-dir` remains accepted as a deprecated alias. +(s/def ::config-directory ::non-blank-string) (s/def ::skill-directories (s/coll-of ::non-blank-string)) ;; instructionDirectories — additional directories to search for custom ;; instruction files. Upstream PR #1190 (@github/copilot-sdk). (s/def ::instruction-directories (s/coll-of ::non-blank-string)) +;; pluginDirectories — extra directories to load plugins from, loaded even +;; when `enableConfigDiscovery` is false (upstream PR #1482). +(s/def ::plugin-directories (s/coll-of ::non-blank-string)) (s/def ::disabled-skills (s/coll-of ::non-blank-string)) (s/def ::enabled boolean?) (s/def ::max-size-bytes pos-int?) (s/def ::output-dir ::non-blank-string) +;; Upstream PR #1482: `outputDir` renamed to `outputDirectory` in the official +;; TS SDK. Wire stays `outputDir`; `:output-directory` is the new idiom key +;; and `:output-dir` remains an accepted deprecated alias. +(s/def ::output-directory ::non-blank-string) (s/def ::large-output - (s/keys :opt-un [::enabled ::max-size-bytes ::output-dir])) + (s/keys :opt-un [::enabled ::max-size-bytes ::output-dir ::output-directory])) ;; Working directory (s/def ::working-directory ::non-blank-string) @@ -575,20 +587,67 @@ ;; modelCapabilities override for session config / setModel (upstream PR #1029). ;; DeepPartial — same shape as ::model-capabilities since all fields are already optional. +;; ----------------------------------------------------------------------------- +;; Round 6 (post-v1.0.0-beta.4) SessionConfigBase additions +;; ----------------------------------------------------------------------------- + +;; mcpOAuthTokenStorage (PR #1326): controls where MCP OAuth tokens are stored +;; on disk vs in process memory. Idiom uses keywords; wire emits the original +;; hyphenated string (NOT camel-cased — csk would wrongly produce "inMemory"). +;; embeddingCacheStorage (PR #1474) shares the same enum. +(s/def ::storage-mode #{:persistent :in-memory}) +(s/def ::mcp-oauth-token-storage ::storage-mode) +(s/def ::embedding-cache-storage ::storage-mode) + +;; Multitenancy hardening flags (upstream PR #1474). All optional, plain +;; booleans/strings. Application-mode behavior is driven by Client Mode +;; (upstream PR #1428), which has been deferred to a dedicated future round. +(s/def ::skip-embedding-retrieval boolean?) +(s/def ::organization-custom-instructions string?) +(s/def ::enable-on-demand-instruction-discovery boolean?) +(s/def ::enable-file-hooks boolean?) +(s/def ::enable-host-git-operations boolean?) +(s/def ::enable-session-store boolean?) +(s/def ::enable-skills boolean?) + +;; Reasoning summary mode (upstream PR #813 - pre-existing parity gap). +;; Wire enum: "none" | "concise" | "detailed". Mirrors upstream's ReasoningSummary type. +(s/def ::reasoning-summary #{"none" "concise" "detailed"}) + +;; Explicit context tier for the selected model (upstream — pre-existing parity gap). +;; Idiom uses keywords; wire emits the underscored enum: "default" | "long_context". +;; nil clears the previous explicit choice; missing leaves it untouched. +(s/def ::context-tier (s/nilable #{:default :long-context})) + (def session-config-keys #{:session-id :client-name :model :tools :commands :system-message :available-tools :excluded-tools :provider :on-permission-request :streaming? :mcp-servers - :custom-agents :default-agent :config-dir :skill-directories + :custom-agents :default-agent + ;; Directory rename (PR #1482): :config-directory is the new spelling; + ;; :config-dir is accepted as a deprecated alias. + :config-dir :config-directory + :skill-directories :instruction-directories + :plugin-directories :disabled-skills :large-output :infinite-sessions - :reasoning-effort :on-user-input-request :on-elicitation-request :hooks + :reasoning-effort :reasoning-summary :context-tier + :on-user-input-request :on-elicitation-request :hooks :on-exit-plan-mode :on-auto-mode-switch :working-directory :agent :on-event :create-session-fs-handler :enable-config-discovery :model-capabilities :github-token :enable-session-telemetry? :remote-session :cloud + :mcp-oauth-token-storage + :embedding-cache-storage + :skip-embedding-retrieval + :organization-custom-instructions + :enable-on-demand-instruction-discovery + :enable-file-hooks + :enable-host-git-operations + :enable-session-store + :enable-skills :include-sub-agent-streaming-events?}) (s/def ::session-config @@ -600,31 +659,57 @@ ::session-id ::client-name ::model ::tools ::commands ::system-message ::available-tools ::excluded-tools ::provider ::streaming? ::mcp-servers - ::custom-agents ::default-agent ::config-dir ::skill-directories + ::custom-agents ::default-agent + ::config-dir ::config-directory + ::skill-directories ::instruction-directories + ::plugin-directories ::disabled-skills ::large-output ::infinite-sessions - ::reasoning-effort ::on-user-input-request ::on-elicitation-request ::hooks + ::reasoning-effort ::reasoning-summary ::context-tier + ::on-user-input-request ::on-elicitation-request ::hooks ::on-exit-plan-mode ::on-auto-mode-switch ::working-directory ::agent ::on-event ::create-session-fs-handler ::enable-config-discovery ::model-capabilities ::github-token ::enable-session-telemetry? ::remote-session ::cloud + ::mcp-oauth-token-storage + ::embedding-cache-storage + ::skip-embedding-retrieval + ::organization-custom-instructions + ::enable-on-demand-instruction-discovery + ::enable-file-hooks + ::enable-host-git-operations + ::enable-session-store + ::enable-skills ::include-sub-agent-streaming-events?]) session-config-keys)) (def ^:private resume-session-config-keys #{:client-name :model :tools :commands :system-message :available-tools :excluded-tools :provider :streaming? :on-permission-request - :mcp-servers :custom-agents :default-agent :config-dir :skill-directories + :mcp-servers :custom-agents :default-agent + :config-dir :config-directory + :skill-directories :instruction-directories - :disabled-skills :infinite-sessions :reasoning-effort + :plugin-directories + :disabled-skills :large-output :infinite-sessions + :reasoning-effort :reasoning-summary :context-tier :on-user-input-request :on-elicitation-request :hooks :working-directory :disable-resume? :agent :on-event :on-exit-plan-mode :on-auto-mode-switch :continue-pending-work? :create-session-fs-handler :enable-config-discovery :model-capabilities :github-token :enable-session-telemetry? :remote-session + :mcp-oauth-token-storage + :embedding-cache-storage + :skip-embedding-retrieval + :organization-custom-instructions + :enable-on-demand-instruction-discovery + :enable-file-hooks + :enable-host-git-operations + :enable-session-store + :enable-skills :include-sub-agent-streaming-events?}) (s/def ::resume-session-config @@ -633,9 +718,13 @@ (s/keys :opt-un [::on-permission-request ::client-name ::model ::tools ::commands ::system-message ::available-tools ::excluded-tools ::provider ::streaming? - ::mcp-servers ::custom-agents ::default-agent ::config-dir ::skill-directories + ::mcp-servers ::custom-agents ::default-agent + ::config-dir ::config-directory + ::skill-directories ::instruction-directories - ::disabled-skills ::infinite-sessions ::reasoning-effort + ::plugin-directories + ::disabled-skills ::large-output ::infinite-sessions + ::reasoning-effort ::reasoning-summary ::context-tier ::on-user-input-request ::on-elicitation-request ::hooks ::working-directory ::disable-resume? ::agent ::on-exit-plan-mode ::on-auto-mode-switch ::on-event ::create-session-fs-handler @@ -643,6 +732,15 @@ ::continue-pending-work? ::enable-session-telemetry? ::remote-session + ::mcp-oauth-token-storage + ::embedding-cache-storage + ::skip-embedding-retrieval + ::organization-custom-instructions + ::enable-on-demand-instruction-discovery + ::enable-file-hooks + ::enable-host-git-operations + ::enable-session-store + ::enable-skills ::include-sub-agent-streaming-events?]) resume-session-config-keys)) @@ -653,9 +751,13 @@ (s/keys :opt-un [::on-permission-request ::client-name ::model ::tools ::commands ::system-message ::available-tools ::excluded-tools ::provider ::streaming? - ::mcp-servers ::custom-agents ::default-agent ::config-dir ::skill-directories + ::mcp-servers ::custom-agents ::default-agent + ::config-dir ::config-directory + ::skill-directories ::instruction-directories - ::disabled-skills ::infinite-sessions ::reasoning-effort + ::plugin-directories + ::disabled-skills ::large-output ::infinite-sessions + ::reasoning-effort ::reasoning-summary ::context-tier ::on-user-input-request ::on-elicitation-request ::hooks ::working-directory ::disable-resume? ::agent ::on-exit-plan-mode ::on-auto-mode-switch ::on-event ::create-session-fs-handler @@ -663,6 +765,15 @@ ::continue-pending-work? ::enable-session-telemetry? ::remote-session + ::mcp-oauth-token-storage + ::embedding-cache-storage + ::skip-embedding-retrieval + ::organization-custom-instructions + ::enable-on-demand-instruction-discovery + ::enable-file-hooks + ::enable-host-git-operations + ::enable-session-store + ::enable-skills ::include-sub-agent-streaming-events?]) resume-session-config-keys)) @@ -754,9 +865,20 @@ ;; Map of header name → value, merged with any provider-level headers. (s/def ::request-headers (s/map-of string? string?)) +;; UI agent mode (upstream PR #1438, post-v1.0.0-beta.4). +;; Wire enum: "interactive" | "plan" | "autopilot" | "shell". Used for both +;; per-turn ::send-options (defaults to the session's current mode when +;; omitted on send) and inbound ::user.message-data echoes. +(s/def ::agent-mode #{:interactive :plan :autopilot :shell}) + +;; Display-only prompt shown in the timeline instead of the model :prompt +;; (upstream PR #1470, post-v1.0.0-beta.4). +(s/def ::display-prompt string?) + (s/def ::send-options (s/keys :req-un [::prompt] - :opt-un [::attachments ::mode ::timeout-ms ::request-headers])) + :opt-un [::attachments ::mode ::timeout-ms ::request-headers + ::agent-mode ::display-prompt])) ;; :timeout-ms as used in option maps for send-async /