openvibers/openvibe.codes Commits
OpenVibe Link: the new name in pages, docs and messages (#326)
Changes
@@@ -20,7 +20,7 @@ One place to send a coding task to the agent that fits it — by what the task n| The harness | `openvibe-codes` ([bin/openvibe-codes.js](bin/openvibe-codes.js), [harness/](harness/)) | Runs on a person's own machine with their CLIs and keys; nothing goes through OpenVibe. See **Use the harness** below. || Home | `/` | What the harness is, the agents in the catalog, how a task finds its agent, Improve OpenVibe, and an honest table of what works today and what comes next. || Get started | `/start` | Install, the agents and what each needs, the commands, permission levels, hand-offs, the event format and using it from code. |--| Run on your machine | `/run`, `/run/tasks/:id` ([server/http/run.js](server/http/run.js)) | A signed-in person starts a coding task in any browser and their own machine runs it: an OpenVibe Run job (class `code`, artifact `[email protected]`, `trust: user-owned`) that Codes starts for them in their project (project delegation: `X-OV-Subject`, `X-OV-Project`; [server/domain/run-client.js](server/domain/run-client.js)). Run places it only on a machine of theirs (added on openvibe.bot/machines), whose OpenVibe Node runs `openvibe-codes job` ([harness/job.js](harness/job.js)) in its job sandbox: clone, run the agents with hand-offs, hand back the summary, the changed files and the patch, and, when asked and the machine holds `GITHUB_TOKEN`, push a new branch (never the one it started from) and open a pull request. The page streams the job's lines (Run's job stream through Codes) and keeps the result (`run_tasks`, migration 0003). Each task is its owner's; account export and deletion cover them (ADR-033, [server/account-data.js](server/account-data.js)). The task page shows the **timeline of attempts** above the agents' **session** (each attempt with its why: "Codex hit its limit, so Command Code took over with what it had done") and the session with openvibe-shared/agent-session.js: steps grouped, each step's command, change and output (live while it runs), helpers nested, hand-offs and the plan; after the end, the session from `codes_session_lines` (migration 0007): the lines are kept as the stream arrives, back-filled from the machine's final log, and replayed at `GET /run/tasks/:id/session.json?after=` (the owner, or a service with `codes.task.read` and `X-OV-Subject`); tasks from before keep the old `log`. Run's `run.job.*` events finalize a task at `POST /internal/events` ([server/events-consumer.js](server/events-consumer.js), no polling but a 10-minute fallback), and every state change is published as `codes.task.queued|running|succeeded|failed|cancelled` from an outbox ([server/events/outbox.js](server/events/outbox.js), contracts 0.232.0). |++| Run on your machine | `/run`, `/run/tasks/:id` ([server/http/run.js](server/http/run.js)) | A signed-in person starts a coding task in any browser and their own machine runs it: an OpenVibe Run job (class `code`, artifact `[email protected]`, `trust: user-owned`) that Codes starts for them in their project (project delegation: `X-OV-Subject`, `X-OV-Project`; [server/domain/run-client.js](server/domain/run-client.js)). Run places it only on a machine of theirs (added on openvibe.bot/machines), whose OpenVibe Link runs `openvibe-codes job` ([harness/job.js](harness/job.js)) in its job sandbox: clone, run the agents with hand-offs, hand back the summary, the changed files and the patch, and, when asked and the machine holds `GITHUB_TOKEN`, push a new branch (never the one it started from) and open a pull request. The page streams the job's lines (Run's job stream through Codes) and keeps the result (`run_tasks`, migration 0003). Each task is its owner's; account export and deletion cover them (ADR-033, [server/account-data.js](server/account-data.js)). The task page shows the **timeline of attempts** above the agents' **session** (each attempt with its why: "Codex hit its limit, so Command Code took over with what it had done") and the session with openvibe-shared/agent-session.js: steps grouped, each step's command, change and output (live while it runs), helpers nested, hand-offs and the plan; after the end, the session from `codes_session_lines` (migration 0007): the lines are kept as the stream arrives, back-filled from the machine's final log, and replayed at `GET /run/tasks/:id/session.json?after=` (the owner, or a service with `codes.task.read` and `X-OV-Subject`); tasks from before keep the old `log`. Run's `run.job.*` events finalize a task at `POST /internal/events` ([server/events-consumer.js](server/events-consumer.js), no polling but a 10-minute fallback), and every state change is published as `codes.task.queued|running|succeeded|failed|cancelled` from an outbox ([server/events/outbox.js](server/events/outbox.js), contracts 0.232.0). || Knowledge | `/knowledge`, `GET/POST /api/v1/knowledge` ([server/http/knowledge.js](server/http/knowledge.js), [server/domain/knowledge.js](server/domain/knowledge.js)) | Workspace knowledge (Know K5): when a run ends, Codes captures what it taught — the agent's `Learned:` lines, a failure with its reason, the command that failed and the commands the repository uses — redacts secrets and stores it as a private row with its source run (`codes_knowledge`, migration 0025). The next run's job carries the relevant rows in its prompt before the brief, for every harness ([harness/job.js](harness/job.js) `agentPrompt`), so a pin bump, a test command or a trap is recalled without re-reading files. The page shows every row with its run, and edits, pins, shares (with a Network project's team) and deletes it; private by default. The same rows are exported and erased with the account ([server/account-data.js](server/account-data.js)); `ov-know`'s `knowledge_search`/`knowledge_add` ([know/knowledge.js](https://github.com/OpenVibers/openvibe-agents)) answer them for the person's own agents. || Skills hub | `/skills`, `/api/v1/skills`, `/api/v1/me/skills` ([server/http/skills-pages.js](server/http/skills-pages.js), [server/domain/skills.js](server/domain/skills.js)) | Skills, rules, MCP servers, commands and hooks kept per person and per project, versioned (`codes_skills`, `codes_skill_versions`, migration 0028): every save is an immutable version, a rollback copies an old version into a new one, and the body is rendered through the markup gate. Sharing is `openvibe-sdk/share`'s four audiences (`public`, `link`, `people`, `private`); only `public` is in the gallery, and the share sheet is the SDK's at `/api/v1/skill/:id/share`. A person adds a record to their sync, and `openvibe-node skills sync` writes it into Claude Code, Codex, OpenCode and Command Code inside one marked block, with a dry run, a diff and an undo. || The agents | `/harnesses` | Every harness in [`server/data/harness-offers.json`](server/data/harness-offers.json) (validated at boot as `platform.harness-offer@1`, each model as `platform.agent-offer@1`) with its provider, address kind, what it can do, price and limits, and its models with context limits and token prices. No local path is ever shown. |@@@ -20,7 +20,7 @@"openvibe-codes on a person's machine (bin/openvibe-codes.js, harness/): agents, route, run, runs, show, resume; installs from the release tag","openvibe-codes task (bin/task.js): a coding task on openvibe.codes as the person — start from a brief (with --name, --tier, --permission review|edit|full), list running/needs/done, follow the event stream, continue, stop","The conductor (C9b): a task whose single attempt is a long-lived Claude Code session on a machine the person chooses, with their own plan login; each message from the web or the phone is the next turn of that session, and its work is handed to the other agents through openvibe-codes mcp (codes_task_start, list, show, continue, stop, fleet, ship status, approve), with a risky call approved or denied from the phone",-- "The fleet (C9c): what each agent on the person's machines has left — installed, signed in, and every limit window with when it frees — reported by each machine's Node with its heartbeat and kept here (labels, numbers and times only); the router skips an agent whose window is full or that is not signed in, prefers a flat-price plan with room, and leaves out a pay-per-token agent whose balance is nearly gone",++ "The fleet (C9c): what each agent on the person's machines has left — installed, signed in, and every limit window with when it frees — reported by each machine's Link with its heartbeat and kept here (labels, numbers and times only); the router skips an agent whose window is full or that is not signed in, prefers a flat-price plan with room, and leaves out a pay-per-token agent whose balance is nearly gone","A task with no agent named is auto: the router reads the fleet and picks, and a task no agent may take says why and needs a person; a finished or unfinished task tells the person once (Settings: notify_done, notify_needs), and a pull request's CI, merge and deploy are on the task page","Adapters for Claude Code, Codex, OpenCode, Command Code and Aider (their CLIs) and for the DeepSeek API and any OpenAI-compatible server (the harness's own agent loop with list/read/grep/write/edit/run tools, confined to the working directory)","One event stream for every agent, in Claude Code's stream-json shape, ending with exactly one result; keys and tokens redacted",@@@ -54,12 +54,12 @@"Improve OpenVibe","community documents","redirects for the moved developer console",-- "run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Node)",++ "run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Link)","user JSON API for signed-in people to list, start, read and cancel their own tasks","proof of a finished task: its tests, screenshots, diff and a second-model review, and the outcome in plain words","the conductor (C9b): one Claude Code session on the person's own machine, driven from the browser or the phone, with its approvals","the job runner (CLI 0.6.0): each turn at the effort a person picks, in each agent's own words, and a conversation's next turn starting from the work before it (the last diff applied with git apply --3way, and the conversation so far), on any agent",-- "the job runner (CLI 0.6.1): \"edit\" means edits on the person's own computer (OpenCode and Command Code run unattended only in a Node sandbox or for full; Aider suggests no shell commands there), and a private repository clones with a token the machine holds (GITHUB_TOKEN, GITLAB_TOKEN, OPENVIBE_GIT_CREDENTIALS) sent as a header, never in the URL or .git/config",++ "the job runner (CLI 0.6.1): \"edit\" means edits on the person's own computer (OpenCode and Command Code run unattended only in a Link sandbox or for full; Aider suggests no shell commands there), and a private repository clones with a token the machine holds (GITHUB_TOKEN, GITLAB_TOKEN, OPENVIBE_GIT_CREDENTIALS) sent as a header, never in the URL or .git/config","The guided setup (/setup, owner 2026-11-03): one page of five steps read live: your machine (paired, online), your agents (installed, signed in, usage; the install and sign-in commands for the ones missing), your AI keys on Your AI (optional), OpenVibe Git (optional), your choices (default agent and machine, the conductor's fallbacks, safety); the home says how far along until it is done; every agent picker (Settings, the setup, the composer) says what each agent is ready for, drawn by Shared's select (data-state, data-badge, data-desc, a setup link)","The conductor's failover (CLI 0.7.0, owner 2026-11-03): Settings → Conductor picks up to three fallback agents (Codex, OpenCode) with a model each and a switch point (90% of a usage window by default); before each turn the conductor's own agent and the fallbacks are read against the machine's usage windows, the turn runs on the first with room and returns to the primary once its window frees; each agent resumes its own session and the one taking over is told what happened while it was away; the conductor's page shows each agent's usage and which takes the next turn","OpenVibe Git through the person's own account (/git, CLI 0.6.2): link once (the forge's OAuth2 consent with PKCE, tokens sealed with CODES_SEAL_KEY), make a repository in your name and start a chat on it; a task on a forge repository takes the person's credential line to their machine for git only (never the agent), and the branch and its pull request open on OpenVibe Git"@@@ -97,7 +97,7 @@ const TOOLS = [},{name: 'codes_fleet',-- description: 'The coding agents on the owner\'s machines: installed, signed in, and every limit window with when it frees. Each Node reports its fleet with its heartbeat; this machine is read directly from the CLIs\' own files. Read-only.',++ description: 'The coding agents on the owner\'s machines: installed, signed in, and every limit window with when it frees. Each Link reports its fleet with its heartbeat; this machine is read directly from the CLIs\' own files. Read-only.',inputSchema: { type: 'object', properties: {}, additionalProperties: true },},{@@@ -203,7 +203,7 @@ async function stopTask(args) {return text({ id: t.id, state: t.state, outcome: t.outcome ? t.outcome.state : null });}--/** This machine's own reading: the CLIs' files, the same answer the Node sends with its heartbeat. */++/** This machine's own reading: the CLIs' files, the same answer the Link sends with its heartbeat. */function here() {const rows = probeLogins({ home: os.homedir() });return {@@@ -36,7 +36,7 @@ const USAGE = `openvibe-codes ${VERSION} — code with any agent, from your termopenvibe-codes show <run id> [--json] one run's attempts and result (--json: every event)openvibe-codes resume <run id> "follow-up" continue a run's last session with its agentopenvibe-codes job one task as an OpenVibe Run job (JSON input on stdin; the-- OpenVibe Node's job worker runs it on your own machine)++ OpenVibe Link's job worker runs it on your own machine)openvibe-codes task start|list|show|continue|stop coding tasks on openvibe.codes, as you, on one ofyour machines (openvibe-codes task --help)openvibe-codes mcp the Codes MCP tools for a Claude Code session: tasks, the fleet,@@@ -58,7 +58,7 @@ first `docs/adr/` — the plan (§8) lists **ADR-050 Codes Harness Fabric** amon- **The product half of T16 does not exist in Codes yet.** There is no workspace, editor, terminal, task feed, diff,review verdict, cost, or branch/PR code in `server/` — the strings do not appear. T16's product line is"repository browser, workspace, editor, terminal, task feed, tests, diff, harness explanation, review verdict, cost,-- branch/PR flow — usable hosted and on a Node (T14) for local harnesses", and T16's dependency line is++ branch/PR flow — usable hosted and on a Link (T14) for local harnesses", and T16's dependency line is"T1 (Fabric offers), T2 (projects/grants), T5 (metering/Vibes), T17 (Actor for planning), T14 (Run/Node forexecution)". The plan does not use the phrase "forge model"; the repository/branch/PR authority it describes isForgejo/GitHub under T16, and the project/grant authority is T2.@@@ -45,7 +45,7 @@ normaliser (`transcript.js`) into job lines (`joblines.js`), and a budget stop b### `prompt.js` — the prompt every job getsThe git rule, the house rules, the lessons, the repo digest (`repomap.js`), then the brief. The lessons come from the--shared Know store's own `recall()` (the portal's `know/lessons.js`; on a Node its path is configuration —++shared Know store's own `recall()` (the portal's `know/lessons.js`; on a Link its path is configuration —`CODES_KNOW_LESSONS` — never a hard-coded home). A store that cannot be read gives no lessons, not an error.### `logins.js` — what this machine has@@@ -4,7 +4,7 @@* What this machine has: each coding CLI, whether it is installed, whether it is signed in, and the limit windows it* can read.*-- * The plan logins are the owner's own files (the Node's worker copies the ones the owner names into the job's private++ * The plan logins are the owner's own files (the Link's worker copies the ones the owner names into the job's private* home, read-only, for that job only). This module reads them the way the portal's probes do:** installed the CLI is on PATH@@@ -24,7 +24,7 @@ function fragment(name) {/*** The lessons a job gets: what earlier jobs learned here, through the shared Know store's own `recall()`-- * (the portal's know/lessons.js; on a Node its path comes from the configuration, never a hard-coded home).++ * (the portal's know/lessons.js; on a Link its path comes from the configuration, never a hard-coded home).** The store is optional and off by default. A path that cannot be required, a module without recall(), or a recall* that throws gives no lessons, never an error: a job must not fail because the memory is unreachable.@@@ -55,7 +55,7 @@ function lessons({ file, repo, brief, env = process.env } = {}) {* @param {string} opts.brief what the task is* @param {string} opts.dir the folder the job works in* @param {object} [opts.env] the environment (CODES_KNOW_LESSONS, or the older OPENVIBE_CODES_LESSONS)-- * @param {string} [opts.know] the Know module's path (the Node's config wins over the environment)++ * @param {string} [opts.know] the Know module's path (the Link's config wins over the environment)* @returns {{text: string, parts: object[]}} the prompt and the parts it is made of*/function assemble({ brief, dir, env = process.env, know } = {}) {@@@ -1,14 +1,14 @@'use strict';/**-- * `openvibe-codes job`: one coding task as an OpenVibe Run job on the person's own machine (plan T16; the Node's job++ * `openvibe-codes job`: one coding task as an OpenVibe Run job on the person's own machine (plan T16; the Link's job* worker runs it in its sandbox as artifact `[email protected]`, JOB_PROTOCOL below). The job's input is one JSON* line on stdin:** { repo, ref?, prompt, agent?, model?, permission?: 'edit'|'full', push?: { branch?, pull_request? } }** It clones `repo` (https only) into a scratch directory, runs the harness there with hand-offs, and prints one compact-- * JSON line per event on stdout (what the page shows live). The result goes to fd 3 (the Node's result channel), or as++ * JSON line per event on stdout (what the page shows live). The result goes to fd 3 (the Link's result channel), or as* a last `{ t: 'result_doc' }` line when fd 3 is not open: what the agents said, the changed files and the patch, and,* when `push` is asked and the machine holds GITHUB_TOKEN, the new branch it pushed (never the base branch) and its* pull request. Nothing here ever prints a token.@@@ -22,7 +22,7 @@ const { collectProof } = require('../engine/proof');const JOB_PROTOCOL = '1.0.0';/** The agents a conductor's turn may run on: every harness Codes offers. */// Every harness can lead a conductor; the three that resume a session and take the Codes MCP tools are--// the failover chain's own (server/domain/conductor-failover.js). The job's contract with the Node (stdin, stdout++// the failover chain's own (server/domain/conductor-failover.js). The job's contract with the Link (stdin, stdout// lines, fd 3) is unchanged by 1.1's lines.const CONDUCTOR_AGENTS = ['claude-code', 'claude-other', 'codex', 'opencode', 'command-code', 'minimax-code', 'aider', 'deepseek', 'openvibe-ai', 'openai-compatible'];const LINES = '1.1'; // 1.1: lines carry step ids, inputs, results, progress, helpers; the result keeps `log`@@@ -115,7 +115,7 @@ function cloneProblem(message, repo) {if (!/could not read Username|Authentication failed|terminal prompts disabled|403|401|not found/i.test(m)) return m;let host = 'its host';try { host = new URL(String(repo)).host; } catch { /* keep the words */ }-- return `${m} — if the repository is private, give this machine's Codes runner a token for ${host} (GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https://you:token@${host}) in the Node's config.json env, then restart the Node`;++ return `${m} — if the repository is private, give this machine's Codes runner a token for ${host} (GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https://you:token@${host}) in the Link's config.json env, then restart the Node`;}function scrub(s) {@@@ -734,7 +734,7 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tmprompt: agentPrompt(job), task: 'edit', agent: job.agent, model: job.model, effort: job.effort || null, cwd: work,permission: job.permission, handoff: !job.agent, maxAttempts: job.kind === 'conductor' ? 1 : 3,// A job on the person's Node runs in its sandbox (own namespaces, a read-only root, no home folder, only-- // what the job was given); the Node sets OPENVIBE_JOB_ID there. Only inside it may an agent with no++ // what the job was given); the Link sets OPENVIBE_JOB_ID there. Only inside it may an agent with no// edits-only mode take an "edit" task unattended (harness/index.js unfitFor).sandboxed: !!env.OPENVIBE_JOB_ID,// A task carried on in the same conversation resumes its session (RESUME in the environment, or the job's@@@ -807,7 +807,7 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tmreturn result;}--/** The Node's result channel is fd 3; outside a job the result is the last stdout line. */++/** The Link's result channel is fd 3; outside a job the result is the last stdout line. */function resultWriter(emit) {return (doc) => {const text = JSON.stringify(doc);@@@ -165,7 +165,7 @@ function load(env = process.env) {// The tokens one attempt may spend, when the task does not say (a run token's budget, Codes C3).tokens: int(env.CODES_TASK_TOKENS, 0),},-- // Know (the shared lessons store, Know K): on a Node this is the portal's know/lessons.js, never a hard-coded++ // Know (the shared lessons store, Know K): on a Link this is the portal's know/lessons.js, never a hard-coded// home. The path is configuration; a store that cannot be read gives no lessons, never an error.know: { lessons: String(env.CODES_KNOW_LESSONS || env.OPENVIBE_CODES_LESSONS || '').trim() },// The task's timeline (stage C2): how many automatic continues one task may have before it needs a person.@@@ -547,7 +547,7 @@ function createConductor({ store, config = {}, log = console, run, attempts, tasconst session = ran.session || (agent === (task.agent || PRIMARY_AGENT) && !turn.agent ? (attempt && attempt.session) : null) || null;let note = turn.switch_note || null;if (agent !== asked) {-- note = `${note ? `${note} ` : ''}Your machine ran ${failover.nameOf(agent)} instead of ${failover.nameOf(asked)}: its Codes runner is older than the switch. Update OpenVibe Node on it to let the conductor switch agents.`.slice(0, 1000);++ note = `${note ? `${note} ` : ''}Your machine ran ${failover.nameOf(agent)} instead of ${failover.nameOf(asked)}: its Codes runner is older than the switch. Update OpenVibe Link on it to let the conductor switch agents.`.slice(0, 1000);}await db.exec(`UPDATE codes_conductor_messages SET state = $2, result = $3::jsonb, error = $4, session = $5, finished_at = $6::timestamptz,@@@ -4,10 +4,10 @@* The fleet (Codes C9c): what each of a person's machines says about its coding agents right now — installed, signed* in, and every limit window with when it frees.*-- * The machine reports it with its heartbeat to OpenVibe.Bot (OpenVibe.Node `logins`), and Bot answers it for the++ * The machine reports it with its heartbeat to OpenVibe.Bot (OpenVibe Link `logins`), and Bot answers it for the* machine's owner at GET /api/v1/machines with the person's own token. This module reads that answer, keeps the latest* per machine in codes_machine_fleet, and answers the Agents page, the router and the MCP fleet tool from it. It stores-- * no key, no token and no account name: the Node reports labels, numbers and times only.++ * no key, no token and no account name: the Link reports labels, numbers and times only.** createFleet({ config, store, fetchImpl, log, now })* refresh(subject, token) read Bot now and replace the stored rows → { ok, machines } or { ok: false, problem }@@@ -121,7 +121,7 @@ function createFleet({ config = {}, store, fetchImpl = globalThis.fetch, log = c/*** Read Bot now and replace what Codes holds for each machine whose answer names a fleet. A machine that is gone from-- * the answer is gone here too; a machine that answers no fleet keeps its last reading (the Node may be older).++ * the answer is gone here too; a machine that answers no fleet keeps its last reading (the Link may be older).*/async function refresh(subject, token) {if (!USER.test(String(subject || '')) || !token) return { ok: false, problem: { status: 401, detail: 'sign in again to see your machines' } };@@@ -3,7 +3,7 @@/*** A person's machines, as OpenVibe.Network knows them (Codes C4b).*-- * A machine is paired by redeeming a one-time code on OpenVibe.Network (docs: OpenVibe.Node README and++ * A machine is paired by redeeming a one-time code on OpenVibe.Network (docs: OpenVibe Link README and* internal/link/network.go): openvibe.bot/machines mints `pair_…` with a code that lasts ten minutes, and the machine* redeems it -> `openvibe-node pair --network <URL> --pairing pair_… --code ABCD-1234`. The result is a node principal* (`nod_…`) owned by the person; that principal, its status and when it was last seen are what this module reads.@@@ -287,7 +287,7 @@ function createSkills({ store, sharing, log = console }) {await db.exec('DELETE FROM codes_skill_assignments WHERE subject = $1 AND project = $2 AND skill_id = $3', [subject, scope, id]);}-- /** What the Node syncs: the person's picks, plus this project's picks, at their newest versions. */++ /** What the Link syncs: the person's picks, plus this project's picks, at their newest versions. */async function manifest({ subject, project = '' }) {const scope = String(project || '');const rows = await db.many(`SELECT ${COLUMNS} ${FROM}@@@ -47,7 +47,7 @@ const BUDGETS = {// The skills hub (Compute plan §5): save a version or share a record now and then; a script editing a library// may do a few a minute, not a flood.'codes.skill.write': { minute: 20, hour: 300 },-- // Adding a skill to the sync is how the Node is set up; the first run adds several, then almost never.++ // Adding a skill to the sync is how the Link is set up; the first run adds several, then almost never.'codes.skill.assign': { minute: 30, hour: 600 },};@@@ -7,7 +7,7 @@* / the composer with the person's saved repositories and machines (last used first), running work* /tasks, /tasks/:id the person's tasks; a task's Live, Changes, Proof and Pull request panels are Codes' own* /agents the person's connections: their provider keys (they live in OpenVibe.AI), added, tested, removed-- * /machines their paired Node machines, their state, and Node's pairing flow with a one-time code++ * /machines their paired Link machines, their state, and Node's pairing flow with a one-time code* /usage what Codes knows: runs, minutes and tokens per day (no prices: Billing is Billing's)* /memory what the person's own assistants know about them (the notes live in OpenVibe.AI's store)* /settings their choices, remembered per person (codes_user_settings)@@@ -753,7 +753,7 @@ ${before ? `<h3 class="c4-subhead">Saved in Codes before</h3><ul class="c4-rows"<p class="c4-sub">One command, one code</p><ol class="c4-steps"><li><b>Get a one-time code</b> on <a href="https://openvibe.bot/machines">openvibe.bot/machines</a>. It lasts ten minutes and works once.</li>--<li><b>Run the installer on the machine</b>, with the code it gives you: <code class="c4-code-inline">curl -fsSL https://node.openvibe.network/install | sh -s -- --worker</code></li>++<li><b>Run the installer on the machine</b>, with the code it gives you: <code class="c4-code-inline">curl -fsSL https://openvibe.link/install | sh -s -- --worker</code></li><li><b>It pairs itself</b>, then appears here as Online. A machine runs your jobs and nobody else's.</li></ol><form class="c4-form c4-form-inline" method="get" action="/machines">@@@ -761,7 +761,7 @@ ${before ? `<h3 class="c4-subhead">Saved in Codes before</h3><ul class="c4-rows"<button class="c4-button" type="submit">Show the command</button></form>${badCode ? note('a pairing code is 8 letters and digits, like ABCD-1234', 'bad') : ''}--${code ? `<div class="c4-pair-command"><p class="c4-kv"><span>On the machine</span><code class="c4-code-inline">sudo openvibe-node pair ${esc(code)} --network https://openvibe.network</code></p><p class="c4-kv"><span>Or with the installer</span><code class="c4-code-inline">curl -fsSL https://node.openvibe.network/install | sh -s -- --network https://openvibe.network --pairing <span class="c4-note-inline">pair_… from openvibe.bot</span> --code ${esc(code)} --worker</code></p><p class="c4-hint">The code is the one openvibe.bot gave you for this machine; Codes never mints it. Reload this page once the installer says it is paired.</p></div>` : ''}++${code ? `<div class="c4-pair-command"><p class="c4-kv"><span>On the machine</span><code class="c4-code-inline">sudo openvibe-node pair ${esc(code)} --network https://openvibe.network</code></p><p class="c4-kv"><span>Or with the installer</span><code class="c4-code-inline">curl -fsSL https://openvibe.link/install | sh -s -- --network https://openvibe.network --pairing <span class="c4-note-inline">pair_… from openvibe.bot</span> --code ${esc(code)} --worker</code></p><p class="c4-hint">The code is the one openvibe.bot gave you for this machine; Codes never mints it. Reload this page once the installer says it is paired.</p></div>` : ''}</section>`;const content = `${header('Machines', 'Your own computers, paired with your account.')}${viewer(req) ? `${flash(req)}<section class="c4-card"><div class="c4-card-head"><h2>Your machines</h2></div><p class="c4-sub">${(mine.machines || []).length} paired</p><p class="c4-hint"><a href="https://openvibe.network/machines">All your machines are on openvibe.network/machines</a>.</p>${mine.ok ? (list ? `<ul class="c4-rows">${list}</ul>` : '<div class="c4-emptyline">No paired computers yet. Pair one below and it shows up here.</div>') : note(`Your machines could not be read. ${esc((mine.problem && mine.problem.detail) || 'Sign in again.')}`, 'bad')}</section>${pairCard}` : signIn(req)}`;page(req, res, 'Machines', wrap(content));@@@ -826,7 +826,7 @@ ${report.days.length ? `<div class="table-wrap" tabindex="0" role="region" aria-<p class="c4-hint">When your conductor's agent nears a usage limit (its 5-hour or weekly window), the next turn runs on the first fallback below that has room, and returns once the window frees. Each agent keeps its own session; the one taking over is told what happened while it was away.</p><div class="c4-fallbacks">${[0, 1, 2].map(fallbackRow).join('')}</div><div class="c4-form-grid"><label>Switch at <select name="conductor_switch_pct">${[70, 80, 85, 90, 95].map((v) => `<option value="${v}"${switchPct === v ? ' selected' : ''}>${v}% of a window</option>`).join('')}</select></label></div>--<p class="c4-hint">Codex and OpenCode have no approval prompt: on "Ask first" they work with edits in the folder only. Your machine needs OpenVibe Node with Codes 0.7 or newer to switch.</p>` : '';++<p class="c4-hint">Codex and OpenCode have no approval prompt: on "Ask first" they work with edits in the folder only. Your machine needs OpenVibe Link with Codes 0.7 or newer to switch.</p>` : '';const content = `${header('Settings', 'Your choices, remembered for your account.')}${viewer(req) ? `${flash(req)}<p class="c4-hint c4-setup-link">New here, or something not working? <a href="/setup">The guided setup</a> checks your machine, agents and keys step by step.</p><section class="c4-card"><form class="c4-form" method="post" action="/settings"><div class="c4-form-grid"><label>Default agent <select name="default_agent"><option value="">Auto</option>${agentOptions}</select></label>@@@ -30,7 +30,7 @@ const express = require('express');const { asyncRouter } = require('./router');const { esc } = require('../render/html');--/** The adapter ids the Node reads and the services that write, and the names a person knows them by. */++/** The adapter ids the Link reads and the services that write, and the names a person knows them by. */const SOURCE_NAMES = {'claude-code': 'Claude Code', agents: 'AGENTS.md', gemini: 'Gemini CLI', cursor: 'Cursor',copilot: 'GitHub Copilot', windsurf: 'Windsurf', cline: 'Cline', roo: 'Roo Code', kiro: 'Kiro',@@@ -314,7 +314,7 @@ function createMemoryRoutes(ctx, kit) {if (!rows.length) {return head + '<div class="c4-emptyline">Nothing here yet. On your computer, run '+ '<code class="c4-code-inline">openvibe-node memory upload</code>: it shows what it found and asks before sending anything. '-- + '<a href="https://github.com/OpenVibers/OpenVibe.Node/blob/main/docs/memory.md#the-upload">The Node docs</a></div></section>';++ + '<a href="https://github.com/OpenVibers/OpenVibe.Node/blob/main/docs/memory.md#the-upload">The Link docs</a></div></section>';}const sourceRow = (row) => {const fresh = row.seen && Date.now() - row.seen < SEVEN_DAYS;@@@ -104,7 +104,7 @@ Added test/date.test.js: three cases, all pass.const statusTable = table(['Piece', 'State'], [[html`<a href="/start">The harness on your machine</a> (<code>openvibe-codes</code>)`, `Live, ${VERSION}: runs a task on any of the eight agents with your CLIs and keys, streams one event format, keeps every run to show or resume.`],['Hand-offs between agents', 'Live: a failed, rate-limited or silent agent hands the task to the next one with a note on what was done.'],-- [html`<a href="/run">Run on your machine</a>: start a task in any browser, your phone included; your own computer runs it`, 'Live: add your machine on openvibe.bot/machines (one command), then watch the task live here and get the patch, a branch or a pull request. It runs only on your own machines, in the Node\'s job sandbox.'],++ [html`<a href="/run">Run on your machine</a>: start a task in any browser, your phone included; your own computer runs it`, 'Live: add your machine on openvibe.bot/machines (one command), then watch the task live here and get the patch, a branch or a pull request. It runs only on your own machines, in Link\'s job sandbox.'],[html`<a href="/harnesses">The agent catalog</a>`, 'Live: every harness and model with its capabilities, prices and limits.'],[html`<code>GET /api/v1/harnesses</code>, <code>POST /api/v1/harnesses/route</code>`, 'Live: public, no key needed; per-address and per-caller limits.'],[html`<a href="/improve">Improve OpenVibe</a>: a repository, a change on a branch with its test, a pull request that CI checks and a maintainer reviews`, 'Live: pick a repository, describe the change, and start a guided run on your machine.'],@@@ -139,7 +139,7 @@ ${error ? html`<p class="run-notice run-notice-error" role="alert">${error}</p>`title: 'Run on your machine', index: true,body: html`<h1>Run a coding task on your own machine</h1><p class="lead">Start a task from any browser, your phone included, and your own computer does the work: the agents and keys you already have, in a sandbox, with nothing to host. You watch it live here and get the changes back as a patch, a branch or a pull request.</p>--<ol class="run-steps"><li>Add your computer on <a href="${MACHINES_URL}">openvibe.bot/machines</a>: one command, which installs OpenVibe Node with its job worker.</li><li>Sign in here and describe the task.</li><li>Your machine clones the repository, runs the agent and sends the result back.</li></ol>++<ol class="run-steps"><li>Add your computer on <a href="${MACHINES_URL}">openvibe.bot/machines</a>: one command, which installs OpenVibe Link with its job worker.</li><li>Sign in here and describe the task.</li><li>Your machine clones the repository, runs the agent and sends the result back.</li></ol><p><a class="run-btn run-btn-primary" href="/auth/login?next=%2Frun">Sign in to start</a></p>`,});}@@@ -84,7 +84,7 @@ function createSetupRoutes(ctx, kit) {const problem = d.machines.ok === false && d.machines.problem ? note(esc(d.machines.problem.detail || 'Your machines could not be read.'), 'bad') : '';const how = `<ol class="c4-setup-how"><li><b>Get a one-time code</b> on <a href="https://openvibe.bot/machines" rel="noopener">openvibe.bot/machines</a>. It lasts ten minutes.</li>--<li><b>Run the installer on that computer</b> and give it the code:${command('curl -fsSL https://node.openvibe.network/install | sh -s -- --worker', 'On the machine')}</li>++<li><b>Run the installer on that computer</b> and give it the code:${command('curl -fsSL https://openvibe.link/install | sh -s -- --worker', 'On the machine')}</li><li><b>It pairs itself</b> and shows up here as Online. It runs your work and nobody else's.</li></ol>`;return `${problem}${rows ? `<ul class="c4-rows">${rows}</ul>` : ''}${list.length ? `<details class="c4-setup-more"><summary>Pair another machine</summary>${how}</details>` : how}<div class="c4-setup-actions"><a class="c4-button${list.length ? ' c4-button-light' : ''}" href="/machines#pair">${list.length ? 'Manage machines' : 'I have a code'}</a><a class="c4-opill" href="/setup#step-machine">Check again</a></div>`;@@@ -18,7 +18,7 @@* POST /api/v1/me/skills/:id add a record to the sync* POST /api/v1/me/skills/:id/remove remove it*-- * The Node sync authenticates with the person's access token (Authorization: Bearer), which the same SSO middleware++ * The Link sync authenticates with the person's access token (Authorization: Bearer), which the same SSO middleware* verifies as a browser session. Only public records are in the gallery; the others answer 404 to everyone who is* not allowed to read them.*/@@@ -127,7 +127,7 @@ ${table(['Version', 'When', 'Audience', 'Note'], versions.map((v) => [v.audience,v.note || '—',]))}--<p class="muted small">A rollback copies the chosen version into a new one, so history is never rewritten. The Node records the version it wrote.</p></section>++<p class="muted small">A rollback copies the chosen version into a new one, so history is never rewritten. The Link records the version it wrote.</p></section>${controls}`,}, status);}@@@ -5,7 +5,7 @@* - Home's saved pickers: the repositories a person used here, last used first, and their paired machines* - a task's Changes, Proof and Pull request panels are Codes' own, from run_tasks and codes_task_proof* - Agents: the person's keys, added, tested and removed from the page (the key itself never comes back)-- * - Machines: the person's paired Node machines, their state, and Node's pairing flow with a one-time code++ * - Machines: the person's paired Link machines, their state, and Node's pairing flow with a one-time code* - Settings: remembered per person* - Usage: runs, minutes and tokens per day, and no prices Codes does not know*/@@@ -39,7 +39,7 @@ const RESET_MONTH = new Date(Date.now() + 2.5 * 86400000).toISOString();/** The fleet Ada's machines reported with their last heartbeats. */const window = (name, pct, resetsAt) => JSON.stringify([{ name, pct, resets_at: resetsAt }]);// The machine, as OpenVibe.Bot answers it: the device id Run pins a job to, the Network id the machine is known-- // by, and the fleet its Node last reported with its heartbeat.++ // by, and the fleet its Link last reported with its heartbeat.t.bot.addMachine({id: 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2N1', node_id: NODE, name: 'ada-laptop', online: true, fleet_at: new Date().toISOString(),fleet: [@@@ -185,7 +185,7 @@ const HOUR = 3600_000;await endTurn(taskId, { agent: 'claude-code', session: 'sess_claude_2' });const m = (await messagesOf(taskId))[2];assert.strictEqual(m.agent, 'claude-code');-- assert.match(m.switch_note, /Update OpenVibe Node/);++ assert.match(m.switch_note, /Update OpenVibe Link/);report(10, 30);await refresh();await post(`/api/v1/conductor/${taskId}/messages`, { text: 'status?' });@@@ -294,7 +294,7 @@ function dry(adapter, ctx = {}) {// A job that works in one repository gets that repository's digest; a folder that holds many gets none.require('node:child_process').execFileSync('git', ['init', '-q'], { cwd: dir });fs.writeFileSync(path.join(dir, 'README.md'), '# Demo\n\nA demo repository.\n');-- // On a Node the lessons come from the Know store's module (know/lessons.js), its path from the config.++ // On a Link the lessons come from the Know store's module (know/lessons.js), its path from the config.const knowFile = path.join(dir, 'know.js');fs.writeFileSync(knowFile, "module.exports = { recall: () => '## What earlier jobs learned here (dated)\\n- 2026-10-30 · demo — the tests need the database' };\n");const built = assemble({ brief: 'fix the test', dir, env: { CODES_KNOW_LESSONS: knowFile } });@@@ -304,7 +304,7 @@ function dry(adapter, ctx = {}) {assert.ok(built.text.indexOf('House rules') < built.text.indexOf('learned'));assert.ok(built.text.indexOf('learned') < built.text.indexOf('demo repository'));assert.ok(built.text.trimEnd().endsWith('fix the test'), 'the brief is last');-- // The Node's config path wins over the environment, and a store that cannot be read is skipped, not an error.++ // The Link's config path wins over the environment, and a store that cannot be read is skipped, not an error.const fromConfig = assemble({ brief: 'x', dir, know: knowFile, env: { CODES_KNOW_LESSONS: path.join(dir, 'nope.js') } });assert.ok(fromConfig.parts.some((p) => p.name === 'lessons'), 'the config path is read');const none = assemble({ brief: 'x', dir, env: { CODES_KNOW_LESSONS: path.join(dir, 'nope.js') } });@@@ -32,7 +32,7 @@ const { assemble, lessons, fragment } = require('../engine/prompt');assert.ok(got.trimEnd().endsWith('the database'), 'and its lesson is kept');assert.deepStrictEqual(JSON.parse(fs.readFileSync(path.join(dir, 'seen.json'), 'utf8')),{ brief: 'fix the database test', repo: 'demo', k: 5 }, 'the store gets the brief, the repo and the cap');-- // The environment is how a Node passes the config path when the caller does not.++ // The environment is how a Link passes the config path when the caller does not.assert.equal(lessons({ brief: 'x', env: { CODES_KNOW_LESSONS: file } }), got);fs.rmSync(dir, { recursive: true, force: true });});@@@ -43,7 +43,7 @@ const results = (events) => events.filter((e) => e.type === 'result');for (const [agent, word] of [['claude-code', 'claude'], ['codex', 'codex'], ['opencode', 'opencode'], ['command-code', 'cmd']]) {await check(`${agent}: the agent's output becomes the shared events, with one result`, async () => {-- // OpenCode and Command Code take an "edit" run only inside a sandbox (test/harness-sandbox.test.js): run as a Node job.++ // OpenCode and Command Code take an "edit" run only inside a sandbox (test/harness-sandbox.test.js): run as a Link job.const events = await collect(harness().run({ prompt: 'fix a.js', agent, cwd: f.work, handoff: false, sandboxed: true }));assert.strictEqual(events[0].type, 'system');assert.strictEqual(events[0].subtype, 'init');@@@ -42,7 +42,7 @@ const collect = async (run) => { const out = []; for await (const e of run) out.assert.strictEqual(cli.opencode.editsOnly, false);assert.strictEqual(cli.commandCode.editsOnly, false);assert.ok(!(await argvOf(cli.opencode, { permission: 'edit' })).includes('--auto'), 'edit on the person’s computer: no --auto');-- assert.ok((await argvOf(cli.opencode, { permission: 'edit', sandboxed: true })).includes('--auto'), 'edit in a Node sandbox: --auto');++ assert.ok((await argvOf(cli.opencode, { permission: 'edit', sandboxed: true })).includes('--auto'), 'edit in a Link sandbox: --auto');assert.ok((await argvOf(cli.opencode, { permission: 'full' })).includes('--auto'), 'full: --auto');assert.ok(!(await argvOf(cli.opencode, { permission: 'read', sandboxed: true })).includes('--auto'), 'read: never');assert.ok(!(await argvOf(cli.commandCode, { permission: 'edit' })).includes('--yolo'), 'Command Code: no --yolo for edit outside a sandbox');@@@ -179,7 +179,7 @@ async function fakeGitHub() {assert.strictEqual(typeof a.installed, 'boolean');assert.ok(Array.isArray(a.windows));}-- // Codes answers with every machine's stored report: this machine is in Bot's list, but its Node has not++ // Codes answers with every machine's stored report: this machine is in Bot's list, but its Link has not// reported a fleet yet, so there is nothing stored and nothing is invented.assert.ok(Array.isArray(out.machines), 'the stored fleet of every machine');assert.strictEqual(out.problem, undefined, 'Bot answered, so there is no problem beside the fleet');@@@ -35,7 +35,7 @@ const DEVICE = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2S1';for (const id of ['machine', 'agents', 'keys', 'accounts', 'choices']) assert.match(res.text, new RegExp(`id="step-${id}"`));assert.match(res.text, /0 of 5 done/);assert.match(res.text, /class="c4-card c4-setup-step is-next" id="step-machine"/, 'the machine is next');-- assert.match(res.text, /curl -fsSL https:\/\/node\.openvibe\.network\/install \| sh -s -- --worker/);++ assert.match(res.text, /curl -fsSL https:\/\/openvibe\.link\/install \| sh -s -- --worker/);assert.match(res.text, /npm install -g @openai\/codex/, 'an agent that is not there says how to install it');assert.match(res.text, /href="\/css\/setup\.css|css\/setup\.css/, 'the page brings its own stylesheet');assert.match(res.text, /js\/setup\.js/, 'and its own script (the Copy buttons), which no other page pays for');@@@ -6,7 +6,7 @@* - a new record is private and is not in the gallery;* - public is listed and readable anonymously, link is readable by id but not listed, private answers 404;* - saving a version keeps the old one, and a rollback copies it into a new version (history is never rewritten);-- * - the Node's sync manifest is exactly the person's picks, at their newest version;++ * - the Link's sync manifest is exactly the person's picks, at their newest version;* - the page renders a skill body through the markup gate, never as HTML.*/const assert = require('assert');
@@ -20,7 +20,7 @@ One place to send a coding task to the agent that fits it — by what the task n
@@ -20,7 +20,7 @@ One place to send a coding task to the agent that fits it — by what the task n
| The harness | `openvibe-codes` ([bin/openvibe-codes.js](bin/openvibe-codes.js), [harness/](harness/)) | Runs on a person's own machine with their CLIs and keys; nothing goes through OpenVibe. See **Use the harness** below. |
| The harness | `openvibe-codes` ([bin/openvibe-codes.js](bin/openvibe-codes.js), [harness/](harness/)) | Runs on a person's own machine with their CLIs and keys; nothing goes through OpenVibe. See **Use the harness** below. |
| Home | `/` | What the harness is, the agents in the catalog, how a task finds its agent, Improve OpenVibe, and an honest table of what works today and what comes next. |
| Home | `/` | What the harness is, the agents in the catalog, how a task finds its agent, Improve OpenVibe, and an honest table of what works today and what comes next. |
| Get started | `/start` | Install, the agents and what each needs, the commands, permission levels, hand-offs, the event format and using it from code. |
| Get started | `/start` | Install, the agents and what each needs, the commands, permission levels, hand-offs, the event format and using it from code. |
-| Run on your machine | `/run`, `/run/tasks/:id` ([server/http/run.js](server/http/run.js)) | A signed-in person starts a coding task in any browser and their own machine runs it: an OpenVibe Run job (class `code`, artifact `[email protected]`, `trust: user-owned`) that Codes starts for them in their project (project delegation: `X-OV-Subject`, `X-OV-Project`; [server/domain/run-client.js](server/domain/run-client.js)). Run places it only on a machine of theirs (added on openvibe.bot/machines), whose OpenVibe Node runs `openvibe-codes job` ([harness/job.js](harness/job.js)) in its job sandbox: clone, run the agents with hand-offs, hand back the summary, the changed files and the patch, and, when asked and the machine holds `GITHUB_TOKEN`, push a new branch (never the one it started from) and open a pull request. The page streams the job's lines (Run's job stream through Codes) and keeps the result (`run_tasks`, migration 0003). Each task is its owner's; account export and deletion cover them (ADR-033, [server/account-data.js](server/account-data.js)). The task page shows the **timeline of attempts** above the agents' **session** (each attempt with its why: "Codex hit its limit, so Command Code took over with what it had done") and the session with openvibe-shared/agent-session.js: steps grouped, each step's command, change and output (live while it runs), helpers nested, hand-offs and the plan; after the end, the session from `codes_session_lines` (migration 0007): the lines are kept as the stream arrives, back-filled from the machine's final log, and replayed at `GET /run/tasks/:id/session.json?after=` (the owner, or a service with `codes.task.read` and `X-OV-Subject`); tasks from before keep the old `log`. Run's `run.job.*` events finalize a task at `POST /internal/events` ([server/events-consumer.js](server/events-consumer.js), no polling but a 10-minute fallback), and every state change is published as `codes.task.queued|running|succeeded|failed|cancelled` from an outbox ([server/events/outbox.js](server/events/outbox.js), contracts 0.232.0). |
+| Run on your machine | `/run`, `/run/tasks/:id` ([server/http/run.js](server/http/run.js)) | A signed-in person starts a coding task in any browser and their own machine runs it: an OpenVibe Run job (class `code`, artifact `[email protected]`, `trust: user-owned`) that Codes starts for them in their project (project delegation: `X-OV-Subject`, `X-OV-Project`; [server/domain/run-client.js](server/domain/run-client.js)). Run places it only on a machine of theirs (added on openvibe.bot/machines), whose OpenVibe Link runs `openvibe-codes job` ([harness/job.js](harness/job.js)) in its job sandbox: clone, run the agents with hand-offs, hand back the summary, the changed files and the patch, and, when asked and the machine holds `GITHUB_TOKEN`, push a new branch (never the one it started from) and open a pull request. The page streams the job's lines (Run's job stream through Codes) and keeps the result (`run_tasks`, migration 0003). Each task is its owner's; account export and deletion cover them (ADR-033, [server/account-data.js](server/account-data.js)). The task page shows the **timeline of attempts** above the agents' **session** (each attempt with its why: "Codex hit its limit, so Command Code took over with what it had done") and the session with openvibe-shared/agent-session.js: steps grouped, each step's command, change and output (live while it runs), helpers nested, hand-offs and the plan; after the end, the session from `codes_session_lines` (migration 0007): the lines are kept as the stream arrives, back-filled from the machine's final log, and replayed at `GET /run/tasks/:id/session.json?after=` (the owner, or a service with `codes.task.read` and `X-OV-Subject`); tasks from before keep the old `log`. Run's `run.job.*` events finalize a task at `POST /internal/events` ([server/events-consumer.js](server/events-consumer.js), no polling but a 10-minute fallback), and every state change is published as `codes.task.queued|running|succeeded|failed|cancelled` from an outbox ([server/events/outbox.js](server/events/outbox.js), contracts 0.232.0). |
| Knowledge | `/knowledge`, `GET/POST /api/v1/knowledge` ([server/http/knowledge.js](server/http/knowledge.js), [server/domain/knowledge.js](server/domain/knowledge.js)) | Workspace knowledge (Know K5): when a run ends, Codes captures what it taught — the agent's `Learned:` lines, a failure with its reason, the command that failed and the commands the repository uses — redacts secrets and stores it as a private row with its source run (`codes_knowledge`, migration 0025). The next run's job carries the relevant rows in its prompt before the brief, for every harness ([harness/job.js](harness/job.js) `agentPrompt`), so a pin bump, a test command or a trap is recalled without re-reading files. The page shows every row with its run, and edits, pins, shares (with a Network project's team) and deletes it; private by default. The same rows are exported and erased with the account ([server/account-data.js](server/account-data.js)); `ov-know`'s `knowledge_search`/`knowledge_add` ([know/knowledge.js](https://github.com/OpenVibers/openvibe-agents)) answer them for the person's own agents. |
| Knowledge | `/knowledge`, `GET/POST /api/v1/knowledge` ([server/http/knowledge.js](server/http/knowledge.js), [server/domain/knowledge.js](server/domain/knowledge.js)) | Workspace knowledge (Know K5): when a run ends, Codes captures what it taught — the agent's `Learned:` lines, a failure with its reason, the command that failed and the commands the repository uses — redacts secrets and stores it as a private row with its source run (`codes_knowledge`, migration 0025). The next run's job carries the relevant rows in its prompt before the brief, for every harness ([harness/job.js](harness/job.js) `agentPrompt`), so a pin bump, a test command or a trap is recalled without re-reading files. The page shows every row with its run, and edits, pins, shares (with a Network project's team) and deletes it; private by default. The same rows are exported and erased with the account ([server/account-data.js](server/account-data.js)); `ov-know`'s `knowledge_search`/`knowledge_add` ([know/knowledge.js](https://github.com/OpenVibers/openvibe-agents)) answer them for the person's own agents. |
| Skills hub | `/skills`, `/api/v1/skills`, `/api/v1/me/skills` ([server/http/skills-pages.js](server/http/skills-pages.js), [server/domain/skills.js](server/domain/skills.js)) | Skills, rules, MCP servers, commands and hooks kept per person and per project, versioned (`codes_skills`, `codes_skill_versions`, migration 0028): every save is an immutable version, a rollback copies an old version into a new one, and the body is rendered through the markup gate. Sharing is `openvibe-sdk/share`'s four audiences (`public`, `link`, `people`, `private`); only `public` is in the gallery, and the share sheet is the SDK's at `/api/v1/skill/:id/share`. A person adds a record to their sync, and `openvibe-node skills sync` writes it into Claude Code, Codex, OpenCode and Command Code inside one marked block, with a dry run, a diff and an undo. |
| Skills hub | `/skills`, `/api/v1/skills`, `/api/v1/me/skills` ([server/http/skills-pages.js](server/http/skills-pages.js), [server/domain/skills.js](server/domain/skills.js)) | Skills, rules, MCP servers, commands and hooks kept per person and per project, versioned (`codes_skills`, `codes_skill_versions`, migration 0028): every save is an immutable version, a rollback copies an old version into a new one, and the body is rendered through the markup gate. Sharing is `openvibe-sdk/share`'s four audiences (`public`, `link`, `people`, `private`); only `public` is in the gallery, and the share sheet is the SDK's at `/api/v1/skill/:id/share`. A person adds a record to their sync, and `openvibe-node skills sync` writes it into Claude Code, Codex, OpenCode and Command Code inside one marked block, with a dry run, a diff and an undo. |
| The agents | `/harnesses` | Every harness in [`server/data/harness-offers.json`](server/data/harness-offers.json) (validated at boot as `platform.harness-offer@1`, each model as `platform.agent-offer@1`) with its provider, address kind, what it can do, price and limits, and its models with context limits and token prices. No local path is ever shown. |
| The agents | `/harnesses` | Every harness in [`server/data/harness-offers.json`](server/data/harness-offers.json) (validated at boot as `platform.harness-offer@1`, each model as `platform.agent-offer@1`) with its provider, address kind, what it can do, price and limits, and its models with context limits and token prices. No local path is ever shown. |
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
"openvibe-codes on a person's machine (bin/openvibe-codes.js, harness/): agents, route, run, runs, show, resume; installs from the release tag",
"openvibe-codes on a person's machine (bin/openvibe-codes.js, harness/): agents, route, run, runs, show, resume; installs from the release tag",
"openvibe-codes task (bin/task.js): a coding task on openvibe.codes as the person — start from a brief (with --name, --tier, --permission review|edit|full), list running/needs/done, follow the event stream, continue, stop",
"openvibe-codes task (bin/task.js): a coding task on openvibe.codes as the person — start from a brief (with --name, --tier, --permission review|edit|full), list running/needs/done, follow the event stream, continue, stop",
"The conductor (C9b): a task whose single attempt is a long-lived Claude Code session on a machine the person chooses, with their own plan login; each message from the web or the phone is the next turn of that session, and its work is handed to the other agents through openvibe-codes mcp (codes_task_start, list, show, continue, stop, fleet, ship status, approve), with a risky call approved or denied from the phone",
"The conductor (C9b): a task whose single attempt is a long-lived Claude Code session on a machine the person chooses, with their own plan login; each message from the web or the phone is the next turn of that session, and its work is handed to the other agents through openvibe-codes mcp (codes_task_start, list, show, continue, stop, fleet, ship status, approve), with a risky call approved or denied from the phone",
- "The fleet (C9c): what each agent on the person's machines has left — installed, signed in, and every limit window with when it frees — reported by each machine's Node with its heartbeat and kept here (labels, numbers and times only); the router skips an agent whose window is full or that is not signed in, prefers a flat-price plan with room, and leaves out a pay-per-token agent whose balance is nearly gone",
+ "The fleet (C9c): what each agent on the person's machines has left — installed, signed in, and every limit window with when it frees — reported by each machine's Link with its heartbeat and kept here (labels, numbers and times only); the router skips an agent whose window is full or that is not signed in, prefers a flat-price plan with room, and leaves out a pay-per-token agent whose balance is nearly gone",
"A task with no agent named is auto: the router reads the fleet and picks, and a task no agent may take says why and needs a person; a finished or unfinished task tells the person once (Settings: notify_done, notify_needs), and a pull request's CI, merge and deploy are on the task page",
"A task with no agent named is auto: the router reads the fleet and picks, and a task no agent may take says why and needs a person; a finished or unfinished task tells the person once (Settings: notify_done, notify_needs), and a pull request's CI, merge and deploy are on the task page",
"Adapters for Claude Code, Codex, OpenCode, Command Code and Aider (their CLIs) and for the DeepSeek API and any OpenAI-compatible server (the harness's own agent loop with list/read/grep/write/edit/run tools, confined to the working directory)",
"Adapters for Claude Code, Codex, OpenCode, Command Code and Aider (their CLIs) and for the DeepSeek API and any OpenAI-compatible server (the harness's own agent loop with list/read/grep/write/edit/run tools, confined to the working directory)",
"One event stream for every agent, in Claude Code's stream-json shape, ending with exactly one result; keys and tokens redacted",
"One event stream for every agent, in Claude Code's stream-json shape, ending with exactly one result; keys and tokens redacted",
@@ -54,12 +54,12 @@
@@ -54,12 +54,12 @@
"Improve OpenVibe",
"Improve OpenVibe",
"community documents",
"community documents",
"redirects for the moved developer console",
"redirects for the moved developer console",
- "run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Node)",
+ "run a coding task on your own machine from any browser (/run; openvibe-codes job through OpenVibe Run and the person's own OpenVibe Link)",
"user JSON API for signed-in people to list, start, read and cancel their own tasks",
"user JSON API for signed-in people to list, start, read and cancel their own tasks",
"proof of a finished task: its tests, screenshots, diff and a second-model review, and the outcome in plain words",
"proof of a finished task: its tests, screenshots, diff and a second-model review, and the outcome in plain words",
"the conductor (C9b): one Claude Code session on the person's own machine, driven from the browser or the phone, with its approvals",
"the conductor (C9b): one Claude Code session on the person's own machine, driven from the browser or the phone, with its approvals",
"the job runner (CLI 0.6.0): each turn at the effort a person picks, in each agent's own words, and a conversation's next turn starting from the work before it (the last diff applied with git apply --3way, and the conversation so far), on any agent",
"the job runner (CLI 0.6.0): each turn at the effort a person picks, in each agent's own words, and a conversation's next turn starting from the work before it (the last diff applied with git apply --3way, and the conversation so far), on any agent",
- "the job runner (CLI 0.6.1): \"edit\" means edits on the person's own computer (OpenCode and Command Code run unattended only in a Node sandbox or for full; Aider suggests no shell commands there), and a private repository clones with a token the machine holds (GITHUB_TOKEN, GITLAB_TOKEN, OPENVIBE_GIT_CREDENTIALS) sent as a header, never in the URL or .git/config",
+ "the job runner (CLI 0.6.1): \"edit\" means edits on the person's own computer (OpenCode and Command Code run unattended only in a Link sandbox or for full; Aider suggests no shell commands there), and a private repository clones with a token the machine holds (GITHUB_TOKEN, GITLAB_TOKEN, OPENVIBE_GIT_CREDENTIALS) sent as a header, never in the URL or .git/config",
"The guided setup (/setup, owner 2026-11-03): one page of five steps read live: your machine (paired, online), your agents (installed, signed in, usage; the install and sign-in commands for the ones missing), your AI keys on Your AI (optional), OpenVibe Git (optional), your choices (default agent and machine, the conductor's fallbacks, safety); the home says how far along until it is done; every agent picker (Settings, the setup, the composer) says what each agent is ready for, drawn by Shared's select (data-state, data-badge, data-desc, a setup link)",
"The guided setup (/setup, owner 2026-11-03): one page of five steps read live: your machine (paired, online), your agents (installed, signed in, usage; the install and sign-in commands for the ones missing), your AI keys on Your AI (optional), OpenVibe Git (optional), your choices (default agent and machine, the conductor's fallbacks, safety); the home says how far along until it is done; every agent picker (Settings, the setup, the composer) says what each agent is ready for, drawn by Shared's select (data-state, data-badge, data-desc, a setup link)",
"The conductor's failover (CLI 0.7.0, owner 2026-11-03): Settings → Conductor picks up to three fallback agents (Codex, OpenCode) with a model each and a switch point (90% of a usage window by default); before each turn the conductor's own agent and the fallbacks are read against the machine's usage windows, the turn runs on the first with room and returns to the primary once its window frees; each agent resumes its own session and the one taking over is told what happened while it was away; the conductor's page shows each agent's usage and which takes the next turn",
"The conductor's failover (CLI 0.7.0, owner 2026-11-03): Settings → Conductor picks up to three fallback agents (Codex, OpenCode) with a model each and a switch point (90% of a usage window by default); before each turn the conductor's own agent and the fallbacks are read against the machine's usage windows, the turn runs on the first with room and returns to the primary once its window frees; each agent resumes its own session and the one taking over is told what happened while it was away; the conductor's page shows each agent's usage and which takes the next turn",
"OpenVibe Git through the person's own account (/git, CLI 0.6.2): link once (the forge's OAuth2 consent with PKCE, tokens sealed with CODES_SEAL_KEY), make a repository in your name and start a chat on it; a task on a forge repository takes the person's credential line to their machine for git only (never the agent), and the branch and its pull request open on OpenVibe Git"
"OpenVibe Git through the person's own account (/git, CLI 0.6.2): link once (the forge's OAuth2 consent with PKCE, tokens sealed with CODES_SEAL_KEY), make a repository in your name and start a chat on it; a task on a forge repository takes the person's credential line to their machine for git only (never the agent), and the branch and its pull request open on OpenVibe Git"
@@ -97,7 +97,7 @@ const TOOLS = [
@@ -97,7 +97,7 @@ const TOOLS = [
},
},
{
{
name: 'codes_fleet',
name: 'codes_fleet',
- description: 'The coding agents on the owner\'s machines: installed, signed in, and every limit window with when it frees. Each Node reports its fleet with its heartbeat; this machine is read directly from the CLIs\' own files. Read-only.',
+ description: 'The coding agents on the owner\'s machines: installed, signed in, and every limit window with when it frees. Each Link reports its fleet with its heartbeat; this machine is read directly from the CLIs\' own files. Read-only.',
inputSchema: { type: 'object', properties: {}, additionalProperties: true },
inputSchema: { type: 'object', properties: {}, additionalProperties: true },
},
},
{
{
@@ -203,7 +203,7 @@ async function stopTask(args) {
@@ -203,7 +203,7 @@ async function stopTask(args) {
return text({ id: t.id, state: t.state, outcome: t.outcome ? t.outcome.state : null });
return text({ id: t.id, state: t.state, outcome: t.outcome ? t.outcome.state : null });
}
}
-/** This machine's own reading: the CLIs' files, the same answer the Node sends with its heartbeat. */
+/** This machine's own reading: the CLIs' files, the same answer the Link sends with its heartbeat. */
function here() {
function here() {
const rows = probeLogins({ home: os.homedir() });
const rows = probeLogins({ home: os.homedir() });
return {
return {
@@ -36,7 +36,7 @@ const USAGE = `openvibe-codes ${VERSION} — code with any agent, from your term
@@ -36,7 +36,7 @@ const USAGE = `openvibe-codes ${VERSION} — code with any agent, from your term
openvibe-codes show <run id> [--json] one run's attempts and result (--json: every event)
openvibe-codes show <run id> [--json] one run's attempts and result (--json: every event)
openvibe-codes resume <run id> "follow-up" continue a run's last session with its agent
openvibe-codes resume <run id> "follow-up" continue a run's last session with its agent
openvibe-codes job one task as an OpenVibe Run job (JSON input on stdin; the
openvibe-codes job one task as an OpenVibe Run job (JSON input on stdin; the
- OpenVibe Node's job worker runs it on your own machine)
+ OpenVibe Link's job worker runs it on your own machine)
openvibe-codes task start|list|show|continue|stop coding tasks on openvibe.codes, as you, on one of
openvibe-codes task start|list|show|continue|stop coding tasks on openvibe.codes, as you, on one of
your machines (openvibe-codes task --help)
your machines (openvibe-codes task --help)
openvibe-codes mcp the Codes MCP tools for a Claude Code session: tasks, the fleet,
openvibe-codes mcp the Codes MCP tools for a Claude Code session: tasks, the fleet,
@@ -58,7 +58,7 @@ first `docs/adr/` — the plan (§8) lists **ADR-050 Codes Harness Fabric** amon
@@ -58,7 +58,7 @@ first `docs/adr/` — the plan (§8) lists **ADR-050 Codes Harness Fabric** amon
- **The product half of T16 does not exist in Codes yet.** There is no workspace, editor, terminal, task feed, diff,
- **The product half of T16 does not exist in Codes yet.** There is no workspace, editor, terminal, task feed, diff,
review verdict, cost, or branch/PR code in `server/` — the strings do not appear. T16's product line is
review verdict, cost, or branch/PR code in `server/` — the strings do not appear. T16's product line is
"repository browser, workspace, editor, terminal, task feed, tests, diff, harness explanation, review verdict, cost,
"repository browser, workspace, editor, terminal, task feed, tests, diff, harness explanation, review verdict, cost,
- branch/PR flow — usable hosted and on a Node (T14) for local harnesses", and T16's dependency line is
+ branch/PR flow — usable hosted and on a Link (T14) for local harnesses", and T16's dependency line is
"T1 (Fabric offers), T2 (projects/grants), T5 (metering/Vibes), T17 (Actor for planning), T14 (Run/Node for
"T1 (Fabric offers), T2 (projects/grants), T5 (metering/Vibes), T17 (Actor for planning), T14 (Run/Node for
execution)". The plan does not use the phrase "forge model"; the repository/branch/PR authority it describes is
execution)". The plan does not use the phrase "forge model"; the repository/branch/PR authority it describes is
Forgejo/GitHub under T16, and the project/grant authority is T2.
Forgejo/GitHub under T16, and the project/grant authority is T2.
@@ -45,7 +45,7 @@ normaliser (`transcript.js`) into job lines (`joblines.js`), and a budget stop b
@@ -45,7 +45,7 @@ normaliser (`transcript.js`) into job lines (`joblines.js`), and a budget stop b
### `prompt.js` — the prompt every job gets
### `prompt.js` — the prompt every job gets
The git rule, the house rules, the lessons, the repo digest (`repomap.js`), then the brief. The lessons come from the
The git rule, the house rules, the lessons, the repo digest (`repomap.js`), then the brief. The lessons come from the
-shared Know store's own `recall()` (the portal's `know/lessons.js`; on a Node its path is configuration —
+shared Know store's own `recall()` (the portal's `know/lessons.js`; on a Link its path is configuration —
`CODES_KNOW_LESSONS` — never a hard-coded home). A store that cannot be read gives no lessons, not an error.
`CODES_KNOW_LESSONS` — never a hard-coded home). A store that cannot be read gives no lessons, not an error.
### `logins.js` — what this machine has
### `logins.js` — what this machine has
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
* What this machine has: each coding CLI, whether it is installed, whether it is signed in, and the limit windows it
* What this machine has: each coding CLI, whether it is installed, whether it is signed in, and the limit windows it
* can read.
* can read.
*
*
- * The plan logins are the owner's own files (the Node's worker copies the ones the owner names into the job's private
+ * The plan logins are the owner's own files (the Link's worker copies the ones the owner names into the job's private
* home, read-only, for that job only). This module reads them the way the portal's probes do:
* home, read-only, for that job only). This module reads them the way the portal's probes do:
*
*
* installed the CLI is on PATH
* installed the CLI is on PATH
@@ -24,7 +24,7 @@ function fragment(name) {
@@ -24,7 +24,7 @@ function fragment(name) {
/**
/**
* The lessons a job gets: what earlier jobs learned here, through the shared Know store's own `recall()`
* The lessons a job gets: what earlier jobs learned here, through the shared Know store's own `recall()`
- * (the portal's know/lessons.js; on a Node its path comes from the configuration, never a hard-coded home).
+ * (the portal's know/lessons.js; on a Link its path comes from the configuration, never a hard-coded home).
*
*
* The store is optional and off by default. A path that cannot be required, a module without recall(), or a recall
* The store is optional and off by default. A path that cannot be required, a module without recall(), or a recall
* that throws gives no lessons, never an error: a job must not fail because the memory is unreachable.
* that throws gives no lessons, never an error: a job must not fail because the memory is unreachable.
@@ -55,7 +55,7 @@ function lessons({ file, repo, brief, env = process.env } = {}) {
@@ -55,7 +55,7 @@ function lessons({ file, repo, brief, env = process.env } = {}) {
* @param {string} opts.brief what the task is
* @param {string} opts.brief what the task is
* @param {string} opts.dir the folder the job works in
* @param {string} opts.dir the folder the job works in
* @param {object} [opts.env] the environment (CODES_KNOW_LESSONS, or the older OPENVIBE_CODES_LESSONS)
* @param {object} [opts.env] the environment (CODES_KNOW_LESSONS, or the older OPENVIBE_CODES_LESSONS)
- * @param {string} [opts.know] the Know module's path (the Node's config wins over the environment)
+ * @param {string} [opts.know] the Know module's path (the Link's config wins over the environment)
* @returns {{text: string, parts: object[]}} the prompt and the parts it is made of
* @returns {{text: string, parts: object[]}} the prompt and the parts it is made of
*/
*/
function assemble({ brief, dir, env = process.env, know } = {}) {
function assemble({ brief, dir, env = process.env, know } = {}) {
@@ -1,14 +1,14 @@
@@ -1,14 +1,14 @@
'use strict';
'use strict';
/**
/**
- * `openvibe-codes job`: one coding task as an OpenVibe Run job on the person's own machine (plan T16; the Node's job
+ * `openvibe-codes job`: one coding task as an OpenVibe Run job on the person's own machine (plan T16; the Link's job
* worker runs it in its sandbox as artifact `[email protected]`, JOB_PROTOCOL below). The job's input is one JSON
* worker runs it in its sandbox as artifact `[email protected]`, JOB_PROTOCOL below). The job's input is one JSON
* line on stdin:
* line on stdin:
*
*
* { repo, ref?, prompt, agent?, model?, permission?: 'edit'|'full', push?: { branch?, pull_request? } }
* { repo, ref?, prompt, agent?, model?, permission?: 'edit'|'full', push?: { branch?, pull_request? } }
*
*
* It clones `repo` (https only) into a scratch directory, runs the harness there with hand-offs, and prints one compact
* It clones `repo` (https only) into a scratch directory, runs the harness there with hand-offs, and prints one compact
- * JSON line per event on stdout (what the page shows live). The result goes to fd 3 (the Node's result channel), or as
+ * JSON line per event on stdout (what the page shows live). The result goes to fd 3 (the Link's result channel), or as
* a last `{ t: 'result_doc' }` line when fd 3 is not open: what the agents said, the changed files and the patch, and,
* a last `{ t: 'result_doc' }` line when fd 3 is not open: what the agents said, the changed files and the patch, and,
* when `push` is asked and the machine holds GITHUB_TOKEN, the new branch it pushed (never the base branch) and its
* when `push` is asked and the machine holds GITHUB_TOKEN, the new branch it pushed (never the base branch) and its
* pull request. Nothing here ever prints a token.
* pull request. Nothing here ever prints a token.
@@ -22,7 +22,7 @@ const { collectProof } = require('../engine/proof');
@@ -22,7 +22,7 @@ const { collectProof } = require('../engine/proof');
const JOB_PROTOCOL = '1.0.0';
const JOB_PROTOCOL = '1.0.0';
/** The agents a conductor's turn may run on: every harness Codes offers. */
/** The agents a conductor's turn may run on: every harness Codes offers. */
// Every harness can lead a conductor; the three that resume a session and take the Codes MCP tools are
// Every harness can lead a conductor; the three that resume a session and take the Codes MCP tools are
-// the failover chain's own (server/domain/conductor-failover.js). The job's contract with the Node (stdin, stdout
+// the failover chain's own (server/domain/conductor-failover.js). The job's contract with the Link (stdin, stdout
// lines, fd 3) is unchanged by 1.1's lines.
// lines, fd 3) is unchanged by 1.1's lines.
const CONDUCTOR_AGENTS = ['claude-code', 'claude-other', 'codex', 'opencode', 'command-code', 'minimax-code', 'aider', 'deepseek', 'openvibe-ai', 'openai-compatible'];
const CONDUCTOR_AGENTS = ['claude-code', 'claude-other', 'codex', 'opencode', 'command-code', 'minimax-code', 'aider', 'deepseek', 'openvibe-ai', 'openai-compatible'];
const LINES = '1.1'; // 1.1: lines carry step ids, inputs, results, progress, helpers; the result keeps `log`
const LINES = '1.1'; // 1.1: lines carry step ids, inputs, results, progress, helpers; the result keeps `log`
@@ -115,7 +115,7 @@ function cloneProblem(message, repo) {
@@ -115,7 +115,7 @@ function cloneProblem(message, repo) {
if (!/could not read Username|Authentication failed|terminal prompts disabled|403|401|not found/i.test(m)) return m;
if (!/could not read Username|Authentication failed|terminal prompts disabled|403|401|not found/i.test(m)) return m;
let host = 'its host';
let host = 'its host';
try { host = new URL(String(repo)).host; } catch { /* keep the words */ }
try { host = new URL(String(repo)).host; } catch { /* keep the words */ }
- return `${m} — if the repository is private, give this machine's Codes runner a token for ${host} (GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https://you:token@${host}) in the Node's config.json env, then restart the Node`;
+ return `${m} — if the repository is private, give this machine's Codes runner a token for ${host} (GITHUB_TOKEN, GITLAB_TOKEN, or an OPENVIBE_GIT_CREDENTIALS line https://you:token@${host}) in the Link's config.json env, then restart the Node`;
}
}
function scrub(s) {
function scrub(s) {
@@ -734,7 +734,7 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tm
@@ -734,7 +734,7 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tm
prompt: agentPrompt(job), task: 'edit', agent: job.agent, model: job.model, effort: job.effort || null, cwd: work,
prompt: agentPrompt(job), task: 'edit', agent: job.agent, model: job.model, effort: job.effort || null, cwd: work,
permission: job.permission, handoff: !job.agent, maxAttempts: job.kind === 'conductor' ? 1 : 3,
permission: job.permission, handoff: !job.agent, maxAttempts: job.kind === 'conductor' ? 1 : 3,
// A job on the person's Node runs in its sandbox (own namespaces, a read-only root, no home folder, only
// A job on the person's Node runs in its sandbox (own namespaces, a read-only root, no home folder, only
- // what the job was given); the Node sets OPENVIBE_JOB_ID there. Only inside it may an agent with no
+ // what the job was given); the Link sets OPENVIBE_JOB_ID there. Only inside it may an agent with no
// edits-only mode take an "edit" task unattended (harness/index.js unfitFor).
// edits-only mode take an "edit" task unattended (harness/index.js unfitFor).
sandboxed: !!env.OPENVIBE_JOB_ID,
sandboxed: !!env.OPENVIBE_JOB_ID,
// A task carried on in the same conversation resumes its session (RESUME in the environment, or the job's
// A task carried on in the same conversation resumes its session (RESUME in the environment, or the job's
@@ -807,7 +807,7 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tm
@@ -807,7 +807,7 @@ async function runJob({ input, harness, emit, writeResult, env = process.env, tm
return result;
return result;
}
}
-/** The Node's result channel is fd 3; outside a job the result is the last stdout line. */
+/** The Link's result channel is fd 3; outside a job the result is the last stdout line. */
function resultWriter(emit) {
function resultWriter(emit) {
return (doc) => {
return (doc) => {
const text = JSON.stringify(doc);
const text = JSON.stringify(doc);
@@ -165,7 +165,7 @@ function load(env = process.env) {
@@ -165,7 +165,7 @@ function load(env = process.env) {
// The tokens one attempt may spend, when the task does not say (a run token's budget, Codes C3).
// The tokens one attempt may spend, when the task does not say (a run token's budget, Codes C3).
tokens: int(env.CODES_TASK_TOKENS, 0),
tokens: int(env.CODES_TASK_TOKENS, 0),
},
},
- // Know (the shared lessons store, Know K): on a Node this is the portal's know/lessons.js, never a hard-coded
+ // Know (the shared lessons store, Know K): on a Link this is the portal's know/lessons.js, never a hard-coded
// home. The path is configuration; a store that cannot be read gives no lessons, never an error.
// home. The path is configuration; a store that cannot be read gives no lessons, never an error.
know: { lessons: String(env.CODES_KNOW_LESSONS || env.OPENVIBE_CODES_LESSONS || '').trim() },
know: { lessons: String(env.CODES_KNOW_LESSONS || env.OPENVIBE_CODES_LESSONS || '').trim() },
// The task's timeline (stage C2): how many automatic continues one task may have before it needs a person.
// The task's timeline (stage C2): how many automatic continues one task may have before it needs a person.
@@ -547,7 +547,7 @@ function createConductor({ store, config = {}, log = console, run, attempts, tas
@@ -547,7 +547,7 @@ function createConductor({ store, config = {}, log = console, run, attempts, tas
const session = ran.session || (agent === (task.agent || PRIMARY_AGENT) && !turn.agent ? (attempt && attempt.session) : null) || null;
const session = ran.session || (agent === (task.agent || PRIMARY_AGENT) && !turn.agent ? (attempt && attempt.session) : null) || null;
let note = turn.switch_note || null;
let note = turn.switch_note || null;
if (agent !== asked) {
if (agent !== asked) {
- note = `${note ? `${note} ` : ''}Your machine ran ${failover.nameOf(agent)} instead of ${failover.nameOf(asked)}: its Codes runner is older than the switch. Update OpenVibe Node on it to let the conductor switch agents.`.slice(0, 1000);
+ note = `${note ? `${note} ` : ''}Your machine ran ${failover.nameOf(agent)} instead of ${failover.nameOf(asked)}: its Codes runner is older than the switch. Update OpenVibe Link on it to let the conductor switch agents.`.slice(0, 1000);
}
}
await db.exec(
await db.exec(
`UPDATE codes_conductor_messages SET state = $2, result = $3::jsonb, error = $4, session = $5, finished_at = $6::timestamptz,
`UPDATE codes_conductor_messages SET state = $2, result = $3::jsonb, error = $4, session = $5, finished_at = $6::timestamptz,
@@ -4,10 +4,10 @@
@@ -4,10 +4,10 @@
* The fleet (Codes C9c): what each of a person's machines says about its coding agents right now — installed, signed
* The fleet (Codes C9c): what each of a person's machines says about its coding agents right now — installed, signed
* in, and every limit window with when it frees.
* in, and every limit window with when it frees.
*
*
- * The machine reports it with its heartbeat to OpenVibe.Bot (OpenVibe.Node `logins`), and Bot answers it for the
+ * The machine reports it with its heartbeat to OpenVibe.Bot (OpenVibe Link `logins`), and Bot answers it for the
* machine's owner at GET /api/v1/machines with the person's own token. This module reads that answer, keeps the latest
* machine's owner at GET /api/v1/machines with the person's own token. This module reads that answer, keeps the latest
* per machine in codes_machine_fleet, and answers the Agents page, the router and the MCP fleet tool from it. It stores
* per machine in codes_machine_fleet, and answers the Agents page, the router and the MCP fleet tool from it. It stores
- * no key, no token and no account name: the Node reports labels, numbers and times only.
+ * no key, no token and no account name: the Link reports labels, numbers and times only.
*
*
* createFleet({ config, store, fetchImpl, log, now })
* createFleet({ config, store, fetchImpl, log, now })
* refresh(subject, token) read Bot now and replace the stored rows → { ok, machines } or { ok: false, problem }
* refresh(subject, token) read Bot now and replace the stored rows → { ok, machines } or { ok: false, problem }
@@ -121,7 +121,7 @@ function createFleet({ config = {}, store, fetchImpl = globalThis.fetch, log = c
@@ -121,7 +121,7 @@ function createFleet({ config = {}, store, fetchImpl = globalThis.fetch, log = c
/**
/**
* Read Bot now and replace what Codes holds for each machine whose answer names a fleet. A machine that is gone from
* Read Bot now and replace what Codes holds for each machine whose answer names a fleet. A machine that is gone from
- * the answer is gone here too; a machine that answers no fleet keeps its last reading (the Node may be older).
+ * the answer is gone here too; a machine that answers no fleet keeps its last reading (the Link may be older).
*/
*/
async function refresh(subject, token) {
async function refresh(subject, token) {
if (!USER.test(String(subject || '')) || !token) return { ok: false, problem: { status: 401, detail: 'sign in again to see your machines' } };
if (!USER.test(String(subject || '')) || !token) return { ok: false, problem: { status: 401, detail: 'sign in again to see your machines' } };
@@ -3,7 +3,7 @@
@@ -3,7 +3,7 @@
/**
/**
* A person's machines, as OpenVibe.Network knows them (Codes C4b).
* A person's machines, as OpenVibe.Network knows them (Codes C4b).
*
*
- * A machine is paired by redeeming a one-time code on OpenVibe.Network (docs: OpenVibe.Node README and
+ * A machine is paired by redeeming a one-time code on OpenVibe.Network (docs: OpenVibe Link README and
* internal/link/network.go): openvibe.bot/machines mints `pair_…` with a code that lasts ten minutes, and the machine
* internal/link/network.go): openvibe.bot/machines mints `pair_…` with a code that lasts ten minutes, and the machine
* redeems it -> `openvibe-node pair --network <URL> --pairing pair_… --code ABCD-1234`. The result is a node principal
* redeems it -> `openvibe-node pair --network <URL> --pairing pair_… --code ABCD-1234`. The result is a node principal
* (`nod_…`) owned by the person; that principal, its status and when it was last seen are what this module reads.
* (`nod_…`) owned by the person; that principal, its status and when it was last seen are what this module reads.
@@ -287,7 +287,7 @@ function createSkills({ store, sharing, log = console }) {
@@ -287,7 +287,7 @@ function createSkills({ store, sharing, log = console }) {
await db.exec('DELETE FROM codes_skill_assignments WHERE subject = $1 AND project = $2 AND skill_id = $3', [subject, scope, id]);
await db.exec('DELETE FROM codes_skill_assignments WHERE subject = $1 AND project = $2 AND skill_id = $3', [subject, scope, id]);
}
}
- /** What the Node syncs: the person's picks, plus this project's picks, at their newest versions. */
+ /** What the Link syncs: the person's picks, plus this project's picks, at their newest versions. */
async function manifest({ subject, project = '' }) {
async function manifest({ subject, project = '' }) {
const scope = String(project || '');
const scope = String(project || '');
const rows = await db.many(`SELECT ${COLUMNS} ${FROM}
const rows = await db.many(`SELECT ${COLUMNS} ${FROM}
@@ -47,7 +47,7 @@ const BUDGETS = {
@@ -47,7 +47,7 @@ const BUDGETS = {
// The skills hub (Compute plan §5): save a version or share a record now and then; a script editing a library
// The skills hub (Compute plan §5): save a version or share a record now and then; a script editing a library
// may do a few a minute, not a flood.
// may do a few a minute, not a flood.
'codes.skill.write': { minute: 20, hour: 300 },
'codes.skill.write': { minute: 20, hour: 300 },
- // Adding a skill to the sync is how the Node is set up; the first run adds several, then almost never.
+ // Adding a skill to the sync is how the Link is set up; the first run adds several, then almost never.
'codes.skill.assign': { minute: 30, hour: 600 },
'codes.skill.assign': { minute: 30, hour: 600 },
};
};
@@ -7,7 +7,7 @@
@@ -7,7 +7,7 @@
* / the composer with the person's saved repositories and machines (last used first), running work
* / the composer with the person's saved repositories and machines (last used first), running work
* /tasks, /tasks/:id the person's tasks; a task's Live, Changes, Proof and Pull request panels are Codes' own
* /tasks, /tasks/:id the person's tasks; a task's Live, Changes, Proof and Pull request panels are Codes' own
* /agents the person's connections: their provider keys (they live in OpenVibe.AI), added, tested, removed
* /agents the person's connections: their provider keys (they live in OpenVibe.AI), added, tested, removed
- * /machines their paired Node machines, their state, and Node's pairing flow with a one-time code
+ * /machines their paired Link machines, their state, and Node's pairing flow with a one-time code
* /usage what Codes knows: runs, minutes and tokens per day (no prices: Billing is Billing's)
* /usage what Codes knows: runs, minutes and tokens per day (no prices: Billing is Billing's)
* /memory what the person's own assistants know about them (the notes live in OpenVibe.AI's store)
* /memory what the person's own assistants know about them (the notes live in OpenVibe.AI's store)
* /settings their choices, remembered per person (codes_user_settings)
* /settings their choices, remembered per person (codes_user_settings)
@@ -753,7 +753,7 @@ ${before ? `<h3 class="c4-subhead">Saved in Codes before</h3><ul class="c4-rows"
@@ -753,7 +753,7 @@ ${before ? `<h3 class="c4-subhead">Saved in Codes before</h3><ul class="c4-rows"
<p class="c4-sub">One command, one code</p>
<p class="c4-sub">One command, one code</p>
<ol class="c4-steps">
<ol class="c4-steps">
<li><b>Get a one-time code</b> on <a href="https://openvibe.bot/machines">openvibe.bot/machines</a>. It lasts ten minutes and works once.</li>
<li><b>Get a one-time code</b> on <a href="https://openvibe.bot/machines">openvibe.bot/machines</a>. It lasts ten minutes and works once.</li>
-<li><b>Run the installer on the machine</b>, with the code it gives you: <code class="c4-code-inline">curl -fsSL https://node.openvibe.network/install | sh -s -- --worker</code></li>
+<li><b>Run the installer on the machine</b>, with the code it gives you: <code class="c4-code-inline">curl -fsSL https://openvibe.link/install | sh -s -- --worker</code></li>
<li><b>It pairs itself</b>, then appears here as Online. A machine runs your jobs and nobody else's.</li>
<li><b>It pairs itself</b>, then appears here as Online. A machine runs your jobs and nobody else's.</li>
</ol>
</ol>
<form class="c4-form c4-form-inline" method="get" action="/machines">
<form class="c4-form c4-form-inline" method="get" action="/machines">
@@ -761,7 +761,7 @@ ${before ? `<h3 class="c4-subhead">Saved in Codes before</h3><ul class="c4-rows"
@@ -761,7 +761,7 @@ ${before ? `<h3 class="c4-subhead">Saved in Codes before</h3><ul class="c4-rows"
<button class="c4-button" type="submit">Show the command</button>
<button class="c4-button" type="submit">Show the command</button>
</form>
</form>
${badCode ? note('a pairing code is 8 letters and digits, like ABCD-1234', 'bad') : ''}
${badCode ? note('a pairing code is 8 letters and digits, like ABCD-1234', 'bad') : ''}
-${code ? `<div class="c4-pair-command"><p class="c4-kv"><span>On the machine</span><code class="c4-code-inline">sudo openvibe-node pair ${esc(code)} --network https://openvibe.network</code></p><p class="c4-kv"><span>Or with the installer</span><code class="c4-code-inline">curl -fsSL https://node.openvibe.network/install | sh -s -- --network https://openvibe.network --pairing <span class="c4-note-inline">pair_… from openvibe.bot</span> --code ${esc(code)} --worker</code></p><p class="c4-hint">The code is the one openvibe.bot gave you for this machine; Codes never mints it. Reload this page once the installer says it is paired.</p></div>` : ''}
+${code ? `<div class="c4-pair-command"><p class="c4-kv"><span>On the machine</span><code class="c4-code-inline">sudo openvibe-node pair ${esc(code)} --network https://openvibe.network</code></p><p class="c4-kv"><span>Or with the installer</span><code class="c4-code-inline">curl -fsSL https://openvibe.link/install | sh -s -- --network https://openvibe.network --pairing <span class="c4-note-inline">pair_… from openvibe.bot</span> --code ${esc(code)} --worker</code></p><p class="c4-hint">The code is the one openvibe.bot gave you for this machine; Codes never mints it. Reload this page once the installer says it is paired.</p></div>` : ''}
</section>`;
</section>`;
const content = `${header('Machines', 'Your own computers, paired with your account.')}${viewer(req) ? `${flash(req)}<section class="c4-card"><div class="c4-card-head"><h2>Your machines</h2></div><p class="c4-sub">${(mine.machines || []).length} paired</p><p class="c4-hint"><a href="https://openvibe.network/machines">All your machines are on openvibe.network/machines</a>.</p>${mine.ok ? (list ? `<ul class="c4-rows">${list}</ul>` : '<div class="c4-emptyline">No paired computers yet. Pair one below and it shows up here.</div>') : note(`Your machines could not be read. ${esc((mine.problem && mine.problem.detail) || 'Sign in again.')}`, 'bad')}</section>${pairCard}` : signIn(req)}`;
const content = `${header('Machines', 'Your own computers, paired with your account.')}${viewer(req) ? `${flash(req)}<section class="c4-card"><div class="c4-card-head"><h2>Your machines</h2></div><p class="c4-sub">${(mine.machines || []).length} paired</p><p class="c4-hint"><a href="https://openvibe.network/machines">All your machines are on openvibe.network/machines</a>.</p>${mine.ok ? (list ? `<ul class="c4-rows">${list}</ul>` : '<div class="c4-emptyline">No paired computers yet. Pair one below and it shows up here.</div>') : note(`Your machines could not be read. ${esc((mine.problem && mine.problem.detail) || 'Sign in again.')}`, 'bad')}</section>${pairCard}` : signIn(req)}`;
page(req, res, 'Machines', wrap(content));
page(req, res, 'Machines', wrap(content));
@@ -826,7 +826,7 @@ ${report.days.length ? `<div class="table-wrap" tabindex="0" role="region" aria-
@@ -826,7 +826,7 @@ ${report.days.length ? `<div class="table-wrap" tabindex="0" role="region" aria-
<p class="c4-hint">When your conductor's agent nears a usage limit (its 5-hour or weekly window), the next turn runs on the first fallback below that has room, and returns once the window frees. Each agent keeps its own session; the one taking over is told what happened while it was away.</p>
<p class="c4-hint">When your conductor's agent nears a usage limit (its 5-hour or weekly window), the next turn runs on the first fallback below that has room, and returns once the window frees. Each agent keeps its own session; the one taking over is told what happened while it was away.</p>
<div class="c4-fallbacks">${[0, 1, 2].map(fallbackRow).join('')}</div>
<div class="c4-fallbacks">${[0, 1, 2].map(fallbackRow).join('')}</div>
<div class="c4-form-grid"><label>Switch at <select name="conductor_switch_pct">${[70, 80, 85, 90, 95].map((v) => `<option value="${v}"${switchPct === v ? ' selected' : ''}>${v}% of a window</option>`).join('')}</select></label></div>
<div class="c4-form-grid"><label>Switch at <select name="conductor_switch_pct">${[70, 80, 85, 90, 95].map((v) => `<option value="${v}"${switchPct === v ? ' selected' : ''}>${v}% of a window</option>`).join('')}</select></label></div>
-<p class="c4-hint">Codex and OpenCode have no approval prompt: on "Ask first" they work with edits in the folder only. Your machine needs OpenVibe Node with Codes 0.7 or newer to switch.</p>` : '';
+<p class="c4-hint">Codex and OpenCode have no approval prompt: on "Ask first" they work with edits in the folder only. Your machine needs OpenVibe Link with Codes 0.7 or newer to switch.</p>` : '';
const content = `${header('Settings', 'Your choices, remembered for your account.')}${viewer(req) ? `${flash(req)}<p class="c4-hint c4-setup-link">New here, or something not working? <a href="/setup">The guided setup</a> checks your machine, agents and keys step by step.</p><section class="c4-card"><form class="c4-form" method="post" action="/settings">
const content = `${header('Settings', 'Your choices, remembered for your account.')}${viewer(req) ? `${flash(req)}<p class="c4-hint c4-setup-link">New here, or something not working? <a href="/setup">The guided setup</a> checks your machine, agents and keys step by step.</p><section class="c4-card"><form class="c4-form" method="post" action="/settings">
<div class="c4-form-grid">
<div class="c4-form-grid">
<label>Default agent <select name="default_agent"><option value="">Auto</option>${agentOptions}</select></label>
<label>Default agent <select name="default_agent"><option value="">Auto</option>${agentOptions}</select></label>
@@ -30,7 +30,7 @@ const express = require('express');
@@ -30,7 +30,7 @@ const express = require('express');
const { asyncRouter } = require('./router');
const { asyncRouter } = require('./router');
const { esc } = require('../render/html');
const { esc } = require('../render/html');
-/** The adapter ids the Node reads and the services that write, and the names a person knows them by. */
+/** The adapter ids the Link reads and the services that write, and the names a person knows them by. */
const SOURCE_NAMES = {
const SOURCE_NAMES = {
'claude-code': 'Claude Code', agents: 'AGENTS.md', gemini: 'Gemini CLI', cursor: 'Cursor',
'claude-code': 'Claude Code', agents: 'AGENTS.md', gemini: 'Gemini CLI', cursor: 'Cursor',
copilot: 'GitHub Copilot', windsurf: 'Windsurf', cline: 'Cline', roo: 'Roo Code', kiro: 'Kiro',
copilot: 'GitHub Copilot', windsurf: 'Windsurf', cline: 'Cline', roo: 'Roo Code', kiro: 'Kiro',
@@ -314,7 +314,7 @@ function createMemoryRoutes(ctx, kit) {
@@ -314,7 +314,7 @@ function createMemoryRoutes(ctx, kit) {
if (!rows.length) {
if (!rows.length) {
return head + '<div class="c4-emptyline">Nothing here yet. On your computer, run '
return head + '<div class="c4-emptyline">Nothing here yet. On your computer, run '
+ '<code class="c4-code-inline">openvibe-node memory upload</code>: it shows what it found and asks before sending anything. '
+ '<code class="c4-code-inline">openvibe-node memory upload</code>: it shows what it found and asks before sending anything. '
- + '<a href="https://github.com/OpenVibers/OpenVibe.Node/blob/main/docs/memory.md#the-upload">The Node docs</a></div></section>';
+ + '<a href="https://github.com/OpenVibers/OpenVibe.Node/blob/main/docs/memory.md#the-upload">The Link docs</a></div></section>';
}
}
const sourceRow = (row) => {
const sourceRow = (row) => {
const fresh = row.seen && Date.now() - row.seen < SEVEN_DAYS;
const fresh = row.seen && Date.now() - row.seen < SEVEN_DAYS;
@@ -104,7 +104,7 @@ Added test/date.test.js: three cases, all pass.
@@ -104,7 +104,7 @@ Added test/date.test.js: three cases, all pass.
const statusTable = table(['Piece', 'State'], [
const statusTable = table(['Piece', 'State'], [
[html`<a href="/start">The harness on your machine</a> (<code>openvibe-codes</code>)`, `Live, ${VERSION}: runs a task on any of the eight agents with your CLIs and keys, streams one event format, keeps every run to show or resume.`],
[html`<a href="/start">The harness on your machine</a> (<code>openvibe-codes</code>)`, `Live, ${VERSION}: runs a task on any of the eight agents with your CLIs and keys, streams one event format, keeps every run to show or resume.`],
['Hand-offs between agents', 'Live: a failed, rate-limited or silent agent hands the task to the next one with a note on what was done.'],
['Hand-offs between agents', 'Live: a failed, rate-limited or silent agent hands the task to the next one with a note on what was done.'],
- [html`<a href="/run">Run on your machine</a>: start a task in any browser, your phone included; your own computer runs it`, 'Live: add your machine on openvibe.bot/machines (one command), then watch the task live here and get the patch, a branch or a pull request. It runs only on your own machines, in the Node\'s job sandbox.'],
+ [html`<a href="/run">Run on your machine</a>: start a task in any browser, your phone included; your own computer runs it`, 'Live: add your machine on openvibe.bot/machines (one command), then watch the task live here and get the patch, a branch or a pull request. It runs only on your own machines, in Link\'s job sandbox.'],
[html`<a href="/harnesses">The agent catalog</a>`, 'Live: every harness and model with its capabilities, prices and limits.'],
[html`<a href="/harnesses">The agent catalog</a>`, 'Live: every harness and model with its capabilities, prices and limits.'],
[html`<code>GET /api/v1/harnesses</code>, <code>POST /api/v1/harnesses/route</code>`, 'Live: public, no key needed; per-address and per-caller limits.'],
[html`<code>GET /api/v1/harnesses</code>, <code>POST /api/v1/harnesses/route</code>`, 'Live: public, no key needed; per-address and per-caller limits.'],
[html`<a href="/improve">Improve OpenVibe</a>: a repository, a change on a branch with its test, a pull request that CI checks and a maintainer reviews`, 'Live: pick a repository, describe the change, and start a guided run on your machine.'],
[html`<a href="/improve">Improve OpenVibe</a>: a repository, a change on a branch with its test, a pull request that CI checks and a maintainer reviews`, 'Live: pick a repository, describe the change, and start a guided run on your machine.'],
@@ -139,7 +139,7 @@ ${error ? html`<p class="run-notice run-notice-error" role="alert">${error}</p>`
@@ -139,7 +139,7 @@ ${error ? html`<p class="run-notice run-notice-error" role="alert">${error}</p>`
title: 'Run on your machine', index: true,
title: 'Run on your machine', index: true,
body: html`<h1>Run a coding task on your own machine</h1>
body: html`<h1>Run a coding task on your own machine</h1>
<p class="lead">Start a task from any browser, your phone included, and your own computer does the work: the agents and keys you already have, in a sandbox, with nothing to host. You watch it live here and get the changes back as a patch, a branch or a pull request.</p>
<p class="lead">Start a task from any browser, your phone included, and your own computer does the work: the agents and keys you already have, in a sandbox, with nothing to host. You watch it live here and get the changes back as a patch, a branch or a pull request.</p>
-<ol class="run-steps"><li>Add your computer on <a href="${MACHINES_URL}">openvibe.bot/machines</a>: one command, which installs OpenVibe Node with its job worker.</li><li>Sign in here and describe the task.</li><li>Your machine clones the repository, runs the agent and sends the result back.</li></ol>
+<ol class="run-steps"><li>Add your computer on <a href="${MACHINES_URL}">openvibe.bot/machines</a>: one command, which installs OpenVibe Link with its job worker.</li><li>Sign in here and describe the task.</li><li>Your machine clones the repository, runs the agent and sends the result back.</li></ol>
<p><a class="run-btn run-btn-primary" href="/auth/login?next=%2Frun">Sign in to start</a></p>`,
<p><a class="run-btn run-btn-primary" href="/auth/login?next=%2Frun">Sign in to start</a></p>`,
});
});
}
}
@@ -84,7 +84,7 @@ function createSetupRoutes(ctx, kit) {
@@ -84,7 +84,7 @@ function createSetupRoutes(ctx, kit) {
const problem = d.machines.ok === false && d.machines.problem ? note(esc(d.machines.problem.detail || 'Your machines could not be read.'), 'bad') : '';
const problem = d.machines.ok === false && d.machines.problem ? note(esc(d.machines.problem.detail || 'Your machines could not be read.'), 'bad') : '';
const how = `<ol class="c4-setup-how">
const how = `<ol class="c4-setup-how">
<li><b>Get a one-time code</b> on <a href="https://openvibe.bot/machines" rel="noopener">openvibe.bot/machines</a>. It lasts ten minutes.</li>
<li><b>Get a one-time code</b> on <a href="https://openvibe.bot/machines" rel="noopener">openvibe.bot/machines</a>. It lasts ten minutes.</li>
-<li><b>Run the installer on that computer</b> and give it the code:${command('curl -fsSL https://node.openvibe.network/install | sh -s -- --worker', 'On the machine')}</li>
+<li><b>Run the installer on that computer</b> and give it the code:${command('curl -fsSL https://openvibe.link/install | sh -s -- --worker', 'On the machine')}</li>
<li><b>It pairs itself</b> and shows up here as Online. It runs your work and nobody else's.</li></ol>`;
<li><b>It pairs itself</b> and shows up here as Online. It runs your work and nobody else's.</li></ol>`;
return `${problem}${rows ? `<ul class="c4-rows">${rows}</ul>` : ''}${list.length ? `<details class="c4-setup-more"><summary>Pair another machine</summary>${how}</details>` : how}
return `${problem}${rows ? `<ul class="c4-rows">${rows}</ul>` : ''}${list.length ? `<details class="c4-setup-more"><summary>Pair another machine</summary>${how}</details>` : how}
<div class="c4-setup-actions"><a class="c4-button${list.length ? ' c4-button-light' : ''}" href="/machines#pair">${list.length ? 'Manage machines' : 'I have a code'}</a><a class="c4-opill" href="/setup#step-machine">Check again</a></div>`;
<div class="c4-setup-actions"><a class="c4-button${list.length ? ' c4-button-light' : ''}" href="/machines#pair">${list.length ? 'Manage machines' : 'I have a code'}</a><a class="c4-opill" href="/setup#step-machine">Check again</a></div>`;
@@ -18,7 +18,7 @@
@@ -18,7 +18,7 @@
* POST /api/v1/me/skills/:id add a record to the sync
* POST /api/v1/me/skills/:id add a record to the sync
* POST /api/v1/me/skills/:id/remove remove it
* POST /api/v1/me/skills/:id/remove remove it
*
*
- * The Node sync authenticates with the person's access token (Authorization: Bearer), which the same SSO middleware
+ * The Link sync authenticates with the person's access token (Authorization: Bearer), which the same SSO middleware
* verifies as a browser session. Only public records are in the gallery; the others answer 404 to everyone who is
* verifies as a browser session. Only public records are in the gallery; the others answer 404 to everyone who is
* not allowed to read them.
* not allowed to read them.
*/
*/
@@ -127,7 +127,7 @@ ${table(['Version', 'When', 'Audience', 'Note'], versions.map((v) => [
@@ -127,7 +127,7 @@ ${table(['Version', 'When', 'Audience', 'Note'], versions.map((v) => [
v.audience,
v.audience,
v.note || '—',
v.note || '—',
]))}
]))}
-<p class="muted small">A rollback copies the chosen version into a new one, so history is never rewritten. The Node records the version it wrote.</p></section>
+<p class="muted small">A rollback copies the chosen version into a new one, so history is never rewritten. The Link records the version it wrote.</p></section>
${controls}`,
${controls}`,
}, status);
}, status);
}
}
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
* - Home's saved pickers: the repositories a person used here, last used first, and their paired machines
* - Home's saved pickers: the repositories a person used here, last used first, and their paired machines
* - a task's Changes, Proof and Pull request panels are Codes' own, from run_tasks and codes_task_proof
* - a task's Changes, Proof and Pull request panels are Codes' own, from run_tasks and codes_task_proof
* - Agents: the person's keys, added, tested and removed from the page (the key itself never comes back)
* - Agents: the person's keys, added, tested and removed from the page (the key itself never comes back)
- * - Machines: the person's paired Node machines, their state, and Node's pairing flow with a one-time code
+ * - Machines: the person's paired Link machines, their state, and Node's pairing flow with a one-time code
* - Settings: remembered per person
* - Settings: remembered per person
* - Usage: runs, minutes and tokens per day, and no prices Codes does not know
* - Usage: runs, minutes and tokens per day, and no prices Codes does not know
*/
*/
@@ -39,7 +39,7 @@ const RESET_MONTH = new Date(Date.now() + 2.5 * 86400000).toISOString();
@@ -39,7 +39,7 @@ const RESET_MONTH = new Date(Date.now() + 2.5 * 86400000).toISOString();
/** The fleet Ada's machines reported with their last heartbeats. */
/** The fleet Ada's machines reported with their last heartbeats. */
const window = (name, pct, resetsAt) => JSON.stringify([{ name, pct, resets_at: resetsAt }]);
const window = (name, pct, resetsAt) => JSON.stringify([{ name, pct, resets_at: resetsAt }]);
// The machine, as OpenVibe.Bot answers it: the device id Run pins a job to, the Network id the machine is known
// The machine, as OpenVibe.Bot answers it: the device id Run pins a job to, the Network id the machine is known
- // by, and the fleet its Node last reported with its heartbeat.
+ // by, and the fleet its Link last reported with its heartbeat.
t.bot.addMachine({
t.bot.addMachine({
id: 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2N1', node_id: NODE, name: 'ada-laptop', online: true, fleet_at: new Date().toISOString(),
id: 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2N1', node_id: NODE, name: 'ada-laptop', online: true, fleet_at: new Date().toISOString(),
fleet: [
fleet: [
@@ -185,7 +185,7 @@ const HOUR = 3600_000;
@@ -185,7 +185,7 @@ const HOUR = 3600_000;
await endTurn(taskId, { agent: 'claude-code', session: 'sess_claude_2' });
await endTurn(taskId, { agent: 'claude-code', session: 'sess_claude_2' });
const m = (await messagesOf(taskId))[2];
const m = (await messagesOf(taskId))[2];
assert.strictEqual(m.agent, 'claude-code');
assert.strictEqual(m.agent, 'claude-code');
- assert.match(m.switch_note, /Update OpenVibe Node/);
+ assert.match(m.switch_note, /Update OpenVibe Link/);
report(10, 30);
report(10, 30);
await refresh();
await refresh();
await post(`/api/v1/conductor/${taskId}/messages`, { text: 'status?' });
await post(`/api/v1/conductor/${taskId}/messages`, { text: 'status?' });
@@ -294,7 +294,7 @@ function dry(adapter, ctx = {}) {
@@ -294,7 +294,7 @@ function dry(adapter, ctx = {}) {
// A job that works in one repository gets that repository's digest; a folder that holds many gets none.
// A job that works in one repository gets that repository's digest; a folder that holds many gets none.
require('node:child_process').execFileSync('git', ['init', '-q'], { cwd: dir });
require('node:child_process').execFileSync('git', ['init', '-q'], { cwd: dir });
fs.writeFileSync(path.join(dir, 'README.md'), '# Demo\n\nA demo repository.\n');
fs.writeFileSync(path.join(dir, 'README.md'), '# Demo\n\nA demo repository.\n');
- // On a Node the lessons come from the Know store's module (know/lessons.js), its path from the config.
+ // On a Link the lessons come from the Know store's module (know/lessons.js), its path from the config.
const knowFile = path.join(dir, 'know.js');
const knowFile = path.join(dir, 'know.js');
fs.writeFileSync(knowFile, "module.exports = { recall: () => '## What earlier jobs learned here (dated)\\n- 2026-10-30 · demo — the tests need the database' };\n");
fs.writeFileSync(knowFile, "module.exports = { recall: () => '## What earlier jobs learned here (dated)\\n- 2026-10-30 · demo — the tests need the database' };\n");
const built = assemble({ brief: 'fix the test', dir, env: { CODES_KNOW_LESSONS: knowFile } });
const built = assemble({ brief: 'fix the test', dir, env: { CODES_KNOW_LESSONS: knowFile } });
@@ -304,7 +304,7 @@ function dry(adapter, ctx = {}) {
@@ -304,7 +304,7 @@ function dry(adapter, ctx = {}) {
assert.ok(built.text.indexOf('House rules') < built.text.indexOf('learned'));
assert.ok(built.text.indexOf('House rules') < built.text.indexOf('learned'));
assert.ok(built.text.indexOf('learned') < built.text.indexOf('demo repository'));
assert.ok(built.text.indexOf('learned') < built.text.indexOf('demo repository'));
assert.ok(built.text.trimEnd().endsWith('fix the test'), 'the brief is last');
assert.ok(built.text.trimEnd().endsWith('fix the test'), 'the brief is last');
- // The Node's config path wins over the environment, and a store that cannot be read is skipped, not an error.
+ // The Link's config path wins over the environment, and a store that cannot be read is skipped, not an error.
const fromConfig = assemble({ brief: 'x', dir, know: knowFile, env: { CODES_KNOW_LESSONS: path.join(dir, 'nope.js') } });
const fromConfig = assemble({ brief: 'x', dir, know: knowFile, env: { CODES_KNOW_LESSONS: path.join(dir, 'nope.js') } });
assert.ok(fromConfig.parts.some((p) => p.name === 'lessons'), 'the config path is read');
assert.ok(fromConfig.parts.some((p) => p.name === 'lessons'), 'the config path is read');
const none = assemble({ brief: 'x', dir, env: { CODES_KNOW_LESSONS: path.join(dir, 'nope.js') } });
const none = assemble({ brief: 'x', dir, env: { CODES_KNOW_LESSONS: path.join(dir, 'nope.js') } });
@@ -32,7 +32,7 @@ const { assemble, lessons, fragment } = require('../engine/prompt');
@@ -32,7 +32,7 @@ const { assemble, lessons, fragment } = require('../engine/prompt');
assert.ok(got.trimEnd().endsWith('the database'), 'and its lesson is kept');
assert.ok(got.trimEnd().endsWith('the database'), 'and its lesson is kept');
assert.deepStrictEqual(JSON.parse(fs.readFileSync(path.join(dir, 'seen.json'), 'utf8')),
assert.deepStrictEqual(JSON.parse(fs.readFileSync(path.join(dir, 'seen.json'), 'utf8')),
{ brief: 'fix the database test', repo: 'demo', k: 5 }, 'the store gets the brief, the repo and the cap');
{ brief: 'fix the database test', repo: 'demo', k: 5 }, 'the store gets the brief, the repo and the cap');
- // The environment is how a Node passes the config path when the caller does not.
+ // The environment is how a Link passes the config path when the caller does not.
assert.equal(lessons({ brief: 'x', env: { CODES_KNOW_LESSONS: file } }), got);
assert.equal(lessons({ brief: 'x', env: { CODES_KNOW_LESSONS: file } }), got);
fs.rmSync(dir, { recursive: true, force: true });
fs.rmSync(dir, { recursive: true, force: true });
});
});
@@ -43,7 +43,7 @@ const results = (events) => events.filter((e) => e.type === 'result');
@@ -43,7 +43,7 @@ const results = (events) => events.filter((e) => e.type === 'result');
for (const [agent, word] of [['claude-code', 'claude'], ['codex', 'codex'], ['opencode', 'opencode'], ['command-code', 'cmd']]) {
for (const [agent, word] of [['claude-code', 'claude'], ['codex', 'codex'], ['opencode', 'opencode'], ['command-code', 'cmd']]) {
await check(`${agent}: the agent's output becomes the shared events, with one result`, async () => {
await check(`${agent}: the agent's output becomes the shared events, with one result`, async () => {
- // OpenCode and Command Code take an "edit" run only inside a sandbox (test/harness-sandbox.test.js): run as a Node job.
+ // OpenCode and Command Code take an "edit" run only inside a sandbox (test/harness-sandbox.test.js): run as a Link job.
const events = await collect(harness().run({ prompt: 'fix a.js', agent, cwd: f.work, handoff: false, sandboxed: true }));
const events = await collect(harness().run({ prompt: 'fix a.js', agent, cwd: f.work, handoff: false, sandboxed: true }));
assert.strictEqual(events[0].type, 'system');
assert.strictEqual(events[0].type, 'system');
assert.strictEqual(events[0].subtype, 'init');
assert.strictEqual(events[0].subtype, 'init');
@@ -42,7 +42,7 @@ const collect = async (run) => { const out = []; for await (const e of run) out.
@@ -42,7 +42,7 @@ const collect = async (run) => { const out = []; for await (const e of run) out.
assert.strictEqual(cli.opencode.editsOnly, false);
assert.strictEqual(cli.opencode.editsOnly, false);
assert.strictEqual(cli.commandCode.editsOnly, false);
assert.strictEqual(cli.commandCode.editsOnly, false);
assert.ok(!(await argvOf(cli.opencode, { permission: 'edit' })).includes('--auto'), 'edit on the person’s computer: no --auto');
assert.ok(!(await argvOf(cli.opencode, { permission: 'edit' })).includes('--auto'), 'edit on the person’s computer: no --auto');
- assert.ok((await argvOf(cli.opencode, { permission: 'edit', sandboxed: true })).includes('--auto'), 'edit in a Node sandbox: --auto');
+ assert.ok((await argvOf(cli.opencode, { permission: 'edit', sandboxed: true })).includes('--auto'), 'edit in a Link sandbox: --auto');
assert.ok((await argvOf(cli.opencode, { permission: 'full' })).includes('--auto'), 'full: --auto');
assert.ok((await argvOf(cli.opencode, { permission: 'full' })).includes('--auto'), 'full: --auto');
assert.ok(!(await argvOf(cli.opencode, { permission: 'read', sandboxed: true })).includes('--auto'), 'read: never');
assert.ok(!(await argvOf(cli.opencode, { permission: 'read', sandboxed: true })).includes('--auto'), 'read: never');
assert.ok(!(await argvOf(cli.commandCode, { permission: 'edit' })).includes('--yolo'), 'Command Code: no --yolo for edit outside a sandbox');
assert.ok(!(await argvOf(cli.commandCode, { permission: 'edit' })).includes('--yolo'), 'Command Code: no --yolo for edit outside a sandbox');
@@ -179,7 +179,7 @@ async function fakeGitHub() {
@@ -179,7 +179,7 @@ async function fakeGitHub() {
assert.strictEqual(typeof a.installed, 'boolean');
assert.strictEqual(typeof a.installed, 'boolean');
assert.ok(Array.isArray(a.windows));
assert.ok(Array.isArray(a.windows));
}
}
- // Codes answers with every machine's stored report: this machine is in Bot's list, but its Node has not
+ // Codes answers with every machine's stored report: this machine is in Bot's list, but its Link has not
// reported a fleet yet, so there is nothing stored and nothing is invented.
// reported a fleet yet, so there is nothing stored and nothing is invented.
assert.ok(Array.isArray(out.machines), 'the stored fleet of every machine');
assert.ok(Array.isArray(out.machines), 'the stored fleet of every machine');
assert.strictEqual(out.problem, undefined, 'Bot answered, so there is no problem beside the fleet');
assert.strictEqual(out.problem, undefined, 'Bot answered, so there is no problem beside the fleet');
@@ -35,7 +35,7 @@ const DEVICE = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2S1';
@@ -35,7 +35,7 @@ const DEVICE = 'dev_01J8Z3Q4R5S6T7V8W9X0Y1Z2S1';
for (const id of ['machine', 'agents', 'keys', 'accounts', 'choices']) assert.match(res.text, new RegExp(`id="step-${id}"`));
for (const id of ['machine', 'agents', 'keys', 'accounts', 'choices']) assert.match(res.text, new RegExp(`id="step-${id}"`));
assert.match(res.text, /0 of 5 done/);
assert.match(res.text, /0 of 5 done/);
assert.match(res.text, /class="c4-card c4-setup-step is-next" id="step-machine"/, 'the machine is next');
assert.match(res.text, /class="c4-card c4-setup-step is-next" id="step-machine"/, 'the machine is next');
- assert.match(res.text, /curl -fsSL https:\/\/node\.openvibe\.network\/install \| sh -s -- --worker/);
+ assert.match(res.text, /curl -fsSL https:\/\/openvibe\.link\/install \| sh -s -- --worker/);
assert.match(res.text, /npm install -g @openai\/codex/, 'an agent that is not there says how to install it');
assert.match(res.text, /npm install -g @openai\/codex/, 'an agent that is not there says how to install it');
assert.match(res.text, /href="\/css\/setup\.css|css\/setup\.css/, 'the page brings its own stylesheet');
assert.match(res.text, /href="\/css\/setup\.css|css\/setup\.css/, 'the page brings its own stylesheet');
assert.match(res.text, /js\/setup\.js/, 'and its own script (the Copy buttons), which no other page pays for');
assert.match(res.text, /js\/setup\.js/, 'and its own script (the Copy buttons), which no other page pays for');
@@ -6,7 +6,7 @@
@@ -6,7 +6,7 @@
* - a new record is private and is not in the gallery;
* - a new record is private and is not in the gallery;
* - public is listed and readable anonymously, link is readable by id but not listed, private answers 404;
* - public is listed and readable anonymously, link is readable by id but not listed, private answers 404;
* - saving a version keeps the old one, and a rollback copies it into a new version (history is never rewritten);
* - saving a version keeps the old one, and a rollback copies it into a new version (history is never rewritten);
- * - the Node's sync manifest is exactly the person's picks, at their newest version;
+ * - the Link's sync manifest is exactly the person's picks, at their newest version;
* - the page renders a skill body through the markup gate, never as HTML.
* - the page renders a skill body through the markup gate, never as HTML.
*/
*/
const assert = require('assert');
const assert = require('assert');