openvibers/openvibe.codes Commits
Add Muse Code harness (#394)
Changes
@@@ -17,7 +17,7 @@ engine.outcome(attempts); // { state,### `adapters/` — every harness Codes can run, with the portal's argv and environmentSix 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`. Anadapter says how its CLI is started (the argv and the environment — the portal's, not a guess), how what it prints isread into the one transcript shape (the portal's feed converters), and what its reasoning levels are.@@@ -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'],};/**@@@ -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. */nnew file mode 100644@@@ -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 };@@@ -42,6 +42,7 @@ const HARNESS_FAMILY = {'claude-code': 'anthropic',codex: 'openai','command-code': 'deepseek',++ muse: 'meta',opencode: 'qwen',deepseek: 'deepseek',aider: 'openai',@@@ -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',});@@@ -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 };@@@ -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,@@@ -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',@@@ -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": []@@@ -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' };@@@ -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}$/;@@@ -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) {@@@ -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 },@@@ -75,7 +75,7 @@ const ico = (name, cls = '') => `<svg class="ch-i${cls ? ` ${cls}` : ''}" aria-hconst 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'],};@@@ -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 })) },@@@ -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,@@@ -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',@@@ -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');@@@ -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 () => {@@@ -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));@@@ -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);
@@ -17,7 +17,7 @@ engine.outcome(attempts); // { state,
@@ -17,7 +17,7 @@ engine.outcome(attempts); // { state,
### `adapters/` — every harness Codes can run, with the portal's argv and environment
### `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
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
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.
read into the one transcript shape (the portal's feed converters), and what its reasoning levels are.
@@ -66,6 +66,7 @@ const EFFORT_LEVELS = {
@@ -66,6 +66,7 @@ const EFFORT_LEVELS = {
opencode: [],
opencode: [],
aider: ['low', 'medium', 'high'],
aider: ['low', 'medium', 'high'],
'minimax-code': ['low', 'medium', 'high', 'xhigh', 'max'],
'minimax-code': ['low', 'medium', 'high', 'xhigh', 'max'],
+ muse: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
};
};
/**
/**
@@ -12,6 +12,7 @@
@@ -12,6 +12,7 @@
* command-code Command Code cc-run.sh
* command-code Command Code cc-run.sh
* opencode OpenCode oc-run.sh
* opencode OpenCode oc-run.sh
* minimax-code MiniMax Code mm-run.sh
* minimax-code MiniMax Code mm-run.sh
+ * muse Muse Code muse exec
*/
*/
const { claudeCode, claudeOther } = require('./claude-code');
const { claudeCode, claudeOther } = require('./claude-code');
@@ -19,6 +20,7 @@ const { codex } = require('./codex');
@@ -19,6 +20,7 @@ const { codex } = require('./codex');
const { commandCode } = require('./command-code');
const { commandCode } = require('./command-code');
const { opencode } = require('./opencode');
const { opencode } = require('./opencode');
const { minimaxCode } = require('./minimax-code');
const { minimaxCode } = require('./minimax-code');
+const { muse } = require('./muse');
const { effortFor, EFFORT_LEVELS } = require('./base');
const { effortFor, EFFORT_LEVELS } = require('./base');
const ADAPTERS = {
const ADAPTERS = {
@@ -28,6 +30,7 @@ const ADAPTERS = {
@@ -28,6 +30,7 @@ const ADAPTERS = {
'command-code': commandCode,
'command-code': commandCode,
opencode,
opencode,
'minimax-code': minimaxCode,
'minimax-code': minimaxCode,
+ muse,
};
};
/** The adapter of a harness, or null. */
/** The adapter of a harness, or null. */
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -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 };
@@ -42,6 +42,7 @@ const HARNESS_FAMILY = {
@@ -42,6 +42,7 @@ const HARNESS_FAMILY = {
'claude-code': 'anthropic',
'claude-code': 'anthropic',
codex: 'openai',
codex: 'openai',
'command-code': 'deepseek',
'command-code': 'deepseek',
+ muse: 'meta',
opencode: 'qwen',
opencode: 'qwen',
deepseek: 'deepseek',
deepseek: 'deepseek',
aider: 'openai',
aider: 'openai',
@@ -98,6 +98,10 @@ const MANIFEST = Object.freeze({
@@ -98,6 +98,10 @@ const MANIFEST = Object.freeze({
terminal_status: 'structured', steering: 'resume', checkpointing: 'git',
terminal_status: 'structured', steering: 'resume', checkpointing: 'git',
subagents: 'none', context_handoff: 'session', enforcement: 'native',
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({
deepseek: Object.freeze({
terminal_status: 'structured', steering: 'resume', checkpointing: 'git',
terminal_status: 'structured', steering: 'resume', checkpointing: 'git',
subagents: 'none', context_handoff: 'session', enforcement: 'client-boundary',
subagents: 'none', context_handoff: 'session', enforcement: 'client-boundary',
@@ -117,7 +121,7 @@ const MANIFEST = Object.freeze({
@@ -117,7 +121,7 @@ const MANIFEST = Object.freeze({
* `claude-other`, and `cm`/`claude` are Claude Code on Anthropic; the others are one-to-one.
* `claude-other`, and `cm`/`claude` are Claude Code on Anthropic; the others are one-to-one.
*/
*/
const RUNNER_HARNESS = Object.freeze({
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',
cx: 'codex', cc: 'command-code', oc: 'opencode', dsh: 'deepseek', mm: 'minimax-code',
});
});
@@ -335,6 +335,40 @@ const minimaxCode = {
@@ -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) ──
// ── 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
// 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
// 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 = {
@@ -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 };
@@ -139,6 +139,7 @@ function adaptersFor(env) {
@@ -139,6 +139,7 @@ function adaptersFor(env) {
'claude-other': cli.claudeOther,
'claude-other': cli.claudeOther,
// MiniMax Code: the person's own MiniMax sign-in on this machine.
// MiniMax Code: the person's own MiniMax sign-in on this machine.
'minimax-code': cli.minimaxCode,
'minimax-code': cli.minimaxCode,
+ muse: cli.muse,
codex: cli.codex,
codex: cli.codex,
opencode: cli.opencode,
opencode: cli.opencode,
'command-code': cli.commandCode,
'command-code': cli.commandCode,
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
module.exports = {
module.exports = {
id: 'codes',
id: 'codes',
name: 'OpenVibe.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',
home: 'https://openvibe.codes',
site: 'openvibe.codes',
site: 'openvibe.codes',
api: '/api/v1',
api: '/api/v1',
@@ -311,6 +311,92 @@
@@ -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",
"id": "aider",
"name": "Aider",
"name": "Aider",
@@ -405,7 +491,7 @@
@@ -405,7 +491,7 @@
"concurrency": 8,
"concurrency": 8,
"price_per_1k_tokens": {
"price_per_1k_tokens": {
"fresh_usd": 0.00027,
"fresh_usd": 0.00027,
- "cached_usd": 7e-05,
+ "cached_usd": 0.00007,
"output_usd": 0.0011
"output_usd": 0.0011
},
},
"availability_windows": []
"availability_windows": []
@@ -29,6 +29,11 @@ const MODELS = {
@@ -29,6 +29,11 @@ const MODELS = {
{ id: 'deepseek-chat', name: 'Chat', note: 'quick and very cheap' },
{ id: 'deepseek-chat', name: 'Chat', note: 'quick and very cheap' },
{ id: 'deepseek-reasoner', name: 'Reasoner', note: 'thinks first: harder problems' },
{ 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' };
const DEFAULT = { id: '', name: 'its default', note: 'the model the agent picks for itself' };
@@ -27,7 +27,7 @@ const { windowFull, FULL_PCT } = require('../../engine/router');
@@ -27,7 +27,7 @@ const { windowFull, FULL_PCT } = require('../../engine/router');
const CONDUCTOR_AGENTS = ['claude-code', 'codex', 'opencode'];
const CONDUCTOR_AGENTS = ['claude-code', 'codex', 'opencode'];
const AGENT_NAMES = {
const AGENT_NAMES = {
'claude-code': 'Claude Code', 'claude-other': 'Claude Code (another provider)', codex: 'Codex', opencode: 'OpenCode',
'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',
'openvibe-ai': 'OpenVibe.AI', 'openai-compatible': 'Your own model',
};
};
const MODEL = /^[A-Za-z0-9._:/-]{1,100}$/;
const MODEL = /^[A-Za-z0-9._:/-]{1,100}$/;
@@ -23,6 +23,7 @@ const SETUP = {
@@ -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' },
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' },
'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' },
'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) {
function readiness(rows) {
@@ -399,7 +399,7 @@ ${pr.problem ? `<p class="c4-hint">${esc(pr.problem.detail)}</p>` : `<dl class="
@@ -399,7 +399,7 @@ ${pr.problem ? `<p class="c4-hint">${esc(pr.problem.detail)}</p>` : `<dl class="
return `${showcase.hero({
return `${showcase.hero({
eyebrow: 'OpenVibe.Codes · alpha',
eyebrow: 'OpenVibe.Codes · alpha',
title: 'Every coding agent.', accent: 'One place.',
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' }],
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.',
note: 'Open source (AGPL-3.0). No fee on your own plans and keys.',
aside: { html: thread },
aside: { html: thread },
@@ -75,7 +75,7 @@ const ico = (name, cls = '') => `<svg class="ch-i${cls ? ` ${cls}` : ''}" aria-h
@@ -75,7 +75,7 @@ const ico = (name, cls = '') => `<svg class="ch-i${cls ? ` ${cls}` : ''}" aria-h
const AGENTS = {
const AGENTS = {
'claude-code': ['Claude Code', 'C', '#d97757'], codex: ['Codex', 'Cx', '#10a37f'], opencode: ['OpenCode', 'O', '#7c6cff'],
'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'],
'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'],
'openvibe-ai': ['OpenVibe.AI', 'AI', '#64748b'], 'openai-compatible': ['Your model', 'Y', '#64748b'],
'claude-other': ['Claude Code (another provider)', 'C', '#d97757'],
'claude-other': ['Claude Code (another provider)', 'C', '#d97757'],
};
};
@@ -20,7 +20,7 @@ const { asyncRouter } = require('./router');
@@ -20,7 +20,7 @@ const { asyncRouter } = require('./router');
const SITE_NAME = 'OpenVibe.Codes';
const SITE_NAME = 'OpenVibe.Codes';
const SERVICES_ORIGIN = 'https://openvibe.services';
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 { version: VERSION } = require('../../package.json');
const INSTALL_HINT = `npm install -g https://codeload.github.com/OpenVibers/OpenVibe.Codes/tar.gz/refs/tags/v${VERSION}`;
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.
// 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 = {
@@ -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.'],
'/': ['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.'],
'/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.'],
'/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.'],
'/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.'],
'/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.'],
'/updates': ['What shipped on OpenVibe.Codes', 'This site\'s update log, from the network changelog feed.'],
@@ -150,7 +150,7 @@ function createDiscoveryRoutes(ctx) {
@@ -150,7 +150,7 @@ function createDiscoveryRoutes(ctx) {
res.type('text/plain').set('Cache-Control', TEXT).send(llmsTxt({
res.type('text/plain').set('Cache-Control', TEXT).send(llmsTxt({
name: SITE_NAME,
name: SITE_NAME,
base: site,
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}.`,
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: [
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 })) },
{ 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 })) },
@@ -102,7 +102,7 @@ Added test/date.test.js: three cases, all pass.
@@ -102,7 +102,7 @@ Added test/date.test.js: three cases, all pass.
✔ done (48 s · $0.0400 · 31k in / 1.2k out)`;
✔ done (48 s · $0.0400 · 31k in / 1.2k out)`;
// What works today: the state of each piece, read with how it works.
// What works today: the state of each piece, read with how it works.
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 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.'],
['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="/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.'],
[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.
@@ -117,7 +117,7 @@ Added test/date.test.js: three cases, all pass.
body: html`${raw(showcase.hero({
body: html`${raw(showcase.hero({
eyebrow: 'OpenVibe.Codes · the open coding-agent harness · alpha',
eyebrow: 'OpenVibe.Codes · the open coding-agent harness · alpha',
title: 'Code with', accent: 'any agent',
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.
// 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' }],
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.`,
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: '.'
@@ -176,7 +176,7 @@ for await (const event of harness.run({ prompt: 'fix the failing test', cwd: '.'
return {
return {
agent: true, index: true, cache: PUBLIC_CACHE,
agent: true, index: true, cache: PUBLIC_CACHE,
title: 'Get started with the coding-agent harness',
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' }],
crumbs: [{ label: 'Get started' }],
body: html`<h1>Get started</h1>
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>
<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'], [
@@ -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-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#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#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#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#opencode">OpenCode</a>`, html`<code>opencode</code> on PATH`],
[html`<a href="/harnesses#command-code">Command Code</a>`, html`<code>cmd</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'], [
@@ -248,10 +249,10 @@ ${table(['Level', 'What the agent may do'], [
return {
return {
agent: true, index: true, cache: PUBLIC_CACHE,
agent: true, index: true, cache: PUBLIC_CACHE,
title: 'The coding agents',
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' }],
crumbs: [{ label: 'Agents' }],
body: html`<h1>The coding agents</h1>
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) => [
${table(['Harness', 'Provider', 'Address', 'Can', 'Price', 'Limits'], offers.map((o) => [
html`<strong id="${o.id}">${o.name}</strong><br><code>${o.id}</code>`,
html`<strong id="${o.id}">${o.name}</strong><br><code>${o.id}</code>`,
o.provider,
o.provider,
@@ -131,7 +131,7 @@ function renderPage(o) {
@@ -131,7 +131,7 @@ function renderPage(o) {
title: o.title || `${SITE_NAME}: code with any agent`,
title: o.title || `${SITE_NAME}: code with any agent`,
titleSuffix: o.title ? ` · ${SITE_NAME}` : '',
titleSuffix: o.title ? ` · ${SITE_NAME}` : '',
siteName: 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,
canonical,
image: new URL(require('openvibe-shared/social').url('codes'), o.config.baseUrl).href,
image: new URL(require('openvibe-shared/social').url('codes'), o.config.baseUrl).href,
imageAlt: 'OpenVibe Codes — coding agents',
imageAlt: 'OpenVibe Codes — coding agents',
@@ -21,6 +21,7 @@ const { codex } = require('../engine/adapters/codex');
@@ -21,6 +21,7 @@ const { codex } = require('../engine/adapters/codex');
const { commandCode } = require('../engine/adapters/command-code');
const { commandCode } = require('../engine/adapters/command-code');
const { opencode } = require('../engine/adapters/opencode');
const { opencode } = require('../engine/adapters/opencode');
const { minimaxCode } = require('../engine/adapters/minimax-code');
const { minimaxCode } = require('../engine/adapters/minimax-code');
+const { muse } = require('../engine/adapters/muse');
const transcript = require('../engine/transcript');
const transcript = require('../engine/transcript');
const { runAttempt } = require('../engine/jobrunner');
const { runAttempt } = require('../engine/jobrunner');
const { assemble } = require('../engine/prompt');
const { assemble } = require('../engine/prompt');
@@ -34,7 +35,7 @@ function dry(adapter, ctx = {}) {
@@ -34,7 +35,7 @@ function dry(adapter, ctx = {}) {
(async () => {
(async () => {
await check('every harness Codes can run is an adapter, and each one names the portal script it came from', () => {
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()) {
for (const id of ids()) {
const a = adapterFor(id);
const a = adapterFor(id);
assert.ok(a.portal, `${id} names no portal script`);
assert.ok(a.portal, `${id} names no portal script`);
@@ -43,6 +44,17 @@ function dry(adapter, ctx = {}) {
@@ -43,6 +44,17 @@ function dry(adapter, ctx = {}) {
assert.equal(adapterFor('nope'), null);
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', () => {
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 });
const d = dry(claudeCode, { model: 'M', effort: 'high', permission: 'edit', maxTurns: 200 });
assert.equal(d.cmd, 'claude');
assert.equal(d.cmd, 'claude');
@@ -69,7 +69,7 @@ function priced(row, freshUsd) {
@@ -69,7 +69,7 @@ function priced(row, freshUsd) {
await check('every seed offer validates as platform.resource-offer@1 of kind harness', async () => {
await check('every seed offer validates as platform.resource-offer@1 of kind harness', async () => {
const harnesses = createHarnesses({ catalogPath });
const harnesses = createHarnesses({ catalogPath });
const offers = harnesses.list().flatMap((harness) => harnesses.agents(harness.id).map((agent) => [harness, toOffer(harness, agent, { now })]));
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) {
for (const [harness, offer] of offers) {
const result = contracts.validate('platform.resource-offer@1', JSON.parse(JSON.stringify(offer)));
const result = contracts.validate('platform.resource-offer@1', JSON.parse(JSON.stringify(offer)));
assert.ok(result.valid, `${offer.offer_id}: ${JSON.stringify(result.errors)}`);
assert.ok(result.valid, `${offer.offer_id}: ${JSON.stringify(result.errors)}`);
@@ -136,7 +136,7 @@ function priced(row, freshUsd) {
@@ -136,7 +136,7 @@ function priced(row, freshUsd) {
const [harnessId] = r.selected.split(':');
const [harnessId] = r.selected.split(':');
assert.ok(toOffer(harnesses.get(harnessId), harnesses.agents(harnessId)[0]).capabilities.includes('task:review'));
assert.ok(toOffer(harnesses.get(harnessId), harnesses.agents(harnessId)[0]).capabilities.includes('task:review'));
assert.ok(r.reasons.length);
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 () => {
await check('an offer whose health is down is excluded with a reason', async () => {
@@ -29,6 +29,14 @@ const rows = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'server', 'da
@@ -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 () => {
await check('POST /harnesses/route {task:review} returns a selected offer and candidates', async () => {
const r = await post({ task: 'review' });
const r = await post({ task: 'review' });
assert.strictEqual(r.status, 200, r.text.slice(0, 300));
assert.strictEqual(r.status, 200, r.text.slice(0, 300));
@@ -13,7 +13,7 @@ const rows = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
@@ -13,7 +13,7 @@ const rows = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
(async () => {
(async () => {
await check('every seed row matches the pinned harness and agent contracts', 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) {
for (const row of rows) {
// The row is one contract instance: the offer and the agents (models) it runs.
// The row is one contract instance: the offer and the agents (models) it runs.
const harnessCheck = contracts.validate('platform.harness-offer@1', row);
const harnessCheck = contracts.validate('platform.harness-offer@1', row);
@@ -54,6 +54,12 @@ const rows = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
@@ -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 () => {
await check('harness and agent ids are unique', async () => {
const ids = rows.flatMap((row) => [row.id, ...row.agents.map((agent) => agent.id)]);
const ids = rows.flatMap((row) => [row.id, ...row.agents.map((agent) => agent.id)]);
assert.strictEqual(new Set(ids).size, ids.length);
assert.strictEqual(new Set(ids).size, ids.length);