diff --git a/app/docs/build/models/page.mdx b/app/docs/build/models/page.mdx
index 34d20548..38242c69 100644
--- a/app/docs/build/models/page.mdx
+++ b/app/docs/build/models/page.mdx
@@ -45,6 +45,37 @@ The setup wizard walks you through adding a credential, setting a default, or re
It also auto-detects credentials already on your machine (API keys in environment variables,
logged-in CLIs, and local servers) so you can confirm them with one tap.
+### From the web UI
+
+You don't always need a terminal. When a [connected host](/docs/deploy/overview)
+is missing a provider credential, the web UI's setup dialog offers an **Add a
+credential** action so you can configure it in the browser. It writes the
+credential onto that host with the same non-interactive logic `omni setup` uses:
+the secret goes to the host's OS keychain (falling back to
+`~/.omnigent/secrets.json`), and the host's `config.yaml` gets a `providers:`
+entry that references the secret rather than storing the raw value. The server
+is only an authenticated pass-through — it forwards the credential to the host
+over the tunnel and never persists or logs it.
+
+The dialog supports three kinds:
+
+- **API key** — a vendor key stored on the host.
+- **Gateway** — an OpenAI- or Anthropic-compatible proxy; provide a `base_url`
+ (must start with `http://` or `https://`) plus a key.
+- **Adopt** — reuse a credential the host already has. The dialog lists the
+ adoptable API keys it detects in the host's environment (for example
+ `$ANTHROPIC_API_KEY`) and, on one click, writes a provider entry that
+ references that variable by name — the value is never read or copied.
+
+Once written, the host recomputes readiness and the dialog updates in place, so
+the harness becomes runnable without reconnecting.
+
+This flow covers the harnesses whose provider credential Omnigent owns — the
+**Claude**, **Codex**, and **Pi** families. Environment-authenticated harnesses
+such as **OpenCode** and **Qwen** are not configurable from the UI; set those up
+on the host with `omni setup`. The web-UI credential flow is only available when
+the server has `OMNIGENT_HARNESS_INSTALL_ENABLED` turned on.
+
## Credential types
| Type | What it is |
diff --git a/app/docs/build/routing/page.mdx b/app/docs/build/routing/page.mdx
new file mode 100644
index 00000000..49321119
--- /dev/null
+++ b/app/docs/build/routing/page.mdx
@@ -0,0 +1,178 @@
+import { pageMeta } from "@/lib/og";
+
+export const metadata = pageMeta(
+ "Smart Routing",
+ "Let a router pick the best harness and model for each session automatically, using the built-in judge or an external routing API.",
+ {
+ eyebrow: "Build",
+ path: "/docs/build/routing",
+ },
+);
+
+# Smart Routing
+
+Smart routing lets the server pick the best harness and model for a session
+**automatically**, based on the first message. Instead of committing to one model
+up front, the server asks a router, "given this task, which harness and model
+fit?", and applies the answer for the rest of the session.
+
+
+
+Two routers are available:
+
+- **Built-in judge**: an LLM call using the server's own `llm:` block.
+ No extra infrastructure.
+- **External routing API**: the server delegates the decision to an external
+ `routes:select` service that you point it at.
+
+## Enable smart routing
+
+Smart routing is enabled by configuration alone:
+
+- Configure a server `llm:` block and the server uses the **built-in judge** backed
+ by it, with no `routing:` block required.
+- Or add a `routing:` block with `provider: external` to delegate to an
+ **external routing API** (see below).
+
+Start the server with your config file:
+
+```
+omni server -c path/to/config.yaml
+```
+
+With a router configured, users see an **Auto** option in the harness picker;
+picking it defers harness + model selection to the router on the first message.
+With neither an `llm:` block nor an external `routing:` block, routing stays off
+and the Auto option is hidden.
+
+## Configure the external routing API
+
+To delegate routing to an external service, add a top-level `routing:` block to the
+server config with `provider: external`:
+
+```yaml
+routing:
+ provider: external
+ base_url: https://gateway.example.com/ai-gateway/routing/v1
+ router_name: task_v0
+ # Optional: strip a prefix from catalog model ids before sending them to
+ # the router (and restore it on the answer). Accepts a string or a list.
+ model_prefix: databricks-
+ # Optional auth, see "Authentication" below.
+ api_key: ${ROUTING_API_KEY}
+```
+
+| Field | Required | Description |
+| -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `provider` | yes | Must be `external` to select the external routing API. Any other value (or omitting the block) falls back to the built-in judge. |
+| `base_url` | yes | Base URL of the routing service. The server appends `/routes:select` to it. |
+| `router_name` | yes | The routing strategy the gateway should apply, e.g. `task_v0`. Sent as `route_selector.router_name`. |
+| `model_prefix` | no | A prefix (or list of prefixes) stripped from catalog model ids before they are sent to the router, and restored on its answer. Example: `databricks-`. |
+| `api_key` | no | Static bearer token. `${ENV}` references are expanded. Takes precedence over `profile`. |
+| `profile` | no | Databricks CLI profile. The server mints a fresh bearer per call (OAuth refresh), so a long-lived server never sends an expired token. |
+
+If `base_url` or `router_name` is missing, the external provider is skipped and a
+warning is logged: routing stays off rather than failing the server.
+
+### Authentication
+
+Auth mirrors the `llm:` block, in precedence order:
+
+1. **`api_key`**: an explicit, provider-agnostic bearer token (`${ENV}` expanded).
+ Sent as `Authorization: Bearer `.
+2. **`profile`**: a Databricks CLI profile. The server resolves a fresh token per
+ request via the Databricks SDK, so tokens that expire (~1h) are refreshed
+ automatically.
+3. **Neither**: requests are sent unauthenticated.
+
+## External routing API spec
+
+The server calls a single endpoint:
+
+```
+POST /routes:select
+Content-Type: application/json
+```
+
+The request and response bodies follow the `omnigent.api.routing.v1` schema
+(proto3, serialized as JSON with snake_case field names). This schema is versioned
+independently of any gateway so the contract can evolve without coupling to a
+gateway's release cycle.
+
+### Request: `SelectRouteRequest`
+
+| Field | Type | Description |
+| ----------------- | ----------------- | ---------------------------------------------------------------------------------------------- |
+| `route_options` | `RouteOption[]` | Candidate destinations the router may choose from. One entry per (model, harness) pair. |
+| `task` | `Task` | The unit of work to route. Carries the user's `prompt` (truncated to 4000 chars). |
+| `route_selector` | `RouteSelector` | The routing strategy to apply. Required in practice; a gateway rejects a request that omits it. |
+| `session_history` | `SessionHistory` | Prior turns in the session, when available. Routers may use it to keep turns consistent. |
+
+**`RouteOption`**
+
+| Field | Type | Description |
+| --------- | -------- | ----------------------------------------------------------------------------------------------- |
+| `model` | `string` | Model id to serve the request, e.g. `gpt-5-5`. |
+| `harness` | `string` | Harness that drives the model. May be omitted for a native harness; required for a meta-harness. |
+
+**`RouteSelector`**
+
+| Field | Type | Description |
+| ------------- | -------- | ------------------------------------------------------------------------------------------------- |
+| `router_name` | `string` | Name of the routing strategy to invoke (from the `router_name` config field). |
+| `config` | `Struct` | Optional router-specific configuration, interpreted by the selected router (gateway-defined). |
+
+Example request body:
+
+```json
+{
+ "route_options": [
+ { "model": "claude-opus-4-8", "harness": "claude-sdk" },
+ { "model": "gpt-5-5", "harness": "codex" },
+ { "model": "gpt-5-4-mini", "harness": "pi" }
+ ],
+ "task": { "prompt": "Refactor the auth module and add tests" },
+ "route_selector": { "router_name": "task_v0" }
+}
+```
+
+### Response: `SelectRouteResponse`
+
+| Field | Type | Description |
+| ----------------- | ------------------- | -------------------------------------------------------------- |
+| `route_selection` | `RouteSelection[]` | The routing decision(s). The server uses the first entry. |
+| `rationale` | `string` | Human-readable explanation of why this route was selected. |
+
+**`RouteSelection`**
+
+| Field | Type | Description |
+| -------------- | ------------- | ------------------------------------------------------------------------ |
+| `route_option` | `RouteOption` | The chosen destination (model + harness). |
+| `params` | `Struct` | Optional router-specific parameters emitted alongside the decision. |
+
+Example response body:
+
+```json
+{
+ "route_selection": [
+ {
+ "route_option": { "model": "claude-opus-4-8", "harness": "claude-sdk" }
+ }
+ ],
+ "rationale": "Multi-file refactor with tests: favor the most capable model."
+}
+```
+
+The server maps the chosen `model` (and `harness`, when present) back to the
+matching catalog entry, restoring any stripped `model_prefix`. A model the server
+did not offer in `route_options` is rejected, and the session falls back to its
+default harness. On any error (HTTP 4xx/5xx, unparseable body, empty selection) the
+server surfaces the reason and degrades gracefully rather than blocking the session.
diff --git a/app/docs/deploy/overview/page.mdx b/app/docs/deploy/overview/page.mdx
index 3a4d4462..e4358de5 100644
--- a/app/docs/deploy/overview/page.mdx
+++ b/app/docs/deploy/overview/page.mdx
@@ -155,6 +155,45 @@ kubectl apply -k deploy/kubernetes/overlays/sandbox-runners
# then create the omnigent-creds harness Secret (see the overlay README)
```
+#### Persistent storage for runner Pods
+
+Runner Pods are ephemeral: the workspace lives on an `emptyDir` and is
+destroyed with the Pod. To expose durable data (datasets, model caches, shared
+output directories), mount pre-created PersistentVolumeClaims into every runner
+Pod with `sandbox.kubernetes.pvc_mounts`:
+
+```
+sandbox:
+ provider: kubernetes
+ kubernetes:
+ pvc_mounts:
+ - claim_name: omnigent-datasets
+ mount_path: /mnt/datasets
+ read_only: true # default true; set false only for shared scratch
+```
+
+Each entry is a `{claim_name, mount_path, read_only?}` mapping. `read_only`
+defaults to `true`. Omnigent only **references** the claim — you pre-create the
+PV/PVC in the runner namespace (`omnigent-sandboxes`) out of band, so the server
+RBAC stays unchanged. Mounts land on the host container only; the init container
+that clones the repo never sees them.
+
+Keep these caveats in mind:
+
+- **Mount paths are validated at server startup.** A `mount_path` may not
+ overlap `/home/omnigent`, Secret projections, or the image's OS directories
+ (e.g. `/home`, `/var`, `/run`, `/tmp`, `/etc`, `/opt`); a malformed or
+ reserved path fails startup rather than the first launch. Paths must be
+ absolute and normalized, and may not duplicate or nest within each other.
+- **Writable claims are shared across concurrent runners.** Use a
+ `ReadWriteMany`-capable backend (NFS/SMB/CephFS) for anything writable, and
+ prefer `read_only: true` everywhere else — a writable shared mount lets one
+ session read and modify what another wrote, and its contents outlive the Pod.
+- **`ReadWriteOnce` claims pin all runners to one node** — combine with
+ `node_selector` deliberately, or later Pods sit `Pending`.
+- A mount visible in the Pod is **not** automatically visible to a harness's own
+ OS-level sandbox ([Omnibox](/docs/omnibox) path grants are separate).
+
See `deploy/kubernetes/README.md` and
`deploy/kubernetes/overlays/sandbox-runners/README.md` in the repo for the full
guide.
diff --git a/components/DocsSidebarFull.js b/components/DocsSidebarFull.js
index 7f62f41a..007ba565 100644
--- a/components/DocsSidebarFull.js
+++ b/components/DocsSidebarFull.js
@@ -42,6 +42,7 @@ const SECTIONS = [
pages: [
{ href: "/docs/build/harnesses", label: "Harnesses" },
{ href: "/docs/build/models", label: "Models & Credentials" },
+ { href: "/docs/build/routing", label: "Smart Routing" },
{ href: "/docs/build/prompts", label: "Prompts & Skills" },
{ href: "/docs/build/tools", label: "MCP & Tools" },
],
diff --git a/components/PrevNextNav.js b/components/PrevNextNav.js
index af0fc4a7..4bbdafc4 100644
--- a/components/PrevNextNav.js
+++ b/components/PrevNextNav.js
@@ -65,6 +65,11 @@ const ALL_PAGES = [
label: "Models & Credentials",
section: "Use Omnigent",
},
+ {
+ href: "/docs/build/routing",
+ label: "Smart Routing",
+ section: "Use Omnigent",
+ },
{
href: "/docs/build/prompts",
label: "Prompts & Skills",
diff --git a/lib/og-manifest.json b/lib/og-manifest.json
index 6a2b9306..25420901 100644
--- a/lib/og-manifest.json
+++ b/lib/og-manifest.json
@@ -14,6 +14,11 @@
"description": "Shape agent behavior with system prompts, and extend it with skills: reusable instruction sets the agent loads on demand.",
"eyebrow": "Build"
},
+ "/docs/build/routing": {
+ "title": "Smart Routing",
+ "description": "Let a router pick the best harness and model for each session automatically, using the built-in judge or an external routing API.",
+ "eyebrow": "Build"
+ },
"/docs/build/tools": {
"title": "MCP & Tools",
"description": "Give your agent capabilities beyond conversation. Declare tools and MCP servers in your agent YAML, each with a name you choose.",
diff --git a/public/images/docs/smart-routing-oss.mp4 b/public/images/docs/smart-routing-oss.mp4
new file mode 100644
index 00000000..0b9b1e85
Binary files /dev/null and b/public/images/docs/smart-routing-oss.mp4 differ
diff --git a/public/openapi.json b/public/openapi.json
index dadcdde0..67105221 100644
--- a/public/openapi.json
+++ b/public/openapi.json
@@ -165,6 +165,64 @@
"title": "AgentObject",
"type": "object"
},
+ "AutomaticSessionRenameRequest": {
+ "additionalProperties": false,
+ "description": "Request body for the current-agent automatic rename endpoint.",
+ "properties": {
+ "title": {
+ "maxLength": 60,
+ "minLength": 2,
+ "title": "Title",
+ "type": "string"
+ }
+ },
+ "required": [
+ "title"
+ ],
+ "title": "AutomaticSessionRenameRequest",
+ "type": "object"
+ },
+ "AutomaticSessionRenameResponse": {
+ "description": "Result of a conditional automatic session rename.",
+ "properties": {
+ "reason": {
+ "anyOf": [
+ {
+ "enum": [
+ "not_top_level",
+ "no_seed",
+ "title_changed"
+ ],
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Reason"
+ },
+ "renamed": {
+ "title": "Renamed",
+ "type": "boolean"
+ },
+ "title": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Title"
+ }
+ },
+ "required": [
+ "renamed"
+ ],
+ "title": "AutomaticSessionRenameResponse",
+ "type": "object"
+ },
"Body_update_session_agent_v1_sessions__session_id__agent_put": {
"properties": {
"bundle": {
@@ -193,6 +251,53 @@
"title": "Body_upload_session_file_v1_sessions__session_id__resources_files_post",
"type": "object"
},
+ "BrowserActionRequestEvent": {
+ "description": "Request that the desktop renderer perform one browser action.\n\nEmitted by the server `POST /v1/sessions/{id}/browser/action_request`\nroute when a runner-side `browser_*` tool dispatch needs the\nOmnigent desktop app's embedded browser to act. The event fans out\non the session stream to every subscribed renderer; each renderer\nfirst POSTs `/browser/action_claim/{action_id}` and only the\nwinning claimant executes the action and POSTs the result back to\n`/browser/action_result/{action_id}`. The claim lease prevents\ndouble execution when more than one renderer is subscribed.",
+ "properties": {
+ "action": {
+ "description": "The browser action to perform \u2014 the `browser_` tool name with the prefix stripped, e.g. `\"navigate\"`, `\"snapshot\"`, `\"click\"`, `\"type\"`, `\"screenshot\"`.",
+ "title": "Action",
+ "type": "string"
+ },
+ "action_id": {
+ "description": "Unique correlation id for this request, e.g. `\"baction_abc123\"`. Echoed on the claim and result routes.",
+ "title": "Action Id",
+ "type": "string"
+ },
+ "args": {
+ "additionalProperties": true,
+ "description": "Action arguments forwarded from the tool call, e.g. `{\"url\": \"https://example.com\"}`.",
+ "title": "Args",
+ "type": "object"
+ },
+ "sequence_number": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Sequence Number"
+ },
+ "type": {
+ "const": "browser.action_request",
+ "description": "Always `\"browser.action_request\"`.",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "action_id",
+ "action",
+ "args"
+ ],
+ "title": "BrowserActionRequestEvent",
+ "type": "object"
+ },
"CancelledEvent": {
"description": "Terminal event for a turn cancelled before completion.",
"properties": {
@@ -1062,6 +1167,22 @@
"title": "CreateDirectoryRequest",
"type": "object"
},
+ "CreateProjectRequest": {
+ "additionalProperties": false,
+ "description": "Request body for `POST /v1/projects`.",
+ "properties": {
+ "name": {
+ "description": "Human-readable project name. Trimmed; must be non-empty and at most 100 characters; unique among the caller's projects.",
+ "title": "Name",
+ "type": "string"
+ }
+ },
+ "required": [
+ "name"
+ ],
+ "title": "CreateProjectRequest",
+ "type": "object"
+ },
"CreateSessionPolicyRequest": {
"description": "Request body for `POST /v1/sessions/{session_id}/policies`.",
"properties": {
@@ -1606,6 +1727,110 @@
"title": "HeartbeatEvent",
"type": "object"
},
+ "ImportItemInput": {
+ "description": "One normalized existing Omnigent item received from the CLI.",
+ "properties": {
+ "data": {
+ "additionalProperties": true,
+ "title": "Data",
+ "type": "object"
+ },
+ "response_id": {
+ "maxLength": 64,
+ "minLength": 1,
+ "title": "Response Id",
+ "type": "string"
+ },
+ "type": {
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "response_id",
+ "data"
+ ],
+ "title": "ImportItemInput",
+ "type": "object"
+ },
+ "ImportSessionRequest": {
+ "description": "Request body for importing one local harness session.",
+ "properties": {
+ "external_session_id": {
+ "maxLength": 128,
+ "minLength": 1,
+ "title": "External Session Id",
+ "type": "string"
+ },
+ "items": {
+ "items": {
+ "$ref": "#/components/schemas/ImportItemInput"
+ },
+ "maxItems": 100000,
+ "minItems": 1,
+ "title": "Items",
+ "type": "array"
+ },
+ "source": {
+ "enum": [
+ "claude",
+ "codex",
+ "kimi",
+ "kiro",
+ "opencode",
+ "pi",
+ "qwen"
+ ],
+ "title": "Source",
+ "type": "string"
+ },
+ "workspace": {
+ "anyOf": [
+ {
+ "maxLength": 2048,
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Workspace"
+ }
+ },
+ "required": [
+ "source",
+ "external_session_id",
+ "items"
+ ],
+ "title": "ImportSessionRequest",
+ "type": "object"
+ },
+ "ImportSessionResponse": {
+ "description": "Result of importing or locating one source session.",
+ "properties": {
+ "item_count": {
+ "title": "Item Count",
+ "type": "integer"
+ },
+ "session_id": {
+ "title": "Session Id",
+ "type": "string"
+ },
+ "status": {
+ "const": "imported",
+ "title": "Status",
+ "type": "string"
+ }
+ },
+ "required": [
+ "session_id",
+ "status",
+ "item_count"
+ ],
+ "title": "ImportSessionResponse",
+ "type": "object"
+ },
"InProgressEvent": {
"description": "Event emitted once the task transitions to in-progress.\n\nAlways follows `response.created` (and `response.queued`\nfor background tasks).",
"properties": {
@@ -1720,7 +1945,7 @@
"type": "object"
},
"MCPServerSummary": {
- "description": "Safe subset of an MCP server's configuration for API exposure.\n\nSecret-bearing fields (`headers`, `env`) are intentionally\nexcluded. This model is the wire shape returned inside\n`AgentObject` so clients can display which MCP servers\nan agent is connected to without leaking credentials.",
+ "description": "Safe subset of an MCP server's configuration for API exposure.\n\nHeader values are redacted (`\"[REDACTED]\"`) so callers can see\nwhich headers are configured without leaking the actual secrets.\n`env` is still fully excluded.",
"properties": {
"args": {
"description": "Command-line arguments for `transport=\"stdio\"` servers, e.g. `[\"mcp-server-github\"]`. Empty list when unset.",
@@ -1754,6 +1979,14 @@
"description": "Optional free-text description from the spec, e.g. `\"GitHub MCP server\"`. `None` when unset.",
"title": "Description"
},
+ "headers": {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "description": "HTTP headers for `transport=\"http\"` servers. Values are always `\"[REDACTED]\"`; only the key names are exposed.",
+ "title": "Headers",
+ "type": "object"
+ },
"name": {
"description": "Server name as declared in the agent spec, e.g. `\"github\"`.",
"title": "Name",
@@ -2948,6 +3181,7 @@
"ServerStreamEvent": {
"discriminator": {
"mapping": {
+ "browser.action_request": "#/components/schemas/BrowserActionRequestEvent",
"response.cancelled": "#/components/schemas/CancelledEvent",
"response.client_task.cancel": "#/components/schemas/ClientTaskCancelEvent",
"response.compaction.completed": "#/components/schemas/CompactionCompletedEvent",
@@ -2959,6 +3193,7 @@
"response.elicitation_resolved": "#/components/schemas/ElicitationResolvedEvent",
"response.error": "#/components/schemas/ErrorEvent",
"response.failed": "#/components/schemas/FailedEvent",
+ "response.function_call_output.delta": "#/components/schemas/ToolOutputDeltaEvent",
"response.heartbeat": "#/components/schemas/HeartbeatEvent",
"response.in_progress": "#/components/schemas/InProgressEvent",
"response.incomplete": "#/components/schemas/IncompleteEvent",
@@ -3071,6 +3306,9 @@
{
"$ref": "#/components/schemas/OutputTextDeltaEvent"
},
+ {
+ "$ref": "#/components/schemas/ToolOutputDeltaEvent"
+ },
{
"$ref": "#/components/schemas/ReasoningStartedEvent"
},
@@ -3098,6 +3336,9 @@
{
"$ref": "#/components/schemas/ElicitationResolvedEvent"
},
+ {
+ "$ref": "#/components/schemas/BrowserActionRequestEvent"
+ },
{
"$ref": "#/components/schemas/PolicyDeniedEvent"
},
@@ -3851,6 +4092,17 @@
"description": "The user_id of the session owner, or `None` when permissions are disabled. Included so the sidebar can display the owner without a separate API call.",
"title": "Owner"
},
+ "parent_session_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Parent Session Id"
+ },
"pending_elicitations_count": {
"default": 0,
"description": "Number of approval prompts currently waiting on this session. Powers the sidebar's \"needs attention\" badge so a user with several sessions running can tell which ones are blocked on them without opening each chat. Sourced from the Omnigent server's in-memory `omnigent.runtime.pending_elicitations` index, which mirrors every `response.elicitation_request` event passing through `session_stream` and decrements when a verdict is dispatched. `0` when the session has no outstanding elicitations.",
@@ -5350,6 +5602,35 @@
"title": "SetCodexGoalRequest",
"type": "object"
},
+ "SetSharingRequest": {
+ "description": "Body for `PUT /v1/sharing`.\n\nBoth fields are optional so an admin can update either setting\nindependently; at least one must be present.",
+ "properties": {
+ "public_sharing": {
+ "anyOf": [
+ {
+ "type": "boolean"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Public Sharing"
+ },
+ "sharing_mode": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Sharing Mode"
+ }
+ },
+ "title": "SetSharingRequest",
+ "type": "object"
+ },
"SkillSummary": {
"description": "Safe subset of a discovered skill for API exposure.\n\nSurfaces the skill name and one-line description so clients\n(e.g. the web composer's slash-command menu) can list which\nskills the session has access to. The full skill `content`\nis intentionally omitted \u2014 it's only loaded server-side when\nthe harness invokes the skill, and it can be large.",
"properties": {
@@ -5474,6 +5755,46 @@
"title": "TerminalCommandData",
"type": "object"
},
+ "ToolOutputDeltaEvent": {
+ "description": "Incremental output from an in-progress function call.",
+ "properties": {
+ "call_id": {
+ "description": "Function-call correlation id.",
+ "title": "Call Id",
+ "type": "string"
+ },
+ "delta": {
+ "description": "Command stdout/stderr fragment.",
+ "title": "Delta",
+ "type": "string"
+ },
+ "sequence_number": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "default": null,
+ "title": "Sequence Number"
+ },
+ "type": {
+ "const": "response.function_call_output.delta",
+ "description": "Always `\"response.function_call_output.delta\"`.",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": [
+ "type",
+ "call_id",
+ "delta"
+ ],
+ "title": "ToolOutputDeltaEvent",
+ "type": "object"
+ },
"TurnCancelledEvent": {
"description": "Emitted when a turn is interrupted by the user or system.",
"properties": {
@@ -5711,6 +6032,26 @@
"title": "UpdateDefaultPolicyRequest",
"type": "object"
},
+ "UpdateProjectRequest": {
+ "additionalProperties": false,
+ "description": "Request body for `PATCH /v1/projects/{project_id}`.\n\nAll fields optional; `None` leaves a field unchanged.",
+ "properties": {
+ "name": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "New project name. `None` leaves it unchanged; otherwise trimmed, non-empty, at most 100 characters.",
+ "title": "Name"
+ }
+ },
+ "title": "UpdateProjectRequest",
+ "type": "object"
+ },
"UpdateSessionPolicyRequest": {
"additionalProperties": false,
"description": "Request body for `PATCH /v1/sessions/{session_id}/policies/{policy_id}`.\n\nAll fields are optional; `None` fields are left unchanged.\nUnknown fields (including `type`, which is immutable) are\nrejected with `422`.",
@@ -5896,7 +6237,7 @@
"type": "object"
},
"UpsertMCPServerRequest": {
- "description": "Request body for creating or updating a session agent MCP server.\n\nSecret-bearing fields (`headers` and `env`) are intentionally\nnot accepted by the UI route. Existing secrets are preserved when a\nserver is edited without changing transport.",
+ "description": "Request body for creating or updating a session agent MCP server.\n\n`env` is still excluded. `headers` is accepted for HTTP servers;\nwhen omitted, existing headers in the bundle are preserved unchanged.",
"properties": {
"args": {
"items": {
@@ -5929,9 +6270,23 @@
],
"title": "Description"
},
- "name": {
- "maxLength": 128,
- "minLength": 1,
+ "headers": {
+ "anyOf": [
+ {
+ "additionalProperties": {
+ "type": "string"
+ },
+ "type": "object"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "title": "Headers"
+ },
+ "name": {
+ "maxLength": 128,
+ "minLength": 1,
"pattern": "^[A-Za-z0-9_-][A-Za-z0-9_.-]{0,127}$",
"title": "Name",
"type": "string"
@@ -6316,13 +6671,7 @@
"content": {
"application/json": {
"schema": {
- "additionalProperties": {
- "items": {
- "additionalProperties": true,
- "type": "object"
- },
- "type": "array"
- },
+ "additionalProperties": true,
"title": "Response List Harnesses V1 Harnesses Get",
"type": "object"
}
@@ -6665,6 +7014,62 @@
]
}
},
+ "/v1/hosts/{host_id}/harnesses/{harness}/install": {
+ "post": {
+ "description": "Install a missing, npm-installable harness CLI onto a host.\n\nBacks the Web UI's New Chat dialog \"Install\" action so a user can\ninstall a harness the connected host is missing without dropping to a\nterminal. Owner-scoped like the other host actions: only the host owner\nmay install onto it. Scoped to the UI-installable allowlist (claude,\ncodex, pi, opencode, qwen) \u2014 curl/brew and interactive-auth harnesses\nare refused. The whole route is gated behind\n`OMNIGENT_HARNESS_INSTALL_ENABLED` (default off): when disabled it\nreturns 404 so the feature is invisible until opted in.\n\nConcurrent requests for the same (host, harness) coalesce onto one\nin-flight install so a double-click can't fire two global npm installs.\n\n**Returns:** `{\"object\": \"harness_install\", \"harness\": ..., \"configured_harnesses\": {...}}` \u2014 the host's refreshed readiness map so the UI can flip the badge without a reconnect.\n\n**Raises**\n\n- `HTTPException` \u2014 404 when the feature is disabled or the host is unknown, 400 when the harness is not UI-installable, 403 when the caller is not the host owner, 409 when the host is offline, 502 on a host-side install failure, 504 on host timeout.",
+ "operationId": "install_host_harness_v1_hosts__host_id__harnesses__harness__install_post",
+ "parameters": [
+ {
+ "description": "Host identifier, e.g. `\"host_a1b2c3d4...\"`.",
+ "in": "path",
+ "name": "host_id",
+ "required": true,
+ "schema": {
+ "title": "Host Id",
+ "type": "string"
+ }
+ },
+ {
+ "description": "Harness identifier to install, e.g. `\"claude\"`.",
+ "in": "path",
+ "name": "harness",
+ "required": true,
+ "schema": {
+ "title": "Harness",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": true,
+ "title": "Response Install Host Harness V1 Hosts Host Id Harnesses Harness Install Post",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Install Host Harness",
+ "tags": [
+ "hosts"
+ ]
+ }
+ },
"/v1/hosts/{host_id}/runners": {
"post": {
"description": "Launch a runner on a host for a session.\n\nGenerates a binding token, writes the expected runner_id\nto the session row, sends the launch command to the host,\nand waits for the host's acknowledgement.\n\n**Parameters**\n\n- `body` \u2014 Launch request with `session_id` and `workspace`.\n\n**Returns:** `{\"runner_id\": ..., \"status\": \"launching\"}`.\n\n**Raises**\n\n- `HTTPException` \u2014 404 if host not found, 409 if host offline, 403 if caller doesn't own the host, 400 if session already has a runner.",
@@ -6777,9 +7182,51 @@
]
}
},
+ "/v1/imports": {
+ "post": {
+ "description": "Import one normalized transcript, rejecting duplicate sources.",
+ "operationId": "import_session_v1_imports_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ImportSessionRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ImportSessionResponse"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Import Session",
+ "tags": [
+ "imports"
+ ]
+ }
+ },
"/v1/info": {
"get": {
- "description": "Runtime capabilities probe for the SPA + CLI.\n\nReturned at app boot by the frontend (and by `omnigent login` when it needs to choose between flows). Drives\nconditional route registration and chrome on the SPA side\n\u2014 when `accounts_enabled` is false, the SPA never\nregisters `/login`, `/register`, `/members` and\nnever renders the AccountMenu, so the bundle behaves\nidentically to a pre-PR-2008 build for header / OIDC\ndeploys (in particular, the internal hosted product that\nsyncs from this repo).\n\nAuthentication: this endpoint is intentionally UNAUTHED\nso the SPA can probe it before holding a session cookie.\nIt exposes no sensitive state \u2014 only the active auth\nsource, the login URL, whether first-run admin setup is\nstill pending (`needs_setup`), coarse capability\nbooleans (`databricks_features`,\n`managed_sandboxes_enabled`), the short sandbox\nprovider name (`sandbox_provider`) the web UI labels the\nnew-session sandbox option with, and the installed\n`server_version` (already public via `/api/version`).",
+ "description": "Runtime capabilities probe for the SPA + CLI.\n\nReturned at app boot by the frontend (and by `omnigent login` when it needs to choose between flows). Drives\nconditional route registration and chrome on the SPA side\n\u2014 when `accounts_enabled` is false, the SPA never\nregisters `/login`, `/register`, `/members` and\nnever renders the AccountMenu, so the bundle behaves\nidentically to a pre-PR-2008 build for header / OIDC\ndeploys (in particular, the internal hosted product that\nsyncs from this repo).\n\nAuthentication: this endpoint is intentionally UNAUTHED\nso the SPA can probe it before holding a session cookie.\nIt exposes no sensitive state \u2014 only the active auth\nsource, the login URL, whether first-run admin setup is\nstill pending (`needs_setup`), coarse capability\nbooleans (`databricks_features`,\n`managed_sandboxes_enabled`, `dictation_available`,\n`single_user`), the short sandbox provider name\n(`sandbox_provider`) the web UI labels the new-session\nsandbox option with, and the installed\n`server_version` (already public via `/api/version`).",
"operationId": "info_v1_info_get",
"responses": {
"200": {
@@ -6794,6 +7241,12 @@
{
"type": "string"
},
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
{
"type": "null"
}
@@ -7067,6 +7520,216 @@
]
}
},
+ "/v1/projects": {
+ "get": {
+ "description": "List the caller's projects.\n\n**Returns:** `{\"object\": \"list\", \"data\": [...]}`.\n\n**Raises**\n\n- `OmnigentError` \u2014 401 if unauthenticated in multi-user mode.",
+ "operationId": "list_projects_v1_projects_get",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": true,
+ "title": "Response List Projects V1 Projects Get",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ }
+ },
+ "summary": "List Projects",
+ "tags": [
+ "projects"
+ ]
+ },
+ "post": {
+ "description": "Create a new, empty project owned by the caller.\n\n**Parameters**\n\n- `body` \u2014 Project payload (name).\n\n**Returns:** The created project as a serialized dict.\n\n**Raises**\n\n- `OmnigentError` \u2014 401 if unauthenticated in multi-user mode, 409 if the caller already has a project with this name.",
+ "operationId": "create_project_v1_projects_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CreateProjectRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": true,
+ "title": "Response Create Project V1 Projects Post",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Create Project",
+ "tags": [
+ "projects"
+ ]
+ }
+ },
+ "/v1/projects/{project_id}": {
+ "delete": {
+ "description": "Delete one of the caller's projects.\n\nMember sessions are not deleted; they are left for the caller to\nunfile (clearing their `project_id`).\n\n**Returns:** `{\"id\": ..., \"object\": \"project.deleted\", \"deleted\": True}`.\n\n**Raises**\n\n- `OmnigentError` \u2014 401 if unauthenticated, 404 if not found / not owned by the caller.",
+ "operationId": "delete_project_v1_projects__project_id__delete",
+ "parameters": [
+ {
+ "description": "The project to delete.",
+ "in": "path",
+ "name": "project_id",
+ "required": true,
+ "schema": {
+ "title": "Project Id",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": true,
+ "title": "Response Delete Project V1 Projects Project Id Delete",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Delete Project",
+ "tags": [
+ "projects"
+ ]
+ },
+ "get": {
+ "description": "Return one of the caller's projects.\n\n**Returns:** The project as a serialized dict.\n\n**Raises**\n\n- `OmnigentError` \u2014 401 if unauthenticated, 404 if not found / not owned by the caller.",
+ "operationId": "get_project_v1_projects__project_id__get",
+ "parameters": [
+ {
+ "description": "The project to fetch.",
+ "in": "path",
+ "name": "project_id",
+ "required": true,
+ "schema": {
+ "title": "Project Id",
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": true,
+ "title": "Response Get Project V1 Projects Project Id Get",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Get Project",
+ "tags": [
+ "projects"
+ ]
+ },
+ "patch": {
+ "description": "Update one of the caller's projects (e.g. rename).\n\n**Parameters**\n\n- `body` \u2014 Fields to change; `None` fields are left unchanged.\n\n**Returns:** The updated project as a serialized dict.\n\n**Raises**\n\n- `OmnigentError` \u2014 401 if unauthenticated, 404 if not found / not owned, 409 if the new name collides with another of the caller's projects.",
+ "operationId": "update_project_v1_projects__project_id__patch",
+ "parameters": [
+ {
+ "description": "The project to update.",
+ "in": "path",
+ "name": "project_id",
+ "required": true,
+ "schema": {
+ "title": "Project Id",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/UpdateProjectRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": true,
+ "title": "Response Update Project V1 Projects Project Id Patch",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Update Project",
+ "tags": [
+ "projects"
+ ]
+ }
+ },
"/v1/runners": {
"get": {
"description": "Return currently online runners owned by the requesting user.\n\nWhen auth is active, only runners whose tunnel was\nestablished by the same user are returned. Without auth,\nall online runners are listed (single-user / dev mode).\n\n**Returns:** A `{\"data\": [...]}` list with runner ids and advertised harnesses.",
@@ -7154,7 +7817,7 @@
},
"/v1/runners/{runner_id}/token": {
"post": {
- "description": "Mint a short-lived owner bearer for a managed-sandbox runner.\n\nA managed sandbox runner has no user credential of its own; it\npresents its server-minted tunnel binding token\n(`X-Omnigent-Runner-Tunnel-Token`) and the server returns a\nshort-lived owner JWT the runner then uses on its HTTP callbacks\n(which gate on `require_user`). This is the HTTP analog of the\nrunner tunnel's binding-token handshake: the same SHA-256 gate\n(`token_bound_runner_id(token) == runner_id`) and the same\nowner resolution (`resolve_managed_runner_owner`), minting a\nbearer instead of registering a tunnel.\n\nThe binding-token match is required unconditionally \u2014 the\nallow-list shortcut honored on some other runner-token checks is\ndeliberately NOT accepted here, because this endpoint issues a\nfull owner credential and managed sandboxes always run\ntoken-bound (no allow-list).\n\n**Returns:** `{\"token\": , \"expires_at\": }`.\n\n**Raises**\n\n- `OmnigentError` \u2014 401 when the binding token is absent, doesn't match `runner_id`, or resolves to no managed-launch owner; 400 when the active auth mode can't mint server-side (header/proxy, or no auth provider).",
+ "description": "Mint a short-lived owner bearer for a delegated runner.\n\nA host-launched or managed-sandbox runner does not inherit the host\nuser's credential; it presents its server-minted tunnel binding token\n(`X-Omnigent-Runner-Tunnel-Token`) and the server returns a\nshort-lived owner JWT the runner then uses on its HTTP callbacks\n(which gate on `require_user`). This is the HTTP analog of the\nrunner tunnel's binding-token handshake: the same SHA-256 gate\n(`token_bound_runner_id(token) == runner_id`) and the same\nowner resolution (`resolve_managed_runner_owner`), minting a\nbearer instead of registering a tunnel.\n\nThe binding-token match is required unconditionally \u2014 the\nallow-list shortcut honored on some other runner-token checks is\ndeliberately NOT accepted here, because this endpoint issues a\nfull owner credential and delegated runners are token-bound.\n\n**Returns:** `{\"token\": , \"expires_at\": }`.\n\n**Raises**\n\n- `OmnigentError` \u2014 401 when the binding token is absent, doesn't match `runner_id`, or resolves to no managed-launch owner; 400 when the active auth mode can't mint server-side (header/proxy, or no auth provider).",
"operationId": "mint_runner_owner_token_v1_runners__runner_id__token_post",
"parameters": [
{
@@ -7962,6 +8625,59 @@
]
}
},
+ "/v1/sessions/{session_id}/auto-title": {
+ "post": {
+ "description": "Replace the deterministic first-message title when still current.",
+ "operationId": "automatically_rename_session_v1_sessions__session_id__auto_title_post",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "session_id",
+ "required": true,
+ "schema": {
+ "title": "Session Id",
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AutomaticSessionRenameRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AutomaticSessionRenameResponse"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Automatically Rename Session",
+ "tags": [
+ "sessions"
+ ]
+ }
+ },
"/v1/sessions/{session_id}/child_sessions": {
"get": {
"description": "List sub-agent (child) sessions under a parent session.\n\nReturns a page of `ChildSessionSummary` objects\nderived from child conversations (`kind=\"sub_agent\"`,\n`parent_conversation_id=session_id`) plus each child's\nlatest task. Powers the web / REPL debug surfaces' \"child\nsessions\" panel without parsing parent\n`function_call_output` JSON handles. Pagination contract\nmatches `list_session_items` so existing client code\ncan reuse the same cursor logic.\n\n**Returns:** A `PaginatedList` of `ChildSessionSummary` objects.\n\n**Raises**\n\n- `OmnigentError` \u2014 403 if the caller lacks READ on `session_id`; 404 if no session exists there.",
@@ -9175,7 +9891,7 @@
]
},
"patch": {
- "description": "Update a session policy's mutable fields.\n\n`type` is immutable \u2014 the caller must delete and\nre-create to change it. Requires `LEVEL_EDIT`.\n\n**Parameters**\n\n- `body` \u2014 Fields to update; `None` fields are left unchanged.\n\n**Returns:** The updated policy as a serialized dict.\n\n**Raises**\n\n- `OmnigentError` \u2014 401/403 if the user lacks edit permission, or 404 if the policy is not found.",
+ "description": "Update a session policy's mutable fields.\n\n`type` is immutable \u2014 the caller must delete and\nre-create to change it. Requires `LEVEL_EDIT`.\n\n**Parameters**\n\n- `body` \u2014 Fields to update; `None` fields are left unchanged.\n\n**Returns:** The updated policy as a serialized dict.\n\n**Raises**\n\n- `OmnigentError` \u2014 401/403 if the user lacks edit permission, 404 if the policy is not found, or 409 if renaming would collide with another policy in this session.",
"operationId": "update_policy_v1_sessions__session_id__policies__policy_id__patch",
"parameters": [
{
@@ -10663,7 +11379,7 @@
},
"/v1/sessions/{session_id}/stream": {
"get": {
- "description": "Subscribe to the session's live SSE event stream.\n\nDoes NOT replay history; clients reconcile via the snapshot\nendpoint. The generator handles disconnects via a\n`try/finally` that emits the `[DONE]` sentinel in all\nexit paths \u2014 see `_stream_live_events`.\n\nHolding this stream open registers the caller as a session\n*viewer* (presence): co-viewers' streams receive\n`session.presence` events on join/leave/idle edges, and\nthis stream's snapshot-on-connect includes the current\nviewer list. Presence is scoped to the session tree's root\nconversation, so viewers of different agents/sub-agents in\none session see each other. See\n`omnigent/server/presence.py`.\n\n**Returns:** An SSE `StreamingResponse`.\n\n**Raises**\n\n- `OmnigentError` \u2014 404 if no session exists.",
+ "description": "Subscribe to the session's live SSE event stream.\n\nDoes NOT replay history; clients reconcile via the snapshot\nendpoint. The generator emits `[DONE]` on normal completion\nand uses `finally` only for presence cleanup \u2014 see\n`_stream_live_events`.\n\nHolding this stream open registers the caller as a session\n*viewer* (presence): co-viewers' streams receive\n`session.presence` events on join/leave/idle edges, and\nthis stream's snapshot-on-connect includes the current\nviewer list. Presence is scoped to the session tree's root\nconversation, so viewers of different agents/sub-agents in\none session see each other. See\n`omnigent/server/presence.py`.\n\n**Returns:** An SSE `StreamingResponse`.\n\n**Raises**\n\n- `OmnigentError` \u2014 404 if no session exists.",
"operationId": "stream_session_v1_sessions__session_id__stream_get",
"parameters": [
{
@@ -10826,6 +11542,72 @@
"sessions"
]
}
+ },
+ "/v1/sharing": {
+ "get": {
+ "description": "Report both settings, whether each is editable here, and the tiers.",
+ "operationId": "get_sharing_v1_sharing_get",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": true,
+ "title": "Response Get Sharing V1 Sharing Get",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ }
+ },
+ "summary": "Get Sharing",
+ "tags": [
+ "sharing"
+ ]
+ },
+ "put": {
+ "description": "Set the sharing mode and/or public-access setting (admin only).\n\nUpdates only the fields present in the body; requires at least one.\nRejects an unknown mode value with 400 (no fail-open coercion \u2014 an admin\nsetting a value should learn about a typo). Rejects a write to a setting\nthe deployment manages itself (not file-backed) with 403.",
+ "operationId": "set_sharing_v1_sharing_put",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/SetSharingRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "additionalProperties": true,
+ "title": "Response Set Sharing V1 Sharing Put",
+ "type": "object"
+ }
+ }
+ },
+ "description": "Successful Response"
+ },
+ "422": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HTTPValidationError"
+ }
+ }
+ },
+ "description": "Validation Error"
+ }
+ },
+ "summary": "Set Sharing",
+ "tags": [
+ "sharing"
+ ]
+ }
}
},
"servers": [
@@ -10885,6 +11667,11 @@
"name": "comments",
"x-displayName": "Comments"
},
+ {
+ "description": "First-class, owner-private containers that group sessions \u2014 create, list, rename, and delete.",
+ "name": "projects",
+ "x-displayName": "Projects"
+ },
{
"description": "Health, version, and identity endpoints for the running server.",
"name": "system",