@livedesk/hub 0.1.48 → 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;
@@ -1169,6 +1169,8 @@ async function dispatchAgentMcpTool(session, name, args = {}) {
1169
1169
  'livedesk.write_file': 'file.write',
1170
1170
  'livedesk.delete_file': 'file.delete',
1171
1171
  'livedesk.list_directory': 'file.list',
1172
+ 'livedesk.search_files': 'file.search',
1173
+ 'livedesk.create_directory': 'directory.create',
1172
1174
  'livedesk.run_command': 'command.run',
1173
1175
  'livedesk.run_script': 'script.run',
1174
1176
  'livedesk.install_software': 'software.install',
@@ -1253,8 +1255,8 @@ async function dispatchAgentMcpTool(session, name, args = {}) {
1253
1255
  ? String(args.serviceName || '').replace(/[\0\r\n]/g, ' ').trim().slice(0, 120)
1254
1256
  : '';
1255
1257
  const result = remoteHub.requestAgentTaskBatch(targetIds, {
1256
- instruction: `${tool.readOnly ? `LiveDesk read-only ${operation}` : `LiveDesk ${name}`}${targetQuery ? ` for ${targetQuery}` : ''}. Return only the collected result.`,
1257
- 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}`,
1258
1260
  operation,
1259
1261
  targetQuery,
1260
1262
  approvalLevel: tool.readOnly ? 'read-only' : 'task-only',
@@ -1328,7 +1330,7 @@ const codexRuntime = createCodexAgentRuntime({
1328
1330
  const detail = ['failed', 'recovering'].includes(progress.stage) && progress.message
1329
1331
  ? ` detail=${JSON.stringify(String(progress.message).slice(0, 800))}`
1330
1332
  : '';
1331
- 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}`);
1332
1334
  }
1333
1335
  });
1334
1336
  const agentManager = createAgentManager({
@@ -1759,40 +1761,40 @@ function normalizeLiveOptions(payload = {}) {
1759
1761
  };
1760
1762
  }
1761
1763
 
1762
- function sendJson(ws, payload) {
1764
+ function sendJson(ws, payload) {
1763
1765
  if (!ws || ws.readyState !== 1) {
1764
1766
  return false;
1765
1767
  }
1766
1768
  return safeWebSocketSend(ws, JSON.stringify(payload));
1767
- }
1768
-
1769
- function findLiveRemoteInputClient(hubConnectionId) {
1770
- const requestedId = String(hubConnectionId || '').trim();
1771
- if (!requestedId) {
1772
- return null;
1773
- }
1774
- for (const ws of inputClients) {
1775
- if (ws.readyState === 1 && ws.liveDeskInputClientId === requestedId) {
1776
- return ws;
1777
- }
1778
- }
1779
- return null;
1780
- }
1781
-
1782
- function clipboardHttpStatus(error) {
1783
- const code = String(error || '');
1784
- if (code.includes('blocked-by-settings') || code === 'remote-access-blocked-by-settings') return 403;
1785
- if (code.includes('too-large')) return 413;
1786
- if (code.includes('unavailable') || code === 'device-not-connected') return 503;
1787
- if (code.startsWith('STALE_')
1788
- || code.includes('owner')
1789
- || code.includes('not-ready')
1790
- || code.includes('not-found')
1791
- || code.includes('already-exists')
1792
- || code.includes('busy')
1793
- || code.includes('capacity')) return 409;
1794
- return 400;
1795
- }
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
+ }
1796
1798
 
1797
1799
  function forgetWebSocketClient(ws) {
1798
1800
  unregisterFrameClient(ws);
@@ -1929,12 +1931,12 @@ function scheduleFrameStreamStop(deviceId, streamId, streamPurpose) {
1929
1931
  if (result?.stopPromise) {
1930
1932
  void result.stopPromise.then(confirmation => {
1931
1933
  if (confirmation?.captureStopConfirmed === true) {
1932
- 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}`);
1933
1935
  return;
1934
1936
  }
1935
1937
  if (!hasOtherFrameStreamOwner(null, deviceId, streamId, streamPurpose)) {
1936
1938
  console.warn(
1937
- `[LiveDesk Hub] ${streamPurpose} capture stop was not confirmed device=${deviceId} `
1939
+ `[VuvoDesk Hub] ${streamPurpose} capture stop was not confirmed device=${deviceId} `
1938
1940
  + `stream=${streamId}: ${confirmation?.error || 'capture-stop-unconfirmed'}. `
1939
1941
  + 'Disconnecting the Agent so its final capture drain runs before reconnect.'
1940
1942
  );
@@ -1942,12 +1944,12 @@ function scheduleFrameStreamStop(deviceId, streamId, streamPurpose) {
1942
1944
  }
1943
1945
  }).catch(error => {
1944
1946
  if (!hasOtherFrameStreamOwner(null, deviceId, streamId, streamPurpose)) {
1945
- 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)}`);
1946
1948
  remoteHub.disconnectDevice(deviceId, 'frame-capture-stop-confirmation-failed');
1947
1949
  }
1948
1950
  });
1949
1951
  } else if (result?.ok && !result.alreadyStopped) {
1950
- 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}`);
1951
1953
  }
1952
1954
  }, frameStreamStopGraceMs);
1953
1955
  pendingFrameStreamStops.set(key, { timer });
@@ -3746,11 +3748,11 @@ function stopUnsubscribedAudioOwners(owners, reason) {
3746
3748
  disconnect: (targetDeviceId, disconnectReason) => (
3747
3749
  remoteHub.disconnectDevice(targetDeviceId, disconnectReason)
3748
3750
  ),
3749
- 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.`)
3750
3752
  });
