openvibers/openvibe.actor Commits

openvibe-shared v4.108.0 (was v4.107.4) (#283)

OP OpenVibers committed 10 days ago · a8bce22605 · 6 files, +37 −21

* openvibe-shared v4.108.0 (was v4.107.4)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v4.108.0 release.

* llms.txt links each page at its Markdown twin, with an ## Optional section (AGT-5)

Changes

ddiff --git a/README.md b/README.md
iindex 157c088..bc9cba6 100644
---- a/README.md
++++ b/README.md
@@@ -258,5 +258,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by [
<!-- versions:start -->
- openvibe-contracts: v0.269.0
- openvibe-sdk: v0.82.0
--- openvibe-shared: v4.107.4
++- openvibe-shared: v4.108.0
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex b735459..66f9499 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -9,11 +9,11 @@
"domain": "openvibe.actor",
"contracts": "openvibe-contracts v0.269.0",
"sdk": "openvibe-sdk v0.82.0",
-- "shared": "openvibe-shared v4.107.4",
++ "shared": "openvibe-shared v4.108.0",
"packages": [
"openvibe-contracts v0.269.0",
"openvibe-sdk v0.82.0",
-- "openvibe-shared v4.107.4"
++ "openvibe-shared v4.108.0"
],
"summary": "Public at https://openvibe.actor (sudo ovhost deploy actor; unit openvibe-actor on 127.0.0.1:4950). A task in plain words is classified, routed by openvibe-sdk/placement to the agent system that can do it (the OpenVibe runtime acting through OpenVibe.Search and OpenVibe.Tools, OpenAI's web agent, an open model on OpenVibe's server for private mode), run with its steps streamed live, checked by a model of another family, and delivered as platform.task@1 with its cost to a fraction of a cent and the explanation; a failed agent or check hands it to the next agent. Free tier per person with an operator ceiling.",
"works": [
ddiff --git a/package-lock.json b/package-lock.json
iindex 3b2b3b4..54a0076 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -17,7 +17,7 @@
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.269.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"
},
"devDependencies": {
@@@ -1190,9 +1190,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",
ddiff --git a/package.json b/package.json
iindex 0afe197..8b52990 100644
---- a/package.json
++++ b/package.json
@@@ -30,7 +30,7 @@
"iovalkey": "^0.4.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.269.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": {
ddiff --git a/server/http/discovery.js b/server/http/discovery.js
iindex 84fe3d6..60236e0 100644
---- a/server/http/discovery.js
++++ b/server/http/discovery.js
@@@ -8,6 +8,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');
@@@ -52,12 +53,22 @@ const publicPages = () => [
* 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 (/agents → /agents.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, '');
@@@ -84,6 +95,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.Actor: an AI agent router and OpenVibe\'s own agent. A task in plain words goes to the best agent system for it, by what the task needs and the caller\'s mode (cheapest, balanced, best, fastest, private); a second model family checks the answer; the task records its cost to a fraction of a cent and why that agent was picked.',
details: 'Agents today: the OpenVibe runtime (a DeepSeek model acting through OpenVibe.Search and OpenVibe.Tools: DNS, mail records, certificates, headers, WHOIS and more), OpenAI\'s web agent (live web search with sources), and an open model on OpenVibe\'s own server for private mode. Coding goes to OpenVibe.Codes. Every page is server-rendered and readable without JavaScript. The API: POST /api/v1/tasks, GET /api/v1/tasks/:id, GET /api/v1/tasks/:id/events (server-sent events), POST /api/v1/tasks/:id/cancel, GET /api/v1/agents and POST /api/v1/route (public dry run). Tasks are platform.task@1 in openvibe-contracts.',
sections: [
@@@ -97,17 +109,17 @@ function createDiscoveryRoutes(ctx) {
{ title: 'The agents (JSON)', url: abs('/api/v1/agents'), note: 'actor.agent-list-result@2' },
{ title: 'The agent manifest (JSON)', url: abs('/.well-known/agent.json'), note: 'what this site tells an agent about itself' },
{ title: 'The free tier (JSON)', url: abs('/limits.json') },
-- { title: 'Sitemap', url: abs('/sitemap.xml') },
-- { title: 'Full text for language models', url: abs('/llms-full.txt') },
-- { title: 'Release metadata (JSON)', url: abs('/release.json') },
], notes: [
'POST /api/v1/route — dry-run the router: send { task, mode? } and get which agent would take it and why (platform.placement-result@1). It answers POST only, so there is no page here to fetch; the API is described at /docs.',
] },
-- { title: 'Elsewhere', links: [
-- { title: 'OpenVibe.Codes', url: 'https://openvibe.codes', note: 'the coding-agent harness Actor sends coding work to' },
-- { title: 'OpenVibe.Tools', url: 'https://openvibe.tools', note: 'the tools the OpenVibe runtime uses' },
-- { title: 'OpenVibe.Services', url: 'https://openvibe.services', note: 'apps, keys and actor.task.* grants' },
-- ] },
++ ],
++ optional: [
++ { title: 'Sitemap', url: abs('/sitemap.xml') },
++ { title: 'Full text for language models', url: abs('/llms-full.txt') },
++ { title: 'Release metadata (JSON)', url: abs('/release.json') },
++ { title: 'OpenVibe.Codes', url: 'https://openvibe.codes', note: 'the coding-agent harness Actor sends coding work to' },
++ { title: 'OpenVibe.Tools', url: 'https://openvibe.tools', note: 'the tools the OpenVibe runtime uses' },
++ { title: 'OpenVibe.Services', url: 'https://openvibe.services', note: 'apps, keys and actor.task.* grants' },
],
}));
});
ddiff --git a/test/discovery.test.js b/test/discovery.test.js
iindex 1cb5dab..a077a45 100644
---- a/test/discovery.test.js
++++ b/test/discovery.test.js
@@@ -48,8 +48,12 @@ const PRIVATE = ['/tasks', '/auth/', '/api/', '/limits.json'];
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.Actor'), 'no title');
-- assert.ok(r.text.includes('(https://openvibe.actor/agents)'), 'no agents link');
-- assert.ok(r.text.includes('(https://openvibe.actor/docs)'), 'no API link');
++ // AGT-5: own pages are linked at their Markdown twins; Optional is the last section.
++ assert.ok(r.text.includes('(https://openvibe.actor/agents.md)'), 'no agents link');
++ assert.ok(r.text.includes('(https://openvibe.actor/docs.md)'), 'no API link');
++ assert.ok(r.text.includes('(https://openvibe.actor/index.md)'), 'the home links its twin');
++ assert.ok(r.text.includes('(https://openvibe.actor/api/v1/agents)') && r.text.includes('(https://openvibe.actor/limits.json)'), 'the API and files keep their URL');
++ 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.actor/sitemap.xml'), 'sitemap not listed');
assert.ok(r.text.includes('(https://openvibe.actor/llms-full.txt)'), 'llms-full.txt not listed');