@livedesk/hub 0.1.40 → 0.1.42

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
@@ -51,13 +51,13 @@ import { normalizeRuntimeAuthSession, runtimeRoleError } from '../../runtime-cor
51
51
  import { fetchAuthResponse, AUTH_REQUEST_TIMEOUT_MS } from '../../runtime-core/src/auth-http.js';
52
52
  import { PRODUCTION_SUPABASE_PUBLISHABLE_KEY, PRODUCTION_SUPABASE_URL } from '../../runtime-core/src/auth-config.js';
53
53
  import { createHubRuntime } from './runtime/hub-runtime.js';
54
- import { hubRuntimeStatus } from './runtime/hub-runtime-status.js';
55
- import { roleGuardResponse } from './http/hub-role-guard.js';
56
- import {
57
- clearHubUiSessionCookie,
58
- createHubUiSessionAuthority,
59
- serializeHubUiSessionCookie
60
- } from './http/hub-ui-session.js';
54
+ import { hubRuntimeStatus } from './runtime/hub-runtime-status.js';
55
+ import { roleGuardResponse } from './http/hub-role-guard.js';
56
+ import {
57
+ clearHubUiSessionCookie,
58
+ createHubUiSessionAuthority,
59
+ serializeHubUiSessionCookie
60
+ } from './http/hub-ui-session.js';
61
61
 
62
62
  const __dirname = dirname(fileURLToPath(import.meta.url));