3751
3753
  if (result?.ok === false
3752
3754
  && !['device-not-found', 'device-not-connected', 'device-audio-unavailable'].includes(String(result.error || ''))) {
3753
- 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'}`);
3754
3756
  }
3755
3757
  }
3756
3758
  }
@@ -3858,7 +3860,7 @@ function buildHubHealthPayload({
3858
3860
  };
3859
3861
  return {
3860
3862
  ok: true,
3861
- product: 'LiveDesk',
3863
+ product: 'LiveDesk',
3862
3864
  timestamp: new Date().toISOString(),
3863
3865
  persistentSessionGc,
3864
3866
  memoryBeforeGc,
@@ -4321,7 +4323,7 @@ app.get('/api/remote/status', (_req, res) => {
4321
4323
  res.json({
4322
4324
  ...remoteHub.getStatus({ includeSecrets: false }),
4323
4325
  pairingPin: secretStatus.pairingPin,
4324
- product: 'LiveDesk',
4326
+ product: 'LiveDesk',
4325
4327
  runtimeRole,
4326
4328
  deviceId: runtimeDeviceId,
4327
4329
  deviceName: runtimeDeviceName,
@@ -4651,7 +4653,7 @@ async function notifyHubOnline({ accessToken, target, remoteStatus, endpointCand
4651
4653
  lastEventId: eventId,
4652
4654
  nextAttemptAt: ''
4653
4655
  };
4654
- 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)}.`);
4655
4657
  return { ok: true, eventId, delivered: Number(data?.delivered || 0) };
4656
4658
  } catch (error) {
4657
4659
  const message = error instanceof Error ? error.message : String(error);
@@ -4662,7 +4664,7 @@ async function notifyHubOnline({ accessToken, target, remoteStatus, endpointCand
4662
4664
  lastError: message,
4663
4665
  nextAttemptAt: new Date(hubWakeNextAttemptAtMs).toISOString()
4664
4666
  };
4665
- 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}`);
4666
4668
  return { ok: false, error: message, eventId };
4667
4669
  }
4668
4670
  }
@@ -4712,7 +4714,7 @@ async function promoteCurrentRuntimeToHub() {
4712
4714
  p_assigned_hub_id: null,
4713
4715
  p_expected_role_version: null
4714
4716
  }, accessToken);
4715
- console.log(`[LiveDesk Hub] Sync Server role confirmed for ${runtimeDeviceId}.`);
4717
+ console.log(`[VuvoDesk Hub] Sync Server role confirmed for ${runtimeDeviceId}.`);
4716
4718
  return result;
4717
4719
  }
