openvibers/openvibe.actor Commits

Actor: a task on your own computer is its own class and goes to the runtime (it went to Codes as code) (#55)

OP OpenVibers committed 30 days ago · 1aba116d7e · 5 files, +29 −7

In the production proof, "On my own computer named …: list …, read readme.txt and run uname -s" went to the `codes` agent: the classifier read "run" and "uname" as coding, and Codes asked for a repository.

**Fix:** `computer` is now a task class (`task:computer`), as `robot` is, and only the OpenVibe runtime takes it:
- **Heuristic:** "my computer/laptop/desktop/pc/machine/server", "on my machine", a `dev_` id or a computer tool name, unless the task names a repository to change (then it is still code, for Codes).
- **Model classifier:** its prompt lists `computer`.
- **Router:** a clear message when the runtime is unavailable.
- **Display:** task pages label the class "Your computer" (and robots "Your robot"), and the home page says so.

Tests: `test/computers.test.js` adds the class, heuristic and routing checks. The full suite passes (29/29).

Changes

ddiff --git a/server/agents/catalog.js b/server/agents/catalog.js
iindex 38ffe6d..5aa9910 100644
---- a/server/agents/catalog.js
++++ b/server/agents/catalog.js
@@@ -39,9 +39,9 @@ const AGENTS = [
// The runtime is OpenVibe's code, but its model runs at DeepSeek: the task's text leaves OpenVibe.
trust: 'external',
quality: 'standard',
-- can: ['task:answer', 'task:lookup', 'task:research', 'task:robot', 'tool:openvibe-search', 'tool:openvibe-tools', 'tool:openvibe-bot'],
++ can: ['task:answer', 'task:lookup', 'task:research', 'task:robot', 'task:computer', 'tool:openvibe-search', 'tool:openvibe-tools', 'tool:openvibe-bot'],
rate_card: { input_usd_per_mtok: 0.30, output_usd_per_mtok: 1.20, source: DEEPSEEK_PRICES, verified_at: VERIFIED },
-- blurb: 'OpenVibe\'s own agent: a DeepSeek model that acts through OpenVibe services — search across the network, 160+ tools (DNS, mail, certificates, headers, conversions), a page reader, and your own robots on OpenVibe.Bot when you let it drive them.',
++ blurb: 'OpenVibe\'s own agent: a DeepSeek model that acts through OpenVibe services — search across the network, 160+ tools (DNS, mail, certificates, headers, conversions), a page reader, your own robots on OpenVibe.Bot when you let it drive them, and your own computers when you let it use them.',
},
{
id: 'openai-agent',
ddiff --git a/server/agents/classify.js b/server/agents/classify.js
iindex 40178bd..99761f2 100644
---- a/server/agents/classify.js
++++ b/server/agents/classify.js
@@@ -9,12 +9,13 @@
* research read the pages it names, or OpenVibe's content → task:research
* answer write or explain from what a model knows → task:answer
* robot make the person's own OpenVibe.Bot robot act → task:robot
++ * computer use the person's own computer (files, programs) → task:computer
*
* heuristic(task) is free and is what private mode and the public dry run use. classify() asks a cheap model when one
* is configured, and falls back to the heuristic on any failure; its cost is part of the task's.
*/
--const CLASSES = ['answer', 'lookup', 'research', 'web', 'code', 'robot'];
++const CLASSES = ['answer', 'lookup', 'research', 'web', 'code', 'robot', 'computer'];
const CODE = /\b(code|function|bug|stack ?trace|refactor|unit tests?|pull request|repo(sitory)?|compile|typescript|javascript|python|golang|rust|java|sql query|regex|script|api endpoint|implement)\b|```/i;
const LOOKUP = /\b(dns|mx|spf|dkim|dmarc|whois|rdap|nameservers?|ssl|tls|certificate|headers?|redirects?|ip address|asn|geoip|reverse dns|ptr|ttl|robots\.txt|sitemap|uptime|ping|traceroute|port \d+|hosting provider|registrar)\b/i;
@@@ -22,11 +23,16 @@ const DOMAINISH = /\b(?:[a-z0-9-]+\.)+[a-z]{2,}\b|\b\d{1,3}(?:\.\d{1,3}){3}\b/i;
const WEB = /\b(latest|today|tonight|yesterday|this week|this month|current(ly)?|right now|news|price of|stock|weather|score|election|released?|recent(ly)?|202\d|trending|search the web|look up online|compare prices)\b/i;
const URL = /https?:\/\/\S+/i;
const ROBOT = /\b(my robots?|the robot|robot_steps|rob_[0-9A-HJKMNP-TV-Z]{26}|openvibe\.bot|rover|cozmo)\b/i;
++// The person's own computer through OpenVibe.Bot (agents/computers.js). A task that names a repository to change is
++// coding (OpenVibe.Codes runs it on the person's machine), not this.
++const COMPUTER = /\b(my (own )?(computers?|laptop|desktop|pc|workstation|server|machine)|on my (computer|laptop|desktop|pc|machine)|list_my_computers|computer_(run|read|list|write)|dev_[0-9A-HJKMNP-TV-Z]{26})\b/i;
++const REPO = /github\.com|gitlab\.com|codeberg\.org|\brepo(sitory)?\b|pull request/i;
const RESEARCH = /\b(summari[sz]e|read|according to|sources?|cite|citations?|brief|report on|research)\b/i;
function heuristic(task) {
const t = String(task || '');
if (ROBOT.test(t) && !/```/.test(t)) return 'robot';
++ if (COMPUTER.test(t) && !REPO.test(t) && !/```/.test(t)) return 'computer';
if (CODE.test(t)) return 'code';
if (LOOKUP.test(t) && DOMAINISH.test(t)) return 'lookup';
if (URL.test(t)) return 'research';
@@@ -35,13 +41,14 @@ function heuristic(task) {
return 'answer';
}
--const SYSTEM = `You sort one task for an agent router. Reply with JSON only: {"class": "<one of answer|lookup|research|web|code>"}.
++const SYSTEM = `You sort one task for an agent router. Reply with JSON only: {"class": "<one of answer|lookup|research|web|code|robot|computer>"}.
answer: writing, explaining, advice, maths, anything a capable model can do from what it knows.
lookup: facts about a specific domain, website, IP address or server (DNS, mail records, certificates, headers, ownership, robots.txt).
research: read the specific web pages it links to, or OpenVibe's own content, and report.
web: needs searching the live web: anything recent or changing (news, prices, releases, schedules, people's current roles).
code: write, change, debug or review source code.
--robot: make the person's own robot on OpenVibe.Bot do something or report on it (drive, turn, speak, lights, show a face, its sensors or battery). Commanding a robot is never code.`;
++robot: make the person's own robot on OpenVibe.Bot do something or report on it (drive, turn, speak, lights, show a face, its sensors or battery). Commanding a robot is never code.
++computer: use the person's own computer through OpenVibe.Bot: list or read its files, run a program there, write a file there, or report what is on it. Using their computer is never code, unless the task names a repository to change.`;
/**
* classify(task, { chat }) → { class, cost_usd, by }. `chat` is a provider client (server/agents/providers.js) or
ddiff --git a/server/agents/router.js b/server/agents/router.js
iindex 35baf20..c13ce48 100644
---- a/server/agents/router.js
++++ b/server/agents/router.js
@@@ -77,6 +77,7 @@ function whyNone(result, { cls, mode, budgetUsd }) {
return { code: 'actor.budget.exceeded', detail: `The cheapest agent that can take this task would cost about $${cheapest.toFixed(3)}, over this task's $${Number(budgetUsd).toFixed(3)} budget. Raise the budget or ask something simpler.` };
}
if (mode === 'private') return { code: 'actor.no_agent', detail: `Private mode keeps the task on OpenVibe's own hardware, and no agent there can take a "${cls}" task yet (the open model answers simple questions only).` };
++ if (cls === 'computer') return { code: 'actor.no_agent', detail: 'Tasks on your own computer go to the OpenVibe runtime, which uses the computers you opened to it on OpenVibe.Bot. It is not available right now; try again later.' };
if (cls === 'robot') return { code: 'actor.no_agent', detail: 'Robot tasks go to the OpenVibe runtime, which acts on your own robots on OpenVibe.Bot. It is not available right now; try again later.' };
if (cls === 'code') return { code: 'actor.no_agent', detail: 'Coding tasks go to OpenVibe.Codes, which runs them on your own machine, and it is not connected right now. You can start one yourself at https://openvibe.codes/run' };
const down = cands.filter((c) => !c.eligible).map((c) => `${c.id}: ${c.excluded_because}`).join('; ');
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 4ce8578..194380e 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -107,7 +107,7 @@ ${raw(showcase.features({
${raw(showcase.steps({
title: 'How a task finds its agent',
items: [
-- { title: 'Sort the task', text: 'An answer, a lookup, research, a live web search, code or your own robot: that decides what an agent must be able to do.' },
++ { title: 'Sort the task', text: 'An answer, a lookup, research, a live web search, code, your own robot or your own computer: that decides what an agent must be able to do.' },
{ title: 'Hard limits first', text: 'An agent that cannot do it, that private mode does not trust, that is down or that would cost more than your budget is out, and the task says why.' },
{ title: 'Then your mode', text: 'Cheapest, Balanced, Best, Fastest or Private picks among the rest, through the same placement engine every OpenVibe service uses.' },
{ title: 'Run, check, deliver', text: 'The agent works while you watch its steps; another model checks the answer; you get the answer, its sources, the cost and the explanation.' },
@@@ -205,7 +205,7 @@ ${pageRows.next ? html`<p><a href="/tasks?before=${pageRows.next}">Older tasks</
// live progress), then how it went — one block per agent that tried, its steps, its check, and a refused answer
// folded away — and why that agent, folded.
const STATE_TEXT = { queued: 'Waiting to start', running: 'Working', verifying: 'Checking the answer', succeeded: 'Done', failed: 'Didn\'t finish', cancelled: 'Cancelled' };
-- const CLASS_TEXT = { answer: 'Answer', lookup: 'Lookup', research: 'Research', web: 'Web search', code: 'Code' };
++ const CLASS_TEXT = { answer: 'Answer', lookup: 'Lookup', research: 'Research', web: 'Web search', code: 'Code', robot: 'Your robot', computer: 'Your computer' };
r.get('/tasks/:id', async (req, res) => {
const row = TASK_ID.test(req.params.id) && signedIn(req) ? await store.getTask(s, req.params.id) : null;
ddiff --git a/test/computers.test.js b/test/computers.test.js
iindex 7701f70..f3ebede 100644
---- a/test/computers.test.js
++++ b/test/computers.test.js
@@@ -99,6 +99,20 @@ const as = { requester: `user:${ME}` };
assert.strictEqual(bot.calls.length, before);
});
++ await check('a task on the person\'s computer is its own class, routed to the runtime; a repository is still code', async () => {
++ const { heuristic, CLASSES } = require('../server/agents/classify');
++ const { route } = require('../server/agents/router');
++ const catalog = require('../server/agents/catalog');
++ assert.ok(CLASSES.includes('computer'));
++ assert.strictEqual(heuristic('On my own computer named desk-pc: list /home/me and run uname -s'), 'computer');
++ assert.strictEqual(heuristic('What files are on my laptop in ~/Documents?'), 'computer');
++ assert.strictEqual(heuristic('In https://github.com/ada/site fix the failing bug on my machine'), 'code', 'a repository to change goes to Codes');
++ assert.strictEqual(heuristic('Explain what a mutex is'), 'answer');
++ assert.ok(catalog.byId('openvibe-runtime').can.includes('task:computer'));
++ const placed = route({ cls: 'computer', config: { providers: { deepseek: { apiKey: 'k' }, openai: { apiKey: '' }, local: { url: '', model: '' } } } });
++ assert.strictEqual(placed.selected, 'openvibe-runtime', JSON.stringify(placed.candidates));
++ });
++
await check('off without a Bot URL or a client secret', async () => {
assert.strictEqual(createComputers({ config: { ...config, services: { ...config.services, botUrl: '' } } }).enabled, false);
assert.strictEqual(createComputers({ config: { ...config, oauth: { clientId: 'actor', clientSecret: '' } } }).enabled, false);