openvibers/openvibe.contracts Commits
bot.device-message@1: Vibe by voice on a person's own machine (voice_ask from the device, voice_answer to it, config voice and call_word) (0.214.0) (#214)
Changes
@@@ -6,6 +6,14 @@ the notes were in the tag and commit messages (`git tag -n1`).## 0.214.0 — 2026-10-26++- **Vibe by voice on a person's own machine** (the owner, 2026-10-24: "a call word on Live and on Node"; consumer plan++ §8). `bot.device-message@1` gains two frames and two config fields, all additive:++ - `voice_ask` (device to server): `id` and `text`, what the person asked after the machine's call word, transcribed++ on the machine (nothing leaves it before the call word); optional `lang` (BCP 47) and `robot_id`.++ - `voice_answer` (server to device): the same `id`, `state` working | answered | refused, the `text` to speak and the++ `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.- **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@@@ -2,7 +2,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/bot/device-message.v1.json","title": "BotDeviceMessage",-- "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state. The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).",++ "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).","oneOf": [{"description": "Server to device, once authenticated.",@@@ -122,6 +122,16 @@},"estop_latched": {"type": "boolean"++ },++ "voice": {++ "type": "boolean",++ "description": "Vibe by voice: the owner turned it on for this machine, so it may listen for its call word and send voice_ask (absent or false: it does not)."++ },++ "call_word": {++ "type": "string",++ "minLength": 3,++ "maxLength": 40,++ "description": "The call word the owner chose (\"hey vibe\" unless they chose another); sent with voice on."}}},@@@ -903,6 +913,108 @@"type": "string"}}++ },++ {++ "description": "Server to device (Vibe by voice): Vibe's answer to a voice_ask, for the machine to speak and show. `working` says Vibe is on it (a longer task), `answered` carries the answer, `refused` says why nothing was done (voice off for the machine, the daily allowance spent, the request refused).",++ "type": "object",++ "required": [++ "v",++ "seq",++ "ts",++ "type",++ "id",++ "state"++ ],++ "additionalProperties": false,++ "properties": {++ "v": {++ "const": 1++ },++ "seq": {++ "type": "integer",++ "minimum": 0++ },++ "ts": {++ "type": "integer",++ "minimum": 0++ },++ "type": {++ "const": "voice_answer"++ },++ "id": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",++ "description": "The voice_ask this answers."++ },++ "state": {++ "enum": [++ "working",++ "answered",++ "refused"++ ]++ },++ "text": {++ "type": "string",++ "maxLength": 2000,++ "description": "The answer to speak (answered), a short line (working), or why not (refused)."++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://",++ "description": "Where the person can read it later: the Vibe chat that keeps the question and the answer."++ }++ }++ },++ {++ "description": "Device to server (Vibe by voice, consumer plan §8): the person said the machine's call word, and this is what they asked after it, transcribed on the machine itself (nothing leaves it before the call word). OpenVibe.Bot hands it to Vibe as the machine's owner, only when the owner turned voice on for the machine (config `voice`), and answers with voice_answer frames carrying the same id.",++ "type": "object",++ "required": [++ "v",++ "seq",++ "ts",++ "type",++ "id",++ "text"++ ],++ "additionalProperties": false,++ "properties": {++ "v": {++ "const": 1++ },++ "seq": {++ "type": "integer",++ "minimum": 0++ },++ "ts": {++ "type": "integer",++ "minimum": 0++ },++ "type": {++ "const": "voice_ask"++ },++ "id": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",++ "description": "The device's id for this request (unique per device for a day); the answers carry it."++ },++ "text": {++ "type": "string",++ "minLength": 1,++ "maxLength": 2000,++ "description": "What the person asked after the call word, as the machine heard it."++ },++ "lang": {++ "type": "string",++ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$",++ "description": "The language it was heard in (BCP 47), when the recogniser knows."++ },++ "robot_id": {++ "type": "string",++ "pattern": "^rob_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The robot whose microphone heard it, when the device is a robot's bridge."++ }++ }}]}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "v": 1,++ "seq": 1,++ "ts": 1,++ "type": "voice_answer",++ "id": "x",++ "state": "done"++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "v": 1,++ "seq": 1,++ "ts": 1,++ "type": "voice_ask",++ "id": "x",++ "text": ""++}nnew file mode 100644@@@ -0,0 +1,12 @@++{++ "v": 1,++ "seq": 2,++ "ts": 1792940000000,++ "type": "config",++ "heartbeat_ms": 1000,++ "limits": {},++ "allowed_commands": [],++ "estop_latched": false,++ "voice": true,++ "call_word": "hey vibe"++}nnew file mode 100644@@@ -0,0 +1,10 @@++{++ "v": 1,++ "seq": 12,++ "ts": 1792940003000,++ "type": "voice_answer",++ "id": "va-20261026-001",++ "state": "answered",++ "text": "You have a call at three and nothing after four.",++ "url": "https://openvibe.chat/vibe/v-0123456789ab"++}nnew file mode 100644@@@ -0,0 +1,9 @@++{++ "v": 1,++ "seq": 41,++ "ts": 1792940000000,++ "type": "voice_ask",++ "id": "va-20261026-001",++ "text": "what is on my calendar this afternoon",++ "lang": "en-US"++}@@@ -71571,7 +71571,7 @@"$schema": "https://json-schema.org/draft/2020-12/schema","$id": "https://openvibe.network/contracts/bot/device-message.v1.json","title": "BotDeviceMessage",-- "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state. The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).",++ "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).","oneOf": [{"description": "Server to device, once authenticated.",@@@ -71691,6 +71691,16 @@},"estop_latched": {"type": "boolean"++ },++ "voice": {++ "type": "boolean",++ "description": "Vibe by voice: the owner turned it on for this machine, so it may listen for its call word and send voice_ask (absent or false: it does not)."++ },++ "call_word": {++ "type": "string",++ "minLength": 3,++ "maxLength": 40,++ "description": "The call word the owner chose (\"hey vibe\" unless they chose another); sent with voice on."}}},@@@ -72472,6 +72482,108 @@"type": "string"}}++ },++ {++ "description": "Server to device (Vibe by voice): Vibe's answer to a voice_ask, for the machine to speak and show. `working` says Vibe is on it (a longer task), `answered` carries the answer, `refused` says why nothing was done (voice off for the machine, the daily allowance spent, the request refused).",++ "type": "object",++ "required": [++ "v",++ "seq",++ "ts",++ "type",++ "id",++ "state"++ ],++ "additionalProperties": false,++ "properties": {++ "v": {++ "const": 1++ },++ "seq": {++ "type": "integer",++ "minimum": 0++ },++ "ts": {++ "type": "integer",++ "minimum": 0++ },++ "type": {++ "const": "voice_answer"++ },++ "id": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",++ "description": "The voice_ask this answers."++ },++ "state": {++ "enum": [++ "working",++ "answered",++ "refused"++ ]++ },++ "text": {++ "type": "string",++ "maxLength": 2000,++ "description": "The answer to speak (answered), a short line (working), or why not (refused)."++ },++ "url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https://",++ "description": "Where the person can read it later: the Vibe chat that keeps the question and the answer."++ }++ }++ },++ {++ "description": "Device to server (Vibe by voice, consumer plan §8): the person said the machine's call word, and this is what they asked after it, transcribed on the machine itself (nothing leaves it before the call word). OpenVibe.Bot hands it to Vibe as the machine's owner, only when the owner turned voice on for the machine (config `voice`), and answers with voice_answer frames carrying the same id.",++ "type": "object",++ "required": [++ "v",++ "seq",++ "ts",++ "type",++ "id",++ "text"++ ],++ "additionalProperties": false,++ "properties": {++ "v": {++ "const": 1++ },++ "seq": {++ "type": "integer",++ "minimum": 0++ },++ "ts": {++ "type": "integer",++ "minimum": 0++ },++ "type": {++ "const": "voice_ask"++ },++ "id": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",++ "description": "The device's id for this request (unique per device for a day); the answers carry it."++ },++ "text": {++ "type": "string",++ "minLength": 1,++ "maxLength": 2000,++ "description": "What the person asked after the call word, as the machine heard it."++ },++ "lang": {++ "type": "string",++ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$",++ "description": "The language it was heard in (BCP 47), when the recogniser knows."++ },++ "robot_id": {++ "type": "string",++ "pattern": "^rob_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The robot whose microphone heard it, when the device is a robot's bridge."++ }++ }}]}@@@ -196794,7 +196794,7 @@ export interface BotRobotProfile {/** [email protected] (owner: bot) *//**-- * bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state. The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).++ * bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).*/export type BotDeviceMessage =| {@@@ -196821,6 +196821,14 @@ export type BotDeviceMessage =};allowed_commands: ("drive" | "actuator" | "ptz" | "say" | "display" | "halt")[];estop_latched: boolean;++ /**++ * Vibe by voice: the owner turned it on for this machine, so it may listen for its call word and send voice_ask (absent or false: it does not).++ */++ voice?: boolean;++ /**++ * The call word the owner chose ("hey vibe" unless they chose another); sent with voice on.++ */++ call_word?: string;}| {v: 1;@@@ -197033,6 +197041,47 @@ export type BotDeviceMessage =robot_id?: string;local_stop?: boolean;reason?: string;++ }++ | {++ v: 1;++ seq: number;++ ts: number;++ type: "voice_answer";++ /**++ * The voice_ask this answers.++ */++ id: string;++ state: "working" | "answered" | "refused";++ /**++ * The answer to speak (answered), a short line (working), or why not (refused).++ */++ text?: string;++ /**++ * Where the person can read it later: the Vibe chat that keeps the question and the answer.++ */++ url?: string;++ }++ | {++ v: 1;++ seq: number;++ ts: number;++ type: "voice_ask";++ /**++ * The device's id for this request (unique per device for a day); the answers carry it.++ */++ id: string;++ /**++ * What the person asked after the call word, as the machine heard it.++ */++ text: string;++ /**++ * The language it was heard in (BCP 47), when the recogniser knows.++ */++ lang?: string;++ /**++ * The robot whose microphone heard it, when the device is a robot's bridge.++ */++ robot_id?: string;};/** [email protected] (owner: bot) */
@@ -6,6 +6,14 @@ the notes were in the tag and commit messages (`git tag -n1`).
@@ -6,6 +6,14 @@ the notes were in the tag and commit messages (`git tag -n1`).
## 0.214.0 — 2026-10-26
## 0.214.0 — 2026-10-26
+- **Vibe by voice on a person's own machine** (the owner, 2026-10-24: "a call word on Live and on Node"; consumer plan
+ §8). `bot.device-message@1` gains two frames and two config fields, all additive:
+ - `voice_ask` (device to server): `id` and `text`, what the person asked after the machine's call word, transcribed
+ on the machine (nothing leaves it before the call word); optional `lang` (BCP 47) and `robot_id`.
+ - `voice_answer` (server to device): the same `id`, `state` working | answered | refused, the `text` to speak and the
+ `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.
- **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
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/device-message.v1.json",
"$id": "https://openvibe.network/contracts/bot/device-message.v1.json",
"title": "BotDeviceMessage",
"title": "BotDeviceMessage",
- "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state. The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).",
+ "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).",
"oneOf": [
"oneOf": [
{
{
"description": "Server to device, once authenticated.",
"description": "Server to device, once authenticated.",
@@ -122,6 +122,16 @@
@@ -122,6 +122,16 @@
},
},
"estop_latched": {
"estop_latched": {
"type": "boolean"
"type": "boolean"
+ },
+ "voice": {
+ "type": "boolean",
+ "description": "Vibe by voice: the owner turned it on for this machine, so it may listen for its call word and send voice_ask (absent or false: it does not)."
+ },
+ "call_word": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 40,
+ "description": "The call word the owner chose (\"hey vibe\" unless they chose another); sent with voice on."
}
}
}
}
},
},
@@ -903,6 +913,108 @@
@@ -903,6 +913,108 @@
"type": "string"
"type": "string"
}
}
}
}
+ },
+ {
+ "description": "Server to device (Vibe by voice): Vibe's answer to a voice_ask, for the machine to speak and show. `working` says Vibe is on it (a longer task), `answered` carries the answer, `refused` says why nothing was done (voice off for the machine, the daily allowance spent, the request refused).",
+ "type": "object",
+ "required": [
+ "v",
+ "seq",
+ "ts",
+ "type",
+ "id",
+ "state"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "v": {
+ "const": 1
+ },
+ "seq": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "ts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "type": {
+ "const": "voice_answer"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
+ "description": "The voice_ask this answers."
+ },
+ "state": {
+ "enum": [
+ "working",
+ "answered",
+ "refused"
+ ]
+ },
+ "text": {
+ "type": "string",
+ "maxLength": 2000,
+ "description": "The answer to speak (answered), a short line (working), or why not (refused)."
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://",
+ "description": "Where the person can read it later: the Vibe chat that keeps the question and the answer."
+ }
+ }
+ },
+ {
+ "description": "Device to server (Vibe by voice, consumer plan §8): the person said the machine's call word, and this is what they asked after it, transcribed on the machine itself (nothing leaves it before the call word). OpenVibe.Bot hands it to Vibe as the machine's owner, only when the owner turned voice on for the machine (config `voice`), and answers with voice_answer frames carrying the same id.",
+ "type": "object",
+ "required": [
+ "v",
+ "seq",
+ "ts",
+ "type",
+ "id",
+ "text"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "v": {
+ "const": 1
+ },
+ "seq": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "ts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "type": {
+ "const": "voice_ask"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
+ "description": "The device's id for this request (unique per device for a day); the answers carry it."
+ },
+ "text": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2000,
+ "description": "What the person asked after the call word, as the machine heard it."
+ },
+ "lang": {
+ "type": "string",
+ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$",
+ "description": "The language it was heard in (BCP 47), when the recogniser knows."
+ },
+ "robot_id": {
+ "type": "string",
+ "pattern": "^rob_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The robot whose microphone heard it, when the device is a robot's bridge."
+ }
+ }
}
}
]
]
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "v": 1,
+ "seq": 1,
+ "ts": 1,
+ "type": "voice_answer",
+ "id": "x",
+ "state": "done"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "v": 1,
+ "seq": 1,
+ "ts": 1,
+ "type": "voice_ask",
+ "id": "x",
+ "text": ""
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
+{
+ "v": 1,
+ "seq": 2,
+ "ts": 1792940000000,
+ "type": "config",
+ "heartbeat_ms": 1000,
+ "limits": {},
+ "allowed_commands": [],
+ "estop_latched": false,
+ "voice": true,
+ "call_word": "hey vibe"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
+{
+ "v": 1,
+ "seq": 12,
+ "ts": 1792940003000,
+ "type": "voice_answer",
+ "id": "va-20261026-001",
+ "state": "answered",
+ "text": "You have a call at three and nothing after four.",
+ "url": "https://openvibe.chat/vibe/v-0123456789ab"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
+{
+ "v": 1,
+ "seq": 41,
+ "ts": 1792940000000,
+ "type": "voice_ask",
+ "id": "va-20261026-001",
+ "text": "what is on my calendar this afternoon",
+ "lang": "en-US"
+}
@@ -71571,7 +71571,7 @@
@@ -71571,7 +71571,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openvibe.network/contracts/bot/device-message.v1.json",
"$id": "https://openvibe.network/contracts/bot/device-message.v1.json",
"title": "BotDeviceMessage",
"title": "BotDeviceMessage",
- "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state. The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).",
+ "description": "bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).",
"oneOf": [
"oneOf": [
{
{
"description": "Server to device, once authenticated.",
"description": "Server to device, once authenticated.",
@@ -71691,6 +71691,16 @@
@@ -71691,6 +71691,16 @@
},
},
"estop_latched": {
"estop_latched": {
"type": "boolean"
"type": "boolean"
+ },
+ "voice": {
+ "type": "boolean",
+ "description": "Vibe by voice: the owner turned it on for this machine, so it may listen for its call word and send voice_ask (absent or false: it does not)."
+ },
+ "call_word": {
+ "type": "string",
+ "minLength": 3,
+ "maxLength": 40,
+ "description": "The call word the owner chose (\"hey vibe\" unless they chose another); sent with voice on."
}
}
}
}
},
},
@@ -72472,6 +72482,108 @@
@@ -72472,6 +72482,108 @@
"type": "string"
"type": "string"
}
}
}
}
+ },
+ {
+ "description": "Server to device (Vibe by voice): Vibe's answer to a voice_ask, for the machine to speak and show. `working` says Vibe is on it (a longer task), `answered` carries the answer, `refused` says why nothing was done (voice off for the machine, the daily allowance spent, the request refused).",
+ "type": "object",
+ "required": [
+ "v",
+ "seq",
+ "ts",
+ "type",
+ "id",
+ "state"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "v": {
+ "const": 1
+ },
+ "seq": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "ts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "type": {
+ "const": "voice_answer"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
+ "description": "The voice_ask this answers."
+ },
+ "state": {
+ "enum": [
+ "working",
+ "answered",
+ "refused"
+ ]
+ },
+ "text": {
+ "type": "string",
+ "maxLength": 2000,
+ "description": "The answer to speak (answered), a short line (working), or why not (refused)."
+ },
+ "url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https://",
+ "description": "Where the person can read it later: the Vibe chat that keeps the question and the answer."
+ }
+ }
+ },
+ {
+ "description": "Device to server (Vibe by voice, consumer plan §8): the person said the machine's call word, and this is what they asked after it, transcribed on the machine itself (nothing leaves it before the call word). OpenVibe.Bot hands it to Vibe as the machine's owner, only when the owner turned voice on for the machine (config `voice`), and answers with voice_answer frames carrying the same id.",
+ "type": "object",
+ "required": [
+ "v",
+ "seq",
+ "ts",
+ "type",
+ "id",
+ "text"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "v": {
+ "const": 1
+ },
+ "seq": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "ts": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "type": {
+ "const": "voice_ask"
+ },
+ "id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_.:-]{1,64}$",
+ "description": "The device's id for this request (unique per device for a day); the answers carry it."
+ },
+ "text": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 2000,
+ "description": "What the person asked after the call word, as the machine heard it."
+ },
+ "lang": {
+ "type": "string",
+ "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8}){0,2}$",
+ "description": "The language it was heard in (BCP 47), when the recogniser knows."
+ },
+ "robot_id": {
+ "type": "string",
+ "pattern": "^rob_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The robot whose microphone heard it, when the device is a robot's bridge."
+ }
+ }
}
}
]
]
}
}
@@ -196794,7 +196794,7 @@ export interface BotRobotProfile {
@@ -196794,7 +196794,7 @@ export interface BotRobotProfile {
/** [email protected] (owner: bot) */
/** [email protected] (owner: bot) */
/**
/**
- * bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state. The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).
+ * bot.device-message@1 (ADR-043 decision 5): one frame on OpenVibe.Bot's device WebSocket (wss://openvibe.bot/device), JSON with v, seq and ts on every frame. Server to device: hello, config, command, estop, heartbeat_ack, error, paired (after a pair frame), rotate (a Network-paired machine is asked to rotate its credential with Network), voice_answer (Vibe's answer to a voice_ask). Device to server: pair (the only frame an unauthenticated socket may send), status, telemetry, ack, nack, heartbeat, reauth (Network-paired machines), estop_state, voice_ask (what the person asked after the machine's call word). The job frames on the same socket are platform.job-frame@1, not this contract. This duplicates two implementations that must not drift: OpenVibe.Bot server/realtime.js (the sender and reader in Bot) and OpenVibe.Node internal/protocol/protocol.go (the Go structs of the device agent).
*/
*/
export type BotDeviceMessage =
export type BotDeviceMessage =
| {
| {
@@ -196821,6 +196821,14 @@ export type BotDeviceMessage =
@@ -196821,6 +196821,14 @@ export type BotDeviceMessage =
};
};
allowed_commands: ("drive" | "actuator" | "ptz" | "say" | "display" | "halt")[];
allowed_commands: ("drive" | "actuator" | "ptz" | "say" | "display" | "halt")[];
estop_latched: boolean;
estop_latched: boolean;
+ /**
+ * Vibe by voice: the owner turned it on for this machine, so it may listen for its call word and send voice_ask (absent or false: it does not).
+ */
+ voice?: boolean;
+ /**
+ * The call word the owner chose ("hey vibe" unless they chose another); sent with voice on.
+ */
+ call_word?: string;
}
}
| {
| {
v: 1;
v: 1;
@@ -197033,6 +197041,47 @@ export type BotDeviceMessage =
@@ -197033,6 +197041,47 @@ export type BotDeviceMessage =
robot_id?: string;
robot_id?: string;
local_stop?: boolean;
local_stop?: boolean;
reason?: string;
reason?: string;
+ }
+ | {
+ v: 1;
+ seq: number;
+ ts: number;
+ type: "voice_answer";
+ /**
+ * The voice_ask this answers.
+ */
+ id: string;
+ state: "working" | "answered" | "refused";
+ /**
+ * The answer to speak (answered), a short line (working), or why not (refused).
+ */
+ text?: string;
+ /**
+ * Where the person can read it later: the Vibe chat that keeps the question and the answer.
+ */
+ url?: string;
+ }
+ | {
+ v: 1;
+ seq: number;
+ ts: number;
+ type: "voice_ask";
+ /**
+ * The device's id for this request (unique per device for a day); the answers carry it.
+ */
+ id: string;
+ /**
+ * What the person asked after the call word, as the machine heard it.
+ */
+ text: string;
+ /**
+ * The language it was heard in (BCP 47), when the recogniser knows.
+ */
+ lang?: string;
+ /**
+ * The robot whose microphone heard it, when the device is a robot's bridge.
+ */
+ robot_id?: string;
};
};
/** [email protected] (owner: bot) */
/** [email protected] (owner: bot) */