4718
4720
 
@@ -4782,12 +4784,12 @@ async function publishHubHostTarget({ takeover = false, reason = 'renewal' } = {
4782
4784
  lastPublishedEndpoint: target.endpoint || remoteStatus.agentEndpoint || '',
4783
4785
  lastExpiresAt: target.expiresAt || ''
4784
4786
  });
4785
- 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 || ''}.`);
4786
4788
  return { ok: true, active: true, hostTarget: target, lease: getHubHostTargetLeaseStatus() };
4787
4789
  } catch (error) {
4788
4790
  const message = error instanceof Error ? error.message : String(error);
4789
4791
  updateHubHostTargetLeaseState({ state: 'error', lastError: message });
4790
- console.error(`[LiveDesk Hub] Host target ${reason} publish failed: ${message}`);
4792
+ console.error(`[VuvoDesk Hub] Host target ${reason} publish failed: ${message}`);
4791
4793
  return { ok: false, active: false, error: message, lease: getHubHostTargetLeaseStatus() };
4792
4794
  } finally {
4793
4795
  hubHostTargetRenewInFlight = false;
@@ -4831,7 +4833,7 @@ async function clearHubHostTarget(reason = 'shutdown') {
4831
4833
  }
4832
4834
  } catch (cause) {
4833
4835
  error = cause instanceof Error ? cause.message : String(cause);
4834
- console.error(`[LiveDesk Hub] Host target ${reason} clear failed: ${error}`);
4836
+ console.error(`[VuvoDesk Hub] Host target ${reason} clear failed: ${error}`);
4835
4837
  }
4836
4838
  try {
4837
4839
  await remoteHub.setHostTarget({ enabled: false, nodeId: targetNodeId });
@@ -4846,7 +4848,7 @@ async function clearHubHostTarget(reason = 'shutdown') {
4846
4848
  lastExpiresAt: ''
4847
4849
  });
4848
4850
  if (!error) {
4849
- console.log(`[LiveDesk Hub] Host target cleared (${reason}).`);
4851
+ console.log(`[VuvoDesk Hub] Host target cleared (${reason}).`);
4850
4852
  }
4851
4853
  return { ok: !error, error, lease: getHubHostTargetLeaseStatus() };
4852
4854
  }
@@ -4867,7 +4869,7 @@ function scheduleAuthenticatedHubHostTargetPublication(reason = 'session-receive
4867
4869
  void publishHubHostTargetWithPendingRoleTakeover(reason).catch(error => {
4868
4870
  const message = error instanceof Error ? error.message : String(error);
4869
4871
  updateHubHostTargetLeaseState({ state: 'error', lastError: message });
4870
- console.error(`[LiveDesk Hub] Host target ${reason} background publish failed: ${message}`);
4872
+ console.error(`[VuvoDesk Hub] Host target ${reason} background publish failed: ${message}`);
4871
4873
  });
4872
4874
  });
4873
4875
  }
@@ -4921,7 +4923,7 @@ app.post('/api/hub/sync', async (req, res) => {
4921
4923
  } catch (error) {
4922
4924
  const message = error instanceof Error ? error.message : String(error);
4923
4925
  updateHubHostTargetLeaseState({ state: 'error', lastError: message, lastReason: 'sync-server' });
4924
- console.error(`[LiveDesk Hub] Sync Server failed: ${message}`);
4926
+ console.error(`[VuvoDesk Hub] Sync Server failed: ${message}`);
4925
4927
  res.status(409).json({ ok: false, error: message, lease: getHubHostTargetLeaseStatus() });
4926
4928
  }
4927
4929
  });
@@ -5182,64 +5184,64 @@ app.post('/api/remote/devices/:deviceId/status-diagnostic', (req, res) => {
5182
5184
  }));
5183
5185
  });
5184
5186
 
5185
- app.post('/api/remote/devices/:deviceId/input', requireHubFeatureAccess, (req, res) => {
5186
- noStore(res);
5187
- res.json(remoteHub.sendInputControl(req.params.deviceId, req.body || {}));
5188
- });
5189
-
5190
- app.post('/api/remote/devices/:deviceId/clipboard', requireHubFeatureAccess, async (req, res) => {
5191
- noStore(res);
5192
- try {
5193
- const request = normalizeRemoteClipboardRequest(req.body || {});
5194
- const inputClient = findLiveRemoteInputClient(request.hubConnectionId);
5195
- if (!inputClient) {
5196
- res.status(409).json({ ok: false, error: 'clipboard-input-owner-not-live' });
5197
- return;
5198
- }
5199
- const deviceId = String(req.params.deviceId || '').trim();
5200
- inputClient.liveDeskInputDeviceIds?.add?.(deviceId);
5201
-
5202
- if (REMOTE_CLIPBOARD_INPUT_ACTIONS.has(request.action)) {
5203
- const result = remoteHub.sendInputControl(deviceId, {
5204
- type: request.action,
5205
- clipboardOperationId: request.operationId,
5206
- operationId: request.operationId,
5207
- clipboardDirection: request.clipboardDirection,
5208
- manifest: request.payload.manifest || null,
5209
- contentKind: request.payload.contentKind || request.payload.manifest?.contentKind || '',
5210
- controlSessionId: request.binding.controlSessionId,
5211
- controlCommandId: request.binding.controlCommandId,
5212
- captureGeneration: request.binding.captureGeneration,
5213
- monitorIndex: request.binding.monitorIndex,
5214
- hubConnectionId: request.hubConnectionId,
5215
- requestAck: true
5216
- });
5217
- res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json({
5218
- ok: result.ok === true,
5219
- action: request.action,
5220
- operationId: request.operationId,
5221
- input: result,
5222
- ...(result.ok ? {} : { error: result.error || 'clipboard-input-failed' })
5223
- });
5224
- return;
5225
- }
5226
-
5227
- const result = await remoteHub.sendClipboardCommand(deviceId, request);
5228
- res.status(result.ok ? 200 : clipboardHttpStatus(result.error)).json(result);
5229
- } catch (error) {
5230
- const status = error instanceof RemoteClipboardContractError
5231
- ? error.status
5232
- : 500;
5233
- res.status(status).json({
5234
- ok: false,
5235
- error: error instanceof RemoteClipboardContractError
5236
- ? error.code
5237
- : 'clipboard-request-failed'
5238
- });
5239
- }
5240
- });
5241
-
5242
- 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) => {
5243
5245
  noStore(res);
5244
5246
  try {
5245
5247
  res.json(await hubFilesystem.getRoots({ refresh: /^(1|true|yes|on)$/i.test(String(req.query?.refresh || '')) }));
@@ -5953,14 +5955,14 @@ await remoteHub.start();
5953
5955
  connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
5954
5956
  hubSharedFolders.startAutoSync(
5955
5957
  () => remoteHub.listDevices({ includeDataUrl: false }).filter(device => device.connected).map(device => device.deviceId),
5956
- () => process.env.LIVEDESK_REMOTE_DIRECTORY || 'Desktop/LiveDeskFiles'
5958
+ () => process.env.LIVEDESK_REMOTE_DIRECTORY || 'Desktop/VuvoDeskFiles'
5957
5959
  );
5958
5960
  httpServer.listen(httpPort, httpHost, () => {
5959
5961
  const status = remoteHub.getStatus({ includeSecrets: true });
5960
5962
  const managerVersion = String(process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version || 'dev');
5961
- console.log(`[LiveDesk Hub] Version ${managerVersion}`);
5962
- console.log(`[LiveDesk Hub] HTTP API http://${httpHost}:${httpPort}`);
5963
- 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}`);
5964
5966
  hubConsoleRelay?.start();
5965
5967
  });
5966
5968
 
@@ -5986,9 +5988,9 @@ async function boundedShutdownStep(label, action, timeoutMs) {
5986
5988
  timer.unref?.();
5987
5989
  })
5988
5990
  ]);
5989
- console.log(`[LiveDesk Hub] Shutdown stage completed: ${label}.`);
5991
+ console.log(`[VuvoDesk Hub] Shutdown stage completed: ${label}.`);
5990
5992
  } catch (error) {
5991
- 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)}`);
5992
5994
  } finally {
5993
5995
  if (timer) clearTimeout(timer);
5994
5996
  }
