openvibers/openvibe.network Commits

Exchange joins the AI neighbourhood, and may run ai.complete (#588)

OP OpenVibers committed 7 days ago · 1405330deb · 4 files, +12 −6

The Exchange remake (2026-11-06) makes it the network's AI front: the launcher
moves it out of Shop & swap into For developers beside AI, its menu line becomes
"AI models, one account", and Network grants the exchange client
ai.run.create (namespace ai.complete) so a chat or a Try it runs on the person's
behalf (on_behalf_of).

Changes

ddiff --git a/server/frame/sites.js b/server/frame/sites.js
iindex c2820fe8..67455b63 100644
---- a/server/frame/sites.js
++++ b/server/frame/sites.js
@@@ -37,18 +37,18 @@ const fullName = (s) => (/^Open[A-Z]/.test(String(s && s.name)) ? s.name : `Open
// each. A site missing here lands in the last group, so a new manifest is never left out of the menu.
// The six neighbourhoods of the 2026-10-28 plan (§3.1): each label is two words, so it never collides with a site's own
// name, and they follow what a person came to do. Rooms not open yet (Cam, Tours, Surf, Fun, Meme, Casino, Today, Report,
--// Vision, Website, Design, Domains, open.shoes, Exchange) are named here so they land in their neighbourhood the day
++// Vision, Website, Design, Domains, open.shoes) are named here so they land in their neighbourhood the day
// their manifest opens. The building blocks stay last, "For developers" (the tail of Make & build).
const GROUPS = [
{ id: 'streams', name: 'Streams & places', sites: ['live', 'media-hub', 'stream', 'pics', 'download', 'cam', 'tours'] },
{ id: 'games', name: 'Games & laughs', sites: ['games', 'surf', 'fun', 'meme', 'casino', 'quest'] },
{ id: 'answers', name: 'News & answers', sites: ['today', 'report', 'news', 'search', 'wiki', 'blog', 'reviews', 'vision'] },
{ id: 'make', name: 'Make & build', sites: ['website', 'connect', 'design', 'domains', 'codes', 'space', 'tools', 'host', 'bot'] },
-- { id: 'shop', name: 'Shop & swap', sites: ['deals', 'coupons', 'shoes', 'exchange', 'rent', 'homes', 'work', 'food', 'trade'] },
++ { id: 'shop', name: 'Shop & swap', sites: ['deals', 'coupons', 'shoes', 'rent', 'homes', 'work', 'food', 'trade'] },
{ id: 'people', name: 'People & you', sites: ['chat', 'community', 'tips', 'vip', 'help', 'network', 'inventory'] },
// Actor is the engine behind every agent (Codes' Auto, Vibe, the Dock call it): a developer service, not a second
// place to chat with an agent (plan L39, 2026-10-31).
-- { id: 'build', name: 'For developers', sites: ['services', 'ai', 'actor', 'bench', 'run', 'zone', 'events', 'media', 'watch', 'play'] },
++ { id: 'build', name: 'For developers', sites: ['services', 'ai', 'exchange', 'actor', 'bench', 'run', 'zone', 'events', 'media', 'watch', 'play'] },
];
// What each site is, in a few words, for the menus (the launcher's rows are two to a line on a phone: about 22
// characters fit). The manifests' taglines stay the sites' own sentences everywhere else; a site missing here shows
@@@ -62,7 +62,7 @@ const MENU_LINE = {
deals: 'Deals people share', coupons: 'Codes that work', reviews: 'Honest reviews', work: 'Find a job', rent: 'Places to rent',
homes: 'Homes for sale', food: 'Food near you', help: 'Help nearby', trade: 'Watch the markets', watch: 'Know when it changes',
search: 'Search everything', host: 'Compare hosting', report: 'Your numbers, honest', vision: 'Ask a picture',
-- exchange: 'Apps, skills, compute',
++ exchange: 'AI models, one account',
network: 'Your account', inventory: 'Your items',
services: 'APIs, SDK and keys', ai: 'AI router, your keys', bench: 'Which AI is best', run: 'Run jobs anywhere', zone: 'S3 storage', events: 'Event streams', media: 'Media API', play: 'Game services',
};
ddiff --git a/server/identity/principals.js b/server/identity/principals.js
iindex 5c4ac7f6..80c67f62 100644
---- a/server/identity/principals.js
++++ b/server/identity/principals.js
@@@ -693,8 +693,11 @@ const DEFAULT_GRANTS = [
['vision', 'network.notifications.push', SELF_AUDIENCE, ['vision']],
// OpenVibe.Exchange (contracts 0.235.0): its public listings in Search through the owner API (owner exchange), and
// its account-event subscriptions; its export/deletion pair is above. v1 writes no grant and runs no sandbox code.
++ // The 2026-11-06 remake makes Exchange the network's AI front: chat and Try it run ai.complete on a person's
++ // behalf (on_behalf_of), and the catalog/usage reads are public.
['exchange', 'search.document.write', 'openvibe.search', []],
['exchange', 'events.subscription.manage', 'openvibe.events', []],
++ ['exchange', 'ai.run.create', 'openvibe.ai', ['ai.complete']],
// OpenVibe.Fun (contracts 0.236.0): its public quests and plans in Search through the owner API (owner fun), and
// its account-event subscriptions; its export/deletion pair is above. Live and Chat are public reads and Today
// uses the viewer's own token, so it holds no Live, Chat or Today grant. The optional OpenCoin grants
ddiff --git a/test/frame.test.js b/test/frame.test.js
iindex f7d20d3c..b5fb74b4 100644
---- a/test/frame.test.js
++++ b/test/frame.test.js
@@@ -45,7 +45,7 @@ assert.ok(at('pics') === at('media-hub') + 1 && at('homes') === at('rent') + 1,
assert.deepStrictEqual([p.nav[at('media-hub')].icon, p.nav[at('media-hub')].group, p.nav[at('homes')].group, p.nav[at('tools')].group], ['video', 'streams', 'shop', 'make'], 'Video draws the video icon (there is no media-hub icon)');
assert.deepStrictEqual(['actor', 'bot', 'run', 'zone', 'codes'].map((id) => p.nav[at(id)].group), ['build', 'make', 'build', 'build', 'make'], 'robots and coding agents for people; the agent engine, compute and storage for developers (plan L39: Actor is the engine Codes, Vibe and the Dock call)');
// Games together, items with the account, Alerts with the building blocks, files beside Video (each when its site is open).
--[['games', 'games'], ['connect', 'make'], ['quest', 'games'], ['inventory', 'people'], ['watch', 'build'], ['download', 'streams'], ['search', 'answers'], ['chat', 'people'], ['domains', 'make'], ['report', 'answers'], ['design', 'make'], ['today', 'answers'], ['surf', 'games'], ['meme', 'games'], ['vision', 'answers'], ['exchange', 'shop'], ['fun', 'games'], ['cam', 'streams'], ['bench', 'build'], ['casino', 'games'], ['tours', 'streams'], ['shoes', 'shop']]
++[['games', 'games'], ['connect', 'make'], ['quest', 'games'], ['inventory', 'people'], ['watch', 'build'], ['download', 'streams'], ['search', 'answers'], ['chat', 'people'], ['domains', 'make'], ['report', 'answers'], ['design', 'make'], ['today', 'answers'], ['surf', 'games'], ['meme', 'games'], ['vision', 'answers'], ['exchange', 'build'], ['fun', 'games'], ['cam', 'streams'], ['bench', 'build'], ['casino', 'games'], ['tours', 'streams'], ['shoes', 'shop']]
.filter(([id]) => at(id) >= 0).forEach(([id, group]) => assert.strictEqual(p.nav[at(id)].group, group, `${id} is in ${group}`));
assert.strictEqual(p.nav.length, 55); // Connect since contracts 0.279.0, Tours and Shoes since contracts 0.265.0, Casino since contracts 0.264.0, Domains since contracts 0.226.0, Report since 0.227.0, Design since 0.228.0, Today since 0.230.0, Surf since 0.231.0, Meme since 0.234.0, Vision and Exchange since 0.235.0, Fun since 0.236.0, Cam since 0.237.0, Bench since 0.238.0
assert.deepStrictEqual([p.nav[at('stream')].name, p.nav[at('pics')].name], ['Restream', 'Pics'], 'OpenRestream is "Restream" among the menu\'s short names (owner, 2026-10-28)');
ddiff --git a/test/principals.test.js b/test/principals.test.js
iindex 59d84b30..590ac2f6 100644
---- a/test/principals.test.js
++++ b/test/principals.test.js
@@@ -150,7 +150,10 @@ const server = http.createServer(app);
t = await token({ client_id: 'exchange', client_secret: 'exchange-secret', audience: 'openvibe.search' });
assert.strictEqual(t.body.scope, 'search.document.write', 'Exchange indexes its public listings in Search');
t = await token({ client_id: 'exchange', client_secret: 'exchange-secret', audience: 'openvibe.ai' });
-- assert.strictEqual(t.status, 400, 'Exchange holds no AI capability in v1');
++ assert.strictEqual(t.status, 200, 'the remake runs AI chat and Try it on a person’s behalf');
++ const exchangeAiClaims = serviceAuth.verifyServiceToken(t.body.access_token, { publicKey: keys.publicKey, issuer: ISSUER, audience: 'openvibe.ai' });
++ assert.ok(exchangeAiClaims.ok, exchangeAiClaims.reason);
++ assert.deepStrictEqual([exchangeAiClaims.claims.cap, exchangeAiClaims.claims.ns], [['ai.run.create'], ['ai.complete']], 'Exchange runs ai.complete for a person');
// OpenVibe.Fun (contracts 0.236.0): its public quests and plans in Search, its account-event subscriptions and the
// export/deletion pair. Live and Chat are public reads and Today uses the viewer's token, so it holds no such grant.
await db.prepare("UPDATE oauth_clients SET client_secret = 'fun-secret' WHERE client_id = 'fun'").run();