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)
Changes
@@@ -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',@@@ -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@@@ -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('; ');@@@ -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;@@@ -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);
@@ -39,9 +39,9 @@ const AGENTS = [
@@ -39,9 +39,9 @@ const AGENTS = [
// The runtime is OpenVibe's code, but its model runs at DeepSeek: the task's text leaves OpenVibe.
// The runtime is OpenVibe's code, but its model runs at DeepSeek: the task's text leaves OpenVibe.
trust: 'external',
trust: 'external',
quality: 'standard',
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 },
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',
id: 'openai-agent',
@@ -9,12 +9,13 @@
@@ -9,12 +9,13 @@
* research read the pages it names, or OpenVibe's content → task:research
* research read the pages it names, or OpenVibe's content → task:research
* answer write or explain from what a model knows → task:answer
* answer write or explain from what a model knows → task:answer
* robot make the person's own OpenVibe.Bot robot act → task:robot
* 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
* 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.
* 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 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;
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;
@@ -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 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 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;
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;
const RESEARCH = /\b(summari[sz]e|read|according to|sources?|cite|citations?|brief|report on|research)\b/i;
function heuristic(task) {
function heuristic(task) {
const t = String(task || '');
const t = String(task || '');
if (ROBOT.test(t) && !/```/.test(t)) return 'robot';
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 (CODE.test(t)) return 'code';
if (LOOKUP.test(t) && DOMAINISH.test(t)) return 'lookup';
if (LOOKUP.test(t) && DOMAINISH.test(t)) return 'lookup';
if (URL.test(t)) return 'research';
if (URL.test(t)) return 'research';
@@ -35,13 +41,14 @@ function heuristic(task) {
@@ -35,13 +41,14 @@ function heuristic(task) {
return 'answer';
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.
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).
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.
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).
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.
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
* classify(task, { chat }) → { class, cost_usd, by }. `chat` is a provider client (server/agents/providers.js) or
@@ -77,6 +77,7 @@ function whyNone(result, { cls, mode, budgetUsd }) {
@@ -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.` };
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 (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 === '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' };
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('; ');
const down = cands.filter((c) => !c.eligible).map((c) => `${c.id}: ${c.excluded_because}`).join('; ');
@@ -107,7 +107,7 @@ ${raw(showcase.features({
@@ -107,7 +107,7 @@ ${raw(showcase.features({
${raw(showcase.steps({
${raw(showcase.steps({
title: 'How a task finds its agent',
title: 'How a task finds its agent',
items: [
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: '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: '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.' },
{ 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</
@@ -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
// 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.
// 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 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) => {
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;
const row = TASK_ID.test(req.params.id) && signedIn(req) ? await store.getTask(s, req.params.id) : null;
@@ -99,6 +99,20 @@ const as = { requester: `user:${ME}` };
@@ -99,6 +99,20 @@ const as = { requester: `user:${ME}` };
assert.strictEqual(bot.calls.length, before);
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 () => {
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, services: { ...config.services, botUrl: '' } } }).enabled, false);
assert.strictEqual(createComputers({ config: { ...config, oauth: { clientId: 'actor', clientSecret: '' } } }).enabled, false);
assert.strictEqual(createComputers({ config: { ...config, oauth: { clientId: 'actor', clientSecret: '' } } }).enabled, false);