@@ -5998,7 +6000,7 @@ function runSynchronousShutdownStep(label, action) {
5998
6000
  try {
5999
6001
  action();
6000
6002
  } catch (error) {
6001
- 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)}`);
6002
6004
  }
6003
6005
  }
6004
6006
 
@@ -6008,13 +6010,13 @@ function closeBrowserWebSockets() {
6008
6010
  for (const ws of wss.clients) {
6009
6011
  closeRequested += 1;
6010
6012
  try {
6011
- ws.close(1012, 'LiveDesk Hub restarting');
6013
+ ws.close(1012, 'VuvoDesk Hub restarting');
6012
6014
  } catch {
6013
6015
  try { ws.terminate(); } catch { /* socket already closed */ }
6014
6016
  }
6015
6017
  }
6016
6018
  }
6017
- 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.`);
6018
6020
  }
6019
6021
 
6020
6022
  function terminateOpenConnections() {
@@ -6033,7 +6035,7 @@ function terminateOpenConnections() {
6033
6035
  try { socket.destroy(); } catch { /* socket already closed */ }
6034
6036
  }
6035
6037
  try { httpServer.closeAllConnections?.(); } catch { /* older Node runtime */ }
6036
- 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).`);
6037
6039
  }
6038
6040
 
6039
6041
  let hubShutdownPromise = null;
@@ -6041,7 +6043,7 @@ function shutdownHub(signal) {
6041
6043
  if (hubShutdownPromise) return hubShutdownPromise;
6042
6044
  hubShutdownPromise = (async () => {
6043
6045
  const startedAt = Date.now();
6044
- 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.`);
6045
6047
  if (roleWatchTimer) clearInterval(roleWatchTimer);
