openvibers/openvibe.codes Commits

Add Muse Code harness (#394)

OP OpenVibers committed 2 days ago · a132d17d31 · 22 files, +224 −21

* Add Muse Code harness

* Register Muse across Codes runtime

* Document Muse adapter entry

Changes

ddiff --git a/engine/README.md b/engine/README.md
iindex 0a34637..6860614 100644
---- a/engine/README.md
++++ b/engine/README.md
@@@ -17,7 +17,7 @@ engine.outcome(attempts); // { state,
### `adapters/` — every harness Codes can run, with the portal's argv and environment
Six harnesses, each one the portal's runner script, ported: `claude-code` and `claude-other` (Claude Code, and Claude
--Code on another provider with the cm-style environment), `codex`, `command-code`, `opencode` and `minimax-code`. An
++Code on another provider with the cm-style environment), `codex`, `command-code`, `opencode`, `minimax-code` and `muse`. An
adapter says how its CLI is started (the argv and the environment — the portal's, not a guess), how what it prints is
read into the one transcript shape (the portal's feed converters), and what its reasoning levels are.
ddiff --git a/engine/adapters/base.js b/engine/adapters/base.js
iindex ff57a4e..ef4ff1f 100644
---- a/engine/adapters/base.js
++++ b/engine/adapters/base.js
@@@ -66,6 +66,7 @@ const EFFORT_LEVELS = {
opencode: [],
aider: ['low', 'medium', 'high'],
'minimax-code': ['low', 'medium', 'high', 'xhigh', 'max'],
++ muse: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
};
/**
ddiff --git a/engine/adapters/index.js b/engine/adapters/index.js
iindex 1992c79..a64ccb7 100644
---- a/engine/adapters/index.js
++++ b/engine/adapters/index.js
@@@ -12,6 +12,7 @@
* command-code Command Code cc-run.sh
* opencode OpenCode oc-run.sh
* minimax-code MiniMax Code mm-run.sh
++ * muse Muse Code muse exec
*/
const { claudeCode, claudeOther } = require('./claude-code');
@@@ -19,6 +20,7 @@ const { codex } = require('./codex');
const { commandCode } = require('./command-code');
const { opencode } = require('./opencode');
const { minimaxCode } = require('./minimax-code');
++const { muse } = require('./muse');
const { effortFor, EFFORT_LEVELS } = require('./base');
const ADAPTERS = {
@@@ -28,6 +30,7 @@ const ADAPTERS = {
'command-code': commandCode,
opencode,
'minimax-code': minimaxCode,
++ muse,
};
/** The adapter of a harness, or null. */
ddiff --git a/engine/adapters/muse.js b/engine/adapters/muse.js
nnew file mode 100644
iindex 0000000..7fa64e8
---- /dev/null
++++ b/engine/adapters/muse.js
@@@ -0,0 +1,40 @@
++'use strict';
++
++const { effortFor, emitter } = require('./base');
++
++function build(ctx = {}) {
++ const argv = ['exec', '--json', '--prompt-file', '-', '--workspace', ctx.cwd || process.cwd(), '--disable-request-user-input', '--contributor-mode', 'off'];
++ if (ctx.model) argv.push('--model', ctx.model);
++ const effort = effortFor('muse', ctx.effort);
++ if (effort) argv.push('--reasoning-effort', effort);
++ if (ctx.resume) argv.push('--session-id', String(ctx.resume));
++ return { cmd: ctx.bin || 'muse', argv, env: {}, stdin: ctx.prompt || '' };
++}
++
++function feed({ model, onEvent }) {
++ const state = { sessionId: null, lastText: '', failed: null, usage: null };
++ const emit = emitter(onEvent);
++ return { state, line(raw) {
++ let e; try { e = JSON.parse(raw); } catch { if (String(raw).trim()) emit({ type: 'system', subtype: 'stderr', text: String(raw).slice(0, 500) }); return; }
++ if (!e || typeof e !== 'object') return;
++ if (e.type === 'run.output.delta') {
++ const d = String(e.delta || e.text || ''); if (d) { state.lastText += d; emit({ type: 'assistant', message: { model, content: [{ type: 'text', text: d }] } }); }
++ } else if (e.type === 'task.lifecycle.started') {
++ state.sessionId = e.session_id || e.sessionId || state.sessionId;
++ emit({ type: 'system', subtype: 'init', session_id: state.sessionId, backend: 'muse', model });
++ } else if (e.type === 'run.usage') {
++ state.usage = e.usage || e;
++ } else if (e.type === 'run.terminal.completed' || e.type === 'run.terminal.failed') {
++ if (e.session_id || e.sessionId) state.sessionId = e.session_id || e.sessionId;
++ if (e.error) state.failed = String(e.error.message || e.error);
++ }
++ }, end(info) {
++ const ok = !state.failed && !!state.lastText && (!info || info.code === 0);
++ emit({ type: 'result', subtype: ok ? 'success' : 'error', is_error: !ok,
++ result: ok ? state.lastText : (state.failed || 'Muse Code ended without a result (check its sign-in and exit code)'),
++ session_id: state.sessionId, model, usage: state.usage || undefined });
++ } };
++}
++
++const muse = { id: 'muse', name: 'Muse Code', kind: 'cli', bin: 'muse', portal: 'muse', effortLevels: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'], build, feed };
++module.exports = { muse, build, feed };
ddiff --git a/engine/review.js b/engine/review.js
iindex b4d2388..ee19451 100644
---- a/engine/review.js
++++ b/engine/review.js
@@@ -42,6 +42,7 @@ const HARNESS_FAMILY = {
'claude-code': 'anthropic',
codex: 'openai',
'command-code': 'deepseek',
++ muse: 'meta',
opencode: 'qwen',
deepseek: 'deepseek',
aider: 'openai',
ddiff --git a/engine/runtime.js b/engine/runtime.js
iindex e3188bf..d1812ed 100644
---- a/engine/runtime.js
++++ b/engine/runtime.js
@@@ -98,6 +98,10 @@ const MANIFEST = Object.freeze({
terminal_status: 'structured', steering: 'resume', checkpointing: 'git',
subagents: 'none', context_handoff: 'session', enforcement: 'native',
}),
++ muse: Object.freeze({
++ terminal_status: 'structured', steering: 'resume', checkpointing: 'git',
++ subagents: 'none', context_handoff: 'session', enforcement: 'native',
++ }),
deepseek: Object.freeze({
terminal_status: 'structured', steering: 'resume', checkpointing: 'git',
subagents: 'none', context_handoff: 'session', enforcement: 'client-boundary',
@@@ -117,7 +121,7 @@ const MANIFEST = Object.freeze({
* `claude-other`, and `cm`/`claude` are Claude Code on Anthropic; the others are one-to-one.
*/
const RUNNER_HARNESS = Object.freeze({
-- cm: 'claude-code', claude: 'claude-code',
++ cm: 'claude-code', claude: 'claude-code', mu: 'muse',
cx: 'codex', cc: 'command-code', oc: 'opencode', dsh: 'deepseek', mm: 'minimax-code',
});
ddiff --git a/harness/adapters/cli.js b/harness/adapters/cli.js
iindex d6d1e02..ba03a0f 100644
---- a/harness/adapters/cli.js
++++ b/harness/adapters/cli.js
@@@ -335,6 +335,40 @@ const minimaxCode = {
},
};
++// ── Muse Code: the person's own Muse sign-in; never pass a Meta API key ──
++const muse = {
++ id: 'muse', name: 'Muse Code', kind: 'cli', bin: 'muse',
++ version: (env) => versionOf('muse', env),
++ check(env = {}) {
++ try {
++ const raw = execFileSync('muse', ['usage', '--json'], { env: { ...process.env, ...env }, encoding: 'utf8', timeout: 8000, stdio: ['ignore', 'pipe', 'ignore'] });
++ const meter = JSON.parse(raw);
++ return meter && meter.allowance ? { available: true } : { available: false, reason: 'sign in with your Muse plan (`muse login`)' };
++ } catch { return { available: false, reason: 'sign in with your Muse plan (`muse login`)' }; }
++ },
++ start(t, ctx = {}) {
++ const args = ['exec', '--json', '--prompt-file', '-', '--workspace', t.cwd || process.cwd(),
++ '--disable-request-user-input', '--approval-mode', 'never', '--trust-workspace',
++ '--contributor-mode', 'off', '--sandbox-network', 'restricted'];
++ if (t.model) args.push('--model', t.model);
++ const effort = effortFor('muse', t.effort); if (effort) args.push('--reasoning-effort', effort);
++ if (t.resume) args.push('--session-id', String(t.resume));
++ const startedAt = Date.now();
++ const parser = jsonLines(muse, (o, emit, st) => {
++ st.startedAt = startedAt;
++ if (o.type === 'task.lifecycle.started') { st.sessionId = o.session_id || o.sessionId || null; emit(ev.init(st.sessionId, 'muse', t.model)); }
++ else if (o.type === 'run.output.delta') { const text = String(o.delta || o.text || ''); if (text) { st.lastText = (st.lastText || '') + text; emit(ev.text(text, t.model)); } }
++ else if (o.type === 'run.usage') st.usage = o.usage || o;
++ else if (o.type === 'run.terminal.failed') st.failed = String(o.error?.message || o.error || 'Muse run failed');
++ else if (o.type === 'run.terminal.completed') st.sessionId = o.session_id || o.sessionId || st.sessionId;
++ }, (info, emit, st) => {
++ const ok = !st.failed && !!st.lastText && info.code === 0;
++ emit(ev.result({ ok, text: ok ? st.lastText : (st.failed || exitReason(info, 'Muse Code')), sessionId: st.sessionId, model: t.model, startedAt, usage: st.usage }));
++ });
++ return spawnAgent({ cmd: this.bin, args, cwd: t.cwd, env: ctx.env, timeoutMs: t.timeoutMs, signal: t.signal, parser, stdin: t.prompt });
++ },
++};
++
// ── Claude Code on another provider: the same CLI, the cm-style environment (the portal's cm-run.sh) ──
// The endpoint and the model come from the person's own connection (Codes C3): the base URL and the run token travel
// in the environment, and the model id rides in ANTHROPIC_MODEL. No provider is named here: this harness is whatever
@@@ -390,4 +424,4 @@ const claudeOther = {
},
};
--module.exports = { claudeCode, claudeOther, codex, opencode, commandCode, aider, minimaxCode, jsonLines, versionOf };
++module.exports = { claudeCode, claudeOther, codex, opencode, commandCode, aider, minimaxCode, muse, jsonLines, versionOf };
ddiff --git a/harness/index.js b/harness/index.js
iindex fd2b05c..8d893f7 100644
---- a/harness/index.js
++++ b/harness/index.js
@@@ -139,6 +139,7 @@ function adaptersFor(env) {
'claude-other': cli.claudeOther,
// MiniMax Code: the person's own MiniMax sign-in on this machine.
'minimax-code': cli.minimaxCode,
++ muse: cli.muse,
codex: cli.codex,
opencode: cli.opencode,
'command-code': cli.commandCode,
ddiff --git a/server/agent-descriptor.js b/server/agent-descriptor.js
iindex f8c72cb..f2ba773 100644
---- a/server/agent-descriptor.js
++++ b/server/agent-descriptor.js
@@@ -5,7 +5,7 @@
module.exports = {
id: 'codes',
name: 'OpenVibe.Codes',
-- purpose: 'The open coding-agent harness: one router over Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API and your own models, with hand-offs between agents, and the way to improve OpenVibe itself.',
++ purpose: 'The open coding-agent harness: one router over Claude Code, Codex, OpenCode, Command Code, Aider, Muse Code, the DeepSeek API and your own models, with hand-offs between agents, and the way to improve OpenVibe itself.',
home: 'https://openvibe.codes',
site: 'openvibe.codes',
api: '/api/v1',
ddiff --git a/server/data/harness-offers.json b/server/data/harness-offers.json
iindex 81773a7..53d7df9 100644
---- a/server/data/harness-offers.json
++++ b/server/data/harness-offers.json
@@@ -311,6 +311,92 @@
}
]
},
++ {
++ "id": "muse",
++ "name": "Muse Code",
++ "provider": "meta",
++ "capabilities": {
++ "host_access": true,
++ "mcp": false,
++ "long_autonomy": true,
++ "resume": true,
++ "edit": true,
++ "review": true,
++ "tools": true,
++ "vision": false,
++ "browser": false,
++ "computer_use": false,
++ "runtimes": [
++ "code"
++ ]
++ },
++ "address": {
++ "kind": "cli",
++ "target": "muse"
++ },
++ "price": {
++ "amount_usd": 0,
++ "unit": "hour"
++ },
++ "limits": {
++ "max_duration_seconds": 14400,
++ "max_concurrent_runs": 4,
++ "max_context_tokens": 200000
++ },
++ "agents": [
++ {
++ "id": "muse-spark-1.3",
++ "harness": "muse",
++ "provider": "meta",
++ "model": "muse-spark-1.3",
++ "context_limits": {
++ "input_tokens": 1048576,
++ "output_tokens": 524288
++ },
++ "concurrency": 2,
++ "price_per_1k_tokens": {
++ "fresh_usd": 0,
++ "cached_usd": 0,
++ "output_usd": 0
++ },
++ "availability_windows": []
++ },
++ {
++ "id": "muse-wonder-full",
++ "harness": "muse",
++ "provider": "meta",
++ "model": "muse-wonder-full",
++ "context_limits": {
++ "input_tokens": 1048576,
++ "output_tokens": 524288
++ },
++ "concurrency": 2,
++ "price_per_1k_tokens": {
++ "fresh_usd": 0,
++ "cached_usd": 0,
++ "output_usd": 0
++ },
++ "availability_windows": []
++ },
++ {
++ "id": "muse-ember-1.0",
++ "harness": "muse",
++ "provider": "meta",
++ "model": "muse-ember-1.0",
++ "context_limits": {
++ "input_tokens": 1048576,
++ "output_tokens": 524288
++ },
++ "concurrency": 2,
++ "price_per_1k_tokens": {
++ "fresh_usd": 0,
++ "cached_usd": 0,
++ "output_usd": 0
++ },
++ "availability_windows": []
++ }
++ ]
++ },
{
"id": "aider",
"name": "Aider",
@@@ -405,7 +491,7 @@
"concurrency": 8,
"price_per_1k_tokens": {
"fresh_usd": 0.00027,
-- "cached_usd": 7e-05,
++ "cached_usd": 0.00007,
"output_usd": 0.0011
},
"availability_windows": []
ddiff --git a/server/data/models.js b/server/data/models.js
iindex 74f439f..e37e438 100644
---- a/server/data/models.js
++++ b/server/data/models.js
@@@ -29,6 +29,11 @@ const MODELS = {
{ id: 'deepseek-chat', name: 'Chat', note: 'quick and very cheap' },
{ id: 'deepseek-reasoner', name: 'Reasoner', note: 'thinks first: harder problems' },
],
++ muse: [
++ { id: 'muse-spark-1.3', name: 'Spark 1.3', note: 'default: agentic coding work at scale' },
++ { id: 'muse-wonder-full', name: 'Wonder Full', note: 'complex engineering and long-horizon work' },
++ { id: 'muse-ember-1.0', name: 'Ember', note: 'efficient high-volume repeated work' },
++ ],
};
const DEFAULT = { id: '', name: 'its default', note: 'the model the agent picks for itself' };
ddiff --git a/server/domain/conductor-failover.js b/server/domain/conductor-failover.js
iindex 5d3a4f6..2bf7843 100644
---- a/server/domain/conductor-failover.js
++++ b/server/domain/conductor-failover.js
@@@ -27,7 +27,7 @@ const { windowFull, FULL_PCT } = require('../../engine/router');
const CONDUCTOR_AGENTS = ['claude-code', 'codex', 'opencode'];
const AGENT_NAMES = {
'claude-code': 'Claude Code', 'claude-other': 'Claude Code (another provider)', codex: 'Codex', opencode: 'OpenCode',
-- 'command-code': 'Command Code', 'minimax-code': 'MiniMax Code', aider: 'Aider', deepseek: 'DeepSeek',
++ 'command-code': 'Command Code', 'minimax-code': 'MiniMax Code', muse: 'Muse Code', aider: 'Aider', deepseek: 'DeepSeek',
'openvibe-ai': 'OpenVibe.AI', 'openai-compatible': 'Your own model',
};
const MODEL = /^[A-Za-z0-9._:/-]{1,100}$/;
ddiff --git a/server/domain/readiness.js b/server/domain/readiness.js
iindex 1f09a23..e024030 100644
---- a/server/domain/readiness.js
++++ b/server/domain/readiness.js
@@@ -23,6 +23,7 @@ const SETUP = {
opencode: { name: 'OpenCode', plan: 'OpenCode Go, or your own keys', install: 'npm install -g opencode-ai', signIn: 'opencode auth login', docs: 'https://opencode.ai/docs' },
'command-code': { name: 'Command Code', plan: 'a Command Code plan', install: null, signIn: null, docs: 'https://commandcode.ai' },
'minimax-code': { name: 'MiniMax Code', plan: 'a MiniMax coding plan', install: null, signIn: null, docs: 'https://www.minimax.io' },
++ muse: { name: 'Muse Code', plan: 'your Muse plan', install: null, signIn: 'muse login', docs: 'https://dev.meta.ai/docs/muse-ember' },
};
function readiness(rows) {
ddiff --git a/server/http/c4.js b/server/http/c4.js
iindex 2d4d49e..b6ef185 100644
---- a/server/http/c4.js
++++ b/server/http/c4.js
@@@ -399,7 +399,7 @@ ${pr.problem ? `<p class="c4-hint">${esc(pr.problem.detail)}</p>` : `<dl class="
return `${showcase.hero({
eyebrow: 'OpenVibe.Codes · alpha',
title: 'Every coding agent.', accent: 'One place.',
-- lede: 'Claude Code, Codex, OpenCode, Command Code, DeepSeek and models you run yourself, in one conversation. Bring the plans and keys you already pay for, run on your computer or ours, and follow the work from your phone.',
++ lede: 'Claude Code, Codex, OpenCode, Command Code, Muse Code, DeepSeek and models you run yourself, in one conversation. Bring the plans and keys you already pay for, run on your computer or ours, and follow the work from your phone.',
actions: [{ label: 'Start a task', href: '#c4-prompt', primary: true }, { label: 'How it works', href: '#how' }],
note: 'Open source (AGPL-3.0). No fee on your own plans and keys.',
aside: { html: thread },
ddiff --git a/server/http/chat.js b/server/http/chat.js
iindex a044048..a6e5bce 100644
---- a/server/http/chat.js
++++ b/server/http/chat.js
@@@ -75,7 +75,7 @@ const ico = (name, cls = '') => `<svg class="ch-i${cls ? ` ${cls}` : ''}" aria-h
const AGENTS = {
'claude-code': ['Claude Code', 'C', '#d97757'], codex: ['Codex', 'Cx', '#10a37f'], opencode: ['OpenCode', 'O', '#7c6cff'],
'command-code': ['Command Code', 'Cc', '#f59e0b'], aider: ['Aider', 'A', '#14b8a6'], deepseek: ['DeepSeek', 'D', '#4f7cff'],
-- 'minimax-code': ['MiniMax Code', 'M', '#ec4899'], minimax: ['MiniMax', 'M', '#ec4899'],
++ 'minimax-code': ['MiniMax Code', 'M', '#ec4899'], muse: ['Muse Code', 'Mu', '#0081fb'], minimax: ['MiniMax', 'M', '#ec4899'],
'openvibe-ai': ['OpenVibe.AI', 'AI', '#64748b'], 'openai-compatible': ['Your model', 'Y', '#64748b'],
'claude-other': ['Claude Code (another provider)', 'C', '#d97757'],
};
ddiff --git a/server/http/discovery.js b/server/http/discovery.js
iindex 85e59d6..dd7a6c9 100644
---- a/server/http/discovery.js
++++ b/server/http/discovery.js
@@@ -20,7 +20,7 @@ const { asyncRouter } = require('./router');
const SITE_NAME = 'OpenVibe.Codes';
const SERVICES_ORIGIN = 'https://openvibe.services';
--const DESCRIPTION = 'The open coding-agent harness: run a coding task on Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API or a model you host, with routing by capability and cost, hand-offs between agents, and the way to improve OpenVibe itself.';
++const DESCRIPTION = 'The open coding-agent harness: run a coding task on Claude Code, Codex, OpenCode, Command Code, Aider, Muse Code, the DeepSeek API or a model you host, with routing by capability and cost, hand-offs between agents, and the way to improve OpenVibe itself.';
const { version: VERSION } = require('../../package.json');
const INSTALL_HINT = `npm install -g https://codeload.github.com/OpenVibers/OpenVibe.Codes/tar.gz/refs/tags/v${VERSION}`;
// Sign-in and the API are not for crawlers. The console's old addresses redirect to openvibe.services.
@@@ -32,7 +32,7 @@ const PAGE_TEXT = {
'/': ['OpenVibe.Codes home', 'Code with any agent: the coding-agent harness, how a task is routed and handed off, what works today, and how to improve OpenVibe.'],
'/run': ['Run locally', 'Start coding agents from any browser and run them on your own computer.'],
'/start': ['Get started with the coding-agent harness', 'Install openvibe-codes; the agents it can use and what each needs; the commands, permission levels, hand-offs, the event format and using it from your own code.'],
-- '/harnesses': ['The coding agents', 'Every harness the router can pick (Claude Code, Codex, OpenCode, Command Code, Aider, DeepSeek, your own model) with its models, capabilities, prices and limits.'],
++ '/harnesses': ['The coding agents', 'Every harness the router can pick (Claude Code, Codex, OpenCode, Command Code, Aider, Muse Code, DeepSeek, your own model) with its models, capabilities, prices and limits.'],
'/skills': ['The skills hub', 'Skills, rules and MCP servers kept per person and per project, versioned, shared through the four audiences, and synced into each coding tool on your own machine with a dry run and an undo.'],
'/improve': ['Improve OpenVibe', 'How OpenVibe is built in the open: every repository, and the path from a change to a reviewed pull request.'],
'/updates': ['What shipped on OpenVibe.Codes', 'This site\'s update log, from the network changelog feed.'],
@@@ -150,7 +150,7 @@ function createDiscoveryRoutes(ctx) {
res.type('text/plain').set('Cache-Control', TEXT).send(llmsTxt({
name: SITE_NAME,
base: site,
-- summary: 'OpenVibe.Codes: the open coding-agent harness. openvibe-codes runs a coding task on Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API or your own OpenAI-compatible model, hands it to the next agent when one fails, and keeps every run; plus a public catalog and routing API, and the way to improve OpenVibe itself.',
++ summary: 'OpenVibe.Codes: the open coding-agent harness. openvibe-codes runs a coding task on Claude Code, Codex, OpenCode, Command Code, Aider, Muse Code, the DeepSeek API or your own OpenAI-compatible model, hands it to the next agent when one fails, and keeps every run; plus a public catalog and routing API, and the way to improve OpenVibe itself.',
details: `Every page is server-rendered and readable without JavaScript. The harness is a command-line tool and a Node.js module that runs on your machine with your agents and keys (install: ${INSTALL_HINT}). Every agent's output becomes one event stream in Claude Code's stream-json shape. The catalog and the router are public: GET /api/v1/harnesses lists every harness with its models, and POST /api/v1/harnesses/route picks one for a task by capability first, then cost. A hosted runner comes next. The developer console that used to live here (projects, apps, keys, the API reference, OAuth and webhook tools, releases) is OpenVibe.Services at ${SERVICES_ORIGIN}.`,
sections: [
{ title: 'Public pages', links: (await allPages()).map(p => ({ title: p.title || describePage(p.path, { governance }).title, url: abs(p.path), note: p.text || describePage(p.path, { governance }).text, canonical: true })) },
ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 848bff2..bec0960 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -102,7 +102,7 @@ Added test/date.test.js: three cases, all pass.
✔ done (48 s · $0.0400 · 31k in / 1.2k out)`;
// What works today: the state of each piece, read with how it works.
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 catalog agent 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 locally</a>: start a task in any browser, your phone included; your own computer runs it`, 'Live: pair your machine at openvibe.my/#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.'],
@@@ -117,7 +117,7 @@ Added test/date.test.js: three cases, all pass.
body: html`${raw(showcase.hero({
eyebrow: 'OpenVibe.Codes · the open coding-agent harness · alpha',
title: 'Code with', accent: 'any agent',
-- lede: 'One open harness for Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API and models you host yourself: send a coding task to the agent that fits it, hand it over when one gets stuck, and keep the whole run in one place. Then point it at OpenVibe itself.',
++ lede: 'One open harness for Claude Code, Codex, OpenCode, Command Code, Aider, Muse Code, the DeepSeek API and models you host yourself: send a coding task to the agent that fits it, hand it over when one gets stuck, and keep the whole run in one place. Then point it at OpenVibe itself.',
// Two, so they sit side by side on a phone; the agents are the next section.
actions: [{ label: 'Install the harness', href: '/start', primary: true }, { label: 'Run locally from the web', href: '/run' }],
note: `Open source (AGPL-3.0). Version ${VERSION} runs on your machine with the agents and keys you already have, from your terminal or started from this site in any browser; the list below says exactly what works.`,
@@@ -176,7 +176,7 @@ for await (const event of harness.run({ prompt: 'fix the failing test', cwd: '.'
return {
agent: true, index: true, cache: PUBLIC_CACHE,
title: 'Get started with the coding-agent harness',
-- description: 'Install openvibe-codes and run coding tasks on Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API or your own model, with hand-offs between them, from your terminal or your own code.',
++ description: 'Install openvibe-codes and run coding tasks on Claude Code, Codex, OpenCode, Command Code, Aider, Muse Code, the DeepSeek API or your own model, with hand-offs between them, from your terminal or your own code.',
crumbs: [{ label: 'Get started' }],
body: html`<h1>Get started</h1>
<p><code>openvibe-codes</code> runs a coding task on whichever agent fits it, hands it to the next one when an agent fails, hits its limit or goes quiet, and keeps the whole run. It runs on your machine with the agents and keys you already have: nothing goes through OpenVibe, and it needs no account.</p>
@@@ -190,6 +190,7 @@ ${table(['Agent', 'What it needs'], [
[html`<a href="/harnesses#claude-code">Claude Code</a>`, html`<code>claude</code> on PATH, signed in`],
[html`<a href="/harnesses#claude-other">Claude Code (another provider)</a>`, html`<code>claude</code> on PATH, plus your own Anthropic-compatible endpoint in <code>ANTHROPIC_BASE_URL</code>, its token in <code>ANTHROPIC_AUTH_TOKEN</code> and the model id in <code>ANTHROPIC_MODEL</code>`],
[html`<a href="/harnesses#minimax-code">MiniMax Code</a>`, html`<code>mcode</code> on PATH, signed in to your own MiniMax account`],
++ [html`<a href="/harnesses#muse">Muse Code</a>`, html`<code>muse</code> on PATH, signed in with <code>muse login</code>; uses your Muse plan with contributor mode off`],
[html`<a href="/harnesses#codex">Codex</a>`, html`<code>codex</code> on PATH, signed in`],
[html`<a href="/harnesses#opencode">OpenCode</a>`, html`<code>opencode</code> on PATH`],
[html`<a href="/harnesses#command-code">Command Code</a>`, html`<code>cmd</code> on PATH`],
@@@ -248,10 +249,10 @@ ${table(['Level', 'What the agent may do'], [
return {
agent: true, index: true, cache: PUBLIC_CACHE,
title: 'The coding agents',
-- description: 'Claude Code, Codex, OpenCode, Command Code, Aider and the DeepSeek API: every coding harness OpenVibe.Codes routes a task to, with its models, capabilities, prices and limits.',
++ description: 'Claude Code, Codex, OpenCode, Command Code, Aider, Muse Code and the DeepSeek API: every coding harness OpenVibe.Codes routes a task to, with its models, capabilities, prices and limits.',
crumbs: [{ label: 'Agents' }],
body: html`<h1>The coding agents</h1>
--<p>A task is routed with <code>POST /api/v1/harnesses/route</code> over <code>openvibe-sdk/placement</code>, which picks an offer from this catalog: hard requirements first (a capability the task needs), then cost. The catalog is read and validated when Codes starts; every price and limit is as the offer states it. Machine-readable: <a href="/api/v1/harnesses"><code>GET /api/v1/harnesses</code></a>.</p>
++<p>A task is routed with <code>POST /api/v1/harnesses/route</code> over <code>openvibe-sdk/placement</code>, which picks an offer from this catalog: hard requirements first (a capability the task needs), then cost. The catalog is read and validated when Codes starts; every price and limit is as the offer states it. Muse Code runs with your own Muse sign-in. Its choices include Spark 1.3, Wonder Full and Ember, with reasoning effort. Machine-readable: <a href="/api/v1/harnesses"><code>GET /api/v1/harnesses</code></a>.</p>
${table(['Harness', 'Provider', 'Address', 'Can', 'Price', 'Limits'], offers.map((o) => [
html`<strong id="${o.id}">${o.name}</strong><br><code>${o.id}</code>`,
o.provider,
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex ad2d7d8..c993a92 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -131,7 +131,7 @@ function renderPage(o) {
title: o.title || `${SITE_NAME}: code with any agent`,
titleSuffix: o.title ? ` · ${SITE_NAME}` : '',
siteName: SITE_NAME,
-- description: o.description || 'The open coding-agent harness: Claude Code, Codex, OpenCode, Command Code, Aider, the DeepSeek API and your own models behind one router, and the way to improve OpenVibe itself.',
++ description: o.description || 'The open coding-agent harness: Claude Code, Codex, OpenCode, Command Code, Aider, Muse Code, the DeepSeek API and your own models behind one router, and the way to improve OpenVibe itself.',
canonical,
image: new URL(require('openvibe-shared/social').url('codes'), o.config.baseUrl).href,
imageAlt: 'OpenVibe Codes — coding agents',
ddiff --git a/test/engine-adapters.test.js b/test/engine-adapters.test.js
iindex e7a98ae..1b686b9 100644
---- a/test/engine-adapters.test.js
++++ b/test/engine-adapters.test.js
@@@ -21,6 +21,7 @@ const { codex } = require('../engine/adapters/codex');
const { commandCode } = require('../engine/adapters/command-code');
const { opencode } = require('../engine/adapters/opencode');
const { minimaxCode } = require('../engine/adapters/minimax-code');
++const { muse } = require('../engine/adapters/muse');
const transcript = require('../engine/transcript');
const { runAttempt } = require('../engine/jobrunner');
const { assemble } = require('../engine/prompt');
@@@ -34,7 +35,7 @@ function dry(adapter, ctx = {}) {
(async () => {
await check('every harness Codes can run is an adapter, and each one names the portal script it came from', () => {
-- assert.deepStrictEqual(ids().sort(), ['claude-code', 'claude-other', 'codex', 'command-code', 'minimax-code', 'opencode']);
++ assert.deepStrictEqual(ids().sort(), ['claude-code', 'claude-other', 'codex', 'command-code', 'minimax-code', 'muse', 'opencode']);
for (const id of ids()) {
const a = adapterFor(id);
assert.ok(a.portal, `${id} names no portal script`);
@@@ -43,6 +44,17 @@ function dry(adapter, ctx = {}) {
assert.equal(adapterFor('nope'), null);
});
++ await check('Muse Code uses its local sign-in, model and reasoning flags with contributor mode off', () => {
++ const d = dry(muse, { model: 'muse-ember-1.0', effort: 'high', resume: 'session-1' });
++ assert.equal(d.cmd, 'muse');
++ assert.ok(d.argv.includes('--contributor-mode') && d.argv.includes('off'));
++ assert.ok(d.argv.includes('--disable-request-user-input'));
++ assert.ok(d.argv.includes('--model') && d.argv.includes('muse-ember-1.0'));
++ assert.ok(d.argv.includes('--reasoning-effort') && d.argv.includes('high'));
++ assert.ok(d.argv.includes('--session-id') && d.argv.includes('session-1'));
++ assert.equal(d.stdin, 'do the thing');
++ });
++
await check('Claude Code: the portal\'s argv and the cm-style environment', () => {
const d = dry(claudeCode, { model: 'M', effort: 'high', permission: 'edit', maxTurns: 200 });
assert.equal(d.cmd, 'claude');
ddiff --git a/test/harness-placement.test.js b/test/harness-placement.test.js
iindex 67ad0fc..a3210d6 100644
---- a/test/harness-placement.test.js
++++ b/test/harness-placement.test.js
@@@ -69,7 +69,7 @@ function priced(row, freshUsd) {
await check('every seed offer validates as platform.resource-offer@1 of kind harness', async () => {
const harnesses = createHarnesses({ catalogPath });
const offers = harnesses.list().flatMap((harness) => harnesses.agents(harness.id).map((agent) => [harness, toOffer(harness, agent, { now })]));
-- assert.strictEqual(offers.length, 10);
++ assert.strictEqual(offers.length, 13);
for (const [harness, offer] of offers) {
const result = contracts.validate('platform.resource-offer@1', JSON.parse(JSON.stringify(offer)));
assert.ok(result.valid, `${offer.offer_id}: ${JSON.stringify(result.errors)}`);
@@@ -136,7 +136,7 @@ function priced(row, freshUsd) {
const [harnessId] = r.selected.split(':');
assert.ok(toOffer(harnesses.get(harnessId), harnesses.agents(harnessId)[0]).capabilities.includes('task:review'));
assert.ok(r.reasons.length);
-- assert.deepStrictEqual(r.candidates.map((c) => c.id), ['claude-code:claude-code-sonnet', 'claude-other:claude-other-default', 'codex:codex-default', 'command-code:command-code-default', 'opencode:opencode-default', 'minimax-code:minimax-code-default', 'aider:aider-default', 'deepseek:deepseek-chat', 'openvibe-ai:openvibe-ai-router', 'openai-compatible:openai-compatible-local']);
++ assert.deepStrictEqual(r.candidates.map((c) => c.id), ['claude-code:claude-code-sonnet', 'claude-other:claude-other-default', 'codex:codex-default', 'command-code:command-code-default', 'opencode:opencode-default', 'minimax-code:minimax-code-default', 'muse:muse-spark-1.3', 'muse:muse-wonder-full', 'muse:muse-ember-1.0', 'aider:aider-default', 'deepseek:deepseek-chat', 'openvibe-ai:openvibe-ai-router', 'openai-compatible:openai-compatible-local']);
});
await check('an offer whose health is down is excluded with a reason', async () => {
ddiff --git a/test/harnesses-api.test.js b/test/harnesses-api.test.js
iindex 81462fb..34457dd 100644
---- a/test/harnesses-api.test.js
++++ b/test/harnesses-api.test.js
@@@ -29,6 +29,14 @@ const rows = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'server', 'da
}
});
++ await check('GET /harnesses/models offers Muse models and reasoning levels', async () => {
++ const r = await t.get('/api/v1/harnesses/models');
++ assert.strictEqual(r.status, 200, r.text.slice(0, 300));
++ const muse = r.json().agents.find((agent) => agent.agent === 'muse');
++ assert.deepStrictEqual(muse.models.map((model) => model.id), ['muse-spark-1.3', 'muse-wonder-full', 'muse-ember-1.0']);
++ assert.ok(muse.efforts.includes('high'));
++ });
++
await check('POST /harnesses/route {task:review} returns a selected offer and candidates', async () => {
const r = await post({ task: 'review' });
assert.strictEqual(r.status, 200, r.text.slice(0, 300));
ddiff --git a/test/harnesses.test.js b/test/harnesses.test.js
iindex 7ae2e2b..24e4e24 100644
---- a/test/harnesses.test.js
++++ b/test/harnesses.test.js
@@@ -13,7 +13,7 @@ const rows = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
(async () => {
await check('every seed row matches the pinned harness and agent contracts', async () => {
-- assert.strictEqual(rows.length, 10);
++ assert.strictEqual(rows.length, 11);
for (const row of rows) {
// The row is one contract instance: the offer and the agents (models) it runs.
const harnessCheck = contracts.validate('platform.harness-offer@1', row);
@@@ -54,6 +54,12 @@ const rows = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
}
});
++ await check('Muse Code catalog lists Spark 1.3, Wonder Full and Ember', async () => {
++ const muse = rows.find((row) => row.id === 'muse');
++ assert.ok(muse);
++ assert.deepStrictEqual(muse.agents.map((agent) => agent.model), ['muse-spark-1.3', 'muse-wonder-full', 'muse-ember-1.0']);
++ });
++
await check('harness and agent ids are unique', async () => {
const ids = rows.flatMap((row) => [row.id, ...row.agents.map((agent) => agent.id)]);
assert.strictEqual(new Set(ids).size, ids.length);