openvibers/openvibe.network Commits
Show Muse Code readiness on the machines hub (#816)
Changes
@@@ -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 callBot as the person. A switch is the person's own click; an app's token cannot reach these routes (Network refuses@@@ -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.@@@ -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);@@@ -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 aassert.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');
@@ -48,6 +48,7 @@ function mcCard(m) {
@@ -48,6 +48,7 @@ function mcCard(m) {
card.append(head);
card.append(head);
const roleChips = (m.roles || []).map((id) => (mcRoles || []).find((r) => r.id === id)).filter(Boolean);
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(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)));
(m.pools || []).forEach((pool) => card.append(mcPoolBlock(pool)));
if (Array.isArray(m.running) && m.running.length) {
if (Array.isArray(m.running) && m.running.length) {
@@ -87,6 +88,24 @@ function mcCard(m) {
@@ -87,6 +88,24 @@ function mcCard(m) {
return card;
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 ───────────────────────────────
/* ── 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
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
Bot as the person. A switch is the person's own click; an app's token cannot reach these routes (Network refuses
@@ -114,6 +114,12 @@ function machineOf(m) {
@@ -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.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_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;
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
// 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
// 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.
// the shape is narrowed here (no file roots, tools or paths), as with the rest of the hub's view.
@@ -17,6 +17,12 @@ const principals = require('../server/identity/principals');
@@ -17,6 +17,12 @@ const principals = require('../server/identity/principals');
const machines = require('../server/registry/machines');
const machines = require('../server/registry/machines');
const nodePrincipals = require('../server/registry/link-principals');
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 () => {
(async () => {
const db = getDb();
const db = getDb();
await principals.ensureSchema(db);
await principals.ensureSchema(db);
@@ -70,7 +70,7 @@ assert.ok(NOT_FOUND.ACCOUNT_HUB_PATHS.includes('/machines'), '/machines is the a
@@ -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 ──
// ── Two machines: the online one first, its roles as switches ──
let machines = [
let machines = [
{ id: 'dev_A', kind: 'onboard', online: false, name: 'Old laptop', os: 'Linux', last_seen_at: ago(120), roles: [], running: [] },
{ 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) => {
let p = page({ answers: (m, url, body) => {
if (url === '/api/v1/machines/roles') return { roles: ROLES };
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
@@ -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.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, /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, /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/);
assert.match(cards[1].textContent, /Offline · seen 2 h ago/);
const boxes = cards[0].querySelectorAll('.mc-role input');
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');
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');