6046
6048
  clearInterval(browserWebSocketHeartbeatTimer);
6047
6049
  runSynchronousShutdownStep('update manager close', () => liveDeskUpdateManager?.close());
@@ -6053,15 +6055,15 @@ function shutdownHub(signal) {
6053
6055
  try {
6054
6056
  httpServer.close(error => {
6055
6057
  if (error) {
6056
- console.warn(`[LiveDesk Hub] HTTP listener close warning: ${error.message}`);
6058
+ console.warn(`[VuvoDesk Hub] HTTP listener close warning: ${error.message}`);
6057
6059
  } else {
6058
- console.log('[LiveDesk Hub] Shutdown stage completed: HTTP listener closed.');
6060
+ console.log('[VuvoDesk Hub] Shutdown stage completed: HTTP listener closed.');
6059
6061
  }
6060
6062
  resolveClose();
6061
6063
  });
6062
6064
  httpServer.closeIdleConnections?.();
6063
6065
  } catch (error) {
6064
- 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)}`);
6065
6067
  resolveClose();
6066
6068
  }
6067
6069
  });
@@ -6081,7 +6083,7 @@ function shutdownHub(signal) {
6081
6083
  await Promise.race([httpClosed, waitForShutdownDelay(remainingMs)]);
6082
6084
  clearTimeout(forceCloseTimer);
6083
6085
  terminateOpenConnections();
6084
- console.log(`[LiveDesk Hub] Shutdown complete in ${Date.now() - startedAt}ms.`);
6086
+ console.log(`[VuvoDesk Hub] Shutdown complete in ${Date.now() - startedAt}ms.`);
6085
6087
  })();
6086
6088
  return hubShutdownPromise;
6087
6089
  }
@@ -6091,7 +6093,7 @@ for (const signal of ['SIGINT', 'SIGTERM']) {
6091
6093
  void shutdownHub(signal)
6092
6094
  .then(() => process.exit(0))
6093
6095
  .catch(error => {
6094
- 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)}`);
6095
6097
  terminateOpenConnections();
6096
6098
  process.exit(1);
6097
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: {