openvibers/openvibe.contracts Commits
0.223.0: Run sessions (Desks plan D6): run.session@1 and its create, control and list contracts, run.session.started and .ended, capabilities run.session.create/read/control; Bot desk leases (bot.desk-lease@1, bot.desk.session) (#231)
Changes
@@@ -4,6 +4,25 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,the notes were in the tag and commit messages (`git tag -n1`).++## 0.223.0 — 2026-10-28++++- **Run sessions** (the Desks plan's D6; `OpenVibe_Agent_Desks_Plan_2026-10-27.md`). A session is a browser desk that++ OpenVibe.Run places for one person and OpenVibe.Bot leases to it:++ - `run.session@1` (states placing, starting, running, ended; where it runs and with what trust; desk-seconds),++ `run.session-create-request@1` (`POST /api/v1/sessions`: the person's own named machine, or placement over desk++ hosts, `ttl_s`, `idle_s`, requirements, an idempotency key), `run.session-control-request@1` and++ `run.session-control-result@1` (`POST /api/v1/sessions/:id/commands`: screen, input and browser on the session's++ desk only), `run.session-list-result@1`, and the events `run.session.started` and `run.session.ended`.++ - Capabilities `run.session.create`, `run.session.read` and `run.session.control` (public, the caller's own sessions).++ - Desk time is metered as one `platform.usage-sample@1` a minute (service `run`, operation `browser.desk`, unit `s`,++ idempotency key `run:<session>:<minute>`).++- **Desk leases on Bot**: `bot.desk-lease@1` and `bot.desk-lease-create-request@1`, with the capability++ **`bot.desk.session`** (first-party, sensitive): Run leases one named desk for one session on the person's own machine++ (where they let agents use it) or on a desk host whose owner offers it to other people and whose OpenVibe.Node runs++ every desk in its own sandbox (Node v0.32.0, `desk_isolation: sandbox`); only that session's commands reach the desk,++ never the machine itself.++- Fixtures for each, and `test/platform-run.test.js` holds the two sides together (ids, desk names, sizes, kinds).++## 0.222.0 — 2026-10-27- **Site names say their domain** (owner, 2026-10-27: "it says memberships … couldn't it say VIP memberships since thennew file mode 100644@@@ -0,0 +1,32 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/bot/desk-lease-create-request.v1.json",++ "title": "BotDeskLeaseCreateRequest",++ "description": "bot.desk-lease-create-request@1 (the Desks plan's D6): the body of POST /api/v1/machines/{id}/desk-leases on OpenVibe.Bot (capability bot.desk.session), answered 201 with bot.desk-lease@1. DESK is the name Run chose for the session (unique per session); a desk already leased to another session, or running for the machine's owner, is refused 409 bot.desk_busy, and a repeat for the same session answers its lease (200). A machine with no room (its max_desks) is refused 409 bot.machine_busy, one that is offline 409 bot.device_offline, and one the person may not use 404 bot.device_not_found.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "session",++ "desk",++ "kind"++ ],++ "properties": {++ "session": {++ "$ref": "../run/session.v1.json#/$defs/session_id"++ },++ "desk": {++ "$ref": "desk-lease.v1.json#/properties/desk"++ },++ "kind": {++ "const": "browser"++ },++ "width": {++ "$ref": "desk-lease.v1.json#/properties/width",++ "description": "Default 1280."++ },++ "height": {++ "$ref": "desk-lease.v1.json#/properties/height",++ "description": "Default 800."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,58 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/bot/desk-lease.v1.json",++ "title": "BotDeskLease",++ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "machine_id",++ "desk",++ "session",++ "owner",++ "kind",++ "width",++ "height",++ "shared",++ "created_at"++ ],++ "properties": {++ "machine_id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "desk": {++ "type": "string",++ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$"++ },++ "session": {++ "$ref": "../run/session.v1.json#/$defs/session_id"++ },++ "owner": {++ "type": "string",++ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The session's person, the only one whose commands reach the desk."++ },++ "kind": {++ "const": "browser"++ },++ "width": {++ "type": "integer",++ "minimum": 640,++ "maximum": 3840++ },++ "height": {++ "type": "integer",++ "minimum": 480,++ "maximum": 2160++ },++ "shared": {++ "type": "boolean",++ "description": "true on a desk host that serves other people (the desk runs in its own sandbox there); false on the person's own machine."++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++}@@@ -7485,6 +7485,89 @@"visibility": "public","compatibility": "backward","status": "active"++ },++ {++ "id": "run.session",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/session.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "run.session-create-request",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/session-create-request.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "run.session-control-request",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/session-control-request.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "run.session-control-result",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/session-control-result.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "run.session-list-result",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/session-list-result.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "run.session.started",++ "version": "1.0.0",++ "owner": "run",++ "schema": "events/payloads/run.session.started.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "run.session.ended",++ "version": "1.0.0",++ "owner": "run",++ "schema": "events/payloads/run.session.ended.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "bot.desk-lease",++ "version": "1.0.0",++ "owner": "bot",++ "schema": "bot/desk-lease.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-043"++ },++ {++ "id": "bot.desk-lease-create-request",++ "version": "1.0.0",++ "owner": "bot",++ "schema": "bot/desk-lease-create-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active",++ "adr": "ADR-043"}]}nnew file mode 100644@@@ -0,0 +1,71 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.session.ended.v1.json",++ "title": "RunSessionEndedPayload",++ "description": "run.session.ended v1 (OpenVibe.Run; the Desks plan's D6). A session ended (end.reason ended, ttl, idle, host_lost, unplaceable or failed), its desk stopped and its lease given back; usage.desk_seconds is final. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.",++ "type": "object",++ "required": [++ "id",++ "owner",++ "via",++ "kind",++ "state",++ "size",++ "ttl_s",++ "idle_s",++ "placement",++ "timings",++ "end",++ "usage",++ "idempotency_key"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "$ref": "../../run/session.v1.json#/$defs/session_id"++ },++ "owner": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "via": {++ "$ref": "../../run/session.v1.json#/properties/via"++ },++ "kind": {++ "$ref": "../../run/session.v1.json#/properties/kind"++ },++ "state": {++ "const": "ended"++ },++ "size": {++ "$ref": "../../run/session.v1.json#/$defs/size"++ },++ "ttl_s": {++ "$ref": "../../run/session.v1.json#/properties/ttl_s"++ },++ "idle_s": {++ "$ref": "../../run/session.v1.json#/properties/idle_s"++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "../../run/session.v1.json#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "$ref": "../../run/session.v1.json#/$defs/timings"++ },++ "end": {++ "$ref": "../../run/session.v1.json#/$defs/end"++ },++ "usage": {++ "$ref": "../../run/session.v1.json#/$defs/usage"++ },++ "idempotency_key": {++ "$ref": "../../run/session.v1.json#/properties/idempotency_key"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,64 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.session.started.v1.json",++ "title": "RunSessionStartedPayload",++ "description": "run.session.started v1 (OpenVibe.Run; the Desks plan's D6). A session's desk answered for the first time: it runs on its machine, and desk_seconds count from timings.started_at. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.",++ "type": "object",++ "required": [++ "id",++ "owner",++ "via",++ "kind",++ "state",++ "size",++ "ttl_s",++ "idle_s",++ "placement",++ "timings",++ "end",++ "usage",++ "idempotency_key"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "$ref": "../../run/session.v1.json#/$defs/session_id"++ },++ "owner": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "via": {++ "$ref": "../../run/session.v1.json#/properties/via"++ },++ "kind": {++ "$ref": "../../run/session.v1.json#/properties/kind"++ },++ "state": {++ "const": "running"++ },++ "size": {++ "$ref": "../../run/session.v1.json#/$defs/size"++ },++ "ttl_s": {++ "$ref": "../../run/session.v1.json#/properties/ttl_s"++ },++ "idle_s": {++ "$ref": "../../run/session.v1.json#/properties/idle_s"++ },++ "placement": {++ "$ref": "../../run/session.v1.json#/$defs/placement"++ },++ "timings": {++ "$ref": "../../run/session.v1.json#/$defs/timings"++ },++ "end": {++ "type": "null"++ },++ "usage": {++ "$ref": "../../run/session.v1.json#/$defs/usage"++ },++ "idempotency_key": {++ "$ref": "../../run/session.v1.json#/properties/idempotency_key"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,52 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session-control-request.v1.json",++ "title": "RunSessionControlRequest",++ "description": "run.session-control-request@1 (the Desks plan's D6): one command on a session's desk, the body of POST /api/v1/sessions/{id}/commands on OpenVibe.Run (capability run.session.control), answered with run.session-control-result@1. KIND and VALUE are bot.command@1's for that kind (screen: a picture of the page; input: the mouse, wheel and keys on the page; browser: open, click, fill, press, back, read, screenshot) without desk, which is always the session's own: Run carries the command to OpenVibe.Bot on the session's lease (bot.desk-lease@1) and nothing on the machine other than that desk is reachable. A command on a session that is not running is refused 409 run.session.not_running. ID makes a retry safe: the same id within the session answers the first result.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind",++ "value"++ ],++ "properties": {++ "id": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64,++ "pattern": "^[A-Za-z0-9._:-]+$"++ },++ "kind": {++ "enum": [++ "screen",++ "input",++ "browser"++ ]++ },++ "value": {++ "type": "object",++ "description": "bot.command@1's value for that kind, without desk (refused 422 if it names another desk).",++ "not": {++ "required": [++ "desk"++ ]++ }++ }++ },++ "examples": [++ {++ "kind": "browser",++ "value": {++ "action": "open",++ "url": "https://example.org/"++ }++ },++ {++ "id": "c-7",++ "kind": "screen",++ "value": {++ "max_width": 1024++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,69 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session-control-result.v1.json",++ "title": "RunSessionControlResult",++ "description": "run.session-control-result@1 (the Desks plan's D6): the answer to run.session-control-request@1. RESULT is bot.command-result@1's: ack (the desk did it; DATA is its answer, a page as text and refs, a picture, a receipt), nack (the desk refused; CODE is its fault code and REASON says why), refused (OpenVibe.Bot's gate refused it; CODE is Bot's refusal code) or expired (no answer in time). Every answer also says how the session stands now, so a caller sees it end (idle, ttl, host_lost) without asking again.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "session_id",++ "id",++ "result",++ "state"++ ],++ "properties": {++ "session_id": {++ "$ref": "session.v1.json#/$defs/session_id"++ },++ "id": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64++ },++ "result": {++ "enum": [++ "ack",++ "nack",++ "refused",++ "expired"++ ]++ },++ "code": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80++ },++ "reason": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 1000++ },++ "data": {++ "type": "object",++ "description": "The desk's answer on an ack (bot.command-result@1 data)."++ },++ "state": {++ "$ref": "session.v1.json#/properties/state"++ }++ },++ "examples": [++ {++ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "id": "c-7",++ "result": "ack",++ "code": null,++ "reason": null,++ "data": {++ "format": "jpeg",++ "width": 1024,++ "height": 640,++ "content_b64": "/9j/"++ },++ "state": "running"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,56 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session-create-request.v1.json",++ "title": "RunSessionCreateRequest",++ "description": "run.session-create-request@1 (the Desks plan's D6): the body of POST /api/v1/sessions on OpenVibe.Run (capability run.session.create), answered 201 with run.session@1 (state placing). The session is the caller's own: a person's token, or a first-party service acting for a person (X-OV-Subject; the service is recorded as via). MACHINE names one of the person's own machines (an OpenVibe.Bot device they own and let agents use): the session goes there or ends unplaceable, never elsewhere. Without it Run places the desk: on the person's own machines when REQUIREMENTS name user-owned (and only theirs), else on desk hosts that serve other people (first-party, then community, unless REQUIREMENTS narrow it), each of whose desks runs in its own sandbox. Absent REQUIREMENTS mean { kind: run.browser, mobility: session, latency_class: interactive, objective: balanced }. TTL_S (default 3600) and IDLE_S (default 900) bound its life. IDEMPOTENCY_KEY: a repeat from the same person within 24 h with an identical body answers the first session (200); with a different body 409 run.idempotency.conflict.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind"++ ],++ "properties": {++ "kind": {++ "const": "browser"++ },++ "width": {++ "$ref": "session.v1.json#/$defs/size/properties/width",++ "description": "Default 1280."++ },++ "height": {++ "$ref": "session.v1.json#/$defs/size/properties/height",++ "description": "Default 800."++ },++ "ttl_s": {++ "$ref": "session.v1.json#/properties/ttl_s",++ "description": "Default 3600."++ },++ "idle_s": {++ "$ref": "session.v1.json#/properties/idle_s",++ "description": "Default 900."++ },++ "machine": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "One of the person's own machines, by its OpenVibe.Bot id; the session goes there or nowhere."++ },++ "requirements": {++ "$ref": "../platform/workload-requirements.v1.json"++ },++ "idempotency_key": {++ "$ref": "session.v1.json#/$defs/idempotency_key"++ }++ },++ "examples": [++ {++ "kind": "browser"++ },++ {++ "kind": "browser",++ "width": 1440,++ "height": 900,++ "ttl_s": 1800,++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,28 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session-list-result.v1.json",++ "title": "RunSessionListResult",++ "description": "run.session-list-result@1 (the Desks plan's D6): GET /api/v1/sessions on OpenVibe.Run (capability run.session.read): the caller's own sessions, newest first, running ones by default (?state=ended or ?state=all for others), at most limit (default 20, at most 100) per page; next_cursor continues the list and is null on the last page.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "sessions",++ "next_cursor"++ ],++ "properties": {++ "sessions": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "$ref": "session.v1.json"++ }++ },++ "next_cursor": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 200++ }++ }++}nnew file mode 100644@@@ -0,0 +1,378 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session.v1.json",++ "title": "RunSession",++ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "owner",++ "via",++ "kind",++ "state",++ "size",++ "ttl_s",++ "idle_s",++ "placement",++ "timings",++ "end",++ "usage",++ "idempotency_key"++ ],++ "properties": {++ "id": {++ "$ref": "#/$defs/session_id"++ },++ "owner": {++ "$ref": "../identity/subject-ref.v1.json",++ "description": "The person the session is for: only they (or a first-party service acting for them) read, act on or end it."++ },++ "via": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^service:[a-z][a-z0-9-]{0,40}$",++ "description": "The first-party service that asked for the person (service:actor), or null when they asked themselves."++ },++ "kind": {++ "const": "browser",++ "description": "A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1."++ },++ "state": {++ "enum": [++ "placing",++ "starting",++ "running",++ "ended"++ ]++ },++ "size": {++ "$ref": "#/$defs/size"++ },++ "ttl_s": {++ "type": "integer",++ "minimum": 60,++ "maximum": 14400,++ "description": "The most the session lives, from created_at."++ },++ "idle_s": {++ "type": "integer",++ "minimum": 60,++ "maximum": 3600,++ "description": "A session no command used this long ends (end.reason idle)."++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "$ref": "#/$defs/timings"++ },++ "end": {++ "anyOf": [++ {++ "$ref": "#/$defs/end"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "#/$defs/usage"++ },++ "idempotency_key": {++ "anyOf": [++ {++ "$ref": "#/$defs/idempotency_key"++ },++ {++ "type": "null"++ }++ ]++ }++ },++ "allOf": [++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "const": "ended"++ }++ }++ },++ "then": {++ "properties": {++ "end": {++ "type": "object"++ },++ "timings": {++ "type": "object",++ "properties": {++ "ended_at": {++ "type": "string"++ }++ }++ }++ }++ },++ "else": {++ "properties": {++ "end": {++ "type": "null"++ },++ "timings": {++ "type": "object",++ "properties": {++ "ended_at": {++ "type": "null"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "enum": [++ "starting",++ "running"++ ]++ }++ }++ },++ "then": {++ "properties": {++ "placement": {++ "type": "object"++ }++ }++ }++ }++ ],++ "$defs": {++ "session_id": {++ "type": "string",++ "pattern": "^ses_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "size": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "width",++ "height"++ ],++ "properties": {++ "width": {++ "type": "integer",++ "minimum": 640,++ "maximum": 3840++ },++ "height": {++ "type": "integer",++ "minimum": 480,++ "maximum": 2160++ }++ },++ "description": "The desk's page size (bot.command@1 kind desk width and height)."++ },++ "placement": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "machine",++ "desk",++ "trust",++ "offer_id",++ "region",++ "reasons"++ ],++ "properties": {++ "machine": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine (OpenVibe.Bot device) the desk runs on."++ },++ "desk": {++ "type": "string",++ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$",++ "description": "The desk's name there (bot.command@1 desk), leased to this session alone."++ },++ "trust": {++ "enum": [++ "user-owned",++ "first-party",++ "partner",++ "community"++ ],++ "description": "The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine."++ },++ "offer_id": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 128,++ "description": "The platform.resource-offer@1 it was placed on; null when the request named the person's own machine."++ },++ "region": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 64++ },++ "reasons": {++ "type": "array",++ "maxItems": 8,++ "items": {++ "type": "string",++ "maxLength": 200++ },++ "description": "Why this machine, in a few words each (the placer's reasons)."++ }++ }++ },++ "idempotency_key": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "pattern": "^[A-Za-z0-9._:-]+$"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "started_at",++ "last_used_at",++ "ended_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the desk first answered (state running)."++ },++ "last_used_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "The last command, for idle_s."++ },++ "ended_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ },++ "usage": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "desk_seconds"++ ],++ "properties": {++ "desk_seconds": {++ "type": "number",++ "minimum": 0,++ "description": "Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started."++ }++ }++ },++ "end": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "reason",++ "detail"++ ],++ "properties": {++ "reason": {++ "enum": [++ "ended",++ "ttl",++ "idle",++ "host_lost",++ "unplaceable",++ "failed"++ ]++ },++ "detail": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500++ }++ }++ }++ },++ "examples": [++ {++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "desk": "desk-s0mnpqr",++ "kind": "desktop"++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "desk": "main",++ "kind": "browser"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "desk": "desk-s0mnpqr",++ "kind": "browser",++ "width": 1280,++ "height": 800++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "machine_id": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "private",++ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": "user:usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "kind": "browser",++ "width": 1280,++ "height": 800,++ "shared": true,++ "created_at": "2026-10-28T10:00:01Z"++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "machine_id": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": "service:run",++ "kind": "browser",++ "width": 1280,++ "height": 800,++ "shared": true,++ "created_at": "2026-10-28T10:00:01Z"++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "machine_id": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": "user:usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "kind": "browser",++ "width": 1280,++ "height": 800,++ "shared": true,++ "created_at": "2026-10-28T10:00:01Z"++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "kind": "exec",++ "value": {++ "argv": [++ "id"++ ]++ }++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "kind": "browser",++ "value": {++ "action": "read",++ "desk": "desk-other"++ }++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "kind": "browser",++ "value": {++ "action": "open",++ "url": "https://example.org/"++ }++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "id": "c-7",++ "kind": "screen",++ "value": {++ "max_width": 1024++ }++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "id": "c-7",++ "result": "ack"++}nnew file mode 100644@@@ -0,0 +1,14 @@++{++ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "id": "c-7",++ "result": "ack",++ "code": null,++ "reason": null,++ "data": {++ "format": "jpeg",++ "width": 1024,++ "height": 640,++ "content_b64": "/9j/"++ },++ "state": "running"++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "id": "c-8",++ "result": "refused",++ "code": "bot.device_offline",++ "reason": "the machine is offline",++ "state": "running"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "width": 1280++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "kind": "browser",++ "machine": "rob_01J8Z4M2Q0R7T9YV3K6N8P1W2X"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "kind": "browser",++ "ttl_s": 86400++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "kind": "browser",++ "requirements": {++ "kind": "run.browser",++ "mobility": "session",++ "latency_class": "interactive",++ "objective": "cheapest",++ "trust": [++ "community"++ ]++ }++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "kind": "browser"++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "kind": "browser",++ "width": 1440,++ "height": 900,++ "ttl_s": 1800,++ "idle_s": 600,++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "sessions": []++}nnew file mode 100644@@@ -0,0 +1,42 @@++{++ "sessions": [++ {++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++ }++ ],++ "next_cursor": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "ended",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": "2026-10-28T10:20:00Z"++ },++ "end": null,++ "usage": {++ "desk_seconds": 1196++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "ended",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": "2026-10-28T10:20:00Z"++ },++ "end": {++ "reason": "idle",++ "detail": "no command for 900 s"++ },++ "usage": {++ "desk_seconds": 1196++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "placing",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "desktop",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "ended",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": "2026-10-28T10:20:00Z"++ },++ "end": null,++ "usage": {++ "desk_seconds": 1196++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "main",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,28 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": null,++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": {++ "reason": "idle",++ "detail": null++ },++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "ended",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": "2026-10-28T10:20:00Z"++ },++ "end": {++ "reason": "idle",++ "detail": "no command for 900 s"++ },++ "usage": {++ "desk_seconds": 1196++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,31 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": null,++ "kind": "browser",++ "state": "ended",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": null,++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": null,++ "last_used_at": null,++ "ended_at": "2026-10-28T10:01:00Z"++ },++ "end": {++ "reason": "unplaceable",++ "detail": "no desk host had room"++ },++ "usage": {++ "desk_seconds": 0++ },++ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"++}nnew file mode 100644@@@ -0,0 +1,28 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": null,++ "kind": "browser",++ "state": "placing",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": null,++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": null,++ "last_used_at": null,++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 0++ },++ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "user-owned",++ "offer_id": null,++ "region": null,++ "reasons": [++ "your own machine, as asked"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++}@@@ -83773,6 +83773,859 @@}}}++ },++ "run.session": {++ "version": "1.0.0",++ "owner": "run",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session.v1.json",++ "title": "RunSession",++ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "owner",++ "via",++ "kind",++ "state",++ "size",++ "ttl_s",++ "idle_s",++ "placement",++ "timings",++ "end",++ "usage",++ "idempotency_key"++ ],++ "properties": {++ "id": {++ "$ref": "#/$defs/session_id"++ },++ "owner": {++ "$ref": "../identity/subject-ref.v1.json",++ "description": "The person the session is for: only they (or a first-party service acting for them) read, act on or end it."++ },++ "via": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^service:[a-z][a-z0-9-]{0,40}$",++ "description": "The first-party service that asked for the person (service:actor), or null when they asked themselves."++ },++ "kind": {++ "const": "browser",++ "description": "A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1."++ },++ "state": {++ "enum": [++ "placing",++ "starting",++ "running",++ "ended"++ ]++ },++ "size": {++ "$ref": "#/$defs/size"++ },++ "ttl_s": {++ "type": "integer",++ "minimum": 60,++ "maximum": 14400,++ "description": "The most the session lives, from created_at."++ },++ "idle_s": {++ "type": "integer",++ "minimum": 60,++ "maximum": 3600,++ "description": "A session no command used this long ends (end.reason idle)."++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "$ref": "#/$defs/timings"++ },++ "end": {++ "anyOf": [++ {++ "$ref": "#/$defs/end"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "#/$defs/usage"++ },++ "idempotency_key": {++ "anyOf": [++ {++ "$ref": "#/$defs/idempotency_key"++ },++ {++ "type": "null"++ }++ ]++ }++ },++ "allOf": [++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "const": "ended"++ }++ }++ },++ "then": {++ "properties": {++ "end": {++ "type": "object"++ },++ "timings": {++ "type": "object",++ "properties": {++ "ended_at": {++ "type": "string"++ }++ }++ }++ }++ },++ "else": {++ "properties": {++ "end": {++ "type": "null"++ },++ "timings": {++ "type": "object",++ "properties": {++ "ended_at": {++ "type": "null"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "enum": [++ "starting",++ "running"++ ]++ }++ }++ },++ "then": {++ "properties": {++ "placement": {++ "type": "object"++ }++ }++ }++ }++ ],++ "$defs": {++ "session_id": {++ "type": "string",++ "pattern": "^ses_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "size": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "width",++ "height"++ ],++ "properties": {++ "width": {++ "type": "integer",++ "minimum": 640,++ "maximum": 3840++ },++ "height": {++ "type": "integer",++ "minimum": 480,++ "maximum": 2160++ }++ },++ "description": "The desk's page size (bot.command@1 kind desk width and height)."++ },++ "placement": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "machine",++ "desk",++ "trust",++ "offer_id",++ "region",++ "reasons"++ ],++ "properties": {++ "machine": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine (OpenVibe.Bot device) the desk runs on."++ },++ "desk": {++ "type": "string",++ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$",++ "description": "The desk's name there (bot.command@1 desk), leased to this session alone."++ },++ "trust": {++ "enum": [++ "user-owned",++ "first-party",++ "partner",++ "community"++ ],++ "description": "The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine."++ },++ "offer_id": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 128,++ "description": "The platform.resource-offer@1 it was placed on; null when the request named the person's own machine."++ },++ "region": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 64++ },++ "reasons": {++ "type": "array",++ "maxItems": 8,++ "items": {++ "type": "string",++ "maxLength": 200++ },++ "description": "Why this machine, in a few words each (the placer's reasons)."++ }++ }++ },++ "idempotency_key": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "pattern": "^[A-Za-z0-9._:-]+$"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "started_at",++ "last_used_at",++ "ended_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the desk first answered (state running)."++ },++ "last_used_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "The last command, for idle_s."++ },++ "ended_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ },++ "usage": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "desk_seconds"++ ],++ "properties": {++ "desk_seconds": {++ "type": "number",++ "minimum": 0,++ "description": "Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started."++ }++ }++ },++ "end": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "reason",++ "detail"++ ],++ "properties": {++ "reason": {++ "enum": [++ "ended",++ "ttl",++ "idle",++ "host_lost",++ "unplaceable",++ "failed"++ ]++ },++ "detail": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500++ }++ }++ }++ },++ "examples": [++ {++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++ }++ ]++ }++ },++ "run.session-create-request": {++ "version": "1.0.0",++ "owner": "run",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session-create-request.v1.json",++ "title": "RunSessionCreateRequest",++ "description": "run.session-create-request@1 (the Desks plan's D6): the body of POST /api/v1/sessions on OpenVibe.Run (capability run.session.create), answered 201 with run.session@1 (state placing). The session is the caller's own: a person's token, or a first-party service acting for a person (X-OV-Subject; the service is recorded as via). MACHINE names one of the person's own machines (an OpenVibe.Bot device they own and let agents use): the session goes there or ends unplaceable, never elsewhere. Without it Run places the desk: on the person's own machines when REQUIREMENTS name user-owned (and only theirs), else on desk hosts that serve other people (first-party, then community, unless REQUIREMENTS narrow it), each of whose desks runs in its own sandbox. Absent REQUIREMENTS mean { kind: run.browser, mobility: session, latency_class: interactive, objective: balanced }. TTL_S (default 3600) and IDLE_S (default 900) bound its life. IDEMPOTENCY_KEY: a repeat from the same person within 24 h with an identical body answers the first session (200); with a different body 409 run.idempotency.conflict.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind"++ ],++ "properties": {++ "kind": {++ "const": "browser"++ },++ "width": {++ "$ref": "session.v1.json#/$defs/size/properties/width",++ "description": "Default 1280."++ },++ "height": {++ "$ref": "session.v1.json#/$defs/size/properties/height",++ "description": "Default 800."++ },++ "ttl_s": {++ "$ref": "session.v1.json#/properties/ttl_s",++ "description": "Default 3600."++ },++ "idle_s": {++ "$ref": "session.v1.json#/properties/idle_s",++ "description": "Default 900."++ },++ "machine": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "One of the person's own machines, by its OpenVibe.Bot id; the session goes there or nowhere."++ },++ "requirements": {++ "$ref": "../platform/workload-requirements.v1.json"++ },++ "idempotency_key": {++ "$ref": "session.v1.json#/$defs/idempotency_key"++ }++ },++ "examples": [++ {++ "kind": "browser"++ },++ {++ "kind": "browser",++ "width": 1440,++ "height": 900,++ "ttl_s": 1800,++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"++ }++ ]++ }++ },++ "run.session-control-request": {++ "version": "1.0.0",++ "owner": "run",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session-control-request.v1.json",++ "title": "RunSessionControlRequest",++ "description": "run.session-control-request@1 (the Desks plan's D6): one command on a session's desk, the body of POST /api/v1/sessions/{id}/commands on OpenVibe.Run (capability run.session.control), answered with run.session-control-result@1. KIND and VALUE are bot.command@1's for that kind (screen: a picture of the page; input: the mouse, wheel and keys on the page; browser: open, click, fill, press, back, read, screenshot) without desk, which is always the session's own: Run carries the command to OpenVibe.Bot on the session's lease (bot.desk-lease@1) and nothing on the machine other than that desk is reachable. A command on a session that is not running is refused 409 run.session.not_running. ID makes a retry safe: the same id within the session answers the first result.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind",++ "value"++ ],++ "properties": {++ "id": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64,++ "pattern": "^[A-Za-z0-9._:-]+$"++ },++ "kind": {++ "enum": [++ "screen",++ "input",++ "browser"++ ]++ },++ "value": {++ "type": "object",++ "description": "bot.command@1's value for that kind, without desk (refused 422 if it names another desk).",++ "not": {++ "required": [++ "desk"++ ]++ }++ }++ },++ "examples": [++ {++ "kind": "browser",++ "value": {++ "action": "open",++ "url": "https://example.org/"++ }++ },++ {++ "id": "c-7",++ "kind": "screen",++ "value": {++ "max_width": 1024++ }++ }++ ]++ }++ },++ "run.session-control-result": {++ "version": "1.0.0",++ "owner": "run",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session-control-result.v1.json",++ "title": "RunSessionControlResult",++ "description": "run.session-control-result@1 (the Desks plan's D6): the answer to run.session-control-request@1. RESULT is bot.command-result@1's: ack (the desk did it; DATA is its answer, a page as text and refs, a picture, a receipt), nack (the desk refused; CODE is its fault code and REASON says why), refused (OpenVibe.Bot's gate refused it; CODE is Bot's refusal code) or expired (no answer in time). Every answer also says how the session stands now, so a caller sees it end (idle, ttl, host_lost) without asking again.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "session_id",++ "id",++ "result",++ "state"++ ],++ "properties": {++ "session_id": {++ "$ref": "session.v1.json#/$defs/session_id"++ },++ "id": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64++ },++ "result": {++ "enum": [++ "ack",++ "nack",++ "refused",++ "expired"++ ]++ },++ "code": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80++ },++ "reason": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 1000++ },++ "data": {++ "type": "object",++ "description": "The desk's answer on an ack (bot.command-result@1 data)."++ },++ "state": {++ "$ref": "session.v1.json#/properties/state"++ }++ },++ "examples": [++ {++ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "id": "c-7",++ "result": "ack",++ "code": null,++ "reason": null,++ "data": {++ "format": "jpeg",++ "width": 1024,++ "height": 640,++ "content_b64": "/9j/"++ },++ "state": "running"++ }++ ]++ }++ },++ "run.session-list-result": {++ "version": "1.0.0",++ "owner": "run",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/session-list-result.v1.json",++ "title": "RunSessionListResult",++ "description": "run.session-list-result@1 (the Desks plan's D6): GET /api/v1/sessions on OpenVibe.Run (capability run.session.read): the caller's own sessions, newest first, running ones by default (?state=ended or ?state=all for others), at most limit (default 20, at most 100) per page; next_cursor continues the list and is null on the last page.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "sessions",++ "next_cursor"++ ],++ "properties": {++ "sessions": {++ "type": "array",++ "maxItems": 100,++ "items": {++ "$ref": "session.v1.json"++ }++ },++ "next_cursor": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 200++ }++ }++ }++ },++ "run.session.started": {++ "version": "1.0.0",++ "owner": "run",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.session.started.v1.json",++ "title": "RunSessionStartedPayload",++ "description": "run.session.started v1 (OpenVibe.Run; the Desks plan's D6). A session's desk answered for the first time: it runs on its machine, and desk_seconds count from timings.started_at. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.",++ "type": "object",++ "required": [++ "id",++ "owner",++ "via",++ "kind",++ "state",++ "size",++ "ttl_s",++ "idle_s",++ "placement",++ "timings",++ "end",++ "usage",++ "idempotency_key"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "$ref": "../../run/session.v1.json#/$defs/session_id"++ },++ "owner": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "via": {++ "$ref": "../../run/session.v1.json#/properties/via"++ },++ "kind": {++ "$ref": "../../run/session.v1.json#/properties/kind"++ },++ "state": {++ "const": "running"++ },++ "size": {++ "$ref": "../../run/session.v1.json#/$defs/size"++ },++ "ttl_s": {++ "$ref": "../../run/session.v1.json#/properties/ttl_s"++ },++ "idle_s": {++ "$ref": "../../run/session.v1.json#/properties/idle_s"++ },++ "placement": {++ "$ref": "../../run/session.v1.json#/$defs/placement"++ },++ "timings": {++ "$ref": "../../run/session.v1.json#/$defs/timings"++ },++ "end": {++ "type": "null"++ },++ "usage": {++ "$ref": "../../run/session.v1.json#/$defs/usage"++ },++ "idempotency_key": {++ "$ref": "../../run/session.v1.json#/properties/idempotency_key"++ }++ }++ }++ },++ "run.session.ended": {++ "version": "1.0.0",++ "owner": "run",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.session.ended.v1.json",++ "title": "RunSessionEndedPayload",++ "description": "run.session.ended v1 (OpenVibe.Run; the Desks plan's D6). A session ended (end.reason ended, ttl, idle, host_lost, unplaceable or failed), its desk stopped and its lease given back; usage.desk_seconds is final. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.",++ "type": "object",++ "required": [++ "id",++ "owner",++ "via",++ "kind",++ "state",++ "size",++ "ttl_s",++ "idle_s",++ "placement",++ "timings",++ "end",++ "usage",++ "idempotency_key"++ ],++ "additionalProperties": false,++ "properties": {++ "id": {++ "$ref": "../../run/session.v1.json#/$defs/session_id"++ },++ "owner": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "via": {++ "$ref": "../../run/session.v1.json#/properties/via"++ },++ "kind": {++ "$ref": "../../run/session.v1.json#/properties/kind"++ },++ "state": {++ "const": "ended"++ },++ "size": {++ "$ref": "../../run/session.v1.json#/$defs/size"++ },++ "ttl_s": {++ "$ref": "../../run/session.v1.json#/properties/ttl_s"++ },++ "idle_s": {++ "$ref": "../../run/session.v1.json#/properties/idle_s"++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "../../run/session.v1.json#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "$ref": "../../run/session.v1.json#/$defs/timings"++ },++ "end": {++ "$ref": "../../run/session.v1.json#/$defs/end"++ },++ "usage": {++ "$ref": "../../run/session.v1.json#/$defs/usage"++ },++ "idempotency_key": {++ "$ref": "../../run/session.v1.json#/properties/idempotency_key"++ }++ }++ }++ },++ "bot.desk-lease": {++ "version": "1.0.0",++ "owner": "bot",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/bot/desk-lease.v1.json",++ "title": "BotDeskLease",++ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "machine_id",++ "desk",++ "session",++ "owner",++ "kind",++ "width",++ "height",++ "shared",++ "created_at"++ ],++ "properties": {++ "machine_id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "desk": {++ "type": "string",++ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$"++ },++ "session": {++ "$ref": "../run/session.v1.json#/$defs/session_id"++ },++ "owner": {++ "type": "string",++ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The session's person, the only one whose commands reach the desk."++ },++ "kind": {++ "const": "browser"++ },++ "width": {++ "type": "integer",++ "minimum": 640,++ "maximum": 3840++ },++ "height": {++ "type": "integer",++ "minimum": 480,++ "maximum": 2160++ },++ "shared": {++ "type": "boolean",++ "description": "true on a desk host that serves other people (the desk runs in its own sandbox there); false on the person's own machine."++ },++ "created_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ }++ },++ "bot.desk-lease-create-request": {++ "version": "1.0.0",++ "owner": "bot",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/bot/desk-lease-create-request.v1.json",++ "title": "BotDeskLeaseCreateRequest",++ "description": "bot.desk-lease-create-request@1 (the Desks plan's D6): the body of POST /api/v1/machines/{id}/desk-leases on OpenVibe.Bot (capability bot.desk.session), answered 201 with bot.desk-lease@1. DESK is the name Run chose for the session (unique per session); a desk already leased to another session, or running for the machine's owner, is refused 409 bot.desk_busy, and a repeat for the same session answers its lease (200). A machine with no room (its max_desks) is refused 409 bot.machine_busy, one that is offline 409 bot.device_offline, and one the person may not use 404 bot.device_not_found.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "session",++ "desk",++ "kind"++ ],++ "properties": {++ "session": {++ "$ref": "../run/session.v1.json#/$defs/session_id"++ },++ "desk": {++ "$ref": "desk-lease.v1.json#/properties/desk"++ },++ "kind": {++ "const": "browser"++ },++ "width": {++ "$ref": "desk-lease.v1.json#/properties/width",++ "description": "Default 1280."++ },++ "height": {++ "$ref": "desk-lease.v1.json#/properties/height",++ "description": "Default 800."++ }++ }++ }}}}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Actor API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bot API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -472,6 +472,350 @@}}},++ "/api/v1/machines/{id}/desk-leases": {++ "post": {++ "operationId": "post_api_v1_machines_id_desk_leases",++ "summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",++ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",++ "tags": [++ "bot.desk"++ ],++ "x-openvibe-capabilities": [++ "bot.desk.session"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-control",++ "security": [++ {++ "openvibeToken": [++ "bot.desk.session"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease-create-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/{id}/desk-leases/{desk}": {++ "delete": {++ "operationId": "delete_api_v1_machines_id_desk_leases_desk",++ "summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",++ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",++ "tags": [++ "bot.desk"++ ],++ "x-openvibe-capabilities": [++ "bot.desk.session"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-control",++ "security": [++ {++ "openvibeToken": [++ "bot.desk.session"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/bot.desk-lease-create-request.v1"++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "desk",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "session",++ "in": "query",++ "required": true,++ "schema": {++ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"++ }++ },++ {++ "name": "kind",++ "in": "query",++ "required": true,++ "schema": {++ "const": "browser"++ }++ },++ {++ "name": "width",++ "in": "query",++ "required": false,++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/width",++ "description": "Default 1280."++ },++ "description": "Default 1280."++ },++ {++ "name": "height",++ "in": "query",++ "required": false,++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/height",++ "description": "Default 800."++ },++ "description": "Default 800."++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "get": {++ "operationId": "get_api_v1_machines_id_desk_leases_desk",++ "summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",++ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",++ "tags": [++ "bot.desk"++ ],++ "x-openvibe-capabilities": [++ "bot.desk.session"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-control",++ "security": [++ {++ "openvibeToken": [++ "bot.desk.session"++ ]++ }++ ],++ "x-openvibe-input": {++ "$ref": "#/components/schemas/bot.desk-lease-create-request.v1"++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "desk",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "session",++ "in": "query",++ "required": true,++ "schema": {++ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"++ }++ },++ {++ "name": "kind",++ "in": "query",++ "required": true,++ "schema": {++ "const": "browser"++ }++ },++ {++ "name": "width",++ "in": "query",++ "required": false,++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/width",++ "description": "Default 1280."++ },++ "description": "Default 1280."++ },++ {++ "name": "height",++ "in": "query",++ "required": false,++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/height",++ "description": "Default 800."++ },++ "description": "Default 800."++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/machines/{id}/desk-leases/{desk}/commands": {++ "post": {++ "operationId": "post_api_v1_machines_id_desk_leases_desk_commands",++ "summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",++ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.\n\nRoute note: bot.command@1, answered by bot.command-result@1",++ "tags": [++ "bot.desk"++ ],++ "x-openvibe-capabilities": [++ "bot.desk.session"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "bot-control",++ "security": [++ {++ "openvibeToken": [++ "bot.desk.session"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease-create-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ },++ {++ "name": "desk",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/bot.desk-lease.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/api/v1/resources": {"get": {"operationId": "get_api_v1_resources",@@@ -1538,22 +1882,108 @@}}},-- "default": {-- "description": "An error (RFC 9457 problem details)",-- "content": {-- "application/problem+json": {-- "schema": {-- "$ref": "#/components/schemas/errors.problem.v1"-- }-- }-- }++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ }++ },++ "components": {++ "schemas": {++ "bot.desk-lease-create-request.v1": {++ "title": "BotDeskLeaseCreateRequest",++ "description": "bot.desk-lease-create-request@1 (the Desks plan's D6): the body of POST /api/v1/machines/{id}/desk-leases on OpenVibe.Bot (capability bot.desk.session), answered 201 with bot.desk-lease@1. DESK is the name Run chose for the session (unique per session); a desk already leased to another session, or running for the machine's owner, is refused 409 bot.desk_busy, and a repeat for the same session answers its lease (200). A machine with no room (its max_desks) is refused 409 bot.machine_busy, one that is offline 409 bot.device_offline, and one the person may not use 404 bot.device_not_found.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "session",++ "desk",++ "kind"++ ],++ "properties": {++ "session": {++ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"++ },++ "desk": {++ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/desk"++ },++ "kind": {++ "const": "browser"++ },++ "width": {++ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/width",++ "description": "Default 1280."++ },++ "height": {++ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/height",++ "description": "Default 800."++ }++ }++ },++ "bot.desk-lease.v1": {++ "title": "BotDeskLease",++ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "machine_id",++ "desk",++ "session",++ "owner",++ "kind",++ "width",++ "height",++ "shared",++ "created_at"++ ],++ "properties": {++ "machine_id": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "desk": {++ "type": "string",++ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$"++ },++ "session": {++ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"++ },++ "owner": {++ "type": "string",++ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The session's person, the only one whose commands reach the desk."++ },++ "kind": {++ "const": "browser"++ },++ "width": {++ "type": "integer",++ "minimum": 640,++ "maximum": 3840++ },++ "height": {++ "type": "integer",++ "minimum": 480,++ "maximum": 2160++ },++ "shared": {++ "type": "boolean",++ "description": "true on a desk host that serves other people (the desk runs in its own sandbox there); false on the person's own machine."++ },++ "created_at": {++ "type": "string",++ "format": "date-time"}}-- }-- }-- },-- "components": {-- "schemas": {++ },"bot.device-connect-result.v1": {"title": "BotDeviceConnectResult","description": "bot.device-connect-result@1: answers of OpenVibe.Bot's device credential routes, which Bot gates with bot.robot.manage (bot.device.connect is reserved, not enforced). POST /api/v1/devices/:id/rotate → { device, credential, publish_key } (both secrets shown once); POST …/devices/:id/revoke → { device } (its socket closes at once).",@@@ -3112,6 +3542,382 @@"function","gpu"]++ },++ "run.session.v1": {++ "title": "RunSession",++ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "owner",++ "via",++ "kind",++ "state",++ "size",++ "ttl_s",++ "idle_s",++ "placement",++ "timings",++ "end",++ "usage",++ "idempotency_key"++ ],++ "properties": {++ "id": {++ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"++ },++ "owner": {++ "$ref": "#/components/schemas/identity.subject-ref.v1",++ "description": "The person the session is for: only they (or a first-party service acting for them) read, act on or end it."++ },++ "via": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^service:[a-z][a-z0-9-]{0,40}$",++ "description": "The first-party service that asked for the person (service:actor), or null when they asked themselves."++ },++ "kind": {++ "const": "browser",++ "description": "A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1."++ },++ "state": {++ "enum": [++ "placing",++ "starting",++ "running",++ "ended"++ ]++ },++ "size": {++ "$ref": "#/components/schemas/run.session.v1/$defs/size"++ },++ "ttl_s": {++ "type": "integer",++ "minimum": 60,++ "maximum": 14400,++ "description": "The most the session lives, from created_at."++ },++ "idle_s": {++ "type": "integer",++ "minimum": 60,++ "maximum": 3600,++ "description": "A session no command used this long ends (end.reason idle)."++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "#/components/schemas/run.session.v1/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "$ref": "#/components/schemas/run.session.v1/$defs/timings"++ },++ "end": {++ "anyOf": [++ {++ "$ref": "#/components/schemas/run.session.v1/$defs/end"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "#/components/schemas/run.session.v1/$defs/usage"++ },++ "idempotency_key": {++ "anyOf": [++ {++ "$ref": "#/components/schemas/run.session.v1/$defs/idempotency_key"++ },++ {++ "type": "null"++ }++ ]++ }++ },++ "allOf": [++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "const": "ended"++ }++ }++ },++ "then": {++ "properties": {++ "end": {++ "type": "object"++ },++ "timings": {++ "type": "object",++ "properties": {++ "ended_at": {++ "type": "string"++ }++ }++ }++ }++ },++ "else": {++ "properties": {++ "end": {++ "type": "null"++ },++ "timings": {++ "type": "object",++ "properties": {++ "ended_at": {++ "type": "null"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "enum": [++ "starting",++ "running"++ ]++ }++ }++ },++ "then": {++ "properties": {++ "placement": {++ "type": "object"++ }++ }++ }++ }++ ],++ "$defs": {++ "session_id": {++ "type": "string",++ "pattern": "^ses_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "size": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "width",++ "height"++ ],++ "properties": {++ "width": {++ "type": "integer",++ "minimum": 640,++ "maximum": 3840++ },++ "height": {++ "type": "integer",++ "minimum": 480,++ "maximum": 2160++ }++ },++ "description": "The desk's page size (bot.command@1 kind desk width and height)."++ },++ "placement": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "machine",++ "desk",++ "trust",++ "offer_id",++ "region",++ "reasons"++ ],++ "properties": {++ "machine": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine (OpenVibe.Bot device) the desk runs on."++ },++ "desk": {++ "type": "string",++ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$",++ "description": "The desk's name there (bot.command@1 desk), leased to this session alone."++ },++ "trust": {++ "enum": [++ "user-owned",++ "first-party",++ "partner",++ "community"++ ],++ "description": "The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine."++ },++ "offer_id": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 128,++ "description": "The platform.resource-offer@1 it was placed on; null when the request named the person's own machine."++ },++ "region": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 64++ },++ "reasons": {++ "type": "array",++ "maxItems": 8,++ "items": {++ "type": "string",++ "maxLength": 200++ },++ "description": "Why this machine, in a few words each (the placer's reasons)."++ }++ }++ },++ "idempotency_key": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "pattern": "^[A-Za-z0-9._:-]+$"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "started_at",++ "last_used_at",++ "ended_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the desk first answered (state running)."++ },++ "last_used_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "The last command, for idle_s."++ },++ "ended_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ },++ "usage": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "desk_seconds"++ ],++ "properties": {++ "desk_seconds": {++ "type": "number",++ "minimum": 0,++ "description": "Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started."++ }++ }++ },++ "end": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "reason",++ "detail"++ ],++ "properties": {++ "reason": {++ "enum": [++ "ended",++ "ttl",++ "idle",++ "host_lost",++ "unplaceable",++ "failed"++ ]++ },++ "detail": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500++ }++ }++ }++ },++ "examples": [++ {++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++ }++ ]}},"securitySchemes": {@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Food API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -33,8 +33,8 @@"service": "bot","name": "OpenVibe.Bot","origin": "https://openvibe.bot",-- "operations": 21,-- "capabilities": 8++ "operations": 25,++ "capabilities": 9},{"service": "chat",@@@ -173,8 +173,8 @@"service": "run","name": "OpenVibe.Run","origin": "https://openvibe.run",-- "operations": 7,-- "capabilities": 6++ "operations": 12,++ "capabilities": 9},{"service": "search",@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Inventory API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.MediaHub API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRestream API",-- "version": "0.222.0",-- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Play API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Quest API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Run API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -511,6 +511,294 @@}}}++ },++ "/api/v1/sessions": {++ "get": {++ "operationId": "get_api_v1_sessions",++ "summary": "Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds).",++ "description": "**run.session.read** (public): Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds). Never another person's.",++ "tags": [++ "run.session"++ ],++ "x-openvibe-capabilities": [++ "run.session.read"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "run-read",++ "security": [++ {++ "openvibeToken": [++ "run.session.read"++ ]++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/run.session.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ },++ "post": {++ "operationId": "post_api_v1_sessions",++ "summary": "Open a browser session for yourself (the Desks plan's D6): POST /api/v1/sessions with run.session-create-request@1.",++ "description": "**run.session.create** (public): Open a browser session for yourself (the Desks plan's D6): POST /api/v1/sessions with run.session-create-request@1. Run places a browser desk on one of your own machines (when you name it or your requirements name user-owned) or on a desk host that serves other people, where every desk runs in its own sandbox, and answers run.session@1. A first-party service acting for a person (X-OV-Subject) opens it for them and is recorded as via. Desk time is metered in desk-seconds.",++ "tags": [++ "run.session"++ ],++ "x-openvibe-capabilities": [++ "run.session.create"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "run-write",++ "security": [++ {++ "openvibeToken": [++ "run.session.create"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/run.session-create-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/run.session.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/sessions/{id}": {++ "get": {++ "operationId": "get_api_v1_sessions_id",++ "summary": "Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds).",++ "description": "**run.session.read** (public): Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds). Never another person's.",++ "tags": [++ "run.session"++ ],++ "x-openvibe-capabilities": [++ "run.session.read"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "run-read",++ "security": [++ {++ "openvibeToken": [++ "run.session.read"++ ]++ }++ ],++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/run.session.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/sessions/{id}/commands": {++ "post": {++ "operationId": "post_api_v1_sessions_id_commands",++ "summary": "Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-resul",++ "description": "**run.session.control** (public): Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-result@1) and POST /api/v1/sessions/:id/end (run.session@1, ended). Nothing else on the machine it runs on is reachable.",++ "tags": [++ "run.session"++ ],++ "x-openvibe-capabilities": [++ "run.session.control"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "run-write",++ "security": [++ {++ "openvibeToken": [++ "run.session.control"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/run.session-control-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/run.session-control-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },++ "/api/v1/sessions/{id}/end": {++ "post": {++ "operationId": "post_api_v1_sessions_id_end",++ "summary": "Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-resul",++ "description": "**run.session.control** (public): Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-result@1) and POST /api/v1/sessions/:id/end (run.session@1, ended). Nothing else on the machine it runs on is reachable.",++ "tags": [++ "run.session"++ ],++ "x-openvibe-capabilities": [++ "run.session.control"++ ],++ "x-openvibe-visibility": [++ "public"++ ],++ "x-openvibe-quota-class": "run-write",++ "security": [++ {++ "openvibeToken": [++ "run.session.control"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/run.session-control-request.v1"++ }++ }++ }++ },++ "parameters": [++ {++ "name": "id",++ "in": "path",++ "required": true,++ "schema": {++ "type": "string"++ }++ }++ ],++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/run.session-control-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }}},"components": {@@@ -1721,6 +2009,553 @@"pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"}}++ },++ "run.session-control-request.v1": {++ "title": "RunSessionControlRequest",++ "description": "run.session-control-request@1 (the Desks plan's D6): one command on a session's desk, the body of POST /api/v1/sessions/{id}/commands on OpenVibe.Run (capability run.session.control), answered with run.session-control-result@1. KIND and VALUE are bot.command@1's for that kind (screen: a picture of the page; input: the mouse, wheel and keys on the page; browser: open, click, fill, press, back, read, screenshot) without desk, which is always the session's own: Run carries the command to OpenVibe.Bot on the session's lease (bot.desk-lease@1) and nothing on the machine other than that desk is reachable. A command on a session that is not running is refused 409 run.session.not_running. ID makes a retry safe: the same id within the session answers the first result.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind",++ "value"++ ],++ "properties": {++ "id": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64,++ "pattern": "^[A-Za-z0-9._:-]+$"++ },++ "kind": {++ "enum": [++ "screen",++ "input",++ "browser"++ ]++ },++ "value": {++ "type": "object",++ "description": "bot.command@1's value for that kind, without desk (refused 422 if it names another desk).",++ "not": {++ "required": [++ "desk"++ ]++ }++ }++ },++ "examples": [++ {++ "kind": "browser",++ "value": {++ "action": "open",++ "url": "https://example.org/"++ }++ },++ {++ "id": "c-7",++ "kind": "screen",++ "value": {++ "max_width": 1024++ }++ }++ ]++ },++ "run.session-control-result.v1": {++ "title": "RunSessionControlResult",++ "description": "run.session-control-result@1 (the Desks plan's D6): the answer to run.session-control-request@1. RESULT is bot.command-result@1's: ack (the desk did it; DATA is its answer, a page as text and refs, a picture, a receipt), nack (the desk refused; CODE is its fault code and REASON says why), refused (OpenVibe.Bot's gate refused it; CODE is Bot's refusal code) or expired (no answer in time). Every answer also says how the session stands now, so a caller sees it end (idle, ttl, host_lost) without asking again.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "session_id",++ "id",++ "result",++ "state"++ ],++ "properties": {++ "session_id": {++ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"++ },++ "id": {++ "type": "string",++ "minLength": 1,++ "maxLength": 64++ },++ "result": {++ "enum": [++ "ack",++ "nack",++ "refused",++ "expired"++ ]++ },++ "code": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 80++ },++ "reason": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 1000++ },++ "data": {++ "type": "object",++ "description": "The desk's answer on an ack (bot.command-result@1 data)."++ },++ "state": {++ "$ref": "#/components/schemas/run.session.v1/properties/state"++ }++ },++ "examples": [++ {++ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "id": "c-7",++ "result": "ack",++ "code": null,++ "reason": null,++ "data": {++ "format": "jpeg",++ "width": 1024,++ "height": 640,++ "content_b64": "/9j/"++ },++ "state": "running"++ }++ ]++ },++ "run.session-create-request.v1": {++ "title": "RunSessionCreateRequest",++ "description": "run.session-create-request@1 (the Desks plan's D6): the body of POST /api/v1/sessions on OpenVibe.Run (capability run.session.create), answered 201 with run.session@1 (state placing). The session is the caller's own: a person's token, or a first-party service acting for a person (X-OV-Subject; the service is recorded as via). MACHINE names one of the person's own machines (an OpenVibe.Bot device they own and let agents use): the session goes there or ends unplaceable, never elsewhere. Without it Run places the desk: on the person's own machines when REQUIREMENTS name user-owned (and only theirs), else on desk hosts that serve other people (first-party, then community, unless REQUIREMENTS narrow it), each of whose desks runs in its own sandbox. Absent REQUIREMENTS mean { kind: run.browser, mobility: session, latency_class: interactive, objective: balanced }. TTL_S (default 3600) and IDLE_S (default 900) bound its life. IDEMPOTENCY_KEY: a repeat from the same person within 24 h with an identical body answers the first session (200); with a different body 409 run.idempotency.conflict.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "kind"++ ],++ "properties": {++ "kind": {++ "const": "browser"++ },++ "width": {++ "$ref": "#/components/schemas/run.session.v1/$defs/size/properties/width",++ "description": "Default 1280."++ },++ "height": {++ "$ref": "#/components/schemas/run.session.v1/$defs/size/properties/height",++ "description": "Default 800."++ },++ "ttl_s": {++ "$ref": "#/components/schemas/run.session.v1/properties/ttl_s",++ "description": "Default 3600."++ },++ "idle_s": {++ "$ref": "#/components/schemas/run.session.v1/properties/idle_s",++ "description": "Default 900."++ },++ "machine": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "One of the person's own machines, by its OpenVibe.Bot id; the session goes there or nowhere."++ },++ "requirements": {++ "$ref": "#/components/schemas/platform.workload-requirements.v1"++ },++ "idempotency_key": {++ "$ref": "#/components/schemas/run.session.v1/$defs/idempotency_key"++ }++ },++ "examples": [++ {++ "kind": "browser"++ },++ {++ "kind": "browser",++ "width": 1440,++ "height": 900,++ "ttl_s": 1800,++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"++ }++ ]++ },++ "run.session.v1": {++ "title": "RunSession",++ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "owner",++ "via",++ "kind",++ "state",++ "size",++ "ttl_s",++ "idle_s",++ "placement",++ "timings",++ "end",++ "usage",++ "idempotency_key"++ ],++ "properties": {++ "id": {++ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"++ },++ "owner": {++ "$ref": "#/components/schemas/identity.subject-ref.v1",++ "description": "The person the session is for: only they (or a first-party service acting for them) read, act on or end it."++ },++ "via": {++ "type": [++ "string",++ "null"++ ],++ "pattern": "^service:[a-z][a-z0-9-]{0,40}$",++ "description": "The first-party service that asked for the person (service:actor), or null when they asked themselves."++ },++ "kind": {++ "const": "browser",++ "description": "A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1."++ },++ "state": {++ "enum": [++ "placing",++ "starting",++ "running",++ "ended"++ ]++ },++ "size": {++ "$ref": "#/components/schemas/run.session.v1/$defs/size"++ },++ "ttl_s": {++ "type": "integer",++ "minimum": 60,++ "maximum": 14400,++ "description": "The most the session lives, from created_at."++ },++ "idle_s": {++ "type": "integer",++ "minimum": 60,++ "maximum": 3600,++ "description": "A session no command used this long ends (end.reason idle)."++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "#/components/schemas/run.session.v1/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "$ref": "#/components/schemas/run.session.v1/$defs/timings"++ },++ "end": {++ "anyOf": [++ {++ "$ref": "#/components/schemas/run.session.v1/$defs/end"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "#/components/schemas/run.session.v1/$defs/usage"++ },++ "idempotency_key": {++ "anyOf": [++ {++ "$ref": "#/components/schemas/run.session.v1/$defs/idempotency_key"++ },++ {++ "type": "null"++ }++ ]++ }++ },++ "allOf": [++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "const": "ended"++ }++ }++ },++ "then": {++ "properties": {++ "end": {++ "type": "object"++ },++ "timings": {++ "type": "object",++ "properties": {++ "ended_at": {++ "type": "string"++ }++ }++ }++ }++ },++ "else": {++ "properties": {++ "end": {++ "type": "null"++ },++ "timings": {++ "type": "object",++ "properties": {++ "ended_at": {++ "type": "null"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "required": [++ "state"++ ],++ "properties": {++ "state": {++ "enum": [++ "starting",++ "running"++ ]++ }++ }++ },++ "then": {++ "properties": {++ "placement": {++ "type": "object"++ }++ }++ }++ }++ ],++ "$defs": {++ "session_id": {++ "type": "string",++ "pattern": "^ses_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "size": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "width",++ "height"++ ],++ "properties": {++ "width": {++ "type": "integer",++ "minimum": 640,++ "maximum": 3840++ },++ "height": {++ "type": "integer",++ "minimum": 480,++ "maximum": 2160++ }++ },++ "description": "The desk's page size (bot.command@1 kind desk width and height)."++ },++ "placement": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "machine",++ "desk",++ "trust",++ "offer_id",++ "region",++ "reasons"++ ],++ "properties": {++ "machine": {++ "type": "string",++ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The machine (OpenVibe.Bot device) the desk runs on."++ },++ "desk": {++ "type": "string",++ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$",++ "description": "The desk's name there (bot.command@1 desk), leased to this session alone."++ },++ "trust": {++ "enum": [++ "user-owned",++ "first-party",++ "partner",++ "community"++ ],++ "description": "The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine."++ },++ "offer_id": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 128,++ "description": "The platform.resource-offer@1 it was placed on; null when the request named the person's own machine."++ },++ "region": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 64++ },++ "reasons": {++ "type": "array",++ "maxItems": 8,++ "items": {++ "type": "string",++ "maxLength": 200++ },++ "description": "Why this machine, in a few words each (the placer's reasons)."++ }++ }++ },++ "idempotency_key": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "pattern": "^[A-Za-z0-9._:-]+$"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "started_at",++ "last_used_at",++ "ended_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the desk first answered (state running)."++ },++ "last_used_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "The last command, for idle_s."++ },++ "ended_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ }++ }++ },++ "usage": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "desk_seconds"++ ],++ "properties": {++ "desk_seconds": {++ "type": "number",++ "minimum": 0,++ "description": "Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started."++ }++ }++ },++ "end": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "reason",++ "detail"++ ],++ "properties": {++ "reason": {++ "enum": [++ "ended",++ "ttl",++ "idle",++ "host_lost",++ "unplaceable",++ "failed"++ ]++ },++ "detail": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 500++ }++ }++ }++ },++ "examples": [++ {++ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",++ "owner": {++ "type": "user",++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"++ },++ "via": "service:actor",++ "kind": "browser",++ "state": "running",++ "size": {++ "width": 1280,++ "height": 800++ },++ "ttl_s": 3600,++ "idle_s": 900,++ "placement": {++ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "desk": "desk-s0mnpqr",++ "trust": "community",++ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",++ "region": "eu",++ "reasons": [++ "a shared desk host with room"++ ]++ },++ "timings": {++ "created_at": "2026-10-28T10:00:00Z",++ "started_at": "2026-10-28T10:00:04Z",++ "last_used_at": "2026-10-28T10:05:00Z",++ "ended_at": null++ },++ "end": null,++ "usage": {++ "desk_seconds": 296++ },++ "idempotency_key": null++ }++ ]}},"securitySchemes": {@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Services API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Space API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Watch API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Work API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Zone API",-- "version": "0.222.0",-- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",++ "version": "0.223.0",++ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.","license": {"name": "MIT","identifier": "MIT"@@@ -206118,3 +206118,384 @@ export interface ChatInboxConversationsResult {message?: {};replies?: {}[];}++++/** [email protected] (owner: run) */++/**++ * run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).++ */++export type RunSession = {++ [k: string]: unknown | undefined;++} & {++ id: string;++ owner: SubjectRef;++ /**++ * The first-party service that asked for the person (service:actor), or null when they asked themselves.++ */++ via: string | null;++ /**++ * A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1.++ */++ kind: "browser";++ state: "placing" | "starting" | "running" | "ended";++ /**++ * The desk's page size (bot.command@1 kind desk width and height).++ */++ size: {++ width: number;++ height: number;++ };++ /**++ * The most the session lives, from created_at.++ */++ ttl_s: number;++ /**++ * A session no command used this long ends (end.reason idle).++ */++ idle_s: number;++ placement: {++ /**++ * The machine (OpenVibe.Bot device) the desk runs on.++ */++ machine: string;++ /**++ * The desk's name there (bot.command@1 desk), leased to this session alone.++ */++ desk: string;++ /**++ * The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine.++ */++ trust: "user-owned" | "first-party" | "partner" | "community";++ /**++ * The platform.resource-offer@1 it was placed on; null when the request named the person's own machine.++ */++ offer_id: string | null;++ region: string | null;++ /**++ * Why this machine, in a few words each (the placer's reasons).++ *++ * @maxItems 8++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string];++ } | null;++ timings: {++ created_at: string;++ /**++ * When the desk first answered (state running).++ */++ started_at: string | null;++ /**++ * The last command, for idle_s.++ */++ last_used_at: string | null;++ ended_at: string | null;++ };++ end: {++ reason: "ended" | "ttl" | "idle" | "host_lost" | "unplaceable" | "failed";++ detail: string | null;++ } | null;++ usage: {++ /**++ * Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started.++ */++ desk_seconds: number;++ };++ idempotency_key: string | null;++};++++/** [email protected] (owner: run) */++/**++ * run.session-create-request@1 (the Desks plan's D6): the body of POST /api/v1/sessions on OpenVibe.Run (capability run.session.create), answered 201 with run.session@1 (state placing). The session is the caller's own: a person's token, or a first-party service acting for a person (X-OV-Subject; the service is recorded as via). MACHINE names one of the person's own machines (an OpenVibe.Bot device they own and let agents use): the session goes there or ends unplaceable, never elsewhere. Without it Run places the desk: on the person's own machines when REQUIREMENTS name user-owned (and only theirs), else on desk hosts that serve other people (first-party, then community, unless REQUIREMENTS narrow it), each of whose desks runs in its own sandbox. Absent REQUIREMENTS mean { kind: run.browser, mobility: session, latency_class: interactive, objective: balanced }. TTL_S (default 3600) and IDLE_S (default 900) bound its life. IDEMPOTENCY_KEY: a repeat from the same person within 24 h with an identical body answers the first session (200); with a different body 409 run.idempotency.conflict.++ */++export interface RunSessionCreateRequest {++ kind: "browser";++ /**++ * Default 1280.++ */++ width?: number;++ /**++ * Default 800.++ */++ height?: number;++ /**++ * Default 3600.++ */++ ttl_s?: number;++ /**++ * Default 900.++ */++ idle_s?: number;++ /**++ * One of the person's own machines, by its OpenVibe.Bot id; the session goes there or nowhere.++ */++ machine?: string;++ requirements?: WorkloadRequirements;++ idempotency_key?: string;++}++++/** [email protected] (owner: run) */++/**++ * run.session-control-request@1 (the Desks plan's D6): one command on a session's desk, the body of POST /api/v1/sessions/{id}/commands on OpenVibe.Run (capability run.session.control), answered with run.session-control-result@1. KIND and VALUE are bot.command@1's for that kind (screen: a picture of the page; input: the mouse, wheel and keys on the page; browser: open, click, fill, press, back, read, screenshot) without desk, which is always the session's own: Run carries the command to OpenVibe.Bot on the session's lease (bot.desk-lease@1) and nothing on the machine other than that desk is reachable. A command on a session that is not running is refused 409 run.session.not_running. ID makes a retry safe: the same id within the session answers the first result.++ */++export interface RunSessionControlRequest {++ id?: string;++ kind: "screen" | "input" | "browser";++ /**++ * bot.command@1's value for that kind, without desk (refused 422 if it names another desk).++ */++ value: {};++}++++/** [email protected] (owner: run) */++/**++ * run.session-control-result@1 (the Desks plan's D6): the answer to run.session-control-request@1. RESULT is bot.command-result@1's: ack (the desk did it; DATA is its answer, a page as text and refs, a picture, a receipt), nack (the desk refused; CODE is its fault code and REASON says why), refused (OpenVibe.Bot's gate refused it; CODE is Bot's refusal code) or expired (no answer in time). Every answer also says how the session stands now, so a caller sees it end (idle, ttl, host_lost) without asking again.++ */++export interface RunSessionControlResult {++ session_id: string;++ id: string;++ result: "ack" | "nack" | "refused" | "expired";++ code?: string | null;++ reason?: string | null;++ /**++ * The desk's answer on an ack (bot.command-result@1 data).++ */++ data?: {};++ state: "placing" | "starting" | "running" | "ended";++}++++/** [email protected] (owner: run) */++/**++ * run.session-list-result@1 (the Desks plan's D6): GET /api/v1/sessions on OpenVibe.Run (capability run.session.read): the caller's own sessions, newest first, running ones by default (?state=ended or ?state=all for others), at most limit (default 20, at most 100) per page; next_cursor continues the list and is null on the last page.++ */++export interface RunSessionListResult {++ /**++ * @maxItems 100++ */++ sessions: RunSession[];++ next_cursor: string | null;++}++++/** [email protected] (owner: run) */++/**++ * run.session.started v1 (OpenVibe.Run; the Desks plan's D6). A session's desk answered for the first time: it runs on its machine, and desk_seconds count from timings.started_at. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.++ */++export interface RunSessionStartedPayload {++ id: string;++ owner: SubjectRef;++ /**++ * The first-party service that asked for the person (service:actor), or null when they asked themselves.++ */++ via: string | null;++ /**++ * A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1.++ */++ kind: "browser";++ state: "running";++ /**++ * The desk's page size (bot.command@1 kind desk width and height).++ */++ size: {++ width: number;++ height: number;++ };++ /**++ * The most the session lives, from created_at.++ */++ ttl_s: number;++ /**++ * A session no command used this long ends (end.reason idle).++ */++ idle_s: number;++ placement: {++ /**++ * The machine (OpenVibe.Bot device) the desk runs on.++ */++ machine: string;++ /**++ * The desk's name there (bot.command@1 desk), leased to this session alone.++ */++ desk: string;++ /**++ * The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine.++ */++ trust: "user-owned" | "first-party" | "partner" | "community";++ /**++ * The platform.resource-offer@1 it was placed on; null when the request named the person's own machine.++ */++ offer_id: string | null;++ region: string | null;++ /**++ * Why this machine, in a few words each (the placer's reasons).++ *++ * @maxItems 8++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string];++ };++ timings: {++ created_at: string;++ /**++ * When the desk first answered (state running).++ */++ started_at: string | null;++ /**++ * The last command, for idle_s.++ */++ last_used_at: string | null;++ ended_at: string | null;++ };++ end: null;++ usage: {++ /**++ * Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started.++ */++ desk_seconds: number;++ };++ idempotency_key: string | null;++}++++/** [email protected] (owner: run) */++/**++ * run.session.ended v1 (OpenVibe.Run; the Desks plan's D6). A session ended (end.reason ended, ttl, idle, host_lost, unplaceable or failed), its desk stopped and its lease given back; usage.desk_seconds is final. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.++ */++export interface RunSessionEndedPayload {++ id: string;++ owner: SubjectRef;++ /**++ * The first-party service that asked for the person (service:actor), or null when they asked themselves.++ */++ via: string | null;++ /**++ * A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1.++ */++ kind: "browser";++ state: "ended";++ /**++ * The desk's page size (bot.command@1 kind desk width and height).++ */++ size: {++ width: number;++ height: number;++ };++ /**++ * The most the session lives, from created_at.++ */++ ttl_s: number;++ /**++ * A session no command used this long ends (end.reason idle).++ */++ idle_s: number;++ placement: {++ /**++ * The machine (OpenVibe.Bot device) the desk runs on.++ */++ machine: string;++ /**++ * The desk's name there (bot.command@1 desk), leased to this session alone.++ */++ desk: string;++ /**++ * The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine.++ */++ trust: "user-owned" | "first-party" | "partner" | "community";++ /**++ * The platform.resource-offer@1 it was placed on; null when the request named the person's own machine.++ */++ offer_id: string | null;++ region: string | null;++ /**++ * Why this machine, in a few words each (the placer's reasons).++ *++ * @maxItems 8++ */++ reasons:++ | []++ | [string]++ | [string, string]++ | [string, string, string]++ | [string, string, string, string]++ | [string, string, string, string, string]++ | [string, string, string, string, string, string]++ | [string, string, string, string, string, string, string]++ | [string, string, string, string, string, string, string, string];++ } | null;++ timings: {++ created_at: string;++ /**++ * When the desk first answered (state running).++ */++ started_at: string | null;++ /**++ * The last command, for idle_s.++ */++ last_used_at: string | null;++ ended_at: string | null;++ };++ end: {++ reason: "ended" | "ttl" | "idle" | "host_lost" | "unplaceable" | "failed";++ detail: string | null;++ };++ usage: {++ /**++ * Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started.++ */++ desk_seconds: number;++ };++ idempotency_key: string | null;++}++++/** [email protected] (owner: bot) */++/**++ * bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).++ */++export interface BotDeskLease {++ machine_id: string;++ desk: string;++ session: string;++ /**++ * The session's person, the only one whose commands reach the desk.++ */++ owner: string;++ kind: "browser";++ width: number;++ height: number;++ /**++ * true on a desk host that serves other people (the desk runs in its own sandbox there); false on the person's own machine.++ */++ shared: boolean;++ created_at: string;++}++++/** [email protected] (owner: bot) */++/**++ * bot.desk-lease-create-request@1 (the Desks plan's D6): the body of POST /api/v1/machines/{id}/desk-leases on OpenVibe.Bot (capability bot.desk.session), answered 201 with bot.desk-lease@1. DESK is the name Run chose for the session (unique per session); a desk already leased to another session, or running for the machine's owner, is refused 409 bot.desk_busy, and a repeat for the same session answers its lease (200). A machine with no room (its max_desks) is refused 409 bot.machine_busy, one that is offline 409 bot.device_offline, and one the person may not use 404 bot.device_not_found.++ */++export interface BotDeskLeaseCreateRequest {++ session: string;++ desk: string;++ kind: "browser";++ /**++ * Default 1280.++ */++ width?: number;++ /**++ * Default 800.++ */++ height?: number;++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "id": "bot.desk.session",++ "version": "1.0",++ "owner": "bot",++ "status": "active",++ "visibility": "first-party",++ "sensitive": true,++ "description": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",++ "inputSchema": "bot.desk-lease-create-request@1",++ "outputSchema": "bot.desk-lease@1",++ "permissions": [++ "bot:desk:session"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "bot-control",++ "events": [++ "bot.command.refused"++ ],++ "implementedBy": [++ "POST /api/v1/machines/:id/desk-leases",++ "GET /api/v1/machines/:id/desk-leases/:desk",++ "POST /api/v1/machines/:id/desk-leases/:desk/commands (bot.command@1, answered by bot.command-result@1)",++ "DELETE /api/v1/machines/:id/desk-leases/:desk"++ ]++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "run.session.control",++ "version": "1.0",++ "owner": "run",++ "status": "active",++ "visibility": "public",++ "description": "Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-result@1) and POST /api/v1/sessions/:id/end (run.session@1, ended). Nothing else on the machine it runs on is reachable.",++ "inputSchema": "run.session-control-request@1",++ "outputSchema": "run.session-control-result@1",++ "permissions": [++ "run:session:control"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "run-write",++ "events": [++ "run.session.ended"++ ],++ "implementedBy": [++ "POST /api/v1/sessions/:id/commands",++ "POST /api/v1/sessions/:id/end"++ ]++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "id": "run.session.create",++ "version": "1.0",++ "owner": "run",++ "status": "active",++ "visibility": "public",++ "description": "Open a browser session for yourself (the Desks plan's D6): POST /api/v1/sessions with run.session-create-request@1. Run places a browser desk on one of your own machines (when you name it or your requirements name user-owned) or on a desk host that serves other people, where every desk runs in its own sandbox, and answers run.session@1. A first-party service acting for a person (X-OV-Subject) opens it for them and is recorded as via. Desk time is metered in desk-seconds.",++ "inputSchema": "run.session-create-request@1",++ "outputSchema": "run.session@1",++ "permissions": [++ "run:session:create"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "run-write",++ "events": [++ "run.session.started",++ "run.session.ended"++ ],++ "implementedBy": [++ "POST /api/v1/sessions"++ ]++}nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "id": "run.session.read",++ "version": "1.0",++ "owner": "run",++ "status": "active",++ "visibility": "public",++ "description": "Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds). Never another person's.",++ "inputSchema": "common.no-body@1",++ "outputSchema": "run.session@1",++ "permissions": [++ "run:session:read"++ ],++ "resourceConstraints": [++ "owner"++ ],++ "quotaClass": "run-read",++ "events": [],++ "implementedBy": [++ "GET /api/v1/sessions",++ "GET /api/v1/sessions/:id"++ ]++}@@@ -35,7 +35,8 @@"bot.device.connect","bot.job.dispatch","bot.resource.read",-- "bot.resource.control"++ "bot.resource.control",++ "bot.desk.session"],"eventsProduced": ["bot.robot.online",@@@ -25,7 +25,10 @@"run.job.list","run.job.cancel","run.job.stream",-- "run.job.admin"++ "run.job.admin",++ "run.session.create",++ "run.session.read",++ "run.session.control"],"eventsProduced": ["run.job.queued",@@@ -33,7 +36,9 @@"run.job.succeeded","run.job.failed","run.job.cancelled",-- "run.job.expired"++ "run.job.expired",++ "run.session.started",++ "run.session.ended"],"eventsConsumed": [],"namespacesOwned": [],@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.222.0",++ "version": "0.223.0","description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.","main": "index.js","types": "generated/typescript/index.d.ts",@@@ -11,7 +11,8 @@ const c = require('..');// the computer kinds).// bot.resource.control: an owner stops or resumes their robot from OpenVibe.Services (0.176.0, ADR-048 step 8 phase 2).// bot.machine.ai: a model on the person's own machine, for that person (0.219.0, the AI-on-your-machines plan).--const BOT_CAPABILITIES = ['bot.robot.read', 'bot.robot.manage', 'bot.robot.control', 'bot.computer.control', 'bot.machine.ai', 'bot.device.connect', 'bot.job.dispatch', 'bot.resource.read', 'bot.resource.control'];++// bot.desk.session: Run's sessions lease one desk each (0.223.0, the Desks plan's D6).++const BOT_CAPABILITIES = ['bot.robot.read', 'bot.robot.manage', 'bot.robot.control', 'bot.computer.control', 'bot.machine.ai', 'bot.desk.session', 'bot.device.connect', 'bot.job.dispatch', 'bot.resource.read', 'bot.resource.control'];const BOT_EVENTS = ['bot.robot.online', 'bot.robot.offline', 'bot.estop.set', 'bot.estop.cleared', 'bot.command.refused'];const bot = c.services.get('bot');@@@ -134,4 +134,34 @@ checks++;const sandboxDispatch = contracts.capabilities.get('sandbox_provider.job.dispatch');ok(sandboxDispatch && sandboxDispatch.owner === 'run' && sandboxDispatch.status === 'planned' && sandboxDispatch.visibility === 'internal' && sandboxDispatch.sensitive === true && sandboxDispatch.implementedBy.length === 0 && sandboxDispatch.inputSchema === 'sandbox-provider.job-create-request@1' && sandboxDispatch.outputSchema === 'sandbox-provider.job-create-result@1' && !run.capabilities.includes('sandbox_provider.job.dispatch'), 'sandbox_provider.job.dispatch is a planned internal sensitive capability of Run naming the two contracts');--console.log(`platform run: ${checks} job, frame and metering checks passed`);++// Run sessions (the Desks plan's D6): a session is a browser desk that Bot leases to it. The ids, the desk names and the++// size bounds are the same on both sides, a session's desk is always a named desk (never main or private), and its++// command is bot.command@1's for the kinds that act on a page, never on the machine.++const session = contracts.schema('run.session');++const lease = contracts.schema('bot.desk-lease');++ok(lease.properties.session.$ref === '../run/session.v1.json#/$defs/session_id', 'a lease names its session by run.session@1 id');++const deskPatterns = [];++(function walk(node) {++ if (!node || typeof node !== 'object') return;++ if (node.desk && typeof node.desk.pattern === 'string' && node.desk.pattern.includes('desk-')) deskPatterns.push(new RegExp(node.desk.pattern));++ Object.values(node).forEach(walk);++})(contracts.schema('bot.command'));++ok(deskPatterns.length > 0, 'bot.command@1 names desks');++const named = ['desk-s0mnpqr', 'desk-a'];++ok(named.every((d) => new RegExp(session.$defs.placement.properties.desk.pattern).test(d) && new RegExp(lease.properties.desk.pattern).test(d) && deskPatterns.every((re) => re.test(d))),++ 'a session desk is a bot.command@1 desk');++ok(!['main', 'private'].some((d) => new RegExp(lease.properties.desk.pattern).test(d)), 'a session never leases main or private');++assert.deepStrictEqual([lease.properties.width.minimum, lease.properties.width.maximum, lease.properties.height.minimum, lease.properties.height.maximum],++ [session.$defs.size.properties.width.minimum, session.$defs.size.properties.width.maximum, session.$defs.size.properties.height.minimum, session.$defs.size.properties.height.maximum]);++checks++;++assert.deepStrictEqual(contracts.schema('run.session-control-request').properties.kind.enum, ['screen', 'input', 'browser']);++checks++;++ok(['run.session.create', 'run.session.read', 'run.session.control'].every((id) => run.capabilities.includes(id) && contracts.capabilities.get(id).owner === 'run'), 'Run serves the three session capabilities');++const deskSession = contracts.capabilities.get('bot.desk.session');++ok(deskSession && deskSession.owner === 'bot' && deskSession.visibility === 'first-party' && deskSession.sensitive === true, 'bot.desk.session is a first-party sensitive capability of Bot');++// The usage a session meters: one reading per minute of desk time, keyed by session and minute (Billing replays, never 409s).++const deskReading = { id: 'run:ses_01JD2C3D4E5F6G7H8J9K0MNPQR:0', idempotency_key: 'run:ses_01JD2C3D4E5F6G7H8J9K0MNPQR:0', service: 'run', subject: 'user:usr_01JAB2C3D4E5F6G7H8J9K0MNPQ',++ resource: 'desk-seconds', node: 'dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X', operation: 'browser.desk', quantity: 60, unit: 's', at: '2026-10-28T10:01:04Z', source: 'openvibe-run.session' };++ok(contracts.validate('platform.usage-sample', deskReading).valid, 'a minute of desk time is a valid usage reading');++++console.log(`platform run: ${checks} job, frame, session and metering checks passed`);
@@ -4,6 +4,25 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,25 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
the notes were in the tag and commit messages (`git tag -n1`).
+## 0.223.0 — 2026-10-28
+
+- **Run sessions** (the Desks plan's D6; `OpenVibe_Agent_Desks_Plan_2026-10-27.md`). A session is a browser desk that
+ OpenVibe.Run places for one person and OpenVibe.Bot leases to it:
+ - `run.session@1` (states placing, starting, running, ended; where it runs and with what trust; desk-seconds),
+ `run.session-create-request@1` (`POST /api/v1/sessions`: the person's own named machine, or placement over desk
+ hosts, `ttl_s`, `idle_s`, requirements, an idempotency key), `run.session-control-request@1` and
+ `run.session-control-result@1` (`POST /api/v1/sessions/:id/commands`: screen, input and browser on the session's
+ desk only), `run.session-list-result@1`, and the events `run.session.started` and `run.session.ended`.
+ - Capabilities `run.session.create`, `run.session.read` and `run.session.control` (public, the caller's own sessions).
+ - Desk time is metered as one `platform.usage-sample@1` a minute (service `run`, operation `browser.desk`, unit `s`,
+ idempotency key `run:<session>:<minute>`).
+- **Desk leases on Bot**: `bot.desk-lease@1` and `bot.desk-lease-create-request@1`, with the capability
+ **`bot.desk.session`** (first-party, sensitive): Run leases one named desk for one session on the person's own machine
+ (where they let agents use it) or on a desk host whose owner offers it to other people and whose OpenVibe.Node runs
+ every desk in its own sandbox (Node v0.32.0, `desk_isolation: sandbox`); only that session's commands reach the desk,
+ never the machine itself.
+- Fixtures for each, and `test/platform-run.test.js` holds the two sides together (ids, desk names, sizes, kinds).
+
## 0.222.0 — 2026-10-27
## 0.222.0 — 2026-10-27
- **Site names say their domain** (owner, 2026-10-27: "it says memberships … couldn't it say VIP memberships since the
- **Site names say their domain** (owner, 2026-10-27: "it says memberships … couldn't it say VIP memberships since the
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/bot/desk-lease-create-request.v1.json",
+ "title": "BotDeskLeaseCreateRequest",
+ "description": "bot.desk-lease-create-request@1 (the Desks plan's D6): the body of POST /api/v1/machines/{id}/desk-leases on OpenVibe.Bot (capability bot.desk.session), answered 201 with bot.desk-lease@1. DESK is the name Run chose for the session (unique per session); a desk already leased to another session, or running for the machine's owner, is refused 409 bot.desk_busy, and a repeat for the same session answers its lease (200). A machine with no room (its max_desks) is refused 409 bot.machine_busy, one that is offline 409 bot.device_offline, and one the person may not use 404 bot.device_not_found.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "session",
+ "desk",
+ "kind"
+ ],
+ "properties": {
+ "session": {
+ "$ref": "../run/session.v1.json#/$defs/session_id"
+ },
+ "desk": {
+ "$ref": "desk-lease.v1.json#/properties/desk"
+ },
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "$ref": "desk-lease.v1.json#/properties/width",
+ "description": "Default 1280."
+ },
+ "height": {
+ "$ref": "desk-lease.v1.json#/properties/height",
+ "description": "Default 800."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/bot/desk-lease.v1.json",
+ "title": "BotDeskLease",
+ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "machine_id",
+ "desk",
+ "session",
+ "owner",
+ "kind",
+ "width",
+ "height",
+ "shared",
+ "created_at"
+ ],
+ "properties": {
+ "machine_id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "desk": {
+ "type": "string",
+ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$"
+ },
+ "session": {
+ "$ref": "../run/session.v1.json#/$defs/session_id"
+ },
+ "owner": {
+ "type": "string",
+ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The session's person, the only one whose commands reach the desk."
+ },
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 640,
+ "maximum": 3840
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 480,
+ "maximum": 2160
+ },
+ "shared": {
+ "type": "boolean",
+ "description": "true on a desk host that serves other people (the desk runs in its own sandbox there); false on the person's own machine."
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+}
@@ -7485,6 +7485,89 @@
@@ -7485,6 +7485,89 @@
"visibility": "public",
"visibility": "public",
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
+ },
+ {
+ "id": "run.session",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/session.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "run.session-create-request",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/session-create-request.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "run.session-control-request",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/session-control-request.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "run.session-control-result",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/session-control-result.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "run.session-list-result",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/session-list-result.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "run.session.started",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "events/payloads/run.session.started.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "run.session.ended",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "events/payloads/run.session.ended.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "bot.desk-lease",
+ "version": "1.0.0",
+ "owner": "bot",
+ "schema": "bot/desk-lease.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-043"
+ },
+ {
+ "id": "bot.desk-lease-create-request",
+ "version": "1.0.0",
+ "owner": "bot",
+ "schema": "bot/desk-lease-create-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active",
+ "adr": "ADR-043"
}
}
]
]
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.session.ended.v1.json",
+ "title": "RunSessionEndedPayload",
+ "description": "run.session.ended v1 (OpenVibe.Run; the Desks plan's D6). A session ended (end.reason ended, ttl, idle, host_lost, unplaceable or failed), its desk stopped and its lease given back; usage.desk_seconds is final. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.",
+ "type": "object",
+ "required": [
+ "id",
+ "owner",
+ "via",
+ "kind",
+ "state",
+ "size",
+ "ttl_s",
+ "idle_s",
+ "placement",
+ "timings",
+ "end",
+ "usage",
+ "idempotency_key"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "$ref": "../../run/session.v1.json#/$defs/session_id"
+ },
+ "owner": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "via": {
+ "$ref": "../../run/session.v1.json#/properties/via"
+ },
+ "kind": {
+ "$ref": "../../run/session.v1.json#/properties/kind"
+ },
+ "state": {
+ "const": "ended"
+ },
+ "size": {
+ "$ref": "../../run/session.v1.json#/$defs/size"
+ },
+ "ttl_s": {
+ "$ref": "../../run/session.v1.json#/properties/ttl_s"
+ },
+ "idle_s": {
+ "$ref": "../../run/session.v1.json#/properties/idle_s"
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "../../run/session.v1.json#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "$ref": "../../run/session.v1.json#/$defs/timings"
+ },
+ "end": {
+ "$ref": "../../run/session.v1.json#/$defs/end"
+ },
+ "usage": {
+ "$ref": "../../run/session.v1.json#/$defs/usage"
+ },
+ "idempotency_key": {
+ "$ref": "../../run/session.v1.json#/properties/idempotency_key"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.session.started.v1.json",
+ "title": "RunSessionStartedPayload",
+ "description": "run.session.started v1 (OpenVibe.Run; the Desks plan's D6). A session's desk answered for the first time: it runs on its machine, and desk_seconds count from timings.started_at. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.",
+ "type": "object",
+ "required": [
+ "id",
+ "owner",
+ "via",
+ "kind",
+ "state",
+ "size",
+ "ttl_s",
+ "idle_s",
+ "placement",
+ "timings",
+ "end",
+ "usage",
+ "idempotency_key"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "$ref": "../../run/session.v1.json#/$defs/session_id"
+ },
+ "owner": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "via": {
+ "$ref": "../../run/session.v1.json#/properties/via"
+ },
+ "kind": {
+ "$ref": "../../run/session.v1.json#/properties/kind"
+ },
+ "state": {
+ "const": "running"
+ },
+ "size": {
+ "$ref": "../../run/session.v1.json#/$defs/size"
+ },
+ "ttl_s": {
+ "$ref": "../../run/session.v1.json#/properties/ttl_s"
+ },
+ "idle_s": {
+ "$ref": "../../run/session.v1.json#/properties/idle_s"
+ },
+ "placement": {
+ "$ref": "../../run/session.v1.json#/$defs/placement"
+ },
+ "timings": {
+ "$ref": "../../run/session.v1.json#/$defs/timings"
+ },
+ "end": {
+ "type": "null"
+ },
+ "usage": {
+ "$ref": "../../run/session.v1.json#/$defs/usage"
+ },
+ "idempotency_key": {
+ "$ref": "../../run/session.v1.json#/properties/idempotency_key"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session-control-request.v1.json",
+ "title": "RunSessionControlRequest",
+ "description": "run.session-control-request@1 (the Desks plan's D6): one command on a session's desk, the body of POST /api/v1/sessions/{id}/commands on OpenVibe.Run (capability run.session.control), answered with run.session-control-result@1. KIND and VALUE are bot.command@1's for that kind (screen: a picture of the page; input: the mouse, wheel and keys on the page; browser: open, click, fill, press, back, read, screenshot) without desk, which is always the session's own: Run carries the command to OpenVibe.Bot on the session's lease (bot.desk-lease@1) and nothing on the machine other than that desk is reachable. A command on a session that is not running is refused 409 run.session.not_running. ID makes a retry safe: the same id within the session answers the first result.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind",
+ "value"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64,
+ "pattern": "^[A-Za-z0-9._:-]+$"
+ },
+ "kind": {
+ "enum": [
+ "screen",
+ "input",
+ "browser"
+ ]
+ },
+ "value": {
+ "type": "object",
+ "description": "bot.command@1's value for that kind, without desk (refused 422 if it names another desk).",
+ "not": {
+ "required": [
+ "desk"
+ ]
+ }
+ }
+ },
+ "examples": [
+ {
+ "kind": "browser",
+ "value": {
+ "action": "open",
+ "url": "https://example.org/"
+ }
+ },
+ {
+ "id": "c-7",
+ "kind": "screen",
+ "value": {
+ "max_width": 1024
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session-control-result.v1.json",
+ "title": "RunSessionControlResult",
+ "description": "run.session-control-result@1 (the Desks plan's D6): the answer to run.session-control-request@1. RESULT is bot.command-result@1's: ack (the desk did it; DATA is its answer, a page as text and refs, a picture, a receipt), nack (the desk refused; CODE is its fault code and REASON says why), refused (OpenVibe.Bot's gate refused it; CODE is Bot's refusal code) or expired (no answer in time). Every answer also says how the session stands now, so a caller sees it end (idle, ttl, host_lost) without asking again.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "session_id",
+ "id",
+ "result",
+ "state"
+ ],
+ "properties": {
+ "session_id": {
+ "$ref": "session.v1.json#/$defs/session_id"
+ },
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "result": {
+ "enum": [
+ "ack",
+ "nack",
+ "refused",
+ "expired"
+ ]
+ },
+ "code": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 1000
+ },
+ "data": {
+ "type": "object",
+ "description": "The desk's answer on an ack (bot.command-result@1 data)."
+ },
+ "state": {
+ "$ref": "session.v1.json#/properties/state"
+ }
+ },
+ "examples": [
+ {
+ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "id": "c-7",
+ "result": "ack",
+ "code": null,
+ "reason": null,
+ "data": {
+ "format": "jpeg",
+ "width": 1024,
+ "height": 640,
+ "content_b64": "/9j/"
+ },
+ "state": "running"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session-create-request.v1.json",
+ "title": "RunSessionCreateRequest",
+ "description": "run.session-create-request@1 (the Desks plan's D6): the body of POST /api/v1/sessions on OpenVibe.Run (capability run.session.create), answered 201 with run.session@1 (state placing). The session is the caller's own: a person's token, or a first-party service acting for a person (X-OV-Subject; the service is recorded as via). MACHINE names one of the person's own machines (an OpenVibe.Bot device they own and let agents use): the session goes there or ends unplaceable, never elsewhere. Without it Run places the desk: on the person's own machines when REQUIREMENTS name user-owned (and only theirs), else on desk hosts that serve other people (first-party, then community, unless REQUIREMENTS narrow it), each of whose desks runs in its own sandbox. Absent REQUIREMENTS mean { kind: run.browser, mobility: session, latency_class: interactive, objective: balanced }. TTL_S (default 3600) and IDLE_S (default 900) bound its life. IDEMPOTENCY_KEY: a repeat from the same person within 24 h with an identical body answers the first session (200); with a different body 409 run.idempotency.conflict.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind"
+ ],
+ "properties": {
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "$ref": "session.v1.json#/$defs/size/properties/width",
+ "description": "Default 1280."
+ },
+ "height": {
+ "$ref": "session.v1.json#/$defs/size/properties/height",
+ "description": "Default 800."
+ },
+ "ttl_s": {
+ "$ref": "session.v1.json#/properties/ttl_s",
+ "description": "Default 3600."
+ },
+ "idle_s": {
+ "$ref": "session.v1.json#/properties/idle_s",
+ "description": "Default 900."
+ },
+ "machine": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "One of the person's own machines, by its OpenVibe.Bot id; the session goes there or nowhere."
+ },
+ "requirements": {
+ "$ref": "../platform/workload-requirements.v1.json"
+ },
+ "idempotency_key": {
+ "$ref": "session.v1.json#/$defs/idempotency_key"
+ }
+ },
+ "examples": [
+ {
+ "kind": "browser"
+ },
+ {
+ "kind": "browser",
+ "width": 1440,
+ "height": 900,
+ "ttl_s": 1800,
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session-list-result.v1.json",
+ "title": "RunSessionListResult",
+ "description": "run.session-list-result@1 (the Desks plan's D6): GET /api/v1/sessions on OpenVibe.Run (capability run.session.read): the caller's own sessions, newest first, running ones by default (?state=ended or ?state=all for others), at most limit (default 20, at most 100) per page; next_cursor continues the list and is null on the last page.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "sessions",
+ "next_cursor"
+ ],
+ "properties": {
+ "sessions": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "$ref": "session.v1.json"
+ }
+ },
+ "next_cursor": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 200
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,378 @@
@@ -0,0 +1,378 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session.v1.json",
+ "title": "RunSession",
+ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "owner",
+ "via",
+ "kind",
+ "state",
+ "size",
+ "ttl_s",
+ "idle_s",
+ "placement",
+ "timings",
+ "end",
+ "usage",
+ "idempotency_key"
+ ],
+ "properties": {
+ "id": {
+ "$ref": "#/$defs/session_id"
+ },
+ "owner": {
+ "$ref": "../identity/subject-ref.v1.json",
+ "description": "The person the session is for: only they (or a first-party service acting for them) read, act on or end it."
+ },
+ "via": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^service:[a-z][a-z0-9-]{0,40}$",
+ "description": "The first-party service that asked for the person (service:actor), or null when they asked themselves."
+ },
+ "kind": {
+ "const": "browser",
+ "description": "A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1."
+ },
+ "state": {
+ "enum": [
+ "placing",
+ "starting",
+ "running",
+ "ended"
+ ]
+ },
+ "size": {
+ "$ref": "#/$defs/size"
+ },
+ "ttl_s": {
+ "type": "integer",
+ "minimum": 60,
+ "maximum": 14400,
+ "description": "The most the session lives, from created_at."
+ },
+ "idle_s": {
+ "type": "integer",
+ "minimum": 60,
+ "maximum": 3600,
+ "description": "A session no command used this long ends (end.reason idle)."
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "$ref": "#/$defs/timings"
+ },
+ "end": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/end"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "#/$defs/usage"
+ },
+ "idempotency_key": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/idempotency_key"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "const": "ended"
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "end": {
+ "type": "object"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "ended_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "end": {
+ "type": "null"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "ended_at": {
+ "type": "null"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "enum": [
+ "starting",
+ "running"
+ ]
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "placement": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ ],
+ "$defs": {
+ "session_id": {
+ "type": "string",
+ "pattern": "^ses_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "size": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "width",
+ "height"
+ ],
+ "properties": {
+ "width": {
+ "type": "integer",
+ "minimum": 640,
+ "maximum": 3840
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 480,
+ "maximum": 2160
+ }
+ },
+ "description": "The desk's page size (bot.command@1 kind desk width and height)."
+ },
+ "placement": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "machine",
+ "desk",
+ "trust",
+ "offer_id",
+ "region",
+ "reasons"
+ ],
+ "properties": {
+ "machine": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine (OpenVibe.Bot device) the desk runs on."
+ },
+ "desk": {
+ "type": "string",
+ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$",
+ "description": "The desk's name there (bot.command@1 desk), leased to this session alone."
+ },
+ "trust": {
+ "enum": [
+ "user-owned",
+ "first-party",
+ "partner",
+ "community"
+ ],
+ "description": "The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine."
+ },
+ "offer_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 128,
+ "description": "The platform.resource-offer@1 it was placed on; null when the request named the person's own machine."
+ },
+ "region": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 64
+ },
+ "reasons": {
+ "type": "array",
+ "maxItems": 8,
+ "items": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "description": "Why this machine, in a few words each (the placer's reasons)."
+ }
+ }
+ },
+ "idempotency_key": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "pattern": "^[A-Za-z0-9._:-]+$"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "started_at",
+ "last_used_at",
+ "ended_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the desk first answered (state running)."
+ },
+ "last_used_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "The last command, for idle_s."
+ },
+ "ended_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "desk_seconds"
+ ],
+ "properties": {
+ "desk_seconds": {
+ "type": "number",
+ "minimum": 0,
+ "description": "Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started."
+ }
+ }
+ },
+ "end": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "reason",
+ "detail"
+ ],
+ "properties": {
+ "reason": {
+ "enum": [
+ "ended",
+ "ttl",
+ "idle",
+ "host_lost",
+ "unplaceable",
+ "failed"
+ ]
+ },
+ "detail": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500
+ }
+ }
+ }
+ },
+ "examples": [
+ {
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "desk": "desk-s0mnpqr",
+ "kind": "desktop"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "desk": "main",
+ "kind": "browser"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "desk": "desk-s0mnpqr",
+ "kind": "browser",
+ "width": 1280,
+ "height": 800
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "machine_id": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "private",
+ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": "user:usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "kind": "browser",
+ "width": 1280,
+ "height": 800,
+ "shared": true,
+ "created_at": "2026-10-28T10:00:01Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "machine_id": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": "service:run",
+ "kind": "browser",
+ "width": 1280,
+ "height": 800,
+ "shared": true,
+ "created_at": "2026-10-28T10:00:01Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "machine_id": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "session": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": "user:usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "kind": "browser",
+ "width": 1280,
+ "height": 800,
+ "shared": true,
+ "created_at": "2026-10-28T10:00:01Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "kind": "exec",
+ "value": {
+ "argv": [
+ "id"
+ ]
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "kind": "browser",
+ "value": {
+ "action": "read",
+ "desk": "desk-other"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "kind": "browser",
+ "value": {
+ "action": "open",
+ "url": "https://example.org/"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "id": "c-7",
+ "kind": "screen",
+ "value": {
+ "max_width": 1024
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "id": "c-7",
+ "result": "ack"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+{
+ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "id": "c-7",
+ "result": "ack",
+ "code": null,
+ "reason": null,
+ "data": {
+ "format": "jpeg",
+ "width": 1024,
+ "height": 640,
+ "content_b64": "/9j/"
+ },
+ "state": "running"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "id": "c-8",
+ "result": "refused",
+ "code": "bot.device_offline",
+ "reason": "the machine is offline",
+ "state": "running"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "width": 1280
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "kind": "browser",
+ "machine": "rob_01J8Z4M2Q0R7T9YV3K6N8P1W2X"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "kind": "browser",
+ "ttl_s": 86400
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "kind": "browser",
+ "requirements": {
+ "kind": "run.browser",
+ "mobility": "session",
+ "latency_class": "interactive",
+ "objective": "cheapest",
+ "trust": [
+ "community"
+ ]
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "kind": "browser"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "kind": "browser",
+ "width": 1440,
+ "height": 900,
+ "ttl_s": 1800,
+ "idle_s": 600,
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "sessions": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
+{
+ "sessions": [
+ {
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+ }
+ ],
+ "next_cursor": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "ended",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": "2026-10-28T10:20:00Z"
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 1196
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "ended",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": "2026-10-28T10:20:00Z"
+ },
+ "end": {
+ "reason": "idle",
+ "detail": "no command for 900 s"
+ },
+ "usage": {
+ "desk_seconds": 1196
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "placing",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "desktop",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "ended",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": "2026-10-28T10:20:00Z"
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 1196
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "main",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": {
+ "reason": "idle",
+ "detail": null
+ },
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "ended",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": "2026-10-28T10:20:00Z"
+ },
+ "end": {
+ "reason": "idle",
+ "detail": "no command for 900 s"
+ },
+ "usage": {
+ "desk_seconds": 1196
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": null,
+ "kind": "browser",
+ "state": "ended",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": null,
+ "last_used_at": null,
+ "ended_at": "2026-10-28T10:01:00Z"
+ },
+ "end": {
+ "reason": "unplaceable",
+ "detail": "no desk host had room"
+ },
+ "usage": {
+ "desk_seconds": 0
+ },
+ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": null,
+ "kind": "browser",
+ "state": "placing",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": null,
+ "last_used_at": null,
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 0
+ },
+ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "user-owned",
+ "offer_id": null,
+ "region": null,
+ "reasons": [
+ "your own machine, as asked"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+}
@@ -83773,6 +83773,859 @@
@@ -83773,6 +83773,859 @@
}
}
}
}
}
}
+ },
+ "run.session": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session.v1.json",
+ "title": "RunSession",
+ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "owner",
+ "via",
+ "kind",
+ "state",
+ "size",
+ "ttl_s",
+ "idle_s",
+ "placement",
+ "timings",
+ "end",
+ "usage",
+ "idempotency_key"
+ ],
+ "properties": {
+ "id": {
+ "$ref": "#/$defs/session_id"
+ },
+ "owner": {
+ "$ref": "../identity/subject-ref.v1.json",
+ "description": "The person the session is for: only they (or a first-party service acting for them) read, act on or end it."
+ },
+ "via": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^service:[a-z][a-z0-9-]{0,40}$",
+ "description": "The first-party service that asked for the person (service:actor), or null when they asked themselves."
+ },
+ "kind": {
+ "const": "browser",
+ "description": "A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1."
+ },
+ "state": {
+ "enum": [
+ "placing",
+ "starting",
+ "running",
+ "ended"
+ ]
+ },
+ "size": {
+ "$ref": "#/$defs/size"
+ },
+ "ttl_s": {
+ "type": "integer",
+ "minimum": 60,
+ "maximum": 14400,
+ "description": "The most the session lives, from created_at."
+ },
+ "idle_s": {
+ "type": "integer",
+ "minimum": 60,
+ "maximum": 3600,
+ "description": "A session no command used this long ends (end.reason idle)."
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "$ref": "#/$defs/timings"
+ },
+ "end": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/end"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "#/$defs/usage"
+ },
+ "idempotency_key": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/idempotency_key"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "const": "ended"
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "end": {
+ "type": "object"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "ended_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "end": {
+ "type": "null"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "ended_at": {
+ "type": "null"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "enum": [
+ "starting",
+ "running"
+ ]
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "placement": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ ],
+ "$defs": {
+ "session_id": {
+ "type": "string",
+ "pattern": "^ses_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "size": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "width",
+ "height"
+ ],
+ "properties": {
+ "width": {
+ "type": "integer",
+ "minimum": 640,
+ "maximum": 3840
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 480,
+ "maximum": 2160
+ }
+ },
+ "description": "The desk's page size (bot.command@1 kind desk width and height)."
+ },
+ "placement": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "machine",
+ "desk",
+ "trust",
+ "offer_id",
+ "region",
+ "reasons"
+ ],
+ "properties": {
+ "machine": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine (OpenVibe.Bot device) the desk runs on."
+ },
+ "desk": {
+ "type": "string",
+ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$",
+ "description": "The desk's name there (bot.command@1 desk), leased to this session alone."
+ },
+ "trust": {
+ "enum": [
+ "user-owned",
+ "first-party",
+ "partner",
+ "community"
+ ],
+ "description": "The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine."
+ },
+ "offer_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 128,
+ "description": "The platform.resource-offer@1 it was placed on; null when the request named the person's own machine."
+ },
+ "region": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 64
+ },
+ "reasons": {
+ "type": "array",
+ "maxItems": 8,
+ "items": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "description": "Why this machine, in a few words each (the placer's reasons)."
+ }
+ }
+ },
+ "idempotency_key": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "pattern": "^[A-Za-z0-9._:-]+$"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "started_at",
+ "last_used_at",
+ "ended_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the desk first answered (state running)."
+ },
+ "last_used_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "The last command, for idle_s."
+ },
+ "ended_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "desk_seconds"
+ ],
+ "properties": {
+ "desk_seconds": {
+ "type": "number",
+ "minimum": 0,
+ "description": "Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started."
+ }
+ }
+ },
+ "end": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "reason",
+ "detail"
+ ],
+ "properties": {
+ "reason": {
+ "enum": [
+ "ended",
+ "ttl",
+ "idle",
+ "host_lost",
+ "unplaceable",
+ "failed"
+ ]
+ },
+ "detail": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500
+ }
+ }
+ }
+ },
+ "examples": [
+ {
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+ }
+ ]
+ }
+ },
+ "run.session-create-request": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session-create-request.v1.json",
+ "title": "RunSessionCreateRequest",
+ "description": "run.session-create-request@1 (the Desks plan's D6): the body of POST /api/v1/sessions on OpenVibe.Run (capability run.session.create), answered 201 with run.session@1 (state placing). The session is the caller's own: a person's token, or a first-party service acting for a person (X-OV-Subject; the service is recorded as via). MACHINE names one of the person's own machines (an OpenVibe.Bot device they own and let agents use): the session goes there or ends unplaceable, never elsewhere. Without it Run places the desk: on the person's own machines when REQUIREMENTS name user-owned (and only theirs), else on desk hosts that serve other people (first-party, then community, unless REQUIREMENTS narrow it), each of whose desks runs in its own sandbox. Absent REQUIREMENTS mean { kind: run.browser, mobility: session, latency_class: interactive, objective: balanced }. TTL_S (default 3600) and IDLE_S (default 900) bound its life. IDEMPOTENCY_KEY: a repeat from the same person within 24 h with an identical body answers the first session (200); with a different body 409 run.idempotency.conflict.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind"
+ ],
+ "properties": {
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "$ref": "session.v1.json#/$defs/size/properties/width",
+ "description": "Default 1280."
+ },
+ "height": {
+ "$ref": "session.v1.json#/$defs/size/properties/height",
+ "description": "Default 800."
+ },
+ "ttl_s": {
+ "$ref": "session.v1.json#/properties/ttl_s",
+ "description": "Default 3600."
+ },
+ "idle_s": {
+ "$ref": "session.v1.json#/properties/idle_s",
+ "description": "Default 900."
+ },
+ "machine": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "One of the person's own machines, by its OpenVibe.Bot id; the session goes there or nowhere."
+ },
+ "requirements": {
+ "$ref": "../platform/workload-requirements.v1.json"
+ },
+ "idempotency_key": {
+ "$ref": "session.v1.json#/$defs/idempotency_key"
+ }
+ },
+ "examples": [
+ {
+ "kind": "browser"
+ },
+ {
+ "kind": "browser",
+ "width": 1440,
+ "height": 900,
+ "ttl_s": 1800,
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"
+ }
+ ]
+ }
+ },
+ "run.session-control-request": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session-control-request.v1.json",
+ "title": "RunSessionControlRequest",
+ "description": "run.session-control-request@1 (the Desks plan's D6): one command on a session's desk, the body of POST /api/v1/sessions/{id}/commands on OpenVibe.Run (capability run.session.control), answered with run.session-control-result@1. KIND and VALUE are bot.command@1's for that kind (screen: a picture of the page; input: the mouse, wheel and keys on the page; browser: open, click, fill, press, back, read, screenshot) without desk, which is always the session's own: Run carries the command to OpenVibe.Bot on the session's lease (bot.desk-lease@1) and nothing on the machine other than that desk is reachable. A command on a session that is not running is refused 409 run.session.not_running. ID makes a retry safe: the same id within the session answers the first result.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind",
+ "value"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64,
+ "pattern": "^[A-Za-z0-9._:-]+$"
+ },
+ "kind": {
+ "enum": [
+ "screen",
+ "input",
+ "browser"
+ ]
+ },
+ "value": {
+ "type": "object",
+ "description": "bot.command@1's value for that kind, without desk (refused 422 if it names another desk).",
+ "not": {
+ "required": [
+ "desk"
+ ]
+ }
+ }
+ },
+ "examples": [
+ {
+ "kind": "browser",
+ "value": {
+ "action": "open",
+ "url": "https://example.org/"
+ }
+ },
+ {
+ "id": "c-7",
+ "kind": "screen",
+ "value": {
+ "max_width": 1024
+ }
+ }
+ ]
+ }
+ },
+ "run.session-control-result": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session-control-result.v1.json",
+ "title": "RunSessionControlResult",
+ "description": "run.session-control-result@1 (the Desks plan's D6): the answer to run.session-control-request@1. RESULT is bot.command-result@1's: ack (the desk did it; DATA is its answer, a page as text and refs, a picture, a receipt), nack (the desk refused; CODE is its fault code and REASON says why), refused (OpenVibe.Bot's gate refused it; CODE is Bot's refusal code) or expired (no answer in time). Every answer also says how the session stands now, so a caller sees it end (idle, ttl, host_lost) without asking again.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "session_id",
+ "id",
+ "result",
+ "state"
+ ],
+ "properties": {
+ "session_id": {
+ "$ref": "session.v1.json#/$defs/session_id"
+ },
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "result": {
+ "enum": [
+ "ack",
+ "nack",
+ "refused",
+ "expired"
+ ]
+ },
+ "code": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 1000
+ },
+ "data": {
+ "type": "object",
+ "description": "The desk's answer on an ack (bot.command-result@1 data)."
+ },
+ "state": {
+ "$ref": "session.v1.json#/properties/state"
+ }
+ },
+ "examples": [
+ {
+ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "id": "c-7",
+ "result": "ack",
+ "code": null,
+ "reason": null,
+ "data": {
+ "format": "jpeg",
+ "width": 1024,
+ "height": 640,
+ "content_b64": "/9j/"
+ },
+ "state": "running"
+ }
+ ]
+ }
+ },
+ "run.session-list-result": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/session-list-result.v1.json",
+ "title": "RunSessionListResult",
+ "description": "run.session-list-result@1 (the Desks plan's D6): GET /api/v1/sessions on OpenVibe.Run (capability run.session.read): the caller's own sessions, newest first, running ones by default (?state=ended or ?state=all for others), at most limit (default 20, at most 100) per page; next_cursor continues the list and is null on the last page.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "sessions",
+ "next_cursor"
+ ],
+ "properties": {
+ "sessions": {
+ "type": "array",
+ "maxItems": 100,
+ "items": {
+ "$ref": "session.v1.json"
+ }
+ },
+ "next_cursor": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 200
+ }
+ }
+ }
+ },
+ "run.session.started": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.session.started.v1.json",
+ "title": "RunSessionStartedPayload",
+ "description": "run.session.started v1 (OpenVibe.Run; the Desks plan's D6). A session's desk answered for the first time: it runs on its machine, and desk_seconds count from timings.started_at. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.",
+ "type": "object",
+ "required": [
+ "id",
+ "owner",
+ "via",
+ "kind",
+ "state",
+ "size",
+ "ttl_s",
+ "idle_s",
+ "placement",
+ "timings",
+ "end",
+ "usage",
+ "idempotency_key"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "$ref": "../../run/session.v1.json#/$defs/session_id"
+ },
+ "owner": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "via": {
+ "$ref": "../../run/session.v1.json#/properties/via"
+ },
+ "kind": {
+ "$ref": "../../run/session.v1.json#/properties/kind"
+ },
+ "state": {
+ "const": "running"
+ },
+ "size": {
+ "$ref": "../../run/session.v1.json#/$defs/size"
+ },
+ "ttl_s": {
+ "$ref": "../../run/session.v1.json#/properties/ttl_s"
+ },
+ "idle_s": {
+ "$ref": "../../run/session.v1.json#/properties/idle_s"
+ },
+ "placement": {
+ "$ref": "../../run/session.v1.json#/$defs/placement"
+ },
+ "timings": {
+ "$ref": "../../run/session.v1.json#/$defs/timings"
+ },
+ "end": {
+ "type": "null"
+ },
+ "usage": {
+ "$ref": "../../run/session.v1.json#/$defs/usage"
+ },
+ "idempotency_key": {
+ "$ref": "../../run/session.v1.json#/properties/idempotency_key"
+ }
+ }
+ }
+ },
+ "run.session.ended": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.session.ended.v1.json",
+ "title": "RunSessionEndedPayload",
+ "description": "run.session.ended v1 (OpenVibe.Run; the Desks plan's D6). A session ended (end.reason ended, ttl, idle, host_lost, unplaceable or failed), its desk stopped and its lease given back; usage.desk_seconds is final. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.",
+ "type": "object",
+ "required": [
+ "id",
+ "owner",
+ "via",
+ "kind",
+ "state",
+ "size",
+ "ttl_s",
+ "idle_s",
+ "placement",
+ "timings",
+ "end",
+ "usage",
+ "idempotency_key"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "$ref": "../../run/session.v1.json#/$defs/session_id"
+ },
+ "owner": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "via": {
+ "$ref": "../../run/session.v1.json#/properties/via"
+ },
+ "kind": {
+ "$ref": "../../run/session.v1.json#/properties/kind"
+ },
+ "state": {
+ "const": "ended"
+ },
+ "size": {
+ "$ref": "../../run/session.v1.json#/$defs/size"
+ },
+ "ttl_s": {
+ "$ref": "../../run/session.v1.json#/properties/ttl_s"
+ },
+ "idle_s": {
+ "$ref": "../../run/session.v1.json#/properties/idle_s"
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "../../run/session.v1.json#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "$ref": "../../run/session.v1.json#/$defs/timings"
+ },
+ "end": {
+ "$ref": "../../run/session.v1.json#/$defs/end"
+ },
+ "usage": {
+ "$ref": "../../run/session.v1.json#/$defs/usage"
+ },
+ "idempotency_key": {
+ "$ref": "../../run/session.v1.json#/properties/idempotency_key"
+ }
+ }
+ }
+ },
+ "bot.desk-lease": {
+ "version": "1.0.0",
+ "owner": "bot",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/bot/desk-lease.v1.json",
+ "title": "BotDeskLease",
+ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "machine_id",
+ "desk",
+ "session",
+ "owner",
+ "kind",
+ "width",
+ "height",
+ "shared",
+ "created_at"
+ ],
+ "properties": {
+ "machine_id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "desk": {
+ "type": "string",
+ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$"
+ },
+ "session": {
+ "$ref": "../run/session.v1.json#/$defs/session_id"
+ },
+ "owner": {
+ "type": "string",
+ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The session's person, the only one whose commands reach the desk."
+ },
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 640,
+ "maximum": 3840
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 480,
+ "maximum": 2160
+ },
+ "shared": {
+ "type": "boolean",
+ "description": "true on a desk host that serves other people (the desk runs in its own sandbox there); false on the person's own machine."
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ }
+ },
+ "bot.desk-lease-create-request": {
+ "version": "1.0.0",
+ "owner": "bot",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/bot/desk-lease-create-request.v1.json",
+ "title": "BotDeskLeaseCreateRequest",
+ "description": "bot.desk-lease-create-request@1 (the Desks plan's D6): the body of POST /api/v1/machines/{id}/desk-leases on OpenVibe.Bot (capability bot.desk.session), answered 201 with bot.desk-lease@1. DESK is the name Run chose for the session (unique per session); a desk already leased to another session, or running for the machine's owner, is refused 409 bot.desk_busy, and a repeat for the same session answers its lease (200). A machine with no room (its max_desks) is refused 409 bot.machine_busy, one that is offline 409 bot.device_offline, and one the person may not use 404 bot.device_not_found.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "session",
+ "desk",
+ "kind"
+ ],
+ "properties": {
+ "session": {
+ "$ref": "../run/session.v1.json#/$defs/session_id"
+ },
+ "desk": {
+ "$ref": "desk-lease.v1.json#/properties/desk"
+ },
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "$ref": "desk-lease.v1.json#/properties/width",
+ "description": "Default 1280."
+ },
+ "height": {
+ "$ref": "desk-lease.v1.json#/properties/height",
+ "description": "Default 800."
+ }
+ }
+ }
}
}
}
}
}
}
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Actor API",
"title": "OpenVibe.Actor API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Actor as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bot API",
"title": "OpenVibe.Bot API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -472,6 +472,350 @@
@@ -472,6 +472,350 @@
}
}
}
}
},
},
+ "/api/v1/machines/{id}/desk-leases": {
+ "post": {
+ "operationId": "post_api_v1_machines_id_desk_leases",
+ "summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",
+ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
+ "tags": [
+ "bot.desk"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.desk.session"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.desk.session"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease-create-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/{id}/desk-leases/{desk}": {
+ "delete": {
+ "operationId": "delete_api_v1_machines_id_desk_leases_desk",
+ "summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",
+ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
+ "tags": [
+ "bot.desk"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.desk.session"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.desk.session"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/bot.desk-lease-create-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "desk",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "session",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"
+ }
+ },
+ {
+ "name": "kind",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "const": "browser"
+ }
+ },
+ {
+ "name": "width",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/width",
+ "description": "Default 1280."
+ },
+ "description": "Default 1280."
+ },
+ {
+ "name": "height",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/height",
+ "description": "Default 800."
+ },
+ "description": "Default 800."
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "get": {
+ "operationId": "get_api_v1_machines_id_desk_leases_desk",
+ "summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",
+ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
+ "tags": [
+ "bot.desk"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.desk.session"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.desk.session"
+ ]
+ }
+ ],
+ "x-openvibe-input": {
+ "$ref": "#/components/schemas/bot.desk-lease-create-request.v1"
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "desk",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "session",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"
+ }
+ },
+ {
+ "name": "kind",
+ "in": "query",
+ "required": true,
+ "schema": {
+ "const": "browser"
+ }
+ },
+ {
+ "name": "width",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/width",
+ "description": "Default 1280."
+ },
+ "description": "Default 1280."
+ },
+ {
+ "name": "height",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/height",
+ "description": "Default 800."
+ },
+ "description": "Default 800."
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/machines/{id}/desk-leases/{desk}/commands": {
+ "post": {
+ "operationId": "post_api_v1_machines_id_desk_leases_desk_commands",
+ "summary": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject.",
+ "description": "**bot.desk.session** (first-party): OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.\n\nRoute note: bot.command@1, answered by bot.command-result@1",
+ "tags": [
+ "bot.desk"
+ ],
+ "x-openvibe-capabilities": [
+ "bot.desk.session"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "bot-control",
+ "security": [
+ {
+ "openvibeToken": [
+ "bot.desk.session"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease-create-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "desk",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/api/v1/resources": {
"/api/v1/resources": {
"get": {
"get": {
"operationId": "get_api_v1_resources",
"operationId": "get_api_v1_resources",
@@ -1538,22 +1882,108 @@
@@ -1538,22 +1882,108 @@
}
}
}
}
},
},
- "default": {
- "description": "An error (RFC 9457 problem details)",
- "content": {
- "application/problem+json": {
- "schema": {
- "$ref": "#/components/schemas/errors.problem.v1"
- }
- }
- }
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "bot.desk-lease-create-request.v1": {
+ "title": "BotDeskLeaseCreateRequest",
+ "description": "bot.desk-lease-create-request@1 (the Desks plan's D6): the body of POST /api/v1/machines/{id}/desk-leases on OpenVibe.Bot (capability bot.desk.session), answered 201 with bot.desk-lease@1. DESK is the name Run chose for the session (unique per session); a desk already leased to another session, or running for the machine's owner, is refused 409 bot.desk_busy, and a repeat for the same session answers its lease (200). A machine with no room (its max_desks) is refused 409 bot.machine_busy, one that is offline 409 bot.device_offline, and one the person may not use 404 bot.device_not_found.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "session",
+ "desk",
+ "kind"
+ ],
+ "properties": {
+ "session": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"
+ },
+ "desk": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/desk"
+ },
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/width",
+ "description": "Default 1280."
+ },
+ "height": {
+ "$ref": "#/components/schemas/bot.desk-lease.v1/properties/height",
+ "description": "Default 800."
+ }
+ }
+ },
+ "bot.desk-lease.v1": {
+ "title": "BotDeskLease",
+ "description": "bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "machine_id",
+ "desk",
+ "session",
+ "owner",
+ "kind",
+ "width",
+ "height",
+ "shared",
+ "created_at"
+ ],
+ "properties": {
+ "machine_id": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "desk": {
+ "type": "string",
+ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$"
+ },
+ "session": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"
+ },
+ "owner": {
+ "type": "string",
+ "pattern": "^user:usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The session's person, the only one whose commands reach the desk."
+ },
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "type": "integer",
+ "minimum": 640,
+ "maximum": 3840
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 480,
+ "maximum": 2160
+ },
+ "shared": {
+ "type": "boolean",
+ "description": "true on a desk host that serves other people (the desk runs in its own sandbox there); false on the person's own machine."
+ },
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
}
}
}
}
- }
- }
- },
- "components": {
- "schemas": {
+ },
"bot.device-connect-result.v1": {
"bot.device-connect-result.v1": {
"title": "BotDeviceConnectResult",
"title": "BotDeviceConnectResult",
"description": "bot.device-connect-result@1: answers of OpenVibe.Bot's device credential routes, which Bot gates with bot.robot.manage (bot.device.connect is reserved, not enforced). POST /api/v1/devices/:id/rotate → { device, credential, publish_key } (both secrets shown once); POST …/devices/:id/revoke → { device } (its socket closes at once).",
"description": "bot.device-connect-result@1: answers of OpenVibe.Bot's device credential routes, which Bot gates with bot.robot.manage (bot.device.connect is reserved, not enforced). POST /api/v1/devices/:id/rotate → { device, credential, publish_key } (both secrets shown once); POST …/devices/:id/revoke → { device } (its socket closes at once).",
@@ -3112,6 +3542,382 @@
@@ -3112,6 +3542,382 @@
"function",
"function",
"gpu"
"gpu"
]
]
+ },
+ "run.session.v1": {
+ "title": "RunSession",
+ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "owner",
+ "via",
+ "kind",
+ "state",
+ "size",
+ "ttl_s",
+ "idle_s",
+ "placement",
+ "timings",
+ "end",
+ "usage",
+ "idempotency_key"
+ ],
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"
+ },
+ "owner": {
+ "$ref": "#/components/schemas/identity.subject-ref.v1",
+ "description": "The person the session is for: only they (or a first-party service acting for them) read, act on or end it."
+ },
+ "via": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^service:[a-z][a-z0-9-]{0,40}$",
+ "description": "The first-party service that asked for the person (service:actor), or null when they asked themselves."
+ },
+ "kind": {
+ "const": "browser",
+ "description": "A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1."
+ },
+ "state": {
+ "enum": [
+ "placing",
+ "starting",
+ "running",
+ "ended"
+ ]
+ },
+ "size": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/size"
+ },
+ "ttl_s": {
+ "type": "integer",
+ "minimum": 60,
+ "maximum": 14400,
+ "description": "The most the session lives, from created_at."
+ },
+ "idle_s": {
+ "type": "integer",
+ "minimum": 60,
+ "maximum": 3600,
+ "description": "A session no command used this long ends (end.reason idle)."
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/run.session.v1/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/timings"
+ },
+ "end": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/run.session.v1/$defs/end"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/usage"
+ },
+ "idempotency_key": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/run.session.v1/$defs/idempotency_key"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "const": "ended"
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "end": {
+ "type": "object"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "ended_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "end": {
+ "type": "null"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "ended_at": {
+ "type": "null"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "enum": [
+ "starting",
+ "running"
+ ]
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "placement": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ ],
+ "$defs": {
+ "session_id": {
+ "type": "string",
+ "pattern": "^ses_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "size": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "width",
+ "height"
+ ],
+ "properties": {
+ "width": {
+ "type": "integer",
+ "minimum": 640,
+ "maximum": 3840
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 480,
+ "maximum": 2160
+ }
+ },
+ "description": "The desk's page size (bot.command@1 kind desk width and height)."
+ },
+ "placement": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "machine",
+ "desk",
+ "trust",
+ "offer_id",
+ "region",
+ "reasons"
+ ],
+ "properties": {
+ "machine": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine (OpenVibe.Bot device) the desk runs on."
+ },
+ "desk": {
+ "type": "string",
+ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$",
+ "description": "The desk's name there (bot.command@1 desk), leased to this session alone."
+ },
+ "trust": {
+ "enum": [
+ "user-owned",
+ "first-party",
+ "partner",
+ "community"
+ ],
+ "description": "The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine."
+ },
+ "offer_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 128,
+ "description": "The platform.resource-offer@1 it was placed on; null when the request named the person's own machine."
+ },
+ "region": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 64
+ },
+ "reasons": {
+ "type": "array",
+ "maxItems": 8,
+ "items": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "description": "Why this machine, in a few words each (the placer's reasons)."
+ }
+ }
+ },
+ "idempotency_key": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "pattern": "^[A-Za-z0-9._:-]+$"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "started_at",
+ "last_used_at",
+ "ended_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the desk first answered (state running)."
+ },
+ "last_used_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "The last command, for idle_s."
+ },
+ "ended_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "desk_seconds"
+ ],
+ "properties": {
+ "desk_seconds": {
+ "type": "number",
+ "minimum": 0,
+ "description": "Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started."
+ }
+ }
+ },
+ "end": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "reason",
+ "detail"
+ ],
+ "properties": {
+ "reason": {
+ "enum": [
+ "ended",
+ "ttl",
+ "idle",
+ "host_lost",
+ "unplaceable",
+ "failed"
+ ]
+ },
+ "detail": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500
+ }
+ }
+ }
+ },
+ "examples": [
+ {
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+ }
+ ]
}
}
},
},
"securitySchemes": {
"securitySchemes": {
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Food API",
"title": "OpenVibe.Food API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Food as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -33,8 +33,8 @@
@@ -33,8 +33,8 @@
"service": "bot",
"service": "bot",
"name": "OpenVibe.Bot",
"name": "OpenVibe.Bot",
"origin": "https://openvibe.bot",
"origin": "https://openvibe.bot",
- "operations": 21,
- "capabilities": 8
+ "operations": 25,
+ "capabilities": 9
},
},
{
{
"service": "chat",
"service": "chat",
@@ -173,8 +173,8 @@
@@ -173,8 +173,8 @@
"service": "run",
"service": "run",
"name": "OpenVibe.Run",
"name": "OpenVibe.Run",
"origin": "https://openvibe.run",
"origin": "https://openvibe.run",
- "operations": 7,
- "capabilities": 6
+ "operations": 12,
+ "capabilities": 9
},
},
{
{
"service": "search",
"service": "search",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Inventory API",
"title": "OpenVibe.Inventory API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Inventory as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.MediaHub API",
"title": "OpenVibe.MediaHub API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.MediaHub as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRestream API",
"title": "OpenRestream API",
- "version": "0.222.0",
- "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenRestream as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Play API",
"title": "OpenVibe.Play API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Play as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Quest API",
"title": "OpenVibe.Quest API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Quest as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Run API",
"title": "OpenVibe.Run API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Run as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -511,6 +511,294 @@
@@ -511,6 +511,294 @@
}
}
}
}
}
}
+ },
+ "/api/v1/sessions": {
+ "get": {
+ "operationId": "get_api_v1_sessions",
+ "summary": "Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds).",
+ "description": "**run.session.read** (public): Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds). Never another person's.",
+ "tags": [
+ "run.session"
+ ],
+ "x-openvibe-capabilities": [
+ "run.session.read"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "run-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "run.session.read"
+ ]
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/run.session.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ },
+ "post": {
+ "operationId": "post_api_v1_sessions",
+ "summary": "Open a browser session for yourself (the Desks plan's D6): POST /api/v1/sessions with run.session-create-request@1.",
+ "description": "**run.session.create** (public): Open a browser session for yourself (the Desks plan's D6): POST /api/v1/sessions with run.session-create-request@1. Run places a browser desk on one of your own machines (when you name it or your requirements name user-owned) or on a desk host that serves other people, where every desk runs in its own sandbox, and answers run.session@1. A first-party service acting for a person (X-OV-Subject) opens it for them and is recorded as via. Desk time is metered in desk-seconds.",
+ "tags": [
+ "run.session"
+ ],
+ "x-openvibe-capabilities": [
+ "run.session.create"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "run-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "run.session.create"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/run.session-create-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/run.session.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/sessions/{id}": {
+ "get": {
+ "operationId": "get_api_v1_sessions_id",
+ "summary": "Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds).",
+ "description": "**run.session.read** (public): Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds). Never another person's.",
+ "tags": [
+ "run.session"
+ ],
+ "x-openvibe-capabilities": [
+ "run.session.read"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "run-read",
+ "security": [
+ {
+ "openvibeToken": [
+ "run.session.read"
+ ]
+ }
+ ],
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/run.session.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/sessions/{id}/commands": {
+ "post": {
+ "operationId": "post_api_v1_sessions_id_commands",
+ "summary": "Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-resul",
+ "description": "**run.session.control** (public): Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-result@1) and POST /api/v1/sessions/:id/end (run.session@1, ended). Nothing else on the machine it runs on is reachable.",
+ "tags": [
+ "run.session"
+ ],
+ "x-openvibe-capabilities": [
+ "run.session.control"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "run-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "run.session.control"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/run.session-control-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/run.session-control-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/api/v1/sessions/{id}/end": {
+ "post": {
+ "operationId": "post_api_v1_sessions_id_end",
+ "summary": "Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-resul",
+ "description": "**run.session.control** (public): Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-result@1) and POST /api/v1/sessions/:id/end (run.session@1, ended). Nothing else on the machine it runs on is reachable.",
+ "tags": [
+ "run.session"
+ ],
+ "x-openvibe-capabilities": [
+ "run.session.control"
+ ],
+ "x-openvibe-visibility": [
+ "public"
+ ],
+ "x-openvibe-quota-class": "run-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "run.session.control"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/run.session-control-request.v1"
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "id",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/run.session-control-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
}
}
},
},
"components": {
"components": {
@@ -1721,6 +2009,553 @@
@@ -1721,6 +2009,553 @@
"pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
"pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
}
}
}
}
+ },
+ "run.session-control-request.v1": {
+ "title": "RunSessionControlRequest",
+ "description": "run.session-control-request@1 (the Desks plan's D6): one command on a session's desk, the body of POST /api/v1/sessions/{id}/commands on OpenVibe.Run (capability run.session.control), answered with run.session-control-result@1. KIND and VALUE are bot.command@1's for that kind (screen: a picture of the page; input: the mouse, wheel and keys on the page; browser: open, click, fill, press, back, read, screenshot) without desk, which is always the session's own: Run carries the command to OpenVibe.Bot on the session's lease (bot.desk-lease@1) and nothing on the machine other than that desk is reachable. A command on a session that is not running is refused 409 run.session.not_running. ID makes a retry safe: the same id within the session answers the first result.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind",
+ "value"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64,
+ "pattern": "^[A-Za-z0-9._:-]+$"
+ },
+ "kind": {
+ "enum": [
+ "screen",
+ "input",
+ "browser"
+ ]
+ },
+ "value": {
+ "type": "object",
+ "description": "bot.command@1's value for that kind, without desk (refused 422 if it names another desk).",
+ "not": {
+ "required": [
+ "desk"
+ ]
+ }
+ }
+ },
+ "examples": [
+ {
+ "kind": "browser",
+ "value": {
+ "action": "open",
+ "url": "https://example.org/"
+ }
+ },
+ {
+ "id": "c-7",
+ "kind": "screen",
+ "value": {
+ "max_width": 1024
+ }
+ }
+ ]
+ },
+ "run.session-control-result.v1": {
+ "title": "RunSessionControlResult",
+ "description": "run.session-control-result@1 (the Desks plan's D6): the answer to run.session-control-request@1. RESULT is bot.command-result@1's: ack (the desk did it; DATA is its answer, a page as text and refs, a picture, a receipt), nack (the desk refused; CODE is its fault code and REASON says why), refused (OpenVibe.Bot's gate refused it; CODE is Bot's refusal code) or expired (no answer in time). Every answer also says how the session stands now, so a caller sees it end (idle, ttl, host_lost) without asking again.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "session_id",
+ "id",
+ "result",
+ "state"
+ ],
+ "properties": {
+ "session_id": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"
+ },
+ "id": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "result": {
+ "enum": [
+ "ack",
+ "nack",
+ "refused",
+ "expired"
+ ]
+ },
+ "code": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 80
+ },
+ "reason": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 1000
+ },
+ "data": {
+ "type": "object",
+ "description": "The desk's answer on an ack (bot.command-result@1 data)."
+ },
+ "state": {
+ "$ref": "#/components/schemas/run.session.v1/properties/state"
+ }
+ },
+ "examples": [
+ {
+ "session_id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "id": "c-7",
+ "result": "ack",
+ "code": null,
+ "reason": null,
+ "data": {
+ "format": "jpeg",
+ "width": 1024,
+ "height": 640,
+ "content_b64": "/9j/"
+ },
+ "state": "running"
+ }
+ ]
+ },
+ "run.session-create-request.v1": {
+ "title": "RunSessionCreateRequest",
+ "description": "run.session-create-request@1 (the Desks plan's D6): the body of POST /api/v1/sessions on OpenVibe.Run (capability run.session.create), answered 201 with run.session@1 (state placing). The session is the caller's own: a person's token, or a first-party service acting for a person (X-OV-Subject; the service is recorded as via). MACHINE names one of the person's own machines (an OpenVibe.Bot device they own and let agents use): the session goes there or ends unplaceable, never elsewhere. Without it Run places the desk: on the person's own machines when REQUIREMENTS name user-owned (and only theirs), else on desk hosts that serve other people (first-party, then community, unless REQUIREMENTS narrow it), each of whose desks runs in its own sandbox. Absent REQUIREMENTS mean { kind: run.browser, mobility: session, latency_class: interactive, objective: balanced }. TTL_S (default 3600) and IDLE_S (default 900) bound its life. IDEMPOTENCY_KEY: a repeat from the same person within 24 h with an identical body answers the first session (200); with a different body 409 run.idempotency.conflict.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "kind"
+ ],
+ "properties": {
+ "kind": {
+ "const": "browser"
+ },
+ "width": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/size/properties/width",
+ "description": "Default 1280."
+ },
+ "height": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/size/properties/height",
+ "description": "Default 800."
+ },
+ "ttl_s": {
+ "$ref": "#/components/schemas/run.session.v1/properties/ttl_s",
+ "description": "Default 3600."
+ },
+ "idle_s": {
+ "$ref": "#/components/schemas/run.session.v1/properties/idle_s",
+ "description": "Default 900."
+ },
+ "machine": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "One of the person's own machines, by its OpenVibe.Bot id; the session goes there or nowhere."
+ },
+ "requirements": {
+ "$ref": "#/components/schemas/platform.workload-requirements.v1"
+ },
+ "idempotency_key": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/idempotency_key"
+ }
+ },
+ "examples": [
+ {
+ "kind": "browser"
+ },
+ {
+ "kind": "browser",
+ "width": 1440,
+ "height": 900,
+ "ttl_s": 1800,
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "idempotency_key": "actor:tsk_01JD2C3D4E5F6G7H8J9K0MNPQR"
+ }
+ ]
+ },
+ "run.session.v1": {
+ "title": "RunSession",
+ "description": "run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "owner",
+ "via",
+ "kind",
+ "state",
+ "size",
+ "ttl_s",
+ "idle_s",
+ "placement",
+ "timings",
+ "end",
+ "usage",
+ "idempotency_key"
+ ],
+ "properties": {
+ "id": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/session_id"
+ },
+ "owner": {
+ "$ref": "#/components/schemas/identity.subject-ref.v1",
+ "description": "The person the session is for: only they (or a first-party service acting for them) read, act on or end it."
+ },
+ "via": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "pattern": "^service:[a-z][a-z0-9-]{0,40}$",
+ "description": "The first-party service that asked for the person (service:actor), or null when they asked themselves."
+ },
+ "kind": {
+ "const": "browser",
+ "description": "A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1."
+ },
+ "state": {
+ "enum": [
+ "placing",
+ "starting",
+ "running",
+ "ended"
+ ]
+ },
+ "size": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/size"
+ },
+ "ttl_s": {
+ "type": "integer",
+ "minimum": 60,
+ "maximum": 14400,
+ "description": "The most the session lives, from created_at."
+ },
+ "idle_s": {
+ "type": "integer",
+ "minimum": 60,
+ "maximum": 3600,
+ "description": "A session no command used this long ends (end.reason idle)."
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/run.session.v1/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/timings"
+ },
+ "end": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/run.session.v1/$defs/end"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "#/components/schemas/run.session.v1/$defs/usage"
+ },
+ "idempotency_key": {
+ "anyOf": [
+ {
+ "$ref": "#/components/schemas/run.session.v1/$defs/idempotency_key"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "const": "ended"
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "end": {
+ "type": "object"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "ended_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "end": {
+ "type": "null"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "ended_at": {
+ "type": "null"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "required": [
+ "state"
+ ],
+ "properties": {
+ "state": {
+ "enum": [
+ "starting",
+ "running"
+ ]
+ }
+ }
+ },
+ "then": {
+ "properties": {
+ "placement": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ ],
+ "$defs": {
+ "session_id": {
+ "type": "string",
+ "pattern": "^ses_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "size": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "width",
+ "height"
+ ],
+ "properties": {
+ "width": {
+ "type": "integer",
+ "minimum": 640,
+ "maximum": 3840
+ },
+ "height": {
+ "type": "integer",
+ "minimum": 480,
+ "maximum": 2160
+ }
+ },
+ "description": "The desk's page size (bot.command@1 kind desk width and height)."
+ },
+ "placement": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "machine",
+ "desk",
+ "trust",
+ "offer_id",
+ "region",
+ "reasons"
+ ],
+ "properties": {
+ "machine": {
+ "type": "string",
+ "pattern": "^dev_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The machine (OpenVibe.Bot device) the desk runs on."
+ },
+ "desk": {
+ "type": "string",
+ "pattern": "^desk-[a-z0-9][a-z0-9-]{0,23}$",
+ "description": "The desk's name there (bot.command@1 desk), leased to this session alone."
+ },
+ "trust": {
+ "enum": [
+ "user-owned",
+ "first-party",
+ "partner",
+ "community"
+ ],
+ "description": "The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine."
+ },
+ "offer_id": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 128,
+ "description": "The platform.resource-offer@1 it was placed on; null when the request named the person's own machine."
+ },
+ "region": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 64
+ },
+ "reasons": {
+ "type": "array",
+ "maxItems": 8,
+ "items": {
+ "type": "string",
+ "maxLength": 200
+ },
+ "description": "Why this machine, in a few words each (the placer's reasons)."
+ }
+ }
+ },
+ "idempotency_key": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "pattern": "^[A-Za-z0-9._:-]+$"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "started_at",
+ "last_used_at",
+ "ended_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the desk first answered (state running)."
+ },
+ "last_used_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "The last command, for idle_s."
+ },
+ "ended_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ }
+ }
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "desk_seconds"
+ ],
+ "properties": {
+ "desk_seconds": {
+ "type": "number",
+ "minimum": 0,
+ "description": "Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started."
+ }
+ }
+ },
+ "end": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "reason",
+ "detail"
+ ],
+ "properties": {
+ "reason": {
+ "enum": [
+ "ended",
+ "ttl",
+ "idle",
+ "host_lost",
+ "unplaceable",
+ "failed"
+ ]
+ },
+ "detail": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 500
+ }
+ }
+ }
+ },
+ "examples": [
+ {
+ "id": "ses_01JD2C3D4E5F6G7H8J9K0MNPQR",
+ "owner": {
+ "type": "user",
+ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+ },
+ "via": "service:actor",
+ "kind": "browser",
+ "state": "running",
+ "size": {
+ "width": 1280,
+ "height": 800
+ },
+ "ttl_s": 3600,
+ "idle_s": 900,
+ "placement": {
+ "machine": "dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "desk": "desk-s0mnpqr",
+ "trust": "community",
+ "offer_id": "desk:dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X",
+ "region": "eu",
+ "reasons": [
+ "a shared desk host with room"
+ ]
+ },
+ "timings": {
+ "created_at": "2026-10-28T10:00:00Z",
+ "started_at": "2026-10-28T10:00:04Z",
+ "last_used_at": "2026-10-28T10:05:00Z",
+ "ended_at": null
+ },
+ "end": null,
+ "usage": {
+ "desk_seconds": 296
+ },
+ "idempotency_key": null
+ }
+ ]
}
}
},
},
"securitySchemes": {
"securitySchemes": {
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Services API",
"title": "OpenVibe.Services API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Services as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Space API",
"title": "OpenVibe.Space API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Watch API",
"title": "OpenVibe.Watch API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Watch as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Work API",
"title": "OpenVibe.Work API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Work as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Zone API",
"title": "OpenVibe.Zone API",
- "version": "0.222.0",
- "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.222.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
+ "version": "0.223.0",
+ "description": "Routes of OpenVibe.Zone as the OpenVibe contracts describe them (openvibe-contracts 0.223.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -206118,3 +206118,384 @@ export interface ChatInboxConversationsResult {
@@ -206118,3 +206118,384 @@ export interface ChatInboxConversationsResult {
message?: {};
message?: {};
replies?: {}[];
replies?: {}[];
}
}
+
+/** [email protected] (owner: run) */
+/**
+ * run.session@1 (the Desks plan's D6): one session on OpenVibe.Run, a browser desk held for one person, as GET /api/v1/sessions/{id} answers it (capability run.session.read) and run.session-list-result@1, POST /api/v1/sessions (run.session.create) and POST /api/v1/sessions/{id}/end (run.session.control) carry it. A session is a desk (bot.command@1 kind desk, kind browser) that Run places on a machine and OpenVibe.Bot leases to it (bot.desk-lease@1): on one of the person's own machines where they let agents use it (trust user-owned, never another person's), or on a desk host that serves other people (trust first-party or community: the host's OpenVibe.Node runs every desk in its own sandbox, desk_isolation sandbox, and offers nothing else of the machine). STATES: placing (stored, looking for a machine) -> starting (the desk is leased and starting) -> running (the desk answered) -> ended. An ended session never changes; end.reason says why: ended (POST /api/v1/sessions/{id}/end), ttl (ttl_s ran out), idle (no command for idle_s), host_lost (its machine went away or stopped the desk), unplaceable (no machine had room before ttl_s, or none at all) or failed (the desk would not start). USAGE: desk_seconds counts from started_at to ended_at (or now while running), metered to OpenVibe.Billing as one platform.usage-sample@1 per minute (service run, operation browser.desk, unit s, idempotency_key run:<session id>:<minute>). Run mints the id (ses_<ULID>).
+ */
+export type RunSession = {
+ [k: string]: unknown | undefined;
+} & {
+ id: string;
+ owner: SubjectRef;
+ /**
+ * The first-party service that asked for the person (service:actor), or null when they asked themselves.
+ */
+ via: string | null;
+ /**
+ * A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1.
+ */
+ kind: "browser";
+ state: "placing" | "starting" | "running" | "ended";
+ /**
+ * The desk's page size (bot.command@1 kind desk width and height).
+ */
+ size: {
+ width: number;
+ height: number;
+ };
+ /**
+ * The most the session lives, from created_at.
+ */
+ ttl_s: number;
+ /**
+ * A session no command used this long ends (end.reason idle).
+ */
+ idle_s: number;
+ placement: {
+ /**
+ * The machine (OpenVibe.Bot device) the desk runs on.
+ */
+ machine: string;
+ /**
+ * The desk's name there (bot.command@1 desk), leased to this session alone.
+ */
+ desk: string;
+ /**
+ * The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine.
+ */
+ trust: "user-owned" | "first-party" | "partner" | "community";
+ /**
+ * The platform.resource-offer@1 it was placed on; null when the request named the person's own machine.
+ */
+ offer_id: string | null;
+ region: string | null;
+ /**
+ * Why this machine, in a few words each (the placer's reasons).
+ *
+ * @maxItems 8
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string];
+ } | null;
+ timings: {
+ created_at: string;
+ /**
+ * When the desk first answered (state running).
+ */
+ started_at: string | null;
+ /**
+ * The last command, for idle_s.
+ */
+ last_used_at: string | null;
+ ended_at: string | null;
+ };
+ end: {
+ reason: "ended" | "ttl" | "idle" | "host_lost" | "unplaceable" | "failed";
+ detail: string | null;
+ } | null;
+ usage: {
+ /**
+ * Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started.
+ */
+ desk_seconds: number;
+ };
+ idempotency_key: string | null;
+};
+
+/** [email protected] (owner: run) */
+/**
+ * run.session-create-request@1 (the Desks plan's D6): the body of POST /api/v1/sessions on OpenVibe.Run (capability run.session.create), answered 201 with run.session@1 (state placing). The session is the caller's own: a person's token, or a first-party service acting for a person (X-OV-Subject; the service is recorded as via). MACHINE names one of the person's own machines (an OpenVibe.Bot device they own and let agents use): the session goes there or ends unplaceable, never elsewhere. Without it Run places the desk: on the person's own machines when REQUIREMENTS name user-owned (and only theirs), else on desk hosts that serve other people (first-party, then community, unless REQUIREMENTS narrow it), each of whose desks runs in its own sandbox. Absent REQUIREMENTS mean { kind: run.browser, mobility: session, latency_class: interactive, objective: balanced }. TTL_S (default 3600) and IDLE_S (default 900) bound its life. IDEMPOTENCY_KEY: a repeat from the same person within 24 h with an identical body answers the first session (200); with a different body 409 run.idempotency.conflict.
+ */
+export interface RunSessionCreateRequest {
+ kind: "browser";
+ /**
+ * Default 1280.
+ */
+ width?: number;
+ /**
+ * Default 800.
+ */
+ height?: number;
+ /**
+ * Default 3600.
+ */
+ ttl_s?: number;
+ /**
+ * Default 900.
+ */
+ idle_s?: number;
+ /**
+ * One of the person's own machines, by its OpenVibe.Bot id; the session goes there or nowhere.
+ */
+ machine?: string;
+ requirements?: WorkloadRequirements;
+ idempotency_key?: string;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.session-control-request@1 (the Desks plan's D6): one command on a session's desk, the body of POST /api/v1/sessions/{id}/commands on OpenVibe.Run (capability run.session.control), answered with run.session-control-result@1. KIND and VALUE are bot.command@1's for that kind (screen: a picture of the page; input: the mouse, wheel and keys on the page; browser: open, click, fill, press, back, read, screenshot) without desk, which is always the session's own: Run carries the command to OpenVibe.Bot on the session's lease (bot.desk-lease@1) and nothing on the machine other than that desk is reachable. A command on a session that is not running is refused 409 run.session.not_running. ID makes a retry safe: the same id within the session answers the first result.
+ */
+export interface RunSessionControlRequest {
+ id?: string;
+ kind: "screen" | "input" | "browser";
+ /**
+ * bot.command@1's value for that kind, without desk (refused 422 if it names another desk).
+ */
+ value: {};
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.session-control-result@1 (the Desks plan's D6): the answer to run.session-control-request@1. RESULT is bot.command-result@1's: ack (the desk did it; DATA is its answer, a page as text and refs, a picture, a receipt), nack (the desk refused; CODE is its fault code and REASON says why), refused (OpenVibe.Bot's gate refused it; CODE is Bot's refusal code) or expired (no answer in time). Every answer also says how the session stands now, so a caller sees it end (idle, ttl, host_lost) without asking again.
+ */
+export interface RunSessionControlResult {
+ session_id: string;
+ id: string;
+ result: "ack" | "nack" | "refused" | "expired";
+ code?: string | null;
+ reason?: string | null;
+ /**
+ * The desk's answer on an ack (bot.command-result@1 data).
+ */
+ data?: {};
+ state: "placing" | "starting" | "running" | "ended";
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.session-list-result@1 (the Desks plan's D6): GET /api/v1/sessions on OpenVibe.Run (capability run.session.read): the caller's own sessions, newest first, running ones by default (?state=ended or ?state=all for others), at most limit (default 20, at most 100) per page; next_cursor continues the list and is null on the last page.
+ */
+export interface RunSessionListResult {
+ /**
+ * @maxItems 100
+ */
+ sessions: RunSession[];
+ next_cursor: string | null;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.session.started v1 (OpenVibe.Run; the Desks plan's D6). A session's desk answered for the first time: it runs on its machine, and desk_seconds count from timings.started_at. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.
+ */
+export interface RunSessionStartedPayload {
+ id: string;
+ owner: SubjectRef;
+ /**
+ * The first-party service that asked for the person (service:actor), or null when they asked themselves.
+ */
+ via: string | null;
+ /**
+ * A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1.
+ */
+ kind: "browser";
+ state: "running";
+ /**
+ * The desk's page size (bot.command@1 kind desk width and height).
+ */
+ size: {
+ width: number;
+ height: number;
+ };
+ /**
+ * The most the session lives, from created_at.
+ */
+ ttl_s: number;
+ /**
+ * A session no command used this long ends (end.reason idle).
+ */
+ idle_s: number;
+ placement: {
+ /**
+ * The machine (OpenVibe.Bot device) the desk runs on.
+ */
+ machine: string;
+ /**
+ * The desk's name there (bot.command@1 desk), leased to this session alone.
+ */
+ desk: string;
+ /**
+ * The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine.
+ */
+ trust: "user-owned" | "first-party" | "partner" | "community";
+ /**
+ * The platform.resource-offer@1 it was placed on; null when the request named the person's own machine.
+ */
+ offer_id: string | null;
+ region: string | null;
+ /**
+ * Why this machine, in a few words each (the placer's reasons).
+ *
+ * @maxItems 8
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string];
+ };
+ timings: {
+ created_at: string;
+ /**
+ * When the desk first answered (state running).
+ */
+ started_at: string | null;
+ /**
+ * The last command, for idle_s.
+ */
+ last_used_at: string | null;
+ ended_at: string | null;
+ };
+ end: null;
+ usage: {
+ /**
+ * Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started.
+ */
+ desk_seconds: number;
+ };
+ idempotency_key: string | null;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.session.ended v1 (OpenVibe.Run; the Desks plan's D6). A session ended (end.reason ended, ttl, idle, host_lost, unplaceable or failed), its desk stopped and its lease given back; usage.desk_seconds is final. The session as run.session@1 has it. Envelope: subject { type: session, id: <session id> }, visibility internal, priority low, actor service:run.
+ */
+export interface RunSessionEndedPayload {
+ id: string;
+ owner: SubjectRef;
+ /**
+ * The first-party service that asked for the person (service:actor), or null when they asked themselves.
+ */
+ via: string | null;
+ /**
+ * A platform.runtime-class@1 name; browser (a headless browser driven over its pipe) is the only kind of session in v1.
+ */
+ kind: "browser";
+ state: "ended";
+ /**
+ * The desk's page size (bot.command@1 kind desk width and height).
+ */
+ size: {
+ width: number;
+ height: number;
+ };
+ /**
+ * The most the session lives, from created_at.
+ */
+ ttl_s: number;
+ /**
+ * A session no command used this long ends (end.reason idle).
+ */
+ idle_s: number;
+ placement: {
+ /**
+ * The machine (OpenVibe.Bot device) the desk runs on.
+ */
+ machine: string;
+ /**
+ * The desk's name there (bot.command@1 desk), leased to this session alone.
+ */
+ desk: string;
+ /**
+ * The offer's trust class (platform.resource-offer@1): user-owned is always the owner's own machine.
+ */
+ trust: "user-owned" | "first-party" | "partner" | "community";
+ /**
+ * The platform.resource-offer@1 it was placed on; null when the request named the person's own machine.
+ */
+ offer_id: string | null;
+ region: string | null;
+ /**
+ * Why this machine, in a few words each (the placer's reasons).
+ *
+ * @maxItems 8
+ */
+ reasons:
+ | []
+ | [string]
+ | [string, string]
+ | [string, string, string]
+ | [string, string, string, string]
+ | [string, string, string, string, string]
+ | [string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string]
+ | [string, string, string, string, string, string, string, string];
+ } | null;
+ timings: {
+ created_at: string;
+ /**
+ * When the desk first answered (state running).
+ */
+ started_at: string | null;
+ /**
+ * The last command, for idle_s.
+ */
+ last_used_at: string | null;
+ ended_at: string | null;
+ };
+ end: {
+ reason: "ended" | "ttl" | "idle" | "host_lost" | "unplaceable" | "failed";
+ detail: string | null;
+ };
+ usage: {
+ /**
+ * Seconds the desk ran (started_at to ended_at, or to now while running); 0 when it never started.
+ */
+ desk_seconds: number;
+ };
+ idempotency_key: string | null;
+}
+
+/** [email protected] (owner: bot) */
+/**
+ * bot.desk-lease@1 (the Desks plan's D6): one desk OpenVibe.Bot holds for one OpenVibe.Run session, as POST /api/v1/machines/{id}/desk-leases answers it (201, capability bot.desk.session, a Run service token acting for the session's person in X-OV-Subject) and GET /api/v1/machines/{id}/desk-leases/{desk} reads it. The request is bot.desk-lease-create-request@1. Bot leases a desk only on the person's own machine where they turned on 'Let my agent use this computer' (shared false), or on a desk host whose owner offers its desks to other people and whose OpenVibe.Node runs each desk in its own sandbox (describe desk.isolation sandbox; shared true). The lease starts the desk (bot.command@1 kind desk, start, kind browser, the lease's size) and from then on only the lease's person, through Run, may act on that desk, with screen, input and browser at POST /api/v1/machines/{id}/desk-leases/{desk}/commands (bot.command@1 without desk, answered by bot.command-result@1): never another desk, never the machine's own screen, files, programs or network, and on a shared host not even its owner's own commands reach a leased desk. DELETE /api/v1/machines/{id}/desk-leases/{desk} stops the desk and ends the lease; so does the machine going away (Run sees the lease gone, 404, and ends the session host_lost).
+ */
+export interface BotDeskLease {
+ machine_id: string;
+ desk: string;
+ session: string;
+ /**
+ * The session's person, the only one whose commands reach the desk.
+ */
+ owner: string;
+ kind: "browser";
+ width: number;
+ height: number;
+ /**
+ * true on a desk host that serves other people (the desk runs in its own sandbox there); false on the person's own machine.
+ */
+ shared: boolean;
+ created_at: string;
+}
+
+/** [email protected] (owner: bot) */
+/**
+ * bot.desk-lease-create-request@1 (the Desks plan's D6): the body of POST /api/v1/machines/{id}/desk-leases on OpenVibe.Bot (capability bot.desk.session), answered 201 with bot.desk-lease@1. DESK is the name Run chose for the session (unique per session); a desk already leased to another session, or running for the machine's owner, is refused 409 bot.desk_busy, and a repeat for the same session answers its lease (200). A machine with no room (its max_desks) is refused 409 bot.machine_busy, one that is offline 409 bot.device_offline, and one the person may not use 404 bot.device_not_found.
+ */
+export interface BotDeskLeaseCreateRequest {
+ session: string;
+ desk: string;
+ kind: "browser";
+ /**
+ * Default 1280.
+ */
+ width?: number;
+ /**
+ * Default 800.
+ */
+ height?: number;
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "id": "bot.desk.session",
+ "version": "1.0",
+ "owner": "bot",
+ "status": "active",
+ "visibility": "first-party",
+ "sensitive": true,
+ "description": "OpenVibe.Run's sessions on machines (the Desks plan's D6): lease one desk for one session and act on it, with a Run service token naming the session's person in X-OV-Subject. POST /api/v1/machines/:id/desk-leases (bot.desk-lease-create-request@1, answered by bot.desk-lease@1) starts a browser desk on the person's own machine where they let agents use it, or on a desk host whose owner offers its desks to other people and whose Node sandboxes every desk; POST /api/v1/machines/:id/desk-leases/:desk/commands carries screen, input and browser (bot.command@1 without desk) to that desk alone; DELETE /api/v1/machines/:id/desk-leases/:desk stops it. Bot takes it only from Run, never reaches another desk or the machine itself, and on a shared host keeps even its owner off a leased desk.",
+ "inputSchema": "bot.desk-lease-create-request@1",
+ "outputSchema": "bot.desk-lease@1",
+ "permissions": [
+ "bot:desk:session"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "bot-control",
+ "events": [
+ "bot.command.refused"
+ ],
+ "implementedBy": [
+ "POST /api/v1/machines/:id/desk-leases",
+ "GET /api/v1/machines/:id/desk-leases/:desk",
+ "POST /api/v1/machines/:id/desk-leases/:desk/commands (bot.command@1, answered by bot.command-result@1)",
+ "DELETE /api/v1/machines/:id/desk-leases/:desk"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "run.session.control",
+ "version": "1.0",
+ "owner": "run",
+ "status": "active",
+ "visibility": "public",
+ "description": "Use and end your own browser session: POST /api/v1/sessions/:id/commands with run.session-control-request@1 (screen, input and browser on the session's desk only, answered by run.session-control-result@1) and POST /api/v1/sessions/:id/end (run.session@1, ended). Nothing else on the machine it runs on is reachable.",
+ "inputSchema": "run.session-control-request@1",
+ "outputSchema": "run.session-control-result@1",
+ "permissions": [
+ "run:session:control"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "run-write",
+ "events": [
+ "run.session.ended"
+ ],
+ "implementedBy": [
+ "POST /api/v1/sessions/:id/commands",
+ "POST /api/v1/sessions/:id/end"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "id": "run.session.create",
+ "version": "1.0",
+ "owner": "run",
+ "status": "active",
+ "visibility": "public",
+ "description": "Open a browser session for yourself (the Desks plan's D6): POST /api/v1/sessions with run.session-create-request@1. Run places a browser desk on one of your own machines (when you name it or your requirements name user-owned) or on a desk host that serves other people, where every desk runs in its own sandbox, and answers run.session@1. A first-party service acting for a person (X-OV-Subject) opens it for them and is recorded as via. Desk time is metered in desk-seconds.",
+ "inputSchema": "run.session-create-request@1",
+ "outputSchema": "run.session@1",
+ "permissions": [
+ "run:session:create"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "run-write",
+ "events": [
+ "run.session.started",
+ "run.session.ended"
+ ],
+ "implementedBy": [
+ "POST /api/v1/sessions"
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "id": "run.session.read",
+ "version": "1.0",
+ "owner": "run",
+ "status": "active",
+ "visibility": "public",
+ "description": "Read your own browser sessions: GET /api/v1/sessions (run.session-list-result@1) and GET /api/v1/sessions/:id (run.session@1: state, where it runs, timings, desk-seconds). Never another person's.",
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "run.session@1",
+ "permissions": [
+ "run:session:read"
+ ],
+ "resourceConstraints": [
+ "owner"
+ ],
+ "quotaClass": "run-read",
+ "events": [],
+ "implementedBy": [
+ "GET /api/v1/sessions",
+ "GET /api/v1/sessions/:id"
+ ]
+}
@@ -35,7 +35,8 @@
@@ -35,7 +35,8 @@
"bot.device.connect",
"bot.device.connect",
"bot.job.dispatch",
"bot.job.dispatch",
"bot.resource.read",
"bot.resource.read",
- "bot.resource.control"
+ "bot.resource.control",
+ "bot.desk.session"
],
],
"eventsProduced": [
"eventsProduced": [
"bot.robot.online",
"bot.robot.online",
@@ -25,7 +25,10 @@
@@ -25,7 +25,10 @@
"run.job.list",
"run.job.list",
"run.job.cancel",
"run.job.cancel",
"run.job.stream",
"run.job.stream",
- "run.job.admin"
+ "run.job.admin",
+ "run.session.create",
+ "run.session.read",
+ "run.session.control"
],
],
"eventsProduced": [
"eventsProduced": [
"run.job.queued",
"run.job.queued",
@@ -33,7 +36,9 @@
@@ -33,7 +36,9 @@
"run.job.succeeded",
"run.job.succeeded",
"run.job.failed",
"run.job.failed",
"run.job.cancelled",
"run.job.cancelled",
- "run.job.expired"
+ "run.job.expired",
+ "run.session.started",
+ "run.session.ended"
],
],
"eventsConsumed": [],
"eventsConsumed": [],
"namespacesOwned": [],
"namespacesOwned": [],
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.222.0",
+ "version": "0.223.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",
@@ -11,7 +11,8 @@ const c = require('..');
@@ -11,7 +11,8 @@ const c = require('..');
// the computer kinds).
// the computer kinds).
// bot.resource.control: an owner stops or resumes their robot from OpenVibe.Services (0.176.0, ADR-048 step 8 phase 2).
// bot.resource.control: an owner stops or resumes their robot from OpenVibe.Services (0.176.0, ADR-048 step 8 phase 2).
// bot.machine.ai: a model on the person's own machine, for that person (0.219.0, the AI-on-your-machines plan).
// bot.machine.ai: a model on the person's own machine, for that person (0.219.0, the AI-on-your-machines plan).
-const BOT_CAPABILITIES = ['bot.robot.read', 'bot.robot.manage', 'bot.robot.control', 'bot.computer.control', 'bot.machine.ai', 'bot.device.connect', 'bot.job.dispatch', 'bot.resource.read', 'bot.resource.control'];
+// bot.desk.session: Run's sessions lease one desk each (0.223.0, the Desks plan's D6).
+const BOT_CAPABILITIES = ['bot.robot.read', 'bot.robot.manage', 'bot.robot.control', 'bot.computer.control', 'bot.machine.ai', 'bot.desk.session', 'bot.device.connect', 'bot.job.dispatch', 'bot.resource.read', 'bot.resource.control'];
const BOT_EVENTS = ['bot.robot.online', 'bot.robot.offline', 'bot.estop.set', 'bot.estop.cleared', 'bot.command.refused'];
const BOT_EVENTS = ['bot.robot.online', 'bot.robot.offline', 'bot.estop.set', 'bot.estop.cleared', 'bot.command.refused'];
const bot = c.services.get('bot');
const bot = c.services.get('bot');
@@ -134,4 +134,34 @@ checks++;
@@ -134,4 +134,34 @@ checks++;
const sandboxDispatch = contracts.capabilities.get('sandbox_provider.job.dispatch');
const sandboxDispatch = contracts.capabilities.get('sandbox_provider.job.dispatch');
ok(sandboxDispatch && sandboxDispatch.owner === 'run' && sandboxDispatch.status === 'planned' && sandboxDispatch.visibility === 'internal' && sandboxDispatch.sensitive === true && sandboxDispatch.implementedBy.length === 0 && sandboxDispatch.inputSchema === 'sandbox-provider.job-create-request@1' && sandboxDispatch.outputSchema === 'sandbox-provider.job-create-result@1' && !run.capabilities.includes('sandbox_provider.job.dispatch'), 'sandbox_provider.job.dispatch is a planned internal sensitive capability of Run naming the two contracts');
ok(sandboxDispatch && sandboxDispatch.owner === 'run' && sandboxDispatch.status === 'planned' && sandboxDispatch.visibility === 'internal' && sandboxDispatch.sensitive === true && sandboxDispatch.implementedBy.length === 0 && sandboxDispatch.inputSchema === 'sandbox-provider.job-create-request@1' && sandboxDispatch.outputSchema === 'sandbox-provider.job-create-result@1' && !run.capabilities.includes('sandbox_provider.job.dispatch'), 'sandbox_provider.job.dispatch is a planned internal sensitive capability of Run naming the two contracts');
-console.log(`platform run: ${checks} job, frame and metering checks passed`);
+// Run sessions (the Desks plan's D6): a session is a browser desk that Bot leases to it. The ids, the desk names and the
+// size bounds are the same on both sides, a session's desk is always a named desk (never main or private), and its
+// command is bot.command@1's for the kinds that act on a page, never on the machine.
+const session = contracts.schema('run.session');
+const lease = contracts.schema('bot.desk-lease');
+ok(lease.properties.session.$ref === '../run/session.v1.json#/$defs/session_id', 'a lease names its session by run.session@1 id');
+const deskPatterns = [];
+(function walk(node) {
+ if (!node || typeof node !== 'object') return;
+ if (node.desk && typeof node.desk.pattern === 'string' && node.desk.pattern.includes('desk-')) deskPatterns.push(new RegExp(node.desk.pattern));
+ Object.values(node).forEach(walk);
+})(contracts.schema('bot.command'));
+ok(deskPatterns.length > 0, 'bot.command@1 names desks');
+const named = ['desk-s0mnpqr', 'desk-a'];
+ok(named.every((d) => new RegExp(session.$defs.placement.properties.desk.pattern).test(d) && new RegExp(lease.properties.desk.pattern).test(d) && deskPatterns.every((re) => re.test(d))),
+ 'a session desk is a bot.command@1 desk');
+ok(!['main', 'private'].some((d) => new RegExp(lease.properties.desk.pattern).test(d)), 'a session never leases main or private');
+assert.deepStrictEqual([lease.properties.width.minimum, lease.properties.width.maximum, lease.properties.height.minimum, lease.properties.height.maximum],
+ [session.$defs.size.properties.width.minimum, session.$defs.size.properties.width.maximum, session.$defs.size.properties.height.minimum, session.$defs.size.properties.height.maximum]);
+checks++;
+assert.deepStrictEqual(contracts.schema('run.session-control-request').properties.kind.enum, ['screen', 'input', 'browser']);
+checks++;
+ok(['run.session.create', 'run.session.read', 'run.session.control'].every((id) => run.capabilities.includes(id) && contracts.capabilities.get(id).owner === 'run'), 'Run serves the three session capabilities');
+const deskSession = contracts.capabilities.get('bot.desk.session');
+ok(deskSession && deskSession.owner === 'bot' && deskSession.visibility === 'first-party' && deskSession.sensitive === true, 'bot.desk.session is a first-party sensitive capability of Bot');
+// The usage a session meters: one reading per minute of desk time, keyed by session and minute (Billing replays, never 409s).
+const deskReading = { id: 'run:ses_01JD2C3D4E5F6G7H8J9K0MNPQR:0', idempotency_key: 'run:ses_01JD2C3D4E5F6G7H8J9K0MNPQR:0', service: 'run', subject: 'user:usr_01JAB2C3D4E5F6G7H8J9K0MNPQ',
+ resource: 'desk-seconds', node: 'dev_01J8Z4M2Q0R7T9YV3K6N8P1W2X', operation: 'browser.desk', quantity: 60, unit: 's', at: '2026-10-28T10:01:04Z', source: 'openvibe-run.session' };
+ok(contracts.validate('platform.usage-sample', deskReading).valid, 'a minute of desk time is a valid usage reading');
+
+console.log(`platform run: ${checks} job, frame, session and metering checks passed`);