openvibers/openvibe.network Commits

Show Muse Code readiness on the machines hub (#816)

OP OpenVibers committed 2 days ago · c2ccd82a61 · 4 files, +37 −1

Changes

ddiff --git a/public/js/my/machines.js b/public/js/my/machines.js
iindex 5e2905f0..7c24e7ed 100644
---- a/public/js/my/machines.js
++++ b/public/js/my/machines.js
@@@ -48,6 +48,7 @@ function mcCard(m) {
card.append(head);
const roleChips = (m.roles || []).map((id) => (mcRoles || []).find((r) => r.id === id)).filter(Boolean);
card.append(mcEl('div', 'mc-role-chips', roleChips.length ? roleChips.map((r) => mcEl('span', 'mc-role-chip', [r.name])) : [mcEl('span', 'mc-role-chip is-empty', ['No roles assigned'])]));
++ card.append(mcMuseStatus(m));
(m.pools || []).forEach((pool) => card.append(mcPoolBlock(pool)));
if (Array.isArray(m.running) && m.running.length) {
@@@ -87,6 +88,24 @@ function mcCard(m) {
return card;
}
++/** Muse runs on this machine under its owner's own sign-in; the last Link reading is only a status. */
++function mcMuseStatus(m) {
++ const muse = Array.isArray(m.fleet) ? m.fleet.find((a) => a.agent === 'muse') : null;
++ const fleetAge = m.fleet_at ? Date.now() - Date.parse(m.fleet_at) : NaN;
++ const stale = m.fleet_at && (!Number.isFinite(fleetAge) || fleetAge > 5 * 60000);
++ const state = !m.online ? 'Offline' : !muse || stale ? 'Status unavailable' : !muse.installed ? 'Not installed' : !muse.signed_in ? 'Sign in' : 'Ready';
++ const row = mcEl('div', 'mc-role', [
++ mcEl('span', 'mc-role-ic', [mcIcon('fa-code')]),
++ mcEl('span', 'mc-role-t', [mcEl('b', '', ['Muse Code']), mcEl('small', '', [state])]),
++ ]);
++ const setup = mcEl('details', '', [mcEl('summary', '', ['Set up Muse Code'])]);
++ setup.append(mcEl('p', '', ['Install Muse Code on this machine, run ', mcEl('code', '', ['muse login']), ', then run the Link installer again with ', mcEl('code', '', ['--plan-logins']), '. Jobs use your own Muse sign-in.']));
++ const windows = /windows/i.test(String(m.os || ''));
++ setup.append(mcEl('pre', '', [mcEl('code', '', [windows ? 'irm https://dev.meta.ai/install.ps1 | iex' : 'curl -fsSL https://dev.meta.ai/install.sh | sh'])]));
++ setup.append(mcEl('p', '', [mcEl('a', '', ['Worker setup'], { href: 'https://github.com/OpenVibers/OpenVibe.Link/blob/main/docs/worker.md' })]));
++ return mcEl('div', 'mc-roles', [row, setup]);
++}
++
/* ── The owner's switches, the live screen, its links, and the machine's desks ───────────────────────────────
Everything here answers from what Bot reports (the machine's controls) and calls the hub's routes, which call
Bot as the person. A switch is the person's own click; an app's token cannot reach these routes (Network refuses
ddiff --git a/server/registry/machines.js b/server/registry/machines.js
iindex cef9e89e..c782f737 100644
---- a/server/registry/machines.js
++++ b/server/registry/machines.js
@@@ -114,6 +114,12 @@ function machineOf(m) {
if (typeof m.last_seen_at === 'string' && m.last_seen_at) out.last_seen_at = m.last_seen_at;
if (typeof m.node_version === 'string' && m.node_version) out.node_version = m.node_version;
if (typeof m.node_id === 'string' && PRINCIPAL.test(m.node_id)) out.principal_id = m.node_id;
++ // Bot has already bounded this Link heartbeat. Keep only the readiness facts the
++ // owner's machine card needs; never pass a login, key, or account through.
++ if (Array.isArray(m.fleet)) out.fleet = m.fleet.slice(0, 16).filter((a) => a && /^[a-z0-9-]{1,40}$/.test(a.agent)).map((a) => ({
++ agent: a.agent, installed: a.installed === true, signed_in: a.signed_in === true,
++ }));
++ if (typeof m.fleet_at === 'string' && m.fleet_at) out.fleet_at = m.fleet_at;
// The owner's controls (the Link hub's machine card): each switch the machine itself reported, plus what its
// computer plugin offers. The values are Bot's, never an assumption: a machine that has not said is off. Only
// the shape is narrowed here (no file roots, tools or paths), as with the rest of the hub's view.
ddiff --git a/test/machines-hub.test.js b/test/machines-hub.test.js
iindex cd0d607c..6c69d2dc 100644
---- a/test/machines-hub.test.js
++++ b/test/machines-hub.test.js
@@@ -17,6 +17,12 @@ const principals = require('../server/identity/principals');
const machines = require('../server/registry/machines');
const nodePrincipals = require('../server/registry/link-principals');
++assert.deepStrictEqual(machines.machineOf({ id: 'dev_test', fleet: [
++ { agent: 'muse', installed: true, signed_in: true, windows: [{ name: 'plan', used: 2, limit: 10 }], access_token: 'never-forward' },
++], fleet_at: '2026-11-11T04:00:00Z' }).fleet, [
++ { agent: 'muse', installed: true, signed_in: true },
++], 'the owner gets Muse readiness, without usage or credentials');
++
(async () => {
const db = getDb();
await principals.ensureSchema(db);
ddiff --git a/test/my-machines.test.js b/test/my-machines.test.js
iindex d2b10335..15dac088 100644
---- a/test/my-machines.test.js
++++ b/test/my-machines.test.js
@@@ -70,7 +70,7 @@ assert.ok(NOT_FOUND.ACCOUNT_HUB_PATHS.includes('/machines'), '/machines is the a
// ── Two machines: the online one first, its roles as switches ──
let machines = [
{ id: 'dev_A', kind: 'onboard', online: false, name: 'Old laptop', os: 'Linux', last_seen_at: ago(120), roles: [], running: [] },
-- { id: 'dev_B', kind: 'onboard', online: true, name: 'Studio PC', os: 'Windows 11', node_version: '0.31.0', roles: ['codes.worker'], running: [{ title: 'Fix the WHIP reconnect' }] },
++ { id: 'dev_B', kind: 'onboard', online: true, name: 'Studio PC', os: 'Windows 11', node_version: '0.31.0', roles: ['codes.worker'], running: [{ title: 'Fix the WHIP reconnect' }], fleet: [{ agent: 'muse', installed: true, signed_in: true }], fleet_at: new Date().toISOString() },
];
let p = page({ answers: (m, url, body) => {
if (url === '/api/v1/machines/roles') return { roles: ROLES };
@@@ -85,6 +85,11 @@ assert.ok(NOT_FOUND.ACCOUNT_HUB_PATHS.includes('/machines'), '/machines is the a
assert.deepStrictEqual(cards.map((c) => c.getAttribute('data-machine')), ['dev_B', 'dev_A'], 'online first');
assert.match(cards[0].textContent, /Studio PC.*Online.*Windows 11 · Node 0\.31\.0/);
assert.match(cards[0].textContent, /Fix the WHIP reconnect/, 'what it runs');
++ assert.match(cards[0].textContent, /Muse CodeReady.*muse login.*--plan-logins.*install.ps1/, 'ready and Windows setup on its card');
++ assert.match(p.sandbox.mcMuseStatus({ online: true, fleet: [{ agent: 'muse', installed: true, signed_in: false }] }).textContent, /Muse CodeSign in/);
++ assert.match(p.sandbox.mcMuseStatus({ online: true, fleet: [{ agent: 'muse', installed: false, signed_in: false }] }).textContent, /Muse CodeNot installed/);
++ assert.match(p.sandbox.mcMuseStatus({ online: true }).textContent, /Muse CodeStatus unavailable/);
++ assert.match(p.sandbox.mcMuseStatus({ online: true, fleet: [{ agent: 'muse', installed: true, signed_in: true }], fleet_at: ago(10) }).textContent, /Muse CodeStatus unavailable/, 'old readings are not current readiness');
assert.match(cards[1].textContent, /Offline · seen 2 h ago/);
const boxes = cards[0].querySelectorAll('.mc-role input');
assert.deepStrictEqual(boxes.map((b) => [b.value, b.checked]), [['codes.worker', true], ['desks.host', false], ['ai.host', false]], 'a switch per role, the machine\'s own on');