diff --git a/app/docs/build/tools/page.mdx b/app/docs/build/tools/page.mdx index bf126985..58d58899 100644 --- a/app/docs/build/tools/page.mdx +++ b/app/docs/build/tools/page.mdx @@ -220,6 +220,25 @@ tools: The JSON Schema under `parameters` is optional. If you omit it, Omnigent auto-generates the schema from the function's type annotations and signature. +### Sandboxed execution + +Run Python tools inside a container by adding a `sandbox` block under `tools`. +Set `container_image` to the image to run in; `container_runtime` selects the +container CLI and is either `docker` (the default) or `podman`: + +``` +tools: + sandbox: + container_image: python:3.12-slim + container_runtime: podman # optional; defaults to docker (or OMNIGENT_CONTAINER_RUNTIME) +``` + +You can also set the runtime globally with the `OMNIGENT_CONTAINER_RUNTIME` +environment variable (accepted values: `docker`, `podman`). The per-agent +`container_runtime` key takes precedence over the environment variable, so a +spec can always override the global default. When neither is set, the runtime +falls back to `docker`. + ## Sub-agent tools Declare agents as tools so a supervisor agent can delegate work to them. You can define a 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",