openvibers/openvibe.actor Commits
Actor's robot tools say Stop (#454)
Changes
@@@ -1,5 +1,10 @@# Changelog++++## Unreleased — Stop, not E-STOP++++- The robot tools and page copy say Stop; `list_my_robots` no longer reads a latch field.++What changed in OpenVibe.Actor, newest first. Each site also publishes its patch notes at /updates.## Unreleased — the same run shape as Codes and Run (`agents.run@1`, `agents.step@1`)@@@ -120,7 +120,7 @@ The resource index is for OpenVibe.Services on loopback and is blocked at the pu- **OpenVibe.Network** (`OV_NETWORK_URL`, `OV_NETWORK_INTERNAL_URL`): sign-in (OAuth2 + PKCE, client `actor`), the JWKS that verifies session and app tokens, and the internal routes (with this service's own client-credentials token) where account export parts and deletion confirmations are pushed.- **OpenVibe.Search** (`ACTOR_SEARCH_URL`), through the Search Fabric (`/api/v1/query`): content across the network for the runtime's `search_openvibe` tool (anonymous), and the web for `search_web` (cited results; asked as Actor's service principal with the grant `search.query.delegate`, naming the task's person in `X-OV-Subject`, so the paid steps past the open index count against them).- **OpenVibe.Tools** (`ACTOR_TOOLS_URL`): the catalog, the run API and the page reader, for `find_tools`, `run_tool` and `read_page`. Called as any anonymous caller would.--- **OpenVibe.Bot** (`ACTOR_BOT_URL`, default `http://127.0.0.1:4630`; empty turns it off): a signed-in person's own robots, for `list_my_robots` and `robot_steps`. Actor calls Bot with its own client-credentials token (audience `openvibe.bot`, `bot.robot.read` + `bot.robot.control`) and `X-OV-Subject` naming the person. Bot answers only for the robot's owner, and only on robots where that owner turned on "Let my OpenVibe.Actor agent drive this robot". Bot's gate, limits and e-stop judge every step. App, agent and service tasks never see these tools.++- **OpenVibe.Bot** (`ACTOR_BOT_URL`, default `http://127.0.0.1:4630`; empty turns it off): a signed-in person's own robots, for `list_my_robots` and `robot_steps`. Actor calls Bot with its own client-credentials token (audience `openvibe.bot`, `bot.robot.read` + `bot.robot.control`) and `X-OV-Subject` naming the person. Bot answers only for the robot's owner, and only on robots where that owner turned on "Let my OpenVibe.Actor agent drive this robot". Bot's gate and limits judge every step. App, agent and service tasks never see these tools.- **See it work** (one-agent system, W1): a running task's card shows its desk — the private Live stream Link publishes for the task's desk (or the still at 1/s), the latest step in plain words, a ring where it last clicked, the page address and where it is. A tap opens the viewer: Take over (Run's lease, or Bot input as the person on a Link desk), Pause, Stop and Hand back with a note. A sign-in step is an Ask that opens it in control. A finished run has a replay of each step's still and action; typed text is never kept. The SDK calls are `desks`, `takeOver`, `handBack` and `replay` in `openvibe-sdk/agents` (PR open, released by the main session).- **The chat is the one page** (one-agent system, stage 1): the Console is folded into it. `/console` answers 301 to `/chat`; the running tasks the Console listed are the "Now" strip at the top of the chat (one line each, Stop on each, hidden when nothing runs), and a running task is a card in its conversation that updates in place. The run settings ("New runs: Only me … Apply to all my existing runs") live in `/chat/settings`; the composer is openvibe-shared's composer v2, agent preset (Attach, Speak, the Fast/Balanced/Best mode chip, the round send, progress on each file). The empty chat shows four compact suggestion chips. `/me/spend` stays (`server/http/console-pages.js`, `server/http/chat-pages.js`, `test/one-page.test.js`).- **A person's own computers, also through OpenVibe.Bot** (plan T14/T17, contracts 0.166.0): `list_my_computers`, `computer_list`, `computer_read`, `computer_run` (argv, never a shell line) and `computer_write`, on machines installed with OpenVibe Link's `--computer` plugin. Actor calls Bot's machine API with its own token (`bot.computer.control`) and `X-OV-Subject` naming the person. Bot answers only for the machine's owner, and only where that owner turned on "Let my agent use this computer". The level the owner set on the machine decides what may happen there; at `ask`, each run or write waits up to two minutes for the owner's approval on the machine. Only a signed-in person's task sees these tools (`server/agents/computers.js`, `test/computers.test.js`). `computer_screen` (when an OpenAI key is set) takes a picture of the machine's screen through Bot (1280 px wide) and has OpenAI's vision model answer the agent's question about it. The runtime model gets that answer, never the picture; the picture is not stored; secrets visible on screen are named, not repeated; and each look is metered on the task like any model call (refused once the task's budget is spent). `computer_input` (offered with `computer_screen`; contracts 0.171.0) uses the mouse and keyboard: a click, double or right click or move at a position in the last screen picture (its width and height are passed, and the Link scales them to the real screen), a scroll, typed text, or one key or chord. It is an acting command, so at level `ask` each one waits for the person's approval on the machine; the runtime is told to look first, act in small steps, look again, and never type a secret the task did not give it.@@@ -5,10 +5,10 @@* owner's grant, always through Bot). Actor calls Bot's public API with its own service token (audience openvibe.bot,* bot.robot.read + bot.robot.control) and X-OV-Subject naming the person the task is for. Bot answers only for the* robot's owner, and only where that owner turned on "Let my OpenVibe.Actor agent drive this robot"; its gate, limits,-- * cooldowns and e-stop judge every step, and its audit records each one as svc:actor.++ * cooldowns judge every step, and its audit records each one as svc:actor.** createRobots({ config, fetchImpl, tokens? }) → { enabled, mine(subject), steps(subject, robotId, lines) }-- * mine → [{ id, name, model, agent_control, online?, estop?, battery?, sensors?, steps?[], panel }]++ * mine → [{ id, name, model, agent_control, online?, battery?, sensors?, steps?[], panel }]* steps → { result: 'done' | 'stopped: …', steps } or { error }** Off (enabled false) without ACTOR_BOT_URL or without Actor's client secret.@@@ -50,7 +50,7 @@ function createRobots({ config, fetchImpl = globalThis.fetch, tokens: tokensOveritem.note = `Not open to the agent. The owner can turn on "Let my OpenVibe.Actor agent drive this robot" at ${pages}/panel/${robot.id}#agent`;} else {const s = await call('GET', `/robots/${robot.id}/steps`, subject, { signal });-- if (s.ok && s.data) Object.assign(item, { online: s.data.online, estop: s.data.estop, battery: s.data.battery, sensors: s.data.sensors, steps: s.data.steps, max_steps: s.data.max_steps, max_seconds: s.data.max_seconds });++ if (s.ok && s.data) Object.assign(item, { online: s.data.online, battery: s.data.battery, sensors: s.data.sensors, steps: s.data.steps, max_steps: s.data.max_steps, max_seconds: s.data.max_seconds });else item.note = problem(s, 'the robot').error;}out.push(item);@@@ -413,7 +413,7 @@ function createOpenVibeTools({ config, fetchImpl = globalThis.fetch, now = () =>}const ROBOT_DEFS = [-- { type: 'function', function: { name: 'list_my_robots', description: 'List the robots the person owns on OpenVibe.Bot. For each robot they opened to their agent: whether it is online, the e-stop, battery, sensor readings and the step lines it accepts. A robot not opened to the agent says how the person can open it.', parameters: { type: 'object', properties: {} } } },++ { type: 'function', function: { name: 'list_my_robots', description: 'List the robots the person owns on OpenVibe.Bot. For each robot they opened to their agent: whether it is online, battery, sensor readings and the step lines it accepts. A robot not opened to the agent says how the person can open it.', parameters: { type: 'object', properties: {} } } },{ type: 'function', function: { name: 'robot_steps', description: 'Make one of the person\'s robots act, by step lines it accepts (from list_my_robots), for example ["forward 1", "left 0.5", "say Hello", "headlights #00ff00", "wait 1", "stop"]. At most 20 steps and 30 seconds per call. Answers "done" or "stopped: <why>" once they end.', parameters: { type: 'object', properties: { robot: { type: 'string', description: 'the robot id (rob_…)' }, steps: { type: 'array', items: { type: 'string' } } }, required: ['robot', 'steps'] } } },];@@@ -501,7 +501,7 @@ ${today ? html`<p class="task-allowance muted small">${leftLine(tier, today)}</p[html`The API (<a href="/docs">docs</a>): tasks, the live stream, <a href="/docs#webhooks">signed webhooks</a>, the agents and a dry-run router`, 'Live, for people and for apps with actor.task.* grants from OpenVibe.Services.'],[html`Tasks a watch hands over when its condition fires (<a href="https://openvibe.watch">OpenVibe.Watch</a>)`, 'Live: the task is yours, with your allowance; Watch sees only the tasks it started.'],[html`Your own agents (<a href="/me/agents">Your agents</a>): instructions, memory you can read and edit, a schedule (hourly, daily or weekly), a budget per run and a daily cap`, 'Live.'],-- [html`Your own robots on <a href="https://openvibe.bot">OpenVibe.Bot</a>: see them and drive the ones you opened to your agent, through Bot's limits and e-stop`, 'Live: turn on "Let my OpenVibe.Actor agent drive this robot" on the robot\'s panel.'],++ [html`Your own robots on <a href="https://openvibe.bot">OpenVibe.Bot</a>: see them and drive the ones you opened to your agent, through Bot's limits`, 'Live: turn on "Let my OpenVibe.Actor agent drive this robot" on the robot\'s panel.'],[html`Coding tasks on your own machine, through <a href="https://openvibe.codes/run">OpenVibe.Codes</a>: name the repository and what to change, and get the summary, the files and the patch`, html`Live: add the machine on <a href="https://openvibe.link/machines">openvibe.link/machines</a> and start one task on openvibe.codes/run first. The model use is on your machine's own keys.`],[html`Your own computers, through <a href="https://openvibe.bot/docs/computers">OpenVibe.Bot</a>: list and read files, run programs and write files there, as you, at the level you set on the computer`, html`Live: install OpenVibe Link with <code>--computer ask</code> and turn on "Let my agent use this computer" on <a href="https://openvibe.link/machines">openvibe.link/machines</a>. At <code>ask</code>, you approve each run or write on the computer.`],[html`<a href="/chat">The chat</a>: what runs now in a "Now" strip, your conversations, your agents, and one composer to tell them what to do next`, 'Live, for signed-in people.'],@@@ -4,7 +4,7 @@ const { serviceFor, json, personOf } = require('./_openvibe');module.exports = {name: 'list_my_robots',-- description: 'List the robots the person owns on OpenVibe.Bot. For each robot they opened to their agent: whether it is online, the e-stop, battery, sensor readings and the step lines it accepts. A robot not opened to the agent says how the person can open it.',++ description: 'List the robots the person owns on OpenVibe.Bot. For each robot they opened to their agent: whether it is online, battery, sensor readings and the step lines it accepts. A robot not opened to the agent says how the person can open it.',input: { type: 'object', properties: {} },scope: 'person',costUsd: 0,nnew file mode 100644@@@ -0,0 +1,26 @@++'use strict';++// These are small hobby robots: no page, doc or AGENTS.md says E-STOP, estop or emergency stop. Inline code++// names a legacy protocol field and is skipped.++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const root = path.join(__dirname, '..');++const files = ['AGENTS.md', 'README.md', 'STATUS.json'].map((f) => path.join(root, f)).filter((f) => fs.existsSync(f));++const walk = (dir, ext) => {++ if (!fs.existsSync(dir)) return;++ for (const name of fs.readdirSync(dir)) {++ const p = path.join(dir, name);++ if (fs.statSync(p).isDirectory()) walk(p, ext);++ else if (p.endsWith(ext)) files.push(p);++ }++};++walk(path.join(root, 'docs'), '.md');++walk(path.join(root, 'public', 'fragments'), '.html');++const bad = /(^|[^A-Za-z])e-?stop([^A-Za-z]|$)|emergency\s+stop/i;++for (const file of files) {++ let text = fs.readFileSync(file, 'utf8');++ text = text.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')++ .replace(/<pre[\s\S]*?<\/pre>/gi, '').replace(/<code[\s\S]*?<\/code>/gi, '');++ assert.ok(!bad.test(text), `${path.relative(root, file)} says E-STOP; write Stop`);++}++console.log('plain-stop-text: no page, doc or AGENTS.md says E-STOP');@@@ -26,7 +26,7 @@ function fakeBot() {calls.push({ method: init.method || 'GET', path: u.pathname + u.search, subject: headers['X-OV-Subject'], auth: headers.Authorization, body: init.body ? JSON.parse(init.body) : null });if (u.host === 'tools.test') return reply(200, { tools: [] });if (u.pathname === '/api/v1/robots') return reply(200, { robots: [{ id: OPEN, name: 'Desk rover', profile_id: 'sim.rover', agent_control: true }, { id: CLOSED, name: 'Cozmo', profile_id: 'cozmo', agent_control: false }] });-- if (u.pathname === `/api/v1/robots/${OPEN}/steps` && (init.method || 'GET') === 'GET') return reply(200, { robot_id: OPEN, online: true, estop: { latched: false }, battery: 0.8, sensors: { distance_cm: 42 }, steps: ['forward 2', 'stop'], max_steps: 20, max_seconds: 30 });++ if (u.pathname === `/api/v1/robots/${OPEN}/steps` && (init.method || 'GET') === 'GET') return reply(200, { robot_id: OPEN, online: true, battery: 0.8, sensors: { distance_cm: 42 }, steps: ['forward 2', 'stop'], max_steps: 20, max_seconds: 30 });if (u.pathname === `/api/v1/robots/${OPEN}/steps`) return reply(200, { robot_id: OPEN, result: 'done', steps: 2 });if (u.pathname === `/api/v1/robots/${CLOSED}/steps`) return reply(403, { code: 'bot.agent_not_allowed', detail: 'the owner has not let their agent drive this robot' });return reply(404, { code: 'not_found' });
@@ -1,5 +1,10 @@
@@ -1,5 +1,10 @@
# Changelog
# Changelog
+
+## Unreleased — Stop, not E-STOP
+
+- The robot tools and page copy say Stop; `list_my_robots` no longer reads a latch field.
+
What changed in OpenVibe.Actor, newest first. Each site also publishes its patch notes at /updates.
What changed in OpenVibe.Actor, newest first. Each site also publishes its patch notes at /updates.
## Unreleased — the same run shape as Codes and Run (`agents.run@1`, `agents.step@1`)
## Unreleased — the same run shape as Codes and Run (`agents.run@1`, `agents.step@1`)
@@ -120,7 +120,7 @@ The resource index is for OpenVibe.Services on loopback and is blocked at the pu
@@ -120,7 +120,7 @@ The resource index is for OpenVibe.Services on loopback and is blocked at the pu
- **OpenVibe.Network** (`OV_NETWORK_URL`, `OV_NETWORK_INTERNAL_URL`): sign-in (OAuth2 + PKCE, client `actor`), the JWKS that verifies session and app tokens, and the internal routes (with this service's own client-credentials token) where account export parts and deletion confirmations are pushed.
- **OpenVibe.Network** (`OV_NETWORK_URL`, `OV_NETWORK_INTERNAL_URL`): sign-in (OAuth2 + PKCE, client `actor`), the JWKS that verifies session and app tokens, and the internal routes (with this service's own client-credentials token) where account export parts and deletion confirmations are pushed.
- **OpenVibe.Search** (`ACTOR_SEARCH_URL`), through the Search Fabric (`/api/v1/query`): content across the network for the runtime's `search_openvibe` tool (anonymous), and the web for `search_web` (cited results; asked as Actor's service principal with the grant `search.query.delegate`, naming the task's person in `X-OV-Subject`, so the paid steps past the open index count against them).
- **OpenVibe.Search** (`ACTOR_SEARCH_URL`), through the Search Fabric (`/api/v1/query`): content across the network for the runtime's `search_openvibe` tool (anonymous), and the web for `search_web` (cited results; asked as Actor's service principal with the grant `search.query.delegate`, naming the task's person in `X-OV-Subject`, so the paid steps past the open index count against them).
- **OpenVibe.Tools** (`ACTOR_TOOLS_URL`): the catalog, the run API and the page reader, for `find_tools`, `run_tool` and `read_page`. Called as any anonymous caller would.
- **OpenVibe.Tools** (`ACTOR_TOOLS_URL`): the catalog, the run API and the page reader, for `find_tools`, `run_tool` and `read_page`. Called as any anonymous caller would.
-- **OpenVibe.Bot** (`ACTOR_BOT_URL`, default `http://127.0.0.1:4630`; empty turns it off): a signed-in person's own robots, for `list_my_robots` and `robot_steps`. Actor calls Bot with its own client-credentials token (audience `openvibe.bot`, `bot.robot.read` + `bot.robot.control`) and `X-OV-Subject` naming the person. Bot answers only for the robot's owner, and only on robots where that owner turned on "Let my OpenVibe.Actor agent drive this robot". Bot's gate, limits and e-stop judge every step. App, agent and service tasks never see these tools.
+- **OpenVibe.Bot** (`ACTOR_BOT_URL`, default `http://127.0.0.1:4630`; empty turns it off): a signed-in person's own robots, for `list_my_robots` and `robot_steps`. Actor calls Bot with its own client-credentials token (audience `openvibe.bot`, `bot.robot.read` + `bot.robot.control`) and `X-OV-Subject` naming the person. Bot answers only for the robot's owner, and only on robots where that owner turned on "Let my OpenVibe.Actor agent drive this robot". Bot's gate and limits judge every step. App, agent and service tasks never see these tools.
- **See it work** (one-agent system, W1): a running task's card shows its desk — the private Live stream Link publishes for the task's desk (or the still at 1/s), the latest step in plain words, a ring where it last clicked, the page address and where it is. A tap opens the viewer: Take over (Run's lease, or Bot input as the person on a Link desk), Pause, Stop and Hand back with a note. A sign-in step is an Ask that opens it in control. A finished run has a replay of each step's still and action; typed text is never kept. The SDK calls are `desks`, `takeOver`, `handBack` and `replay` in `openvibe-sdk/agents` (PR open, released by the main session).
- **See it work** (one-agent system, W1): a running task's card shows its desk — the private Live stream Link publishes for the task's desk (or the still at 1/s), the latest step in plain words, a ring where it last clicked, the page address and where it is. A tap opens the viewer: Take over (Run's lease, or Bot input as the person on a Link desk), Pause, Stop and Hand back with a note. A sign-in step is an Ask that opens it in control. A finished run has a replay of each step's still and action; typed text is never kept. The SDK calls are `desks`, `takeOver`, `handBack` and `replay` in `openvibe-sdk/agents` (PR open, released by the main session).
- **The chat is the one page** (one-agent system, stage 1): the Console is folded into it. `/console` answers 301 to `/chat`; the running tasks the Console listed are the "Now" strip at the top of the chat (one line each, Stop on each, hidden when nothing runs), and a running task is a card in its conversation that updates in place. The run settings ("New runs: Only me … Apply to all my existing runs") live in `/chat/settings`; the composer is openvibe-shared's composer v2, agent preset (Attach, Speak, the Fast/Balanced/Best mode chip, the round send, progress on each file). The empty chat shows four compact suggestion chips. `/me/spend` stays (`server/http/console-pages.js`, `server/http/chat-pages.js`, `test/one-page.test.js`).
- **The chat is the one page** (one-agent system, stage 1): the Console is folded into it. `/console` answers 301 to `/chat`; the running tasks the Console listed are the "Now" strip at the top of the chat (one line each, Stop on each, hidden when nothing runs), and a running task is a card in its conversation that updates in place. The run settings ("New runs: Only me … Apply to all my existing runs") live in `/chat/settings`; the composer is openvibe-shared's composer v2, agent preset (Attach, Speak, the Fast/Balanced/Best mode chip, the round send, progress on each file). The empty chat shows four compact suggestion chips. `/me/spend` stays (`server/http/console-pages.js`, `server/http/chat-pages.js`, `test/one-page.test.js`).
- **A person's own computers, also through OpenVibe.Bot** (plan T14/T17, contracts 0.166.0): `list_my_computers`, `computer_list`, `computer_read`, `computer_run` (argv, never a shell line) and `computer_write`, on machines installed with OpenVibe Link's `--computer` plugin. Actor calls Bot's machine API with its own token (`bot.computer.control`) and `X-OV-Subject` naming the person. Bot answers only for the machine's owner, and only where that owner turned on "Let my agent use this computer". The level the owner set on the machine decides what may happen there; at `ask`, each run or write waits up to two minutes for the owner's approval on the machine. Only a signed-in person's task sees these tools (`server/agents/computers.js`, `test/computers.test.js`). `computer_screen` (when an OpenAI key is set) takes a picture of the machine's screen through Bot (1280 px wide) and has OpenAI's vision model answer the agent's question about it. The runtime model gets that answer, never the picture; the picture is not stored; secrets visible on screen are named, not repeated; and each look is metered on the task like any model call (refused once the task's budget is spent). `computer_input` (offered with `computer_screen`; contracts 0.171.0) uses the mouse and keyboard: a click, double or right click or move at a position in the last screen picture (its width and height are passed, and the Link scales them to the real screen), a scroll, typed text, or one key or chord. It is an acting command, so at level `ask` each one waits for the person's approval on the machine; the runtime is told to look first, act in small steps, look again, and never type a secret the task did not give it.
- **A person's own computers, also through OpenVibe.Bot** (plan T14/T17, contracts 0.166.0): `list_my_computers`, `computer_list`, `computer_read`, `computer_run` (argv, never a shell line) and `computer_write`, on machines installed with OpenVibe Link's `--computer` plugin. Actor calls Bot's machine API with its own token (`bot.computer.control`) and `X-OV-Subject` naming the person. Bot answers only for the machine's owner, and only where that owner turned on "Let my agent use this computer". The level the owner set on the machine decides what may happen there; at `ask`, each run or write waits up to two minutes for the owner's approval on the machine. Only a signed-in person's task sees these tools (`server/agents/computers.js`, `test/computers.test.js`). `computer_screen` (when an OpenAI key is set) takes a picture of the machine's screen through Bot (1280 px wide) and has OpenAI's vision model answer the agent's question about it. The runtime model gets that answer, never the picture; the picture is not stored; secrets visible on screen are named, not repeated; and each look is metered on the task like any model call (refused once the task's budget is spent). `computer_input` (offered with `computer_screen`; contracts 0.171.0) uses the mouse and keyboard: a click, double or right click or move at a position in the last screen picture (its width and height are passed, and the Link scales them to the real screen), a scroll, typed text, or one key or chord. It is an acting command, so at level `ask` each one waits for the person's approval on the machine; the runtime is told to look first, act in small steps, look again, and never type a secret the task did not give it.
@@ -5,10 +5,10 @@
@@ -5,10 +5,10 @@
* owner's grant, always through Bot). Actor calls Bot's public API with its own service token (audience openvibe.bot,
* owner's grant, always through Bot). Actor calls Bot's public API with its own service token (audience openvibe.bot,
* bot.robot.read + bot.robot.control) and X-OV-Subject naming the person the task is for. Bot answers only for the
* bot.robot.read + bot.robot.control) and X-OV-Subject naming the person the task is for. Bot answers only for the
* robot's owner, and only where that owner turned on "Let my OpenVibe.Actor agent drive this robot"; its gate, limits,
* robot's owner, and only where that owner turned on "Let my OpenVibe.Actor agent drive this robot"; its gate, limits,
- * cooldowns and e-stop judge every step, and its audit records each one as svc:actor.
+ * cooldowns judge every step, and its audit records each one as svc:actor.
*
*
* createRobots({ config, fetchImpl, tokens? }) → { enabled, mine(subject), steps(subject, robotId, lines) }
* createRobots({ config, fetchImpl, tokens? }) → { enabled, mine(subject), steps(subject, robotId, lines) }
- * mine → [{ id, name, model, agent_control, online?, estop?, battery?, sensors?, steps?[], panel }]
+ * mine → [{ id, name, model, agent_control, online?, battery?, sensors?, steps?[], panel }]
* steps → { result: 'done' | 'stopped: …', steps } or { error }
* steps → { result: 'done' | 'stopped: …', steps } or { error }
*
*
* Off (enabled false) without ACTOR_BOT_URL or without Actor's client secret.
* Off (enabled false) without ACTOR_BOT_URL or without Actor's client secret.
@@ -50,7 +50,7 @@ function createRobots({ config, fetchImpl = globalThis.fetch, tokens: tokensOver
@@ -50,7 +50,7 @@ function createRobots({ config, fetchImpl = globalThis.fetch, tokens: tokensOver
item.note = `Not open to the agent. The owner can turn on "Let my OpenVibe.Actor agent drive this robot" at ${pages}/panel/${robot.id}#agent`;
item.note = `Not open to the agent. The owner can turn on "Let my OpenVibe.Actor agent drive this robot" at ${pages}/panel/${robot.id}#agent`;
} else {
} else {
const s = await call('GET', `/robots/${robot.id}/steps`, subject, { signal });
const s = await call('GET', `/robots/${robot.id}/steps`, subject, { signal });
- if (s.ok && s.data) Object.assign(item, { online: s.data.online, estop: s.data.estop, battery: s.data.battery, sensors: s.data.sensors, steps: s.data.steps, max_steps: s.data.max_steps, max_seconds: s.data.max_seconds });
+ if (s.ok && s.data) Object.assign(item, { online: s.data.online, battery: s.data.battery, sensors: s.data.sensors, steps: s.data.steps, max_steps: s.data.max_steps, max_seconds: s.data.max_seconds });
else item.note = problem(s, 'the robot').error;
else item.note = problem(s, 'the robot').error;
}
}
out.push(item);
out.push(item);
@@ -413,7 +413,7 @@ function createOpenVibeTools({ config, fetchImpl = globalThis.fetch, now = () =>
@@ -413,7 +413,7 @@ function createOpenVibeTools({ config, fetchImpl = globalThis.fetch, now = () =>
}
}
const ROBOT_DEFS = [
const ROBOT_DEFS = [
- { type: 'function', function: { name: 'list_my_robots', description: 'List the robots the person owns on OpenVibe.Bot. For each robot they opened to their agent: whether it is online, the e-stop, battery, sensor readings and the step lines it accepts. A robot not opened to the agent says how the person can open it.', parameters: { type: 'object', properties: {} } } },
+ { type: 'function', function: { name: 'list_my_robots', description: 'List the robots the person owns on OpenVibe.Bot. For each robot they opened to their agent: whether it is online, battery, sensor readings and the step lines it accepts. A robot not opened to the agent says how the person can open it.', parameters: { type: 'object', properties: {} } } },
{ type: 'function', function: { name: 'robot_steps', description: 'Make one of the person\'s robots act, by step lines it accepts (from list_my_robots), for example ["forward 1", "left 0.5", "say Hello", "headlights #00ff00", "wait 1", "stop"]. At most 20 steps and 30 seconds per call. Answers "done" or "stopped: <why>" once they end.', parameters: { type: 'object', properties: { robot: { type: 'string', description: 'the robot id (rob_…)' }, steps: { type: 'array', items: { type: 'string' } } }, required: ['robot', 'steps'] } } },
{ type: 'function', function: { name: 'robot_steps', description: 'Make one of the person\'s robots act, by step lines it accepts (from list_my_robots), for example ["forward 1", "left 0.5", "say Hello", "headlights #00ff00", "wait 1", "stop"]. At most 20 steps and 30 seconds per call. Answers "done" or "stopped: <why>" once they end.', parameters: { type: 'object', properties: { robot: { type: 'string', description: 'the robot id (rob_…)' }, steps: { type: 'array', items: { type: 'string' } } }, required: ['robot', 'steps'] } } },
];
];
@@ -501,7 +501,7 @@ ${today ? html`<p class="task-allowance muted small">${leftLine(tier, today)}</p
@@ -501,7 +501,7 @@ ${today ? html`<p class="task-allowance muted small">${leftLine(tier, today)}</p
[html`The API (<a href="/docs">docs</a>): tasks, the live stream, <a href="/docs#webhooks">signed webhooks</a>, the agents and a dry-run router`, 'Live, for people and for apps with actor.task.* grants from OpenVibe.Services.'],
[html`The API (<a href="/docs">docs</a>): tasks, the live stream, <a href="/docs#webhooks">signed webhooks</a>, the agents and a dry-run router`, 'Live, for people and for apps with actor.task.* grants from OpenVibe.Services.'],
[html`Tasks a watch hands over when its condition fires (<a href="https://openvibe.watch">OpenVibe.Watch</a>)`, 'Live: the task is yours, with your allowance; Watch sees only the tasks it started.'],
[html`Tasks a watch hands over when its condition fires (<a href="https://openvibe.watch">OpenVibe.Watch</a>)`, 'Live: the task is yours, with your allowance; Watch sees only the tasks it started.'],
[html`Your own agents (<a href="/me/agents">Your agents</a>): instructions, memory you can read and edit, a schedule (hourly, daily or weekly), a budget per run and a daily cap`, 'Live.'],
[html`Your own agents (<a href="/me/agents">Your agents</a>): instructions, memory you can read and edit, a schedule (hourly, daily or weekly), a budget per run and a daily cap`, 'Live.'],
- [html`Your own robots on <a href="https://openvibe.bot">OpenVibe.Bot</a>: see them and drive the ones you opened to your agent, through Bot's limits and e-stop`, 'Live: turn on "Let my OpenVibe.Actor agent drive this robot" on the robot\'s panel.'],
+ [html`Your own robots on <a href="https://openvibe.bot">OpenVibe.Bot</a>: see them and drive the ones you opened to your agent, through Bot's limits`, 'Live: turn on "Let my OpenVibe.Actor agent drive this robot" on the robot\'s panel.'],
[html`Coding tasks on your own machine, through <a href="https://openvibe.codes/run">OpenVibe.Codes</a>: name the repository and what to change, and get the summary, the files and the patch`, html`Live: add the machine on <a href="https://openvibe.link/machines">openvibe.link/machines</a> and start one task on openvibe.codes/run first. The model use is on your machine's own keys.`],
[html`Coding tasks on your own machine, through <a href="https://openvibe.codes/run">OpenVibe.Codes</a>: name the repository and what to change, and get the summary, the files and the patch`, html`Live: add the machine on <a href="https://openvibe.link/machines">openvibe.link/machines</a> and start one task on openvibe.codes/run first. The model use is on your machine's own keys.`],
[html`Your own computers, through <a href="https://openvibe.bot/docs/computers">OpenVibe.Bot</a>: list and read files, run programs and write files there, as you, at the level you set on the computer`, html`Live: install OpenVibe Link with <code>--computer ask</code> and turn on "Let my agent use this computer" on <a href="https://openvibe.link/machines">openvibe.link/machines</a>. At <code>ask</code>, you approve each run or write on the computer.`],
[html`Your own computers, through <a href="https://openvibe.bot/docs/computers">OpenVibe.Bot</a>: list and read files, run programs and write files there, as you, at the level you set on the computer`, html`Live: install OpenVibe Link with <code>--computer ask</code> and turn on "Let my agent use this computer" on <a href="https://openvibe.link/machines">openvibe.link/machines</a>. At <code>ask</code>, you approve each run or write on the computer.`],
[html`<a href="/chat">The chat</a>: what runs now in a "Now" strip, your conversations, your agents, and one composer to tell them what to do next`, 'Live, for signed-in people.'],
[html`<a href="/chat">The chat</a>: what runs now in a "Now" strip, your conversations, your agents, and one composer to tell them what to do next`, 'Live, for signed-in people.'],
@@ -4,7 +4,7 @@ const { serviceFor, json, personOf } = require('./_openvibe');
@@ -4,7 +4,7 @@ const { serviceFor, json, personOf } = require('./_openvibe');
module.exports = {
module.exports = {
name: 'list_my_robots',
name: 'list_my_robots',
- description: 'List the robots the person owns on OpenVibe.Bot. For each robot they opened to their agent: whether it is online, the e-stop, battery, sensor readings and the step lines it accepts. A robot not opened to the agent says how the person can open it.',
+ description: 'List the robots the person owns on OpenVibe.Bot. For each robot they opened to their agent: whether it is online, battery, sensor readings and the step lines it accepts. A robot not opened to the agent says how the person can open it.',
input: { type: 'object', properties: {} },
input: { type: 'object', properties: {} },
scope: 'person',
scope: 'person',
costUsd: 0,
costUsd: 0,
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+'use strict';
+// These are small hobby robots: no page, doc or AGENTS.md says E-STOP, estop or emergency stop. Inline code
+// names a legacy protocol field and is skipped.
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const root = path.join(__dirname, '..');
+const files = ['AGENTS.md', 'README.md', 'STATUS.json'].map((f) => path.join(root, f)).filter((f) => fs.existsSync(f));
+const walk = (dir, ext) => {
+ if (!fs.existsSync(dir)) return;
+ for (const name of fs.readdirSync(dir)) {
+ const p = path.join(dir, name);
+ if (fs.statSync(p).isDirectory()) walk(p, ext);
+ else if (p.endsWith(ext)) files.push(p);
+ }
+};
+walk(path.join(root, 'docs'), '.md');
+walk(path.join(root, 'public', 'fragments'), '.html');
+const bad = /(^|[^A-Za-z])e-?stop([^A-Za-z]|$)|emergency\s+stop/i;
+for (const file of files) {
+ let text = fs.readFileSync(file, 'utf8');
+ text = text.replace(/```[\s\S]*?```/g, '').replace(/`[^`]*`/g, '')
+ .replace(/<pre[\s\S]*?<\/pre>/gi, '').replace(/<code[\s\S]*?<\/code>/gi, '');
+ assert.ok(!bad.test(text), `${path.relative(root, file)} says E-STOP; write Stop`);
+}
+console.log('plain-stop-text: no page, doc or AGENTS.md says E-STOP');
@@ -26,7 +26,7 @@ function fakeBot() {
@@ -26,7 +26,7 @@ function fakeBot() {
calls.push({ method: init.method || 'GET', path: u.pathname + u.search, subject: headers['X-OV-Subject'], auth: headers.Authorization, body: init.body ? JSON.parse(init.body) : null });
calls.push({ method: init.method || 'GET', path: u.pathname + u.search, subject: headers['X-OV-Subject'], auth: headers.Authorization, body: init.body ? JSON.parse(init.body) : null });
if (u.host === 'tools.test') return reply(200, { tools: [] });
if (u.host === 'tools.test') return reply(200, { tools: [] });
if (u.pathname === '/api/v1/robots') return reply(200, { robots: [{ id: OPEN, name: 'Desk rover', profile_id: 'sim.rover', agent_control: true }, { id: CLOSED, name: 'Cozmo', profile_id: 'cozmo', agent_control: false }] });
if (u.pathname === '/api/v1/robots') return reply(200, { robots: [{ id: OPEN, name: 'Desk rover', profile_id: 'sim.rover', agent_control: true }, { id: CLOSED, name: 'Cozmo', profile_id: 'cozmo', agent_control: false }] });
- if (u.pathname === `/api/v1/robots/${OPEN}/steps` && (init.method || 'GET') === 'GET') return reply(200, { robot_id: OPEN, online: true, estop: { latched: false }, battery: 0.8, sensors: { distance_cm: 42 }, steps: ['forward 2', 'stop'], max_steps: 20, max_seconds: 30 });
+ if (u.pathname === `/api/v1/robots/${OPEN}/steps` && (init.method || 'GET') === 'GET') return reply(200, { robot_id: OPEN, online: true, battery: 0.8, sensors: { distance_cm: 42 }, steps: ['forward 2', 'stop'], max_steps: 20, max_seconds: 30 });
if (u.pathname === `/api/v1/robots/${OPEN}/steps`) return reply(200, { robot_id: OPEN, result: 'done', steps: 2 });
if (u.pathname === `/api/v1/robots/${OPEN}/steps`) return reply(200, { robot_id: OPEN, result: 'done', steps: 2 });
if (u.pathname === `/api/v1/robots/${CLOSED}/steps`) return reply(403, { code: 'bot.agent_not_allowed', detail: 'the owner has not let their agent drive this robot' });
if (u.pathname === `/api/v1/robots/${CLOSED}/steps`) return reply(403, { code: 'bot.agent_not_allowed', detail: 'the owner has not let their agent drive this robot' });
return reply(404, { code: 'not_found' });
return reply(404, { code: 'not_found' });