openvibers/openvibe.codes Commits
openvibe-shared v4.108.0 (was v4.107.4) (#267)
Changes
@@@ -94,7 +94,7 @@ More: [docs/cli.md](docs/cli.md).- **OpenVibe.Network** — SSO (OAuth client `codes`, PKCE S256) and its JWKS.- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional).--- **openvibe-contracts v0.243.0**, **openvibe-sdk v0.74.0**, **openvibe-shared v4.107.4** (pinned tag tarballs).++- **openvibe-contracts v0.243.0**, **openvibe-sdk v0.74.0**, **openvibe-shared v4.108.0** (pinned tag tarballs).No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).@@@ -171,5 +171,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [<!-- versions:start -->- openvibe-contracts: v0.261.0- openvibe-sdk: v0.82.0--- openvibe-shared: v4.107.4++- openvibe-shared: v4.108.0<!-- versions:end -->@@@ -9,11 +9,11 @@"domain": "openvibe.codes","contracts": "openvibe-contracts v0.261.0","sdk": "openvibe-sdk v0.82.0",-- "shared": "openvibe-shared v4.107.4",++ "shared": "openvibe-shared v4.108.0","packages": ["openvibe-contracts v0.261.0","openvibe-sdk v0.82.0",-- "openvibe-shared v4.107.4"++ "openvibe-shared v4.108.0"],"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.5.2) runs a coding task on Claude Code, Claude Code on another provider, Codex, OpenCode, Command Code, MiniMax Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.","works": [@@@ -17,7 +17,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.261.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0","pg": "^8.23.0"},"bin": {@@@ -1193,9 +1193,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.107.4",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",-- "integrity": "sha512-tThN0m3KMncEHVcBOvE+ysHjsZTlVIDLG1P+/i2Mulmxfc/AUpebAXhpWWrut50OChZ8o+HeePxsSrPI8mUQOQ==",++ "version": "4.108.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",++ "integrity": "sha512-NPzwjxpzGIpFpIm4uw3+VwHfsxbJ2+cD9yuhRzSwolSwqv2kCmwaVtHJ0DA9gKO3K8PCBfIP3UpKzpuSbYEJ+g==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -32,7 +32,7 @@"iovalkey": "^0.4.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.261.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0","pg": "^8.23.0"},"engines": {@@@ -14,6 +14,7 @@const fs = require('fs');const path = require('path');const seo = require('openvibe-shared/seo');++const { twinUrl } = require('openvibe-shared/agent-layer');const cache = require('openvibe-shared/cache-policy');const { asyncRouter } = require('./router');@@@ -90,12 +91,22 @@ function describePage(pathname, { governance = [] }) {* follows it gets nothing. So a section may carry notes: plain lines that describe such an* endpoint (POST /api/v1/route — ...) instead of linking it. Every real GET link stays a link.*/--function llmsTxt({ name, summary, details, sections = [] }) {++// AGT-5: each of the site's own pages is linked at its Markdown twin (/start → /start.md, / → /index.md); a file, an++// /api/ or /.well-known/ path and another site's link keep their URL, as openvibe-shared's seo.llmsTxt markdown mode++// does. `optional` is llmstxt.org's "## Optional", last.++function twinOf(url, base) {++ let u;++ try { u = new URL(url); } catch { return url; }++ if (!base || u.origin !== new URL(base).origin || /^\/(api|\.well-known)(\/|$)/.test(u.pathname) || /\.[a-z0-9]{1,8}$/i.test(u.pathname.split('/').pop())) return url;++ return twinUrl(url);++}++++function llmsTxt({ name, summary, details, sections = [], optional = [], base = null }) {const out = [`# ${name}`, '', `> ${String(summary || '').replace(/\s+/g, ' ').trim()}`, ''];if (details) out.push(String(details).trim(), '');-- for (const sec of sections) {++ for (const sec of [...sections, ...(optional.length ? [{ title: 'Optional', links: optional }] : [])]) {if (!sec) continue;-- const links = (sec.links || []).map((l) => '- [' + l.title + '](' + l.url + ')' + (l.note ? ': ' + String(l.note).replace(/\s+/g, ' ').trim() : ''));++ const links = (sec.links || []).map((l) => '- [' + l.title + '](' + twinOf(l.url, base) + ')' + (l.note ? ': ' + String(l.note).replace(/\s+/g, ' ').trim() : ''));const notes = (sec.notes || []).map((n) => String(n).replace(/\s+/g, ' ').trim());if (!links.length && !notes.length) continue;out.push('## ' + sec.title, '');@@@ -122,6 +133,7 @@ function createDiscoveryRoutes(ctx) {r.get('/llms.txt', (_req, res) => {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.',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: [@@@ -130,7 +142,6 @@ function createDiscoveryRoutes(ctx) {{ title: 'Get started', url: abs('/start'), note: 'install, agents, commands, permission levels, hand-offs, events, from your own code' },{ title: 'The coding agents', url: abs('/harnesses'), note: 'every harness with its models, capabilities, prices and limits' },{ title: 'Improve OpenVibe', url: abs('/improve'), note: 'every repository and the path to a reviewed pull request' },-- { title: 'What shipped on OpenVibe.Codes', url: abs('/updates') },] },{ title: 'Community', links: [{ title: 'Community home', url: abs('/policy') },@@@ -139,15 +150,16 @@ function createDiscoveryRoutes(ctx) {{ title: 'Machine-readable', links: [{ title: 'The agent catalog (JSON)', url: abs('/api/v1/harnesses'), note: 'every harness with its agents (platform.harness-offer@1 rows)' },{ title: 'The agent manifest (JSON)', url: abs('/.well-known/agent.json'), note: 'what this site tells an agent about itself' },-- { title: 'Sitemap', url: abs('/sitemap.xml'), note: 'the public pages, each with a real lastmod' },-- { title: 'Full text for language models', url: abs('/llms-full.txt'), note: 'every public page, one line each' },-- { title: 'Release metadata (JSON)', url: abs('/release.json'), note: 'this service\'s current release, per ADR-016' },], notes: ['POST /api/v1/harnesses/route — pick a harness for a task: send { task, requirements? } and get { selected, reasons, candidates }. It answers POST only, so there is no page here to fetch; the catalog and the router are described at /harnesses and /start.',] },-- { title: 'Elsewhere', links: [-- { title: 'OpenVibe.Services', url: SERVICES_ORIGIN, note: 'the developer platform: projects, keys, grants and the API docs of every service' },-- ] },++ ],++ optional: [++ { title: 'What shipped on OpenVibe.Codes', url: abs('/updates') },++ { title: 'Sitemap', url: abs('/sitemap.xml'), note: 'the public pages, each with a real lastmod' },++ { title: 'Full text for language models', url: abs('/llms-full.txt'), note: 'every public page, one line each' },++ { title: 'Release metadata (JSON)', url: abs('/release.json'), note: 'this service\'s current release, per ADR-016' },++ { title: 'OpenVibe.Services', url: SERVICES_ORIGIN, note: 'the developer platform: projects, keys, grants and the API docs of every service' },],}));});@@@ -47,8 +47,11 @@ const PRIVATE = ['/projects', '/staff', '/auth/', '/oauth/test-callback'];assert.match(r.headers.get('content-type'), /^text\/plain/);assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);assert.ok(r.text.startsWith('# OpenVibe.Codes'), 'no title');-- assert.ok(r.text.includes('(https://openvibe.codes/harnesses)'), 'no agents link');-- assert.ok(r.text.includes('(https://openvibe.codes/improve)'), 'no Improve OpenVibe link');++ // AGT-5: own pages are linked at their Markdown twins; Optional is the last section.++ assert.ok(r.text.includes('(https://openvibe.codes/harnesses.md)'), 'no agents link');++ assert.ok(r.text.includes('(https://openvibe.codes/improve.md)'), 'no Improve OpenVibe link');++ assert.ok(r.text.includes('(https://openvibe.codes/index.md)') && r.text.includes('(https://openvibe.codes/api/v1/harnesses)'), 'the home at its twin, the API as it is');++ assert.ok(r.text.includes('\n## Optional\n') && r.text.lastIndexOf('\n## ') === r.text.indexOf('\n## Optional\n'), 'Optional is last');assert.ok(r.text.includes('(https://openvibe.services)'), 'no pointer to the developer platform');assert.ok(r.text.includes('https://openvibe.codes/sitemap.xml'), 'sitemap not listed');assert.ok(r.text.includes('(https://openvibe.codes/llms-full.txt)'), 'llms-full.txt not listed');
@@ -94,7 +94,7 @@ More: [docs/cli.md](docs/cli.md).
@@ -94,7 +94,7 @@ More: [docs/cli.md](docs/cli.md).
- **OpenVibe.Network** — SSO (OAuth client `codes`, PKCE S256) and its JWKS.
- **OpenVibe.Network** — SSO (OAuth client `codes`, PKCE S256) and its JWKS.
- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional).
- **PostgreSQL 18 and Valkey 9** (OpenVibe.Host `roles/data/`, ADR-035): `openvibe-sdk/db`; Valkey holds the per-actor limit counters (optional).
-- **openvibe-contracts v0.243.0**, **openvibe-sdk v0.74.0**, **openvibe-shared v4.107.4** (pinned tag tarballs).
+- **openvibe-contracts v0.243.0**, **openvibe-sdk v0.74.0**, **openvibe-shared v4.108.0** (pinned tag tarballs).
No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).
No path in Codes sends or accepts a shared loopback key (tested by grep and at runtime).
@@ -171,5 +171,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
@@ -171,5 +171,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.261.0
- openvibe-contracts: v0.261.0
- openvibe-sdk: v0.82.0
- openvibe-sdk: v0.82.0
-- openvibe-shared: v4.107.4
+- openvibe-shared: v4.108.0
<!-- versions:end -->
<!-- versions:end -->
@@ -9,11 +9,11 @@
@@ -9,11 +9,11 @@
"domain": "openvibe.codes",
"domain": "openvibe.codes",
"contracts": "openvibe-contracts v0.261.0",
"contracts": "openvibe-contracts v0.261.0",
"sdk": "openvibe-sdk v0.82.0",
"sdk": "openvibe-sdk v0.82.0",
- "shared": "openvibe-shared v4.107.4",
+ "shared": "openvibe-shared v4.108.0",
"packages": [
"packages": [
"openvibe-contracts v0.261.0",
"openvibe-contracts v0.261.0",
"openvibe-sdk v0.82.0",
"openvibe-sdk v0.82.0",
- "openvibe-shared v4.107.4"
+ "openvibe-shared v4.108.0"
],
],
"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.5.2) runs a coding task on Claude Code, Claude Code on another provider, Codex, OpenCode, Command Code, MiniMax Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.",
"summary": "Public at https://openvibe.codes (sudo ovhost deploy codes; unit openvibe-codes on 127.0.0.1:4900). Since 2026-10-08 Codes is the open coding-agent harness: openvibe-codes (a command-line tool and Node.js module, v0.5.2) runs a coding task on Claude Code, Claude Code on another provider, Codex, OpenCode, Command Code, MiniMax Code, Aider, the DeepSeek API or a person's own OpenAI-compatible model on their machine, hands it to the next agent when one fails or goes quiet, and keeps every run; the site carries the agent catalog with a public routing API, getting started, Improve OpenVibe and the community documents. The developer console that used to live here moved to OpenVibe.Services (https://openvibe.services) and every one of its addresses answers a permanent redirect there.",
"works": [
"works": [
@@ -17,7 +17,7 @@
@@ -17,7 +17,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.261.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.261.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"bin": {
"bin": {
@@ -1193,9 +1193,9 @@
@@ -1193,9 +1193,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.107.4",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
- "integrity": "sha512-tThN0m3KMncEHVcBOvE+ysHjsZTlVIDLG1P+/i2Mulmxfc/AUpebAXhpWWrut50OChZ8o+HeePxsSrPI8mUQOQ==",
+ "version": "4.108.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
+ "integrity": "sha512-NPzwjxpzGIpFpIm4uw3+VwHfsxbJ2+cD9yuhRzSwolSwqv2kCmwaVtHJ0DA9gKO3K8PCBfIP3UpKzpuSbYEJ+g==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -32,7 +32,7 @@
@@ -32,7 +32,7 @@
"iovalkey": "^0.4.0",
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.261.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.261.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.82.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.107.4",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.108.0",
"pg": "^8.23.0"
"pg": "^8.23.0"
},
},
"engines": {
"engines": {
@@ -14,6 +14,7 @@
@@ -14,6 +14,7 @@
const fs = require('fs');
const fs = require('fs');
const path = require('path');
const path = require('path');
const seo = require('openvibe-shared/seo');
const seo = require('openvibe-shared/seo');
+const { twinUrl } = require('openvibe-shared/agent-layer');
const cache = require('openvibe-shared/cache-policy');
const cache = require('openvibe-shared/cache-policy');
const { asyncRouter } = require('./router');
const { asyncRouter } = require('./router');
@@ -90,12 +91,22 @@ function describePage(pathname, { governance = [] }) {
@@ -90,12 +91,22 @@ function describePage(pathname, { governance = [] }) {
* follows it gets nothing. So a section may carry notes: plain lines that describe such an
* follows it gets nothing. So a section may carry notes: plain lines that describe such an
* endpoint (POST /api/v1/route — ...) instead of linking it. Every real GET link stays a link.
* endpoint (POST /api/v1/route — ...) instead of linking it. Every real GET link stays a link.
*/
*/
-function llmsTxt({ name, summary, details, sections = [] }) {
+// AGT-5: each of the site's own pages is linked at its Markdown twin (/start → /start.md, / → /index.md); a file, an
+// /api/ or /.well-known/ path and another site's link keep their URL, as openvibe-shared's seo.llmsTxt markdown mode
+// does. `optional` is llmstxt.org's "## Optional", last.
+function twinOf(url, base) {
+ let u;
+ try { u = new URL(url); } catch { return url; }
+ if (!base || u.origin !== new URL(base).origin || /^\/(api|\.well-known)(\/|$)/.test(u.pathname) || /\.[a-z0-9]{1,8}$/i.test(u.pathname.split('/').pop())) return url;
+ return twinUrl(url);
+}
+
+function llmsTxt({ name, summary, details, sections = [], optional = [], base = null }) {
const out = [`# ${name}`, '', `> ${String(summary || '').replace(/\s+/g, ' ').trim()}`, ''];
const out = [`# ${name}`, '', `> ${String(summary || '').replace(/\s+/g, ' ').trim()}`, ''];
if (details) out.push(String(details).trim(), '');
if (details) out.push(String(details).trim(), '');
- for (const sec of sections) {
+ for (const sec of [...sections, ...(optional.length ? [{ title: 'Optional', links: optional }] : [])]) {
if (!sec) continue;
if (!sec) continue;
- const links = (sec.links || []).map((l) => '- [' + l.title + '](' + l.url + ')' + (l.note ? ': ' + String(l.note).replace(/\s+/g, ' ').trim() : ''));
+ const links = (sec.links || []).map((l) => '- [' + l.title + '](' + twinOf(l.url, base) + ')' + (l.note ? ': ' + String(l.note).replace(/\s+/g, ' ').trim() : ''));
const notes = (sec.notes || []).map((n) => String(n).replace(/\s+/g, ' ').trim());
const notes = (sec.notes || []).map((n) => String(n).replace(/\s+/g, ' ').trim());
if (!links.length && !notes.length) continue;
if (!links.length && !notes.length) continue;
out.push('## ' + sec.title, '');
out.push('## ' + sec.title, '');
@@ -122,6 +133,7 @@ function createDiscoveryRoutes(ctx) {
@@ -122,6 +133,7 @@ function createDiscoveryRoutes(ctx) {
r.get('/llms.txt', (_req, res) => {
r.get('/llms.txt', (_req, res) => {
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,
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, 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: [
@@ -130,7 +142,6 @@ function createDiscoveryRoutes(ctx) {
@@ -130,7 +142,6 @@ function createDiscoveryRoutes(ctx) {
{ title: 'Get started', url: abs('/start'), note: 'install, agents, commands, permission levels, hand-offs, events, from your own code' },
{ title: 'Get started', url: abs('/start'), note: 'install, agents, commands, permission levels, hand-offs, events, from your own code' },
{ title: 'The coding agents', url: abs('/harnesses'), note: 'every harness with its models, capabilities, prices and limits' },
{ title: 'The coding agents', url: abs('/harnesses'), note: 'every harness with its models, capabilities, prices and limits' },
{ title: 'Improve OpenVibe', url: abs('/improve'), note: 'every repository and the path to a reviewed pull request' },
{ title: 'Improve OpenVibe', url: abs('/improve'), note: 'every repository and the path to a reviewed pull request' },
- { title: 'What shipped on OpenVibe.Codes', url: abs('/updates') },
] },
] },
{ title: 'Community', links: [
{ title: 'Community', links: [
{ title: 'Community home', url: abs('/policy') },
{ title: 'Community home', url: abs('/policy') },
@@ -139,15 +150,16 @@ function createDiscoveryRoutes(ctx) {
@@ -139,15 +150,16 @@ function createDiscoveryRoutes(ctx) {
{ title: 'Machine-readable', links: [
{ title: 'Machine-readable', links: [
{ title: 'The agent catalog (JSON)', url: abs('/api/v1/harnesses'), note: 'every harness with its agents (platform.harness-offer@1 rows)' },
{ title: 'The agent catalog (JSON)', url: abs('/api/v1/harnesses'), note: 'every harness with its agents (platform.harness-offer@1 rows)' },
{ title: 'The agent manifest (JSON)', url: abs('/.well-known/agent.json'), note: 'what this site tells an agent about itself' },
{ title: 'The agent manifest (JSON)', url: abs('/.well-known/agent.json'), note: 'what this site tells an agent about itself' },
- { title: 'Sitemap', url: abs('/sitemap.xml'), note: 'the public pages, each with a real lastmod' },
- { title: 'Full text for language models', url: abs('/llms-full.txt'), note: 'every public page, one line each' },
- { title: 'Release metadata (JSON)', url: abs('/release.json'), note: 'this service\'s current release, per ADR-016' },
], notes: [
], notes: [
'POST /api/v1/harnesses/route — pick a harness for a task: send { task, requirements? } and get { selected, reasons, candidates }. It answers POST only, so there is no page here to fetch; the catalog and the router are described at /harnesses and /start.',
'POST /api/v1/harnesses/route — pick a harness for a task: send { task, requirements? } and get { selected, reasons, candidates }. It answers POST only, so there is no page here to fetch; the catalog and the router are described at /harnesses and /start.',
] },
] },
- { title: 'Elsewhere', links: [
- { title: 'OpenVibe.Services', url: SERVICES_ORIGIN, note: 'the developer platform: projects, keys, grants and the API docs of every service' },
- ] },
+ ],
+ optional: [
+ { title: 'What shipped on OpenVibe.Codes', url: abs('/updates') },
+ { title: 'Sitemap', url: abs('/sitemap.xml'), note: 'the public pages, each with a real lastmod' },
+ { title: 'Full text for language models', url: abs('/llms-full.txt'), note: 'every public page, one line each' },
+ { title: 'Release metadata (JSON)', url: abs('/release.json'), note: 'this service\'s current release, per ADR-016' },
+ { title: 'OpenVibe.Services', url: SERVICES_ORIGIN, note: 'the developer platform: projects, keys, grants and the API docs of every service' },
],
],
}));
}));
});
});
@@ -47,8 +47,11 @@ const PRIVATE = ['/projects', '/staff', '/auth/', '/oauth/test-callback'];
@@ -47,8 +47,11 @@ const PRIVATE = ['/projects', '/staff', '/auth/', '/oauth/test-callback'];
assert.match(r.headers.get('content-type'), /^text\/plain/);
assert.match(r.headers.get('content-type'), /^text\/plain/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
assert.match(r.headers.get('cache-control') || '', /public, max-age=\d+/);
assert.ok(r.text.startsWith('# OpenVibe.Codes'), 'no title');
assert.ok(r.text.startsWith('# OpenVibe.Codes'), 'no title');
- assert.ok(r.text.includes('(https://openvibe.codes/harnesses)'), 'no agents link');
- assert.ok(r.text.includes('(https://openvibe.codes/improve)'), 'no Improve OpenVibe link');
+ // AGT-5: own pages are linked at their Markdown twins; Optional is the last section.
+ assert.ok(r.text.includes('(https://openvibe.codes/harnesses.md)'), 'no agents link');
+ assert.ok(r.text.includes('(https://openvibe.codes/improve.md)'), 'no Improve OpenVibe link');
+ assert.ok(r.text.includes('(https://openvibe.codes/index.md)') && r.text.includes('(https://openvibe.codes/api/v1/harnesses)'), 'the home at its twin, the API as it is');
+ assert.ok(r.text.includes('\n## Optional\n') && r.text.lastIndexOf('\n## ') === r.text.indexOf('\n## Optional\n'), 'Optional is last');
assert.ok(r.text.includes('(https://openvibe.services)'), 'no pointer to the developer platform');
assert.ok(r.text.includes('(https://openvibe.services)'), 'no pointer to the developer platform');
assert.ok(r.text.includes('https://openvibe.codes/sitemap.xml'), 'sitemap not listed');
assert.ok(r.text.includes('https://openvibe.codes/sitemap.xml'), 'sitemap not listed');
assert.ok(r.text.includes('(https://openvibe.codes/llms-full.txt)'), 'llms-full.txt not listed');
assert.ok(r.text.includes('(https://openvibe.codes/llms-full.txt)'), 'llms-full.txt not listed');