63
63
  const webDistCandidates = [
@@ -187,10 +187,10 @@ const persistentSessionGcToken = persistentSessionGcEnabled
187
187
  ? String(process.env.LIVEDESK_PERSISTENT_SESSION_TEST_TOKEN || '').trim()
188
188
  : '';
189
189
  let connectedDeviceCount = 0;
190
- let runtimeAccessToken = '';
191
- let runtimeRefreshToken = '';
192
- let runtimeAccessTokenExpiresAt = 0;
193
- const hubUiSessionAuthority = createHubUiSessionAuthority();
190
+ let runtimeAccessToken = '';
191
+ let runtimeRefreshToken = '';
192
+ let runtimeAccessTokenExpiresAt = 0;
193
+ const hubUiSessionAuthority = createHubUiSessionAuthority();
194
194
  let roleWatchInFlight = false;
195
195
  let verifiedLicense = {
196
196
  userId: '',
@@ -491,12 +491,12 @@ function clearPersistedRuntimeSession() {
491
491
  return writePrivateRuntimeAuthState(state);
492
492
  }
493
493
 
494
- function clearRuntimeSession() {
495
- runtimeAccessToken = '';
496
- runtimeRefreshToken = '';
497
- runtimeAccessTokenExpiresAt = 0;
498
- hubUiSessionAuthority.revokeAll();
499
- runtimeManager.setAuthenticated(false);
494
+ function clearRuntimeSession() {
495
+ runtimeAccessToken = '';
496
+ runtimeRefreshToken = '';
497
+ runtimeAccessTokenExpiresAt = 0;
498
+ hubUiSessionAuthority.revokeAll();
499
+ runtimeManager.setAuthenticated(false);
500
500
  try { clearPersistedRuntimeSession(); } catch { /* runtime auth is already invalid */ }
501
501
  }
502
502
 
@@ -796,11 +796,12 @@ function getLiveDeskUpdateStatus() {
796
796
  }
797
797
 
798
798
  if (process.env.LIVEDESK_DESKTOP_HOST !== '1') {
799
- liveDeskUpdateManager = createLiveDeskUpdateManager({
800
- remoteHub,
801
- currentManagerVersion: process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version,
802
- currentClientVersion: process.env.LIVEDESK_CLIENT_PACKAGE_VERSION || '',
803
- restartSupported: !!String(process.env.LIVEDESK_HUB_UPDATE_REQUEST_PATH || '').trim(),
799
+ liveDeskUpdateManager = createLiveDeskUpdateManager({
800
+ remoteHub,
801
+ currentManagerVersion: process.env.LIVEDESK_MANAGER_VERSION || packageInfo.version,
802
+ currentClientVersion: process.env.LIVEDESK_CLIENT_PACKAGE_VERSION || '',
803
+ excludedDeviceIds: [runtimeDeviceId],
804
+ restartSupported: !!String(process.env.LIVEDESK_HUB_UPDATE_REQUEST_PATH || '').trim(),
804
805
  requestHubRestart
805
806
  });
806
807
  }
@@ -1412,7 +1413,7 @@ function requestHostname(req) {
1412
1413
  }
1413
1414
  }
1414
1415
 
1415
- function isTrustedBrowserRequest(req) {
1416
+ function isTrustedBrowserRequest(req) {
1416
1417
  const origin = String(req.headers.origin || '').trim();
1417
1418
  if (!origin) {
1418
1419
  const fetchSite = String(req.headers['sec-fetch-site'] || '').toLowerCase();
@@ -1429,59 +1430,59 @@ function isTrustedBrowserRequest(req) {
1429
1430
  const targetHost = requestHostname(req);
1430
1431
  return (originHost === targetHost && isPrivateHubHostname(targetHost))
1431
1432
  || (isLoopbackHostname(originHost) && isLoopbackHostname(targetHost));
1432
- } catch {
1433
- return false;
1434
- }
1435
- }
1436
-
1437
- function isSecureHubHttpRequest(req) {
1438
- return req.socket?.encrypted === true
1439
- || String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https';
1440
- }
1441
-
1442
- function currentBoundHubUserId() {
1443
- return String(
1444
- runtimeManager.getSnapshot().userId
1445
- || readPersistedRuntimeSession()?.user?.id
1446
- || ''
1447
- ).trim();
1448
- }
1449
-
1450
- function isPublicHubApiRequest(req) {
1451
- const method = String(req.method || 'GET').toUpperCase();
1452
- const path = String(req.path || '');
1453
- return (method === 'GET' && (path === '/api/health' || path === '/api/runtime/status' || path === '/api/auth/status'))
1454
- || (method === 'POST' && path === '/api/auth/session');
1455
- }
1456
-
1457
- function isTrustedNativeLoopbackRequest(req) {
1458
- return isLoopbackAddress(req.socket?.remoteAddress)
1459
- && !String(req.headers.origin || '').trim()
1460
- && !String(req.headers['sec-fetch-site'] || '').trim();
1461
- }
1462
-
1463
- function authorizeHubUiRequest(req) {
1464
- if (process.env.LIVEDESK_TEST_MODE === '1') {
1465
- return { ok: true, explicitTestMode: true };
1466
- }
1467
- if (isTrustedNativeLoopbackRequest(req)) {
1468
- return { ok: true, nativeLoopback: true };
1469
- }
1470
- return hubUiSessionAuthority.authorize(req, currentBoundHubUserId());
1471
- }
1472
-
1473
- app.use((req, res, next) => {
1433
+ } catch {
1434
+ return false;
1435
+ }
1436
+ }
1437
+
1438
+ function isSecureHubHttpRequest(req) {
1439
+ return req.socket?.encrypted === true
1440
+ || String(req.headers['x-forwarded-proto'] || '').trim().toLowerCase() === 'https';
1441
+ }
1442
+
1443
+ function currentBoundHubUserId() {
1444
+ return String(
1445
+ runtimeManager.getSnapshot().userId
1446
+ || readPersistedRuntimeSession()?.user?.id
1447
+ || ''
1448
+ ).trim();
1449
+ }
1450
+
1451
+ function isPublicHubApiRequest(req) {
1452
+ const method = String(req.method || 'GET').toUpperCase();
1453
+ const path = String(req.path || '');
1454
+ return (method === 'GET' && (path === '/api/health' || path === '/api/runtime/status' || path === '/api/auth/status'))
1455
+ || (method === 'POST' && path === '/api/auth/session');
1456
+ }
1457
+
1458
+ function isTrustedNativeLoopbackRequest(req) {
1459
+ return isLoopbackAddress(req.socket?.remoteAddress)
1460
+ && !String(req.headers.origin || '').trim()
1461
+ && !String(req.headers['sec-fetch-site'] || '').trim();
1462
+ }
1463
+
1464
+ function authorizeHubUiRequest(req) {
1465
+ if (process.env.LIVEDESK_TEST_MODE === '1') {
1466
+ return { ok: true, explicitTestMode: true };
1467
+ }
1468
+ if (isTrustedNativeLoopbackRequest(req)) {
1469
+ return { ok: true, nativeLoopback: true };
1470
+ }
1471
+ return hubUiSessionAuthority.authorize(req, currentBoundHubUserId());
1472
+ }
1473
+
1474
+ app.use((req, res, next) => {
1474
1475
  if (!isTrustedBrowserRequest(req)) {
1475
1476
  res.status(403).json({ ok: false, error: 'untrusted-hub-origin' });
1476
1477
  return;
1477
1478
  }
1478
1479
  const origin = String(req.headers.origin || '').trim();
1479
1480
  if (origin) {
1480
- res.setHeader('Access-Control-Allow-Origin', origin);
1481
- res.setHeader('Access-Control-Allow-Credentials', 'true');
1482
- res.setHeader('Access-Control-Allow-Private-Network', 'true');
1481
+ res.setHeader('Access-Control-Allow-Origin', origin);
1482
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
1483
+ res.setHeader('Access-Control-Allow-Private-Network', 'true');
1483
1484
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
1484
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-LiveDesk-CSRF');
1485
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-LiveDesk-CSRF');
1485
1486
  res.setHeader('Vary', 'Origin, Access-Control-Request-Headers, Access-Control-Request-Private-Network');
1486
1487
  }
1487
1488
  if (req.method === 'OPTIONS') {
@@ -1490,21 +1491,21 @@ app.use((req, res, next) => {
1490
1491
  }
1491
1492
  next();
1492
1493
  });
1493
- app.use(express.json({ limit: '32mb' }));
1494
- app.use((req, res, next) => {
1495
- if (!req.path.startsWith('/api/') || isPublicHubApiRequest(req)) {
1496
- next();
1497
- return;
1498
- }
1499
- const authorization = authorizeHubUiRequest(req);
1500
- if (!authorization.ok) {
1501
- noStore(res);
1502
- res.status(authorization.status).json({ ok: false, error: authorization.error });
1503
- return;
1504
- }
1505
- next();
1506
- });
1507
- app.use((req, res, next) => {
1494
+ app.use(express.json({ limit: '32mb' }));
1495
+ app.use((req, res, next) => {
1496
+ if (!req.path.startsWith('/api/') || isPublicHubApiRequest(req)) {
1497
+ next();
1498
+ return;
1499
+ }
1500
+ const authorization = authorizeHubUiRequest(req);
1501
+ if (!authorization.ok) {
1502
+ noStore(res);
1503
+ res.status(authorization.status).json({ ok: false, error: authorization.error });
1504
+ return;
1505
+ }
1506
+ next();
1507
+ });
1508
+ app.use((req, res, next) => {
1508
1509
  if (runtimeRole === 'client' && req.path.startsWith('/api/remote')) {
1509
1510
  res.status(403).json(roleGuardResponse(runtimeRole, 'hub'));
1510
1511
  return;
@@ -2767,7 +2768,7 @@ function enqueueFramePacketForClient(ws, packetOwner, meta) {
2767
2768
  }
2768
2769
  // FRAME_LANE_GENERATION_CONTRACT_END
2769
2770
 
2770
- function updateFrameSubscription(ws, payload = {}) {
2771
+ function updateFrameSubscription(ws, payload = {}) {
2771
2772
  const previousDeviceIds = ws.liveDeskDeviceIds instanceof Set
2772
2773
  ? new Set(ws.liveDeskDeviceIds)
2773
2774
  : new Set();
@@ -2800,43 +2801,43 @@ function updateFrameSubscription(ws, payload = {}) {
2800
2801
  const targetIds = newlySubscribedIds.length > 0 ? newlySubscribedIds : deviceIds;
2801
2802
  for (const deviceId of targetIds.slice(0, 80)) {
2802
2803
  startFrameSubscriptionLive(ws, 'subscribe', deviceId, { reuseExisting: true });
2803
- }
2804
- }
2805
-
2806
- function refreshFrameSubscriptionLive(ws, payload = {}) {
2807
- if (!ws || ws.readyState !== 1) {
2808
- return;
2809
- }
2810
- const requestedIds = normalizeDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
2811
- const subscribedIds = [...(ws.liveDeskDeviceIds || [])];
2812
- const targetIds = requestedIds.length > 0
2813
- ? requestedIds.filter(deviceId => ws.liveDeskDeviceIds?.has?.(deviceId))
2814
- : subscribedIds;
2815
- if (targetIds.length === 0) {
2816
- return;
2817
- }
2818
- ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(
2819
- payload.autoStartLive ?? payload.startLive ?? ws.liveDeskAutoStart ?? ''
2820
- ));
2821
- ws.liveDeskLiveOptions = normalizeLiveOptions({
2822
- ...(ws.liveDeskLiveOptions || {}),
2823
- ...payload,
2824
- forceRestart: false,
2825
- reuseExisting: true,
2826
- monitorSelections: {
2827
- ...(ws.liveDeskLiveOptions?.monitorSelections || {}),
2828
- ...(payload.monitorSelections || {})
2829
- }
2830
- });
2831
- for (const deviceId of targetIds.slice(0, 80)) {
2832
- startFrameSubscriptionLive(ws, 'watchdog', deviceId, {
2833
- ...ws.liveDeskLiveOptions,
2834
- reuseExisting: true
2835
- });
2836
- }
2837
- }
2838
-
2839
- function startFrameSubscriptionLive(
2804
+ }
2805
+ }
2806
+
2807
+ function refreshFrameSubscriptionLive(ws, payload = {}) {
2808
+ if (!ws || ws.readyState !== 1) {
2809
+ return;
2810
+ }
2811
+ const requestedIds = normalizeDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId);
2812
+ const subscribedIds = [...(ws.liveDeskDeviceIds || [])];
2813
+ const targetIds = requestedIds.length > 0
2814
+ ? requestedIds.filter(deviceId => ws.liveDeskDeviceIds?.has?.(deviceId))
2815
+ : subscribedIds;
2816
+ if (targetIds.length === 0) {
2817
+ return;
2818
+ }
2819
+ ws.liveDeskAutoStart = /^(1|true|yes|on|live)$/i.test(String(
2820
+ payload.autoStartLive ?? payload.startLive ?? ws.liveDeskAutoStart ?? ''
2821
+ ));
2822
+ ws.liveDeskLiveOptions = normalizeLiveOptions({
2823
+ ...(ws.liveDeskLiveOptions || {}),
2824
+ ...payload,
2825
+ forceRestart: false,
2826
+ reuseExisting: true,
2827
+ monitorSelections: {
2828
+ ...(ws.liveDeskLiveOptions?.monitorSelections || {}),
2829
+ ...(payload.monitorSelections || {})
2830
+ }
2831
+ });
2832
+ for (const deviceId of targetIds.slice(0, 80)) {
2833
+ startFrameSubscriptionLive(ws, 'watchdog', deviceId, {
2834
+ ...ws.liveDeskLiveOptions,
2835
+ reuseExisting: true
2836
+ });
2837
+ }
2838
+ }
2839
+
2840
+ function startFrameSubscriptionLive(
2840
2841
  ws,
2841
2842
  reason = 'subscribe',
2842
2843
  onlyDeviceId = '',
@@ -4408,19 +4409,19 @@ app.post('/api/auth/session', async (req, res) => {
4408
4409
  res.status(400).json({ ok: false, error: normalized.error });
4409
4410
  return;
4410
4411
  }
4411
- const { access_token: accessToken, refresh_token: refreshToken, expires_at: expiresAt } = normalized.session;
4412
- try {
4413
- const user = await verifySupabaseUser(accessToken);
4414
- const boundUserId = currentBoundHubUserId();
4415
- if (boundUserId && boundUserId !== user.id) {
4416
- res.status(403).json({ ok: false, error: 'hub-account-mismatch' });
4417
- return;
4418
- }
4419
- if (!boundUserId && !isLoopbackAddress(req.socket?.remoteAddress)) {
4420
- res.status(403).json({ ok: false, error: 'hub-first-account-binding-must-be-local' });
4421
- return;
4422
- }
4423
- runtimeAccessToken = accessToken;
4412
+ const { access_token: accessToken, refresh_token: refreshToken, expires_at: expiresAt } = normalized.session;
4413
+ try {
4414
+ const user = await verifySupabaseUser(accessToken);
4415
+ const boundUserId = currentBoundHubUserId();
4416
+ if (boundUserId && boundUserId !== user.id) {
4417
+ res.status(403).json({ ok: false, error: 'hub-account-mismatch' });
4418
+ return;
4419
+ }
4420
+ if (!boundUserId && !isLoopbackAddress(req.socket?.remoteAddress)) {
4421
+ res.status(403).json({ ok: false, error: 'hub-first-account-binding-must-be-local' });
4422
+ return;
4423
+ }
4424
+ runtimeAccessToken = accessToken;
4424
4425
  runtimeRefreshToken = refreshToken;
4425
4426
  runtimeAccessTokenExpiresAt = normalizeSessionExpiryMs(expiresAt);
4426
4427
  runtimeManager.setAuthenticated(true, user.id);
@@ -4431,27 +4432,31 @@ app.post('/api/auth/session', async (req, res) => {
4431
4432
  user
4432
4433
  });
4433
4434
  const hostTarget = runtimeRole === 'hub'
4434
- ? await publishHubHostTargetWithPendingRoleTakeover('session-received')
4435
- : { ok: true, active: false };
4436
- if (runtimeRole === 'hub') {
4437
- startHubHostTargetLeaseRenewal();
4438
- }
4439
- const uiSession = hubUiSessionAuthority.issue(user.id);
4440
- res.setHeader('Set-Cookie', serializeHubUiSessionCookie(uiSession.sessionId, uiSession.expiresAt, {
4441
- secure: isSecureHubHttpRequest(req)
4442
- }));
4443
- res.json({
4444
- ok: true,
4445
- authenticated: true,
4446
- persisted,
4447
- userId: user.id,
4448
- role: runtimeRole,
4449
- hostTarget,
4450
- uiSession: {
4451
- csrfToken: uiSession.csrfToken,
4452
- expiresAt: uiSession.expiresAt
4453
- }
4454
- });
4435
+ ? {
4436
+ ok: true,
4437
+ pending: true,
4438
+ active: remoteHub.getStatus({ includeSecrets: false }).hostTargetActive === true
4439
+ }
4440
+ : { ok: true, pending: false, active: false };
4441
+ const uiSession = hubUiSessionAuthority.issue(user.id);
4442
+ res.setHeader('Set-Cookie', serializeHubUiSessionCookie(uiSession.sessionId, uiSession.expiresAt, {
4443
+ secure: isSecureHubHttpRequest(req)
4444
+ }));
4445
+ res.json({
4446
+ ok: true,
4447
+ authenticated: true,
4448
+ persisted,
4449
+ userId: user.id,
4450
+ role: runtimeRole,
4451
+ hostTarget,
4452
+ uiSession: {
4453
+ csrfToken: uiSession.csrfToken,
4454
+ expiresAt: uiSession.expiresAt
4455
+ }
4456
+ });
4457
+ if (runtimeRole === 'hub') {
4458
+ scheduleAuthenticatedHubHostTargetPublication('session-received');
4459
+ }
4455
4460
  } catch (error) {
4456
4461
  const message = error instanceof Error ? error.message : String(error);
4457
4462
  const status = authVerificationHttpStatus(message);
@@ -4747,15 +4752,26 @@ function startHubHostTargetLeaseRenewal() {
4747
4752
  hubHostTargetRenewTimer.unref?.();
4748
4753
  }
4749
4754
 
4750
- app.delete('/api/auth/session', async (req, res) => {
4755
+ function scheduleAuthenticatedHubHostTargetPublication(reason = 'session-received') {
4756
+ startHubHostTargetLeaseRenewal();
4757
+ setImmediate(() => {
4758
+ void publishHubHostTargetWithPendingRoleTakeover(reason).catch(error => {
4759
+ const message = error instanceof Error ? error.message : String(error);
4760
+ updateHubHostTargetLeaseState({ state: 'error', lastError: message });
4761
+ console.error(`[LiveDesk Hub] Host target ${reason} background publish failed: ${message}`);
4762
+ });
4763
+ });
4764
+ }
4765
+
4766
+ app.delete('/api/auth/session', async (req, res) => {
4751
4767
  noStore(res);
4752
4768
  const hostTarget = runtimeRole === 'hub'
4753
4769
  ? await clearHubHostTarget('logout')
4754
4770
  : { ok: true, active: false };
4755
- clearRuntimeSession();
4756
- res.setHeader('Set-Cookie', clearHubUiSessionCookie({ secure: isSecureHubHttpRequest(req) }));
4757
- res.json({ ok: true, authenticated: false, role: runtimeRole, hostTarget });
4758
- });
4771
+ clearRuntimeSession();
4772
+ res.setHeader('Set-Cookie', clearHubUiSessionCookie({ secure: isSecureHubHttpRequest(req) }));
4773
+ res.json({ ok: true, authenticated: false, role: runtimeRole, hostTarget });
4774
+ });
4759
4775
 
4760
4776
  app.get('/api/hub/status', (_req, res) => {
4761
4777
  noStore(res);
@@ -5556,20 +5572,20 @@ if (existsSync(webIndexPath)) {
5556
5572
  });
5557
5573
  }
5558
5574
 
5559
- httpServer.on('upgrade', (req, socket, head) => {
5560
- if (!isTrustedBrowserRequest(req)) {
5561
- socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
5562
- socket.destroy();
5563
- return;
5564
- }
5565
- const authorization = authorizeHubUiRequest(req);
5566
- if (!authorization.ok) {
5567
- const label = authorization.status === 403 ? 'Forbidden' : 'Unauthorized';
5568
- socket.write(`HTTP/1.1 ${authorization.status} ${label}\r\nConnection: close\r\n\r\n`);
5569
- socket.destroy();
5570
- return;
5571
- }
5572
- try {
5575
+ httpServer.on('upgrade', (req, socket, head) => {
5576
+ if (!isTrustedBrowserRequest(req)) {
5577
+ socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
5578
+ socket.destroy();
5579
+ return;
5580
+ }
5581
+ const authorization = authorizeHubUiRequest(req);
5582
+ if (!authorization.ok) {
5583
+ const label = authorization.status === 403 ? 'Forbidden' : 'Unauthorized';
5584
+ socket.write(`HTTP/1.1 ${authorization.status} ${label}\r\nConnection: close\r\n\r\n`);
5585
+ socket.destroy();
5586
+ return;
5587
+ }
5588
+ try {
5573
5589
  const parsed = new URL(req.url || '/', `http://${req.headers.host || '127.0.0.1'}`);
5574
5590
  if (parsed.pathname === '/api/remote/frames/ws') {
5575
5591
  frameWss.handleUpgrade(req, socket, head, ws => frameWss.emit('connection', ws, req));
@@ -5616,11 +5632,11 @@ frameWss.on('connection', (ws, req) => {
5616
5632
  ws.on('message', data => {
5617
5633
  try {
5618
5634
  const payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
5619
- if (payload?.type === 'subscribe') {
5620
- updateFrameSubscription(ws, payload);
5621
- } else if (payload?.type === 'refresh-live') {
5622
- refreshFrameSubscriptionLive(ws, payload);
5623
- } else if (payload?.type === 'restart-live') {
5635
+ if (payload?.type === 'subscribe') {
5636
+ updateFrameSubscription(ws, payload);
5637
+ } else if (payload?.type === 'refresh-live') {
5638
+ refreshFrameSubscriptionLive(ws, payload);
5639
+ } else if (payload?.type === 'restart-live') {
5624
5640
  restartFrameSubscriptionLive(ws, payload);
5625
5641
  }
5626
5642
  } catch {
@@ -1,16 +1,16 @@
1
- import { effectiveDevicePolicy } from './settings-schema.js';
2
-
3
- export function buildEffectiveDevicePolicy(settings, { deviceId = '', capabilities = {} } = {}) {
4
- const policy = effectiveDevicePolicy(settings);
5
- return {
6
- ...policy,
7
- deviceId: String(deviceId || ''),
8
- supported: {
9
- control: capabilities.control !== false,
10
- clipboardText: capabilities.clipboardText !== false,
11
- fileTransfer: capabilities.fileTransfer !== false,
12
- remoteAudio: capabilities.audio === true || capabilities.remoteAudio === true,
13
- agent: Array.isArray(capabilities.agentTools) && capabilities.agentTools.length > 0
14
- }
15
- };
16
- }
1
+ import { effectiveDevicePolicy } from './settings-schema.js';
2
+
3
+ export function buildEffectiveDevicePolicy(settings, { deviceId = '', capabilities = {} } = {}) {
4
+ const policy = effectiveDevicePolicy(settings);
5
+ return {
6
+ ...policy,
7
+ deviceId: String(deviceId || ''),
8
+ supported: {
9
+ control: capabilities.control !== false,
10
+ clipboardText: capabilities.clipboardText !== false,
11
+ fileTransfer: capabilities.fileTransfer !== false,
12
+ remoteAudio: capabilities.audio === true || capabilities.remoteAudio === true,
13
+ agent: Array.isArray(capabilities.agentTools) && capabilities.agentTools.length > 0
14
+ }
15
+ };
16
+ }