@livedesk/hub 0.1.49 → 0.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/server.js CHANGED
@@ -8,12 +8,12 @@ import { dirname, resolve } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import os from 'node:os';
10
10
  import { WebSocketServer } from 'ws';
11
- import { createRemoteHub } from './remote-hub.js';
12
- import {
13
- REMOTE_CLIPBOARD_INPUT_ACTIONS,
14
- RemoteClipboardContractError,
15
- normalizeRemoteClipboardRequest
16
- } from './remote-clipboard-contract.mjs';
11
+ import { createRemoteHub } from './remote-hub.js';
12
+ import {
13
+ REMOTE_CLIPBOARD_INPUT_ACTIONS,
14
+ RemoteClipboardContractError,
15
+ normalizeRemoteClipboardRequest
16
+ } from './remote-clipboard-contract.mjs';
17
17
  import { createHubConsoleRelay } from './console-relay.js';
18
18
  import {
19
19
  buildImmutableRemoteFramePacket,
@@ -151,7 +151,7 @@ const atlasPool = new Mode4AtlasPool({
151
151
  onStatus: status => {
152
152
  if (status?.type !== 'log') return;
153
153
  const logger = status.level === 'warn' ? console.warn : console.log;
154
- logger(`[LiveDesk Hub] ${status.message}`);
154
+ logger(`[VuvoDesk Hub] ${status.message}`);
155
155
  }
156
156
  });
157
157
  const inputClients = new Set();
@@ -266,7 +266,7 @@ function handleRemoteHubEvent(type, event) {
266
266
  hubTransferJobs?.handleRemoteEvent(type, event);
267
267
  if (traceRemoteTestEventsEnabled
268
268
  && (type === 'RemoteFrameDropped' || type === 'RemoteFrameTransportProofAccepted')) {
269
- console.warn(`[LiveDesk Hub Test Event] ${type} ${JSON.stringify({
269
+ console.warn(`[VuvoDesk Hub Test Event] ${type} ${JSON.stringify({
270
270
  reason: event?.reason || '',
271
271
  deviceId: event?.deviceId || event?.device?.deviceId || '',
272
272
  proofOnly: event?.proofOnly === true,
@@ -572,7 +572,7 @@ async function getRuntimeAccessToken() {
572
572
  expires_at: Math.floor(runtimeAccessTokenExpiresAt / 1000)
573
573
  });
574
574
  } catch (error) {
575
- console.warn(`[LiveDesk Hub] Refreshed session persistence failed: ${error?.message || error}`);
575
+ console.warn(`[VuvoDesk Hub] Refreshed session persistence failed: ${error?.message || error}`);
576
576
  }
577
577
  return runtimeAccessToken;
578
578
  }
@@ -658,7 +658,7 @@ async function watchAuthoritativeRuntimeRole() {
658
658
  try {
659
659
  const role = await queryAuthoritativeRuntimeRole();
660
660
  if (role === 'client') {
661
- console.warn('[LiveDesk Hub] Supabase selected another Sync Server. Transitioning this runtime to Client.');
661
+ console.warn('[VuvoDesk Hub] Supabase selected another Sync Server. Transitioning this runtime to Client.');
662
662
  void clearHubHostTarget('role-demoted').finally(() => {
663
663
  setTimeout(() => process.exit(ROLE_TRANSITION_EXIT_CODE), 150);
664
664
  });
@@ -692,16 +692,16 @@ const agentDataDir = process.env.LIVEDESK_DATA_DIR || undefined;
692
692
  const liveDeskSettingsStore = new LiveDeskSettingsStore({ dataDir: agentDataDir });
693
693
  const captureStore = new CaptureStore({ dataDir: agentDataDir });
694
694
  void captureStore.initialize().catch(error => {
695
- console.warn(`[LiveDesk Hub] capture store initialization failed: ${error instanceof Error ? error.message : String(error)}`);
695
+ console.warn(`[VuvoDesk Hub] capture store initialization failed: ${error instanceof Error ? error.message : String(error)}`);
696
696
  });
697
697
  void liveDeskSettingsStore.getRecord().catch(error => {
698
- console.warn(`[LiveDesk Hub] settings load failed: ${error instanceof Error ? error.message : String(error)}`);
698
+ console.warn(`[VuvoDesk Hub] settings load failed: ${error instanceof Error ? error.message : String(error)}`);
699
699
  });
700
700
 
701
701
  const udpTransport = createHubUdpTransport({
702
702
  env: process.env,
703
- logEvent: (_scope, message) => console.log(`[LiveDesk Hub] ${message}`),
704
- logWarn: (_scope, message) => console.warn(`[LiveDesk Hub] ${message}`)
703
+ logEvent: (_scope, message) => console.log(`[VuvoDesk Hub] ${message}`),
704
+ logWarn: (_scope, message) => console.warn(`[VuvoDesk Hub] ${message}`)
705
705
  });
706
706
 
707
707
  const remoteHub = createRemoteHub({
@@ -713,8 +713,8 @@ const remoteHub = createRemoteHub({
713
713
  MINDEXEC_MANAGER_VERSION: packageInfo.version
714
714
  },
715
715
  udpTransport,
716
- logEvent: (_scope, message) => console.log(`[LiveDesk Hub] ${message}`),
717
- logWarn: (_scope, message) => console.warn(`[LiveDesk Hub] ${message}`),
716
+ logEvent: (_scope, message) => console.log(`[VuvoDesk Hub] ${message}`),
717
+ logWarn: (_scope, message) => console.warn(`[VuvoDesk Hub] ${message}`),
718
718
  emitEvent: handleRemoteHubEvent,
719
719
  emitFrame: broadcastRemoteBinaryFrame,
720
720
  emitAudio: broadcastRemoteBinaryAudio,
@@ -870,7 +870,7 @@ if (process.env.LIVEDESK_UPDATE_CONTINUE === '1') {
870
870
  clearInterval(continueUpdateTimer);
871
871
  }
872
872
  } catch (error) {
873
- console.warn(`[LiveDesk Hub] automatic client update continuation failed: ${error instanceof Error ? error.message : String(error)}`);
873
+ console.warn(`[VuvoDesk Hub] automatic client update continuation failed: ${error instanceof Error ? error.message : String(error)}`);
874
874
  clearInterval(continueUpdateTimer);
875
875
  } finally {
876
876
  continueUpdateInFlight = false;
@@ -1167,11 +1167,11 @@ async function dispatchAgentMcpTool(session, name, args = {}) {
1167
1167
  'livedesk.close_application': 'application.close',
1168
1168
  'livedesk.read_file': 'file.read',
1169
1169
  'livedesk.write_file': 'file.write',
1170
- 'livedesk.delete_file': 'file.delete',
1171
- 'livedesk.list_directory': 'file.list',
1172
- 'livedesk.search_files': 'file.search',
1173
- 'livedesk.create_directory': 'directory.create',
1174
- 'livedesk.run_command': 'command.run',
1170
+ 'livedesk.delete_file': 'file.delete',
1171
+ 'livedesk.list_directory': 'file.list',
1172
+ 'livedesk.search_files': 'file.search',
1173
+ 'livedesk.create_directory': 'directory.create',
1174
+ 'livedesk.run_command': 'command.run',
1175
1175
  'livedesk.run_script': 'script.run',
1176
1176
  'livedesk.install_software': 'software.install',
1177
1177
  'livedesk.get_network_status': 'network.status',
@@ -1255,8 +1255,8 @@ async function dispatchAgentMcpTool(session, name, args = {}) {
1255
1255
  ? String(args.serviceName || '').replace(/[\0\r\n]/g, ' ').trim().slice(0, 120)
1256
1256
  : '';
1257
1257
  const result = remoteHub.requestAgentTaskBatch(targetIds, {
1258
- instruction: `${tool.readOnly ? `LiveDesk read-only ${operation}` : `LiveDesk ${name}`}${targetQuery ? ` for ${targetQuery}` : ''}. Return only the collected result.`,
1259
- title: `LiveDesk ${operation}`,
1258
+ instruction: `${tool.readOnly ? `VuvoDesk read-only ${operation}` : `VuvoDesk ${name}`}${targetQuery ? ` for ${targetQuery}` : ''}. Return only the collected result.`,
1259
+ title: `VuvoDesk ${operation}`,
1260
1260
  operation,
1261
1261
  targetQuery,
1262
1262
  approvalLevel: tool.readOnly ? 'read-only' : 'task-only',
@@ -1330,7 +1330,7 @@ const codexRuntime = createCodexAgentRuntime({
1330
1330
  const detail = ['failed', 'recovering'].includes(progress.stage) && progress.message
1331
1331
  ? ` detail=${JSON.stringify(String(progress.message).slice(0, 800))}`
1332
1332
  : '';
1333
- console.log(`[LiveDesk Hub] Agent ${progress.stage} run=${progress.runId}${tool} clients=${delivery.sent}/${delivery.total}${detail}`);
1333
+ console.log(`[VuvoDesk Hub] Agent ${progress.stage} run=${progress.runId}${tool} clients=${delivery.sent}/${delivery.total}${detail}`);
1334
1334
  }
1335
1335
  });
1336
1336
  const agentManager = createAgentManager({
@@ -1761,40 +1761,40 @@ function normalizeLiveOptions(payload = {}) {
1761
1761
  };
1762
1762
  }
1763
1763
 
1764
- function sendJson(ws, payload) {
1764
+ function sendJson(ws, payload) {
1765
1765
  if (!ws || ws.readyState !== 1) {
1766
1766
  return false;
1767
1767
  }
1768
1768
  return safeWebSocketSend(ws, JSON.stringify(payload));
1769
- }
1770
-
1771
- function findLiveRemoteInputClient(hubConnectionId) {
1772
- const requestedId = String(hubConnectionId || '').trim();
1773
- if (!requestedId) {
1774
- return null;
1775
- }
1776
- for (const ws of inputClients) {
1777
- if (ws.readyState === 1 && ws.liveDeskInputClientId === requestedId) {
1778
- return ws;
1779
- }
1780
- }
1781
- return null;
1782
- }
1783
-
1784
- function clipboardHttpStatus(error) {
1785
- const code = String(error || '');
1786
- if (code.includes('blocked-by-settings') || code === 'remote-access-blocked-by-settings') return 403;
1787
- if (code.includes('too-large')) return 413;
1788
- if (code.includes('unavailable') || code === 'device-not-connected') return 503;
1789
- if (code.startsWith('STALE_')
1790
- || code.includes('owner')
1791
- || code.includes('not-ready')
1792
- || code.includes('not-found')
1793
- || code.includes('already-exists')
1794
- || code.includes('busy')
1795
- || code.includes('capacity')) return 409;
1796
- return 400;
1797
- }
1769
+ }
1770
+
1771
+ function findLiveRemoteInputClient(hubConnectionId) {
1772
+ const requestedId = String(hubConnectionId || '').trim();
1773
+ if (!requestedId) {
1774
+ return null;
1775
+ }
1776
+ for (const ws of inputClients) {
1777
+ if (ws.readyState === 1 && ws.liveDeskInputClientId === requestedId) {
1778
+ return ws;
1779
+ }
1780
+ }
1781
+ return null;
1782
+ }
1783
+
1784
+ function clipboardHttpStatus(error) {
1785
+ const code = String(error || '');
1786
+ if (code.includes('blocked-by-settings') || code === 'remote-access-blocked-by-settings') return 403;
1787
+ if (code.includes('too-large')) return 413;
1788
+ if (code.includes('unavailable') || code === 'device-not-connected') return 503;
1789
+ if (code.startsWith('STALE_')
1790
+ || code.includes('owner')
1791
+ || code.includes('not-ready')
1792
+ || code.includes('not-found')
1793
+ || code.includes('already-exists')
1794
+ || code.includes('busy')
1795
+ || code.includes('capacity')) return 409;
1796
+ return 400;
1797
+ }
1798
1798
 
1799
1799
  function forgetWebSocketClient(ws) {
1800
1800
  unregisterFrameClient(ws);
@@ -1931,12 +1931,12 @@ function scheduleFrameStreamStop(deviceId, streamId, streamPurpose) {
1931
1931
  if (result?.stopPromise) {
1932
1932
  void result.stopPromise.then(confirmation => {
1933
1933
  if (confirmation?.captureStopConfirmed === true) {
1934
- console.log(`[LiveDesk Hub] confirmed ${streamPurpose} capture stopped after frame client closed device=${deviceId} stream=${streamId}`);
1934
+ console.log(`[VuvoDesk Hub] confirmed ${streamPurpose} capture stopped after frame client closed device=${deviceId} stream=${streamId}`);
1935
1935
  return;
1936
1936
  }
1937
1937
  if (!hasOtherFrameStreamOwner(null, deviceId, streamId, streamPurpose)) {
1938
1938
  console.warn(
1939
- `[LiveDesk Hub] ${streamPurpose} capture stop was not confirmed device=${deviceId} `
1939
+ `[VuvoDesk Hub] ${streamPurpose} capture stop was not confirmed device=${deviceId} `
1940
1940
  + `stream=${streamId}: ${confirmation?.error || 'capture-stop-unconfirmed'}. `
1941
1941
  + 'Disconnecting the Agent so its final capture drain runs before reconnect.'
1942
1942
  );
@@ -1944,12 +1944,12 @@ function scheduleFrameStreamStop(deviceId, streamId, streamPurpose) {
1944
1944
  }
1945
1945
  }).catch(error => {
1946
1946
  if (!hasOtherFrameStreamOwner(null, deviceId, streamId, streamPurpose)) {
1947
- console.warn(`[LiveDesk Hub] ${streamPurpose} capture stop confirmation failed device=${deviceId}: ${error instanceof Error ? error.message : String(error)}`);
1947
+ console.warn(`[VuvoDesk Hub] ${streamPurpose} capture stop confirmation failed device=${deviceId}: ${error instanceof Error ? error.message : String(error)}`);
1948
1948
  remoteHub.disconnectDevice(deviceId, 'frame-capture-stop-confirmation-failed');
1949
1949
  }
1950
1950
  });
1951
1951
  } else if (result?.ok && !result.alreadyStopped) {
1952
- console.log(`[LiveDesk Hub] queued ${streamPurpose} capture stop after frame client closed device=${deviceId} stream=${streamId}`);
1952
+ console.log(`[VuvoDesk Hub] queued ${streamPurpose} capture stop after frame client closed device=${deviceId} stream=${streamId}`);
1953
1953
  }
1954
1954
  }, frameStreamStopGraceMs);
1955
1955
  pendingFrameStreamStops.set(key, { timer });
@@ -3748,11 +3748,11 @@ function stopUnsubscribedAudioOwners(owners, reason) {
3748
3748
  disconnect: (targetDeviceId, disconnectReason) => (
3749
3749
  remoteHub.disconnectDevice(targetDeviceId, disconnectReason)
3750
3750
  ),
3751
- warn: message => console.warn(`[LiveDesk Hub] ${message}. Disconnecting the Agent for a final drain.`)
3751
+ warn: message => console.warn(`[VuvoDesk Hub] ${message}. Disconnecting the Agent for a final drain.`)
3752
3752
  });
3753
3753
  if (result?.ok === false
3754
3754
  && !['device-not-found', 'device-not-connected', 'device-audio-unavailable'].includes(String(result.error || ''))) {
3755
- console.warn(`[LiveDesk Hub] Could not stop unused remote audio capture device=${deviceId} reason=${reason}: ${result.error || 'unknown-error'}`);
3755
+ console.warn(`[VuvoDesk Hub] Could not stop unused remote audio capture device=${deviceId} reason=${reason}: ${result.error || 'unknown-error'}`);
3756
3756
  }
3757
3757
  }
3758
3758
  }
@@ -3860,7 +3860,7 @@ function buildHubHealthPayload({
3860
3860
  };
3861
3861
  return {
3862
3862
  ok: true,
3863
- product: 'LiveDesk',
3863
+ product: 'LiveDesk',
3864
3864
  timestamp: new Date().toISOString(),
3865
3865
  persistentSessionGc,
3866
3866
  memoryBeforeGc,
@@ -4323,7 +4323,7 @@ app.get('/api/remote/status', (_req, res) => {
4323
4323
  res.json({
4324
4324
  ...remoteHub.getStatus({ includeSecrets: false }),
4325
4325
  pairingPin: secretStatus.pairingPin,
4326
- product: 'LiveDesk',
4326
+ product: 'LiveDesk',
4327
4327
  runtimeRole,
4328
4328
  deviceId: runtimeDeviceId,
4329
4329
  deviceName: runtimeDeviceName,
@@ -4653,7 +4653,7 @@ async function notifyHubOnline({ accessToken, target, remoteStatus, endpointCand
4653
4653
  lastEventId: eventId,
4654
4654
  nextAttemptAt: ''
4655
4655
  };
4656
- console.log(`[LiveDesk Hub] Hub-online wake signal sent event=${eventId.slice(0, 8)} delivered=${Number(data?.delivered || 0)}.`);
4656
+ console.log(`[VuvoDesk Hub] Hub-online wake signal sent event=${eventId.slice(0, 8)} delivered=${Number(data?.delivered || 0)}.`);
4657
4657
  return { ok: true, eventId, delivered: Number(data?.delivered || 0) };
4658
4658
  } catch (error) {
4659
4659
  const message = error instanceof Error ? error.message : String(error);
@@ -4664,7 +4664,7 @@ async function notifyHubOnline({ accessToken, target, remoteStatus, endpointCand
4664
4664
  lastError: message,
4665
4665
  nextAttemptAt: new Date(hubWakeNextAttemptAtMs).toISOString()
4666
4666
  };
4667
- console.warn(`[LiveDesk Hub] Hub-online wake signal failed; Supabase target remains authoritative: ${message}`);
4667
+ console.warn(`[VuvoDesk Hub] Hub-online wake signal failed; Supabase target remains authoritative: ${message}`);
4668
4668
  return { ok: false, error: message, eventId };
4669
4669
  }
4670
4670
  }
@@ -4714,7 +4714,7 @@ async function promoteCurrentRuntimeToHub() {
4714
4714
  p_assigned_hub_id: null,
4715
4715
  p_expected_role_version: null
4716
4716
  }, accessToken);
4717
- console.log(`[LiveDesk Hub] Sync Server role confirmed for ${runtimeDeviceId}.`);
4717
+ console.log(`[VuvoDesk Hub] Sync Server role confirmed for ${runtimeDeviceId}.`);
4718
4718
  return result;
4719
4719
  }
4720
4720
 
@@ -4784,12 +4784,12 @@ async function publishHubHostTarget({ takeover = false, reason = 'renewal' } = {
4784
4784
  lastPublishedEndpoint: target.endpoint || remoteStatus.agentEndpoint || '',
4785
4785
  lastExpiresAt: target.expiresAt || ''
4786
4786
  });
4787
- console.log(`[LiveDesk Hub] Host target ${reason} published endpoint=${target.endpoint || remoteStatus.agentEndpoint} expires=${target.expiresAt || ''}.`);
4787
+ console.log(`[VuvoDesk Hub] Host target ${reason} published endpoint=${target.endpoint || remoteStatus.agentEndpoint} expires=${target.expiresAt || ''}.`);
4788
4788
  return { ok: true, active: true, hostTarget: target, lease: getHubHostTargetLeaseStatus() };
4789
4789
  } catch (error) {
4790
4790
  const message = error instanceof Error ? error.message : String(error);
4791
4791
  updateHubHostTargetLeaseState({ state: 'error', lastError: message });
4792
- console.error(`[LiveDesk Hub] Host target ${reason} publish failed: ${message}`);
4792
+ console.error(`[VuvoDesk Hub] Host target ${reason} publish failed: ${message}`);
4793
4793
  return { ok: false, active: false, error: message, lease: getHubHostTargetLeaseStatus() };
4794
4794
  } finally {
4795
4795
  hubHostTargetRenewInFlight = false;
@@ -4833,7 +4833,7 @@ async function clearHubHostTarget(reason = 'shutdown') {
4833
4833
  }
4834
4834
  } catch (cause) {
4835
4835
  error = cause instanceof Error ? cause.message : String(cause);
4836
- console.error(`[LiveDesk Hub] Host target ${reason} clear failed: ${error}`);
4836
+ console.error(`[VuvoDesk Hub] Host target ${reason} clear failed: ${error}`);
4837
4837
  }
4838
4838
  try {
4839
4839
  await remoteHub.setHostTarget({ enabled: false, nodeId: targetNodeId });
@@ -4848,7 +4848,7 @@ async function clearHubHostTarget(reason = 'shutdown') {
4848
4848
  lastExpiresAt: ''
4849
4849
  });
4850
4850
  if (!error) {
4851
- console.log(`[LiveDesk Hub] Host target cleared (${reason}).`);
4851
+ console.log(`[VuvoDesk Hub] Host target cleared (${reason}).`);
4852
4852
  }
4853
4853
  return { ok: !error, error, lease: getHubHostTargetLeaseStatus() };
4854
4854
  }
@@ -4869,7 +4869,7 @@ function scheduleAuthenticatedHubHostTargetPublication(reason = 'session-receive
4869
4869
  void publishHubHostTargetWithPendingRoleTakeover(reason).catch(error => {
4870
4870
  const message = error instanceof Error ? error.message : String(error);
4871
4871
  updateHubHostTargetLeaseState({ state: 'error', lastError: message });
4872
- console.error(`[LiveDesk Hub] Host target ${reason} background publish failed: ${message}`);
4872
+ console.error(`[VuvoDesk Hub] Host target ${reason} background publish failed: ${message}`);
4873
4873
  });
4874
4874
  });
4875
4875
  }
@@ -4923,7 +4923,7 @@ app.post('/api/hub/sync', async (req, res) => {
4923
4923
  } catch (error) {
4924
4924
  const message = error instanceof Error ? error.message : String(error);
4925
4925
  updateHubHostTargetLeaseState({ state: 'error', lastError: message, lastReason: 'sync-server' });
4926
- console.error(`[LiveDesk Hub] Sync Server failed: ${message}`);
4926
+ console.error(`[VuvoDesk Hub] Sync Server failed: ${message}`);
4927
4927
  res.status(409).json({ ok: false, error: message, lease: getHubHostTargetLeaseStatus() });
4928
4928
  }
4929
4929
  });
@@ -5184,64 +5184,64 @@ app.post('/api/remote/devices/:deviceId/status-diagnostic', (req, res) => {
5184
5184
  }));
5185
5185
  });
5186
5186
 
5187
- app.post('/api/remote/devices/:deviceId/input', requireHubFeatureAccess, (req, res) => {
5188
- noStore(res);
5189
- res.json(remoteHub.sendInputControl(req.params.deviceId, req.body || {}));
5190
- });
5191
-
5192
- app.post('/api/remote/devices/:deviceId/clipboard', requireHubFeatureAccess, async (req, res) => {
5193
- noStore(res);
5194
- try {
5195
- const request = normalizeRemoteClipboardRequest(req.body || {});
5196
- const inputClient = findLiveRemoteInputClient(request.hubConnectionId);
5197
- if (!inputClient) {
5198
- res.status(409).json({ ok: false, error: 'clipboard-input-owner-not-live' });
5199
- return;
5200
- }
5201
- const deviceId = String(req.params.deviceId || '').trim();
5202
- inputClient.liveDeskInputDeviceIds?.add?.(deviceId);
5203
-
5204
- if (REMOTE_CLIPBOARD_INPUT_ACTIONS.has(request.action)) {
5205
- const result = remoteHub.sendInputControl(deviceId, {
5206
- type: request.action,
5207
- clipboardOperationId: request.operationId,
5208
- operationId: request.operationId,
5209
- clipboardDirection: request.clipboardDirection,
5210
- manifest: request.payload.manifest || null,
5211
- contentKind: request.payload.contentKind || request.payload.manifest?.contentKind || '',
5212
- controlSessionId: request.binding.controlSessionId,
5213
- controlCommandId: request.binding.controlCommandId,
5214
- captureGeneration: request.binding.captureGeneration,
5215
- monitorIndex: request.binding.monitorIndex,
5216
- hubConnectionId: request.hubConnectionId,
5217
- requestAck: true
5218
- });
5219
- res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json({
5220
- ok: result.ok === true,
5221
- action: request.action,
5222
- operationId: request.operationId,
5223
- input: result,
5224
- ...(result.ok ? {} : { error: result.error || 'clipboard-input-failed' })
5225
- });
5226
- return;
5227
- }
5228
-
5229
- const result = await remoteHub.sendClipboardCommand(deviceId, request);
5230
- res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json(result);
5231
- } catch (error) {
5232
- const status = error instanceof RemoteClipboardContractError
5233
- ? error.status
5234
- : 500;
5235
- res.status(status).json({
5236
- ok: false,
5237
- error: error instanceof RemoteClipboardContractError
5238
- ? error.code
5239
- : 'clipboard-request-failed'
5240
- });
5241
- }
5242
- });
5243
-
5244
- app.get('/api/remote/filesystem/roots', requireHubFeatureAccess, async (req, res) => {
5187
+ app.post('/api/remote/devices/:deviceId/input', requireHubFeatureAccess, (req, res) => {
5188
+ noStore(res);
5189
+ res.json(remoteHub.sendInputControl(req.params.deviceId, req.body || {}));
5190
+ });
5191
+
5192
+ app.post('/api/remote/devices/:deviceId/clipboard', requireHubFeatureAccess, async (req, res) => {
5193
+ noStore(res);
5194
+ try {
5195
+ const request = normalizeRemoteClipboardRequest(req.body || {});
5196
+ const inputClient = findLiveRemoteInputClient(request.hubConnectionId);
5197
+ if (!inputClient) {
5198
+ res.status(409).json({ ok: false, error: 'clipboard-input-owner-not-live' });
5199
+ return;
5200
+ }
5201
+ const deviceId = String(req.params.deviceId || '').trim();
5202
+ inputClient.liveDeskInputDeviceIds?.add?.(deviceId);
5203
+
5204
+ if (REMOTE_CLIPBOARD_INPUT_ACTIONS.has(request.action)) {
5205
+ const result = remoteHub.sendInputControl(deviceId, {
5206
+ type: request.action,
5207
+ clipboardOperationId: request.operationId,
5208
+ operationId: request.operationId,
5209
+ clipboardDirection: request.clipboardDirection,
5210
+ manifest: request.payload.manifest || null,
5211
+ contentKind: request.payload.contentKind || request.payload.manifest?.contentKind || '',
5212
+ controlSessionId: request.binding.controlSessionId,
5213
+ controlCommandId: request.binding.controlCommandId,
5214
+ captureGeneration: request.binding.captureGeneration,
5215
+ monitorIndex: request.binding.monitorIndex,
5216
+ hubConnectionId: request.hubConnectionId,
5217
+ requestAck: true
5218
+ });
5219
+ res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json({
5220
+ ok: result.ok === true,
5221
+ action: request.action,
5222
+ operationId: request.operationId,
5223
+ input: result,
5224
+ ...(result.ok ? {} : { error: result.error || 'clipboard-input-failed' })
5225
+ });
5226
+ return;
5227
+ }
5228
+
5229
+ const result = await remoteHub.sendClipboardCommand(deviceId, request);
5230
+ res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json(result);
5231
+ } catch (error) {
5232
+ const status = error instanceof RemoteClipboardContractError
5233
+ ? error.status
5234
+ : 500;
5235
+ res.status(status).json({
5236
+ ok: false,
5237
+ error: error instanceof RemoteClipboardContractError
5238
+ ? error.code
5239
+ : 'clipboard-request-failed'
5240
+ });
5241
+ }
5242
+ });
5243
+
5244
+ app.get('/api/remote/filesystem/roots', requireHubFeatureAccess, async (req, res) => {
5245
5245
  noStore(res);
5246
5246
  try {
5247
5247
  res.json(await hubFilesystem.getRoots({ refresh: /^(1|true|yes|on)$/i.test(String(req.query?.refresh || '')) }));
@@ -5955,14 +5955,14 @@ await remoteHub.start();
5955
5955
  connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
5956
5956
  hubSharedFolders.startAutoSync(
5957
5957
  () => remoteHub.listDevices({ includeDataUrl: false }).filter(device => device.connected).map(device => device.deviceId),
5958
- () => process.env.LIVEDESK_REMOTE_DIRECTORY || 'Desktop/LiveDeskFiles'
5958
+ () => process.env.LIVEDESK_REMOTE_DIRECTORY || 'Desktop/VuvoDeskFiles'
5959
5959
  );
5960
5960
  httpServer.listen(httpPort, httpHost, () => {
5961
5961
  const status = remoteHub.getStatus({ includeSecrets: true });
5962
5962
  const managerVersion = String(process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version || 'dev');
5963
- console.log(`[LiveDesk Hub] Version ${managerVersion}`);
5964
- console.log(`[LiveDesk Hub] HTTP API http://${httpHost}:${httpPort}`);
5965
- console.log(`[LiveDesk Hub] Client endpoint ${status.agentEndpoint} pair=${status.pairTokenPreview}`);
5963
+ console.log(`[VuvoDesk Hub] Version ${managerVersion}`);
5964
+ console.log(`[VuvoDesk Hub] HTTP API http://${httpHost}:${httpPort}`);
5965
+ console.log(`[VuvoDesk Hub] Client endpoint ${status.agentEndpoint} pair=${status.pairTokenPreview}`);
5966
5966
  hubConsoleRelay?.start();
5967
5967
  });
5968
5968
 
@@ -5988,9 +5988,9 @@ async function boundedShutdownStep(label, action, timeoutMs) {
5988
5988
  timer.unref?.();
5989
5989
  })
5990
5990
  ]);
5991
- console.log(`[LiveDesk Hub] Shutdown stage completed: ${label}.`);
5991
+ console.log(`[VuvoDesk Hub] Shutdown stage completed: ${label}.`);
5992
5992
  } catch (error) {
5993
- console.warn(`[LiveDesk Hub] Shutdown stage warning: ${error instanceof Error ? error.message : String(error)}`);
5993
+ console.warn(`[VuvoDesk Hub] Shutdown stage warning: ${error instanceof Error ? error.message : String(error)}`);
5994
5994
  } finally {
5995
5995
  if (timer) clearTimeout(timer);
5996
5996
  }
@@ -6000,7 +6000,7 @@ function runSynchronousShutdownStep(label, action) {
6000
6000
  try {
6001
6001
  action();
6002
6002
  } catch (error) {
6003
- console.warn(`[LiveDesk Hub] Shutdown stage warning: ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
6003
+ console.warn(`[VuvoDesk Hub] Shutdown stage warning: ${label} failed: ${error instanceof Error ? error.message : String(error)}`);
6004
6004
  }
6005
6005
  }
6006
6006
 
@@ -6010,13 +6010,13 @@ function closeBrowserWebSockets() {
6010
6010
  for (const ws of wss.clients) {
6011
6011
  closeRequested += 1;
6012
6012
  try {
6013
- ws.close(1012, 'LiveDesk Hub restarting');
6013
+ ws.close(1012, 'VuvoDesk Hub restarting');
6014
6014
  } catch {
6015
6015
  try { ws.terminate(); } catch { /* socket already closed */ }
6016
6016
  }
6017
6017
  }
6018
6018
  }
6019
- console.log(`[LiveDesk Hub] Shutdown stage: requested ${closeRequested} browser WebSocket connection(s) to close.`);
6019
+ console.log(`[VuvoDesk Hub] Shutdown stage: requested ${closeRequested} browser WebSocket connection(s) to close.`);
6020
6020
  }
6021
6021
 
6022
6022
  function terminateOpenConnections() {
@@ -6035,7 +6035,7 @@ function terminateOpenConnections() {
6035
6035
  try { socket.destroy(); } catch { /* socket already closed */ }
6036
6036
  }
6037
6037
  try { httpServer.closeAllConnections?.(); } catch { /* older Node runtime */ }
6038
- console.log(`[LiveDesk Hub] Shutdown stage: force-closed ${terminatedWebSockets} WebSocket and ${destroyedHttpConnections} HTTP connection(s).`);
6038
+ console.log(`[VuvoDesk Hub] Shutdown stage: force-closed ${terminatedWebSockets} WebSocket and ${destroyedHttpConnections} HTTP connection(s).`);
6039
6039
  }
6040
6040
 
6041
6041
  let hubShutdownPromise = null;
@@ -6043,7 +6043,7 @@ function shutdownHub(signal) {
6043
6043
  if (hubShutdownPromise) return hubShutdownPromise;
6044
6044
  hubShutdownPromise = (async () => {
6045
6045
  const startedAt = Date.now();
6046
- console.log(`[LiveDesk Hub] Shutdown started signal=${signal} grace=${hubShutdownGraceMs}ms timeout=${hubShutdownTimeoutMs}ms.`);
6046
+ console.log(`[VuvoDesk Hub] Shutdown started signal=${signal} grace=${hubShutdownGraceMs}ms timeout=${hubShutdownTimeoutMs}ms.`);
6047
6047
  if (roleWatchTimer) clearInterval(roleWatchTimer);
6048
6048
  clearInterval(browserWebSocketHeartbeatTimer);
6049
6049
  runSynchronousShutdownStep('update manager close', () => liveDeskUpdateManager?.close());
@@ -6055,15 +6055,15 @@ function shutdownHub(signal) {
6055
6055
  try {
6056
6056
  httpServer.close(error => {
6057
6057
  if (error) {
6058
- console.warn(`[LiveDesk Hub] HTTP listener close warning: ${error.message}`);
6058
+ console.warn(`[VuvoDesk Hub] HTTP listener close warning: ${error.message}`);
6059
6059
  } else {
6060
- console.log('[LiveDesk Hub] Shutdown stage completed: HTTP listener closed.');
6060
+ console.log('[VuvoDesk Hub] Shutdown stage completed: HTTP listener closed.');
6061
6061
  }
6062
6062
  resolveClose();
6063
6063
  });
6064
6064
  httpServer.closeIdleConnections?.();
6065
6065
  } catch (error) {
6066
- console.warn(`[LiveDesk Hub] HTTP listener close warning: ${error instanceof Error ? error.message : String(error)}`);
6066
+ console.warn(`[VuvoDesk Hub] HTTP listener close warning: ${error instanceof Error ? error.message : String(error)}`);
6067
6067
  resolveClose();
6068
6068
  }
6069
6069
  });
@@ -6083,7 +6083,7 @@ function shutdownHub(signal) {
6083
6083
  await Promise.race([httpClosed, waitForShutdownDelay(remainingMs)]);
6084
6084
  clearTimeout(forceCloseTimer);
6085
6085
  terminateOpenConnections();
6086
- console.log(`[LiveDesk Hub] Shutdown complete in ${Date.now() - startedAt}ms.`);
6086
+ console.log(`[VuvoDesk Hub] Shutdown complete in ${Date.now() - startedAt}ms.`);
6087
6087
  })();
6088
6088
  return hubShutdownPromise;
6089
6089
  }
@@ -6093,7 +6093,7 @@ for (const signal of ['SIGINT', 'SIGTERM']) {
6093
6093
  void shutdownHub(signal)
6094
6094
  .then(() => process.exit(0))
6095
6095
  .catch(error => {
6096
- console.error(`[LiveDesk Hub] Shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
6096
+ console.error(`[VuvoDesk Hub] Shutdown failed: ${error instanceof Error ? error.message : String(error)}`);
6097
6097
  terminateOpenConnections();
6098
6098
  process.exit(1);
6099
6099
  });
@@ -1,36 +1,36 @@
1
- import { effectiveDevicePolicy } from './settings-schema.js';
2
- import { REMOTE_CLIPBOARD_LIMITS } from '../remote-clipboard-contract.mjs';
1
+ import { effectiveDevicePolicy } from './settings-schema.js';
2
+ import { REMOTE_CLIPBOARD_LIMITS } from '../remote-clipboard-contract.mjs';
3
3
 
4
- export function buildEffectiveDevicePolicy(settings, { deviceId = '', capabilities = {} } = {}) {
5
- const policy = effectiveDevicePolicy(settings);
6
- const clipboardV1 = capabilities.clipboardProtocol === 'livedesk.clipboard.v1';
7
- const clipboardCommonLimits = clipboardV1
8
- && capabilities.sessionBoundSideChannels === true
9
- && Number.isSafeInteger(Number(capabilities.clipboardMaxTextBytes))
10
- && Number(capabilities.clipboardMaxTextBytes) > 0
11
- && Number.isSafeInteger(Number(capabilities.clipboardMaxTotalBytes))
12
- && Number(capabilities.clipboardMaxTotalBytes) > 0
13
- && Number.isSafeInteger(Number(capabilities.clipboardMaxChunkBytes))
14
- && Number(capabilities.clipboardMaxChunkBytes) >= REMOTE_CLIPBOARD_LIMITS.maxChunkBytes;
15
- const clipboardMaxImageBytes = Number(
16
- capabilities.clipboardMaxImageBytes ?? capabilities.clipboardMaxPngBytes);
17
- return {
4
+ export function buildEffectiveDevicePolicy(settings, { deviceId = '', capabilities = {} } = {}) {
5
+ const policy = effectiveDevicePolicy(settings);
6
+ const clipboardV1 = capabilities.clipboardProtocol === 'livedesk.clipboard.v1';
7
+ const clipboardCommonLimits = clipboardV1
8
+ && capabilities.sessionBoundSideChannels === true
9
+ && Number.isSafeInteger(Number(capabilities.clipboardMaxTextBytes))
10
+ && Number(capabilities.clipboardMaxTextBytes) > 0
11
+ && Number.isSafeInteger(Number(capabilities.clipboardMaxTotalBytes))
12
+ && Number(capabilities.clipboardMaxTotalBytes) > 0
13
+ && Number.isSafeInteger(Number(capabilities.clipboardMaxChunkBytes))
14
+ && Number(capabilities.clipboardMaxChunkBytes) >= REMOTE_CLIPBOARD_LIMITS.maxChunkBytes;
15
+ const clipboardMaxImageBytes = Number(
16
+ capabilities.clipboardMaxImageBytes ?? capabilities.clipboardMaxPngBytes);
17
+ return {
18
18
  ...policy,
19
- deviceId: String(deviceId || ''),
20
- supported: {
21
- control: capabilities.control !== false,
22
- clipboardText: clipboardCommonLimits && capabilities.clipboardText === true,
23
- clipboardImage: clipboardCommonLimits
24
- && capabilities.clipboardImage === true
25
- && Number.isSafeInteger(clipboardMaxImageBytes)
26
- && clipboardMaxImageBytes > 0,
27
- clipboardFiles: clipboardCommonLimits
28
- && capabilities.clipboardFiles === true
29
- && Number.isSafeInteger(Number(capabilities.clipboardMaxFiles))
30
- && Number(capabilities.clipboardMaxFiles) > 0,
31
- clipboardCopy: clipboardCommonLimits && capabilities.clipboardCopy === true,
32
- clipboardPaste: clipboardCommonLimits && capabilities.clipboardPaste === true,
33
- fileTransfer: capabilities.fileTransfer !== false,
19
+ deviceId: String(deviceId || ''),
20
+ supported: {
21
+ control: capabilities.control !== false,
22
+ clipboardText: clipboardCommonLimits && capabilities.clipboardText === true,
23
+ clipboardImage: clipboardCommonLimits
24
+ && capabilities.clipboardImage === true
25
+ && Number.isSafeInteger(clipboardMaxImageBytes)
26
+ && clipboardMaxImageBytes > 0,
27
+ clipboardFiles: clipboardCommonLimits
28
+ && capabilities.clipboardFiles === true
29
+ && Number.isSafeInteger(Number(capabilities.clipboardMaxFiles))
30
+ && Number(capabilities.clipboardMaxFiles) > 0,
31
+ clipboardCopy: clipboardCommonLimits && capabilities.clipboardCopy === true,
32
+ clipboardPaste: clipboardCommonLimits && capabilities.clipboardPaste === true,
33
+ fileTransfer: capabilities.fileTransfer !== false,
34
34
  remoteAudio: capabilities.audio === true || capabilities.remoteAudio === true,
35
35
  agent: Array.isArray(capabilities.agentTools) && capabilities.agentTools.length > 0
36
36
  }
@@ -65,7 +65,7 @@ export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
65
65
  openReceivedFolder: false,
66
66
  notifyTransferComplete: true,
67
67
  allowOverwrite: false,
68
- defaultReceiveFolder: 'Desktop/LiveDeskFiles',
68
+ defaultReceiveFolder: 'Desktop/VuvoDeskFiles',
69
69
  maxFileSizeBytes: 1024 * 1024 * 1024,
70
70
  allowRemoteAudio: true,
71
71
  startAudioMuted: false,
@@ -76,7 +76,7 @@ export const DEFAULT_LIVEDESK_SETTINGS = Object.freeze({
76
76
  recordingQuality: 'standard',
77
77
  timelapseIntervalSeconds: 10,
78
78
  includeRemoteCursor: true,
79
- captureSaveLocation: 'LiveDesk Captures',
79
+ captureSaveLocation: 'VuvoDesk Captures',
80
80
  captureAutoDelete: 'never'
81
81
  },
82
82
  agent: {