openvibers/openvibe.contracts Commits
chat.vibe.ask: a first-party service asks Vibe on a person's behalf (Bot's Vibe by voice), kept in their own Vibe chats; chat.vibe-ask-request/result@1 (0.214.0) (#215)
Changes
@@@ -14,6 +14,11 @@ the notes were in the tag and commit messages (`git tag -n1`).`url` of the Vibe chat that keeps it.- `config` carries `voice` (the owner turned it on for this machine) and `call_word`.Fixtures for each, and two invalid ones.++- **`chat.vibe.ask`** (first-party, sensitive, owned by `chat`): a first-party service asks Vibe on a person's behalf++ (`POST /internal/chat/vibe/ask`): Bot's Vibe by voice, with the machine's owner as `subject`. Chat keeps the question++ and the answer in that person's own Vibe chats (one chat per source, "Voice on <machine>") and counts the answer++ against their daily allowance. Schemas `chat.vibe-ask-request@1` (subject, text, source { kind voice, id, name },++ lang, idempotency_key) and `chat.vibe-ask-result@1` (state answered | refused, text, url, chat, code), with fixtures.- **Quiet hours for personal agents** (consumer plan step 4: "recurring work … with quiet hours and a budget").`actor.agent-manage-request@1` and `-result@1` carry `quiet_from` / `quiet_to` (HH:MM UTC, both or neither; the windowmay cross midnight): no scheduled run starts inside it, an hourly run waits until it ends, and a run the owner starts@@@ -5078,6 +5078,24 @@"compatibility": "backward","status": "active"},++ {++ "id": "chat.vibe-ask-request",++ "version": "1.0.0",++ "owner": "chat",++ "schema": "chat/vibe-ask-request.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },++ {++ "id": "chat.vibe-ask-result",++ "version": "1.0.0",++ "owner": "chat",++ "schema": "chat/vibe-ask-result.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "active"++ },{"id": "live.moderation.action","version": "1.0.0",nnew file mode 100644@@@ -0,0 +1,60 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/chat/vibe-ask-request.v1.json",++ "title": "ChatVibeAskRequest",++ "description": "chat.vibe-ask-request@1: a first-party service asks Vibe, OpenVibe's assistant, on a person's behalf (Vibe by voice: OpenVibe.Bot passes what the person said after their machine's call word). Chat keeps it in the person's own Vibe chats, one chat per source (\"Voice on <machine>\"), and answers with Vibe's reply, which counts against the person's daily allowance like any of their Vibe answers. POST /internal/chat/vibe/ask, capability chat.vibe.ask, loopback only.",++ "type": "object",++ "required": [++ "subject",++ "text",++ "source"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person Vibe answers, as the calling service vouches (Bot: the machine's owner, with voice on for that machine)."++ },++ "text": {++ "type": "string",++ "minLength": 1,++ "maxLength": 2000,++ "description": "What they asked."++ },++ "source": {++ "type": "object",++ "required": [++ "kind",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "kind": {++ "enum": [++ "voice"++ ]++ },++ "id": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",++ "description": "The source's stable id (the machine's device id): one chat per source."++ },++ "name": {++ "type": "string",++ "maxLength": 80,++ "description": "What to call it in the chat's name (the machine's name)."++ }++ }++ },++ "lang": {++ "type": "string",++ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$"++ },++ "idempotency_key": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,128}$",++ "description": "The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,38 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/chat/vibe-ask-result.v1.json",++ "title": "ChatVibeAskResult",++ "description": "chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.",++ "type": "object",++ "required": [++ "state",++ "text"++ ],++ "additionalProperties": false,++ "properties": {++ "state": {++ "enum": [++ "answered",++ "refused"++ ]++ },++ "text": {++ "type": "string",++ "maxLength": 4000++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ },++ "chat": {++ "type": "string",++ "pattern": "^v-[0-9a-f]{12}$",++ "description": "The Vibe chat's slug."++ },++ "code": {++ "type": "string",++ "description": "Why it was refused (chat.vibe.off, chat.vibe.allowance)."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "subject": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",++ "text": "hello"++}nnew file mode 100644@@@ -0,0 +1,11 @@++{++ "subject": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",++ "text": "what is the weather like this evening",++ "source": {++ "kind": "voice",++ "id": "dev_01JABCDEFGHJKMNPQRSTVWXYZ0",++ "name": "desk-pc"++ },++ "lang": "en-US",++ "idempotency_key": "dev_01JABCDEFGHJKMNPQRSTVWXYZ0:va-001"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "state": "maybe",++ "text": "x"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "state": "answered",++ "text": "Clear skies after six, around twelve degrees.",++ "url": "https://openvibe.chat/vibe/v-0123456789ab",++ "chat": "v-0123456789ab"++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "state": "refused",++ "text": "You have used today's free answers.",++ "code": "chat.vibe.allowance"++}@@@ -56367,6 +56367,114 @@]}},++ "chat.vibe-ask-request": {++ "version": "1.0.0",++ "owner": "chat",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/chat/vibe-ask-request.v1.json",++ "title": "ChatVibeAskRequest",++ "description": "chat.vibe-ask-request@1: a first-party service asks Vibe, OpenVibe's assistant, on a person's behalf (Vibe by voice: OpenVibe.Bot passes what the person said after their machine's call word). Chat keeps it in the person's own Vibe chats, one chat per source (\"Voice on <machine>\"), and answers with Vibe's reply, which counts against the person's daily allowance like any of their Vibe answers. POST /internal/chat/vibe/ask, capability chat.vibe.ask, loopback only.",++ "type": "object",++ "required": [++ "subject",++ "text",++ "source"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person Vibe answers, as the calling service vouches (Bot: the machine's owner, with voice on for that machine)."++ },++ "text": {++ "type": "string",++ "minLength": 1,++ "maxLength": 2000,++ "description": "What they asked."++ },++ "source": {++ "type": "object",++ "required": [++ "kind",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "kind": {++ "enum": [++ "voice"++ ]++ },++ "id": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",++ "description": "The source's stable id (the machine's device id): one chat per source."++ },++ "name": {++ "type": "string",++ "maxLength": 80,++ "description": "What to call it in the chat's name (the machine's name)."++ }++ }++ },++ "lang": {++ "type": "string",++ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$"++ },++ "idempotency_key": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,128}$",++ "description": "The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once."++ }++ }++ }++ },++ "chat.vibe-ask-result": {++ "version": "1.0.0",++ "owner": "chat",++ "status": "active",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/chat/vibe-ask-result.v1.json",++ "title": "ChatVibeAskResult",++ "description": "chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.",++ "type": "object",++ "required": [++ "state",++ "text"++ ],++ "additionalProperties": false,++ "properties": {++ "state": {++ "enum": [++ "answered",++ "refused"++ ]++ },++ "text": {++ "type": "string",++ "maxLength": 4000++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ },++ "chat": {++ "type": "string",++ "pattern": "^v-[0-9a-f]{12}$",++ "description": "The Vibe chat's slug."++ },++ "code": {++ "type": "string",++ "description": "Why it was refused (chat.vibe.off, chat.vibe.allowance)."++ }++ }++ }++ },"live.moderation.action": {"version": "1.0.0","owner": "live",@@@ -4083,6 +4083,62 @@}}},++ "/internal/chat/vibe/ask": {++ "post": {++ "operationId": "post_internal_chat_vibe_ask",++ "summary": "Ask Vibe, OpenVibe's assistant, on a person's behalf and get its answer: the question and the answer are kept in that person's own Vibe chats (one per source) and count against their daily allowance.",++ "description": "**chat.vibe.ask** (first-party): Ask Vibe, OpenVibe's assistant, on a person's behalf and get its answer: the question and the answer are kept in that person's own Vibe chats (one per source) and count against their daily allowance. For OpenVibe.Bot's Vibe by voice (a machine's owner turned voice on; Bot vouches for the owner). Loopback only, first-party service token; never granted to an app or delegated to an agent.",++ "tags": [++ "chat.vibe"++ ],++ "x-openvibe-capabilities": [++ "chat.vibe.ask"++ ],++ "x-openvibe-visibility": [++ "first-party"++ ],++ "x-openvibe-quota-class": "internal-write",++ "security": [++ {++ "openvibeToken": [++ "chat.vibe.ask"++ ]++ }++ ],++ "requestBody": {++ "required": false,++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/chat.vibe-ask-request.v1"++ }++ }++ }++ },++ "responses": {++ "2XX": {++ "description": "Success",++ "content": {++ "application/json": {++ "schema": {++ "$ref": "#/components/schemas/chat.vibe-ask-result.v1"++ }++ }++ }++ },++ "default": {++ "description": "An error (RFC 9457 problem details)",++ "content": {++ "application/problem+json": {++ "schema": {++ "$ref": "#/components/schemas/errors.problem.v1"++ }++ }++ }++ }++ }++ }++ },"/internal/live/calls": {"post": {"operationId": "post_internal_live_calls",@@@ -8153,6 +8209,100 @@}]},++ "chat.vibe-ask-request.v1": {++ "title": "ChatVibeAskRequest",++ "description": "chat.vibe-ask-request@1: a first-party service asks Vibe, OpenVibe's assistant, on a person's behalf (Vibe by voice: OpenVibe.Bot passes what the person said after their machine's call word). Chat keeps it in the person's own Vibe chats, one chat per source (\"Voice on <machine>\"), and answers with Vibe's reply, which counts against the person's daily allowance like any of their Vibe answers. POST /internal/chat/vibe/ask, capability chat.vibe.ask, loopback only.",++ "type": "object",++ "required": [++ "subject",++ "text",++ "source"++ ],++ "additionalProperties": false,++ "properties": {++ "subject": {++ "type": "string",++ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The person Vibe answers, as the calling service vouches (Bot: the machine's owner, with voice on for that machine)."++ },++ "text": {++ "type": "string",++ "minLength": 1,++ "maxLength": 2000,++ "description": "What they asked."++ },++ "source": {++ "type": "object",++ "required": [++ "kind",++ "id"++ ],++ "additionalProperties": false,++ "properties": {++ "kind": {++ "enum": [++ "voice"++ ]++ },++ "id": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",++ "description": "The source's stable id (the machine's device id): one chat per source."++ },++ "name": {++ "type": "string",++ "maxLength": 80,++ "description": "What to call it in the chat's name (the machine's name)."++ }++ }++ },++ "lang": {++ "type": "string",++ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$"++ },++ "idempotency_key": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,128}$",++ "description": "The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once."++ }++ }++ },++ "chat.vibe-ask-result.v1": {++ "title": "ChatVibeAskResult",++ "description": "chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.",++ "type": "object",++ "required": [++ "state",++ "text"++ ],++ "additionalProperties": false,++ "properties": {++ "state": {++ "enum": [++ "answered",++ "refused"++ ]++ },++ "text": {++ "type": "string",++ "maxLength": 4000++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://"++ },++ "chat": {++ "type": "string",++ "pattern": "^v-[0-9a-f]{12}$",++ "description": "The Vibe chat's slug."++ },++ "code": {++ "type": "string",++ "description": "Why it was refused (chat.vibe.off, chat.vibe.allowance)."++ }++ }++ },"common.owned-resource-control-request.v1": {"title": "OwnedResourceControlRequest","description": "common.owned-resource-control-request@1 (ADR-048 amendment 2026-10-15, step 8 phase 2): one control operation on a person-owned resource. Robots, chat rooms, restream definitions and pastes belong to a person, not to a project, so they carry no OVRN and the project-scoped common.resource-control-request@1 cannot name them. OpenVibe.Services sends this to the owning authority's POST /api/v1/resources/control (the same route, told apart by `owned`), acting for the person in on_behalf_of. owned names the resource by its index summary's kind and id. owner is the person it belongs to: the authority refuses unless its own record says the same (Services also offers actions only on the viewer's own resources). A sensitive action (deleting, physical control) asked by an agent is refused with confirmation_required until the owner approves, as for project resources. idempotency_key makes a retry safe: the authority answers a repeated key from the same caller with the first result, and refuses the key reused with another request. Answered with common.resource-control-result@1 (without resource: there is no OVRN). The actions each authority takes are in its <service>.resource.control capability; any other action is refused unsupported.",@@@ -40,8 +40,8 @@"service": "chat","name": "OpenVibe.Chat","origin": "https://openvibe.chat",-- "operations": 52,-- "capabilities": 17++ "operations": 53,++ "capabilities": 18},{"service": "codes",@@@ -190974,6 +190974,55 @@ export interface ChatTicketConversation {};}++/** [email protected] (owner: chat) */++/**++ * chat.vibe-ask-request@1: a first-party service asks Vibe, OpenVibe's assistant, on a person's behalf (Vibe by voice: OpenVibe.Bot passes what the person said after their machine's call word). Chat keeps it in the person's own Vibe chats, one chat per source ("Voice on <machine>"), and answers with Vibe's reply, which counts against the person's daily allowance like any of their Vibe answers. POST /internal/chat/vibe/ask, capability chat.vibe.ask, loopback only.++ */++export interface ChatVibeAskRequest {++ /**++ * The person Vibe answers, as the calling service vouches (Bot: the machine's owner, with voice on for that machine).++ */++ subject: string;++ /**++ * What they asked.++ */++ text: string;++ source: {++ kind: "voice";++ /**++ * The source's stable id (the machine's device id): one chat per source.++ */++ id: string;++ /**++ * What to call it in the chat's name (the machine's name).++ */++ name?: string;++ };++ lang?: string;++ /**++ * The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once.++ */++ idempotency_key?: string;++}++++/** [email protected] (owner: chat) */++/**++ * chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.++ */++export interface ChatVibeAskResult {++ state: "answered" | "refused";++ text: string;++ url?: string;++ /**++ * The Vibe chat's slug.++ */++ chat?: string;++ /**++ * Why it was refused (chat.vibe.off, chat.vibe.allowance).++ */++ code?: string;++}++/** [email protected] (owner: live) *//*** live.moderation.action v1 (OpenVibe.Live server/db/database.js logModerationAction; ADR-022). A staff or channel moderator action taken on OpenVibe.Live outside chat: site and global bans, IP bans, message deletes and purges from Live's admin panel, a stream force-ended, relay users hidden, channel moderators added or removed. Not a person tidying their own messages or configuring their own channel. Written to Live's outbox in the transaction that records the action. OpenVibe.Network keeps it in the moderation audit log. Envelope: subject { type: moderation_action, id: <action_id> }, visibility internal, actor the acting person when known. details is free-form per action (never secrets or message text beyond what moderators saw).nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "id": "chat.vibe.ask",++ "version": "1.0",++ "owner": "chat",++ "status": "active",++ "visibility": "first-party",++ "sensitive": true,++ "description": "Ask Vibe, OpenVibe's assistant, on a person's behalf and get its answer: the question and the answer are kept in that person's own Vibe chats (one per source) and count against their daily allowance. For OpenVibe.Bot's Vibe by voice (a machine's owner turned voice on; Bot vouches for the owner). Loopback only, first-party service token; never granted to an app or delegated to an agent.",++ "permissions": [++ "chat:write"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "internal-write",++ "events": [],++ "implementedBy": [++ "POST /internal/chat/vibe/ask"++ ],++ "inputSchema": "chat.vibe-ask-request@1",++ "outputSchema": "chat.vibe-ask-result@1"++}@@@ -30,7 +30,8 @@"chat.resource.control","chat.inbox.read","chat.inbox.reply",-- "chat.inbox.manage"++ "chat.inbox.manage",++ "chat.vibe.ask"],"eventsProduced": ["chat.message.created",
@@ -14,6 +14,11 @@ the notes were in the tag and commit messages (`git tag -n1`).
@@ -14,6 +14,11 @@ the notes were in the tag and commit messages (`git tag -n1`).
`url` of the Vibe chat that keeps it.
`url` of the Vibe chat that keeps it.
- `config` carries `voice` (the owner turned it on for this machine) and `call_word`.
- `config` carries `voice` (the owner turned it on for this machine) and `call_word`.
Fixtures for each, and two invalid ones.
Fixtures for each, and two invalid ones.
+- **`chat.vibe.ask`** (first-party, sensitive, owned by `chat`): a first-party service asks Vibe on a person's behalf
+ (`POST /internal/chat/vibe/ask`): Bot's Vibe by voice, with the machine's owner as `subject`. Chat keeps the question
+ and the answer in that person's own Vibe chats (one chat per source, "Voice on <machine>") and counts the answer
+ against their daily allowance. Schemas `chat.vibe-ask-request@1` (subject, text, source { kind voice, id, name },
+ lang, idempotency_key) and `chat.vibe-ask-result@1` (state answered | refused, text, url, chat, code), with fixtures.
- **Quiet hours for personal agents** (consumer plan step 4: "recurring work … with quiet hours and a budget").
- **Quiet hours for personal agents** (consumer plan step 4: "recurring work … with quiet hours and a budget").
`actor.agent-manage-request@1` and `-result@1` carry `quiet_from` / `quiet_to` (HH:MM UTC, both or neither; the window
`actor.agent-manage-request@1` and `-result@1` carry `quiet_from` / `quiet_to` (HH:MM UTC, both or neither; the window
may cross midnight): no scheduled run starts inside it, an hourly run waits until it ends, and a run the owner starts
may cross midnight): no scheduled run starts inside it, an hourly run waits until it ends, and a run the owner starts
@@ -5078,6 +5078,24 @@
@@ -5078,6 +5078,24 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
},
},
+ {
+ "id": "chat.vibe-ask-request",
+ "version": "1.0.0",
+ "owner": "chat",
+ "schema": "chat/vibe-ask-request.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
+ {
+ "id": "chat.vibe-ask-result",
+ "version": "1.0.0",
+ "owner": "chat",
+ "schema": "chat/vibe-ask-result.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "active"
+ },
{
{
"id": "live.moderation.action",
"id": "live.moderation.action",
"version": "1.0.0",
"version": "1.0.0",
new file mode 100644
new file mode 100644
@@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/chat/vibe-ask-request.v1.json",
+ "title": "ChatVibeAskRequest",
+ "description": "chat.vibe-ask-request@1: a first-party service asks Vibe, OpenVibe's assistant, on a person's behalf (Vibe by voice: OpenVibe.Bot passes what the person said after their machine's call word). Chat keeps it in the person's own Vibe chats, one chat per source (\"Voice on <machine>\"), and answers with Vibe's reply, which counts against the person's daily allowance like any of their Vibe answers. POST /internal/chat/vibe/ask, capability chat.vibe.ask, loopback only.",
+ "type": "object",
+ "required": [
+ "subject",
+ "text",
+ "source"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person Vibe answers, as the calling service vouches (Bot: the machine's owner, with voice on for that machine)."
+ },
+ "text": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2000,
+ "description": "What they asked."
+ },
+ "source": {
+ "type": "object",
+ "required": [
+ "kind",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": [
+ "voice"
+ ]
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
+ "description": "The source's stable id (the machine's device id): one chat per source."
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 80,
+ "description": "What to call it in the chat's name (the machine's name)."
+ }
+ }
+ },
+ "lang": {
+ "type": "string",
+ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$"
+ },
+ "idempotency_key": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,128}$",
+ "description": "The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/chat/vibe-ask-result.v1.json",
+ "title": "ChatVibeAskResult",
+ "description": "chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.",
+ "type": "object",
+ "required": [
+ "state",
+ "text"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "state": {
+ "enum": [
+ "answered",
+ "refused"
+ ]
+ },
+ "text": {
+ "type": "string",
+ "maxLength": 4000
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "chat": {
+ "type": "string",
+ "pattern": "^v-[0-9a-f]{12}$",
+ "description": "The Vibe chat's slug."
+ },
+ "code": {
+ "type": "string",
+ "description": "Why it was refused (chat.vibe.off, chat.vibe.allowance)."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "subject": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",
+ "text": "hello"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
+{
+ "subject": "usr_01JABCDEFGHJKMNPQRSTVWXYZ0",
+ "text": "what is the weather like this evening",
+ "source": {
+ "kind": "voice",
+ "id": "dev_01JABCDEFGHJKMNPQRSTVWXYZ0",
+ "name": "desk-pc"
+ },
+ "lang": "en-US",
+ "idempotency_key": "dev_01JABCDEFGHJKMNPQRSTVWXYZ0:va-001"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "state": "maybe",
+ "text": "x"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "state": "answered",
+ "text": "Clear skies after six, around twelve degrees.",
+ "url": "https://openvibe.chat/vibe/v-0123456789ab",
+ "chat": "v-0123456789ab"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "state": "refused",
+ "text": "You have used today's free answers.",
+ "code": "chat.vibe.allowance"
+}
@@ -56367,6 +56367,114 @@
@@ -56367,6 +56367,114 @@
]
]
}
}
},
},
+ "chat.vibe-ask-request": {
+ "version": "1.0.0",
+ "owner": "chat",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/chat/vibe-ask-request.v1.json",
+ "title": "ChatVibeAskRequest",
+ "description": "chat.vibe-ask-request@1: a first-party service asks Vibe, OpenVibe's assistant, on a person's behalf (Vibe by voice: OpenVibe.Bot passes what the person said after their machine's call word). Chat keeps it in the person's own Vibe chats, one chat per source (\"Voice on <machine>\"), and answers with Vibe's reply, which counts against the person's daily allowance like any of their Vibe answers. POST /internal/chat/vibe/ask, capability chat.vibe.ask, loopback only.",
+ "type": "object",
+ "required": [
+ "subject",
+ "text",
+ "source"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person Vibe answers, as the calling service vouches (Bot: the machine's owner, with voice on for that machine)."
+ },
+ "text": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2000,
+ "description": "What they asked."
+ },
+ "source": {
+ "type": "object",
+ "required": [
+ "kind",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": [
+ "voice"
+ ]
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
+ "description": "The source's stable id (the machine's device id): one chat per source."
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 80,
+ "description": "What to call it in the chat's name (the machine's name)."
+ }
+ }
+ },
+ "lang": {
+ "type": "string",
+ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$"
+ },
+ "idempotency_key": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,128}$",
+ "description": "The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once."
+ }
+ }
+ }
+ },
+ "chat.vibe-ask-result": {
+ "version": "1.0.0",
+ "owner": "chat",
+ "status": "active",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/chat/vibe-ask-result.v1.json",
+ "title": "ChatVibeAskResult",
+ "description": "chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.",
+ "type": "object",
+ "required": [
+ "state",
+ "text"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "state": {
+ "enum": [
+ "answered",
+ "refused"
+ ]
+ },
+ "text": {
+ "type": "string",
+ "maxLength": 4000
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "chat": {
+ "type": "string",
+ "pattern": "^v-[0-9a-f]{12}$",
+ "description": "The Vibe chat's slug."
+ },
+ "code": {
+ "type": "string",
+ "description": "Why it was refused (chat.vibe.off, chat.vibe.allowance)."
+ }
+ }
+ }
+ },
"live.moderation.action": {
"live.moderation.action": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "live",
"owner": "live",
@@ -4083,6 +4083,62 @@
@@ -4083,6 +4083,62 @@
}
}
}
}
},
},
+ "/internal/chat/vibe/ask": {
+ "post": {
+ "operationId": "post_internal_chat_vibe_ask",
+ "summary": "Ask Vibe, OpenVibe's assistant, on a person's behalf and get its answer: the question and the answer are kept in that person's own Vibe chats (one per source) and count against their daily allowance.",
+ "description": "**chat.vibe.ask** (first-party): Ask Vibe, OpenVibe's assistant, on a person's behalf and get its answer: the question and the answer are kept in that person's own Vibe chats (one per source) and count against their daily allowance. For OpenVibe.Bot's Vibe by voice (a machine's owner turned voice on; Bot vouches for the owner). Loopback only, first-party service token; never granted to an app or delegated to an agent.",
+ "tags": [
+ "chat.vibe"
+ ],
+ "x-openvibe-capabilities": [
+ "chat.vibe.ask"
+ ],
+ "x-openvibe-visibility": [
+ "first-party"
+ ],
+ "x-openvibe-quota-class": "internal-write",
+ "security": [
+ {
+ "openvibeToken": [
+ "chat.vibe.ask"
+ ]
+ }
+ ],
+ "requestBody": {
+ "required": false,
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/chat.vibe-ask-request.v1"
+ }
+ }
+ }
+ },
+ "responses": {
+ "2XX": {
+ "description": "Success",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/chat.vibe-ask-result.v1"
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An error (RFC 9457 problem details)",
+ "content": {
+ "application/problem+json": {
+ "schema": {
+ "$ref": "#/components/schemas/errors.problem.v1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/internal/live/calls": {
"/internal/live/calls": {
"post": {
"post": {
"operationId": "post_internal_live_calls",
"operationId": "post_internal_live_calls",
@@ -8153,6 +8209,100 @@
@@ -8153,6 +8209,100 @@
}
}
]
]
},
},
+ "chat.vibe-ask-request.v1": {
+ "title": "ChatVibeAskRequest",
+ "description": "chat.vibe-ask-request@1: a first-party service asks Vibe, OpenVibe's assistant, on a person's behalf (Vibe by voice: OpenVibe.Bot passes what the person said after their machine's call word). Chat keeps it in the person's own Vibe chats, one chat per source (\"Voice on <machine>\"), and answers with Vibe's reply, which counts against the person's daily allowance like any of their Vibe answers. POST /internal/chat/vibe/ask, capability chat.vibe.ask, loopback only.",
+ "type": "object",
+ "required": [
+ "subject",
+ "text",
+ "source"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "subject": {
+ "type": "string",
+ "pattern": "^usr_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The person Vibe answers, as the calling service vouches (Bot: the machine's owner, with voice on for that machine)."
+ },
+ "text": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2000,
+ "description": "What they asked."
+ },
+ "source": {
+ "type": "object",
+ "required": [
+ "kind",
+ "id"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "kind": {
+ "enum": [
+ "voice"
+ ]
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
+ "description": "The source's stable id (the machine's device id): one chat per source."
+ },
+ "name": {
+ "type": "string",
+ "maxLength": 80,
+ "description": "What to call it in the chat's name (the machine's name)."
+ }
+ }
+ },
+ "lang": {
+ "type": "string",
+ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$"
+ },
+ "idempotency_key": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,128}$",
+ "description": "The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once."
+ }
+ }
+ },
+ "chat.vibe-ask-result.v1": {
+ "title": "ChatVibeAskResult",
+ "description": "chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.",
+ "type": "object",
+ "required": [
+ "state",
+ "text"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "state": {
+ "enum": [
+ "answered",
+ "refused"
+ ]
+ },
+ "text": {
+ "type": "string",
+ "maxLength": 4000
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://"
+ },
+ "chat": {
+ "type": "string",
+ "pattern": "^v-[0-9a-f]{12}$",
+ "description": "The Vibe chat's slug."
+ },
+ "code": {
+ "type": "string",
+ "description": "Why it was refused (chat.vibe.off, chat.vibe.allowance)."
+ }
+ }
+ },
"common.owned-resource-control-request.v1": {
"common.owned-resource-control-request.v1": {
"title": "OwnedResourceControlRequest",
"title": "OwnedResourceControlRequest",
"description": "common.owned-resource-control-request@1 (ADR-048 amendment 2026-10-15, step 8 phase 2): one control operation on a person-owned resource. Robots, chat rooms, restream definitions and pastes belong to a person, not to a project, so they carry no OVRN and the project-scoped common.resource-control-request@1 cannot name them. OpenVibe.Services sends this to the owning authority's POST /api/v1/resources/control (the same route, told apart by `owned`), acting for the person in on_behalf_of. owned names the resource by its index summary's kind and id. owner is the person it belongs to: the authority refuses unless its own record says the same (Services also offers actions only on the viewer's own resources). A sensitive action (deleting, physical control) asked by an agent is refused with confirmation_required until the owner approves, as for project resources. idempotency_key makes a retry safe: the authority answers a repeated key from the same caller with the first result, and refuses the key reused with another request. Answered with common.resource-control-result@1 (without resource: there is no OVRN). The actions each authority takes are in its <service>.resource.control capability; any other action is refused unsupported.",
"description": "common.owned-resource-control-request@1 (ADR-048 amendment 2026-10-15, step 8 phase 2): one control operation on a person-owned resource. Robots, chat rooms, restream definitions and pastes belong to a person, not to a project, so they carry no OVRN and the project-scoped common.resource-control-request@1 cannot name them. OpenVibe.Services sends this to the owning authority's POST /api/v1/resources/control (the same route, told apart by `owned`), acting for the person in on_behalf_of. owned names the resource by its index summary's kind and id. owner is the person it belongs to: the authority refuses unless its own record says the same (Services also offers actions only on the viewer's own resources). A sensitive action (deleting, physical control) asked by an agent is refused with confirmation_required until the owner approves, as for project resources. idempotency_key makes a retry safe: the authority answers a repeated key from the same caller with the first result, and refuses the key reused with another request. Answered with common.resource-control-result@1 (without resource: there is no OVRN). The actions each authority takes are in its <service>.resource.control capability; any other action is refused unsupported.",
@@ -40,8 +40,8 @@
@@ -40,8 +40,8 @@
"service": "chat",
"service": "chat",
"name": "OpenVibe.Chat",
"name": "OpenVibe.Chat",
"origin": "https://openvibe.chat",
"origin": "https://openvibe.chat",
- "operations": 52,
- "capabilities": 17
+ "operations": 53,
+ "capabilities": 18
},
},
{
{
"service": "codes",
"service": "codes",
@@ -190974,6 +190974,55 @@ export interface ChatTicketConversation {
@@ -190974,6 +190974,55 @@ export interface ChatTicketConversation {
};
};
}
}
+/** [email protected] (owner: chat) */
+/**
+ * chat.vibe-ask-request@1: a first-party service asks Vibe, OpenVibe's assistant, on a person's behalf (Vibe by voice: OpenVibe.Bot passes what the person said after their machine's call word). Chat keeps it in the person's own Vibe chats, one chat per source ("Voice on <machine>"), and answers with Vibe's reply, which counts against the person's daily allowance like any of their Vibe answers. POST /internal/chat/vibe/ask, capability chat.vibe.ask, loopback only.
+ */
+export interface ChatVibeAskRequest {
+ /**
+ * The person Vibe answers, as the calling service vouches (Bot: the machine's owner, with voice on for that machine).
+ */
+ subject: string;
+ /**
+ * What they asked.
+ */
+ text: string;
+ source: {
+ kind: "voice";
+ /**
+ * The source's stable id (the machine's device id): one chat per source.
+ */
+ id: string;
+ /**
+ * What to call it in the chat's name (the machine's name).
+ */
+ name?: string;
+ };
+ lang?: string;
+ /**
+ * The caller's id for this ask (Bot: device id and the voice_ask id): asked again, the same answer, once.
+ */
+ idempotency_key?: string;
+}
+
+/** [email protected] (owner: chat) */
+/**
+ * chat.vibe-ask-result@1: Vibe's answer to a chat.vibe-ask-request@1. `answered` carries the reply (plain text, Markdown stripped, for speech); `refused` says why nothing was asked (Vibe resting, the person's daily allowance spent). `url` is the Vibe chat that keeps the question and the answer.
+ */
+export interface ChatVibeAskResult {
+ state: "answered" | "refused";
+ text: string;
+ url?: string;
+ /**
+ * The Vibe chat's slug.
+ */
+ chat?: string;
+ /**
+ * Why it was refused (chat.vibe.off, chat.vibe.allowance).
+ */
+ code?: string;
+}
+
/** [email protected] (owner: live) */
/** [email protected] (owner: live) */
/**
/**
* live.moderation.action v1 (OpenVibe.Live server/db/database.js logModerationAction; ADR-022). A staff or channel moderator action taken on OpenVibe.Live outside chat: site and global bans, IP bans, message deletes and purges from Live's admin panel, a stream force-ended, relay users hidden, channel moderators added or removed. Not a person tidying their own messages or configuring their own channel. Written to Live's outbox in the transaction that records the action. OpenVibe.Network keeps it in the moderation audit log. Envelope: subject { type: moderation_action, id: <action_id> }, visibility internal, actor the acting person when known. details is free-form per action (never secrets or message text beyond what moderators saw).
* live.moderation.action v1 (OpenVibe.Live server/db/database.js logModerationAction; ADR-022). A staff or channel moderator action taken on OpenVibe.Live outside chat: site and global bans, IP bans, message deletes and purges from Live's admin panel, a stream force-ended, relay users hidden, channel moderators added or removed. Not a person tidying their own messages or configuring their own channel. Written to Live's outbox in the transaction that records the action. OpenVibe.Network keeps it in the moderation audit log. Envelope: subject { type: moderation_action, id: <action_id> }, visibility internal, actor the acting person when known. details is free-form per action (never secrets or message text beyond what moderators saw).
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "id": "chat.vibe.ask",
+ "version": "1.0",
+ "owner": "chat",
+ "status": "active",
+ "visibility": "first-party",
+ "sensitive": true,
+ "description": "Ask Vibe, OpenVibe's assistant, on a person's behalf and get its answer: the question and the answer are kept in that person's own Vibe chats (one per source) and count against their daily allowance. For OpenVibe.Bot's Vibe by voice (a machine's owner turned voice on; Bot vouches for the owner). Loopback only, first-party service token; never granted to an app or delegated to an agent.",
+ "permissions": [
+ "chat:write"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "internal-write",
+ "events": [],
+ "implementedBy": [
+ "POST /internal/chat/vibe/ask"
+ ],
+ "inputSchema": "chat.vibe-ask-request@1",
+ "outputSchema": "chat.vibe-ask-result@1"
+}
@@ -30,7 +30,8 @@
@@ -30,7 +30,8 @@
"chat.resource.control",
"chat.resource.control",
"chat.inbox.read",
"chat.inbox.read",
"chat.inbox.reply",
"chat.inbox.reply",
- "chat.inbox.manage"
+ "chat.inbox.manage",
+ "chat.vibe.ask"
],
],
"eventsProduced": [
"eventsProduced": [
"chat.message.created",
"chat.message.created",