livedesk 0.1.656 → 0.1.657

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.
Files changed (24) hide show
  1. package/hub/src/remote-hub.js +47 -24
  2. package/hub/src/server.js +200 -13
  3. package/hub/src/shared-wall-profile-contract.mjs +40 -0
  4. package/hub/src/shared-wall-profile-contract.test.mjs +38 -0
  5. package/package.json +1 -1
  6. package/web/dist/app.html +3 -3
  7. package/web/dist/app.webmanifest +1 -1
  8. package/web/dist/assets/{AgentSettingsTab-D0271Ynp.js → AgentSettingsTab-CU3kHssT.js} +1 -1
  9. package/web/dist/assets/{AgentsPage-DEdUBoe0.js → AgentsPage-DcUy7xPU.js} +1 -1
  10. package/web/dist/assets/{App-DK9GZcvU.js → App-ARrqCA9B.js} +3 -3
  11. package/web/dist/assets/{CaptureGallery-C0xqFLap.js → CaptureGallery-BPKKxPej.js} +1 -1
  12. package/web/dist/assets/{DesktopUpdatePanel-uMaTnBsl.js → DesktopUpdatePanel-C5wDB5zf.js} +1 -1
  13. package/web/dist/assets/{SettingsPage-BoFLQBfH.js → SettingsPage-ChNfBaQo.js} +1 -1
  14. package/web/dist/assets/{SettingsTabs-D0zQN-Mo.js → SettingsTabs-B-uMhvwn.js} +1 -1
  15. package/web/dist/assets/{ShareFilesPage-BiCS0Nyd.js → ShareFilesPage-C9vhKzGp.js} +1 -1
  16. package/web/dist/assets/{SupportPage-D241Vkb1.js → SupportPage-C-uQ-RTX.js} +1 -1
  17. package/web/dist/assets/{VuvoDeskApp-36bRXXS2.js → VuvoDeskApp-BeeOb_MQ.js} +1 -1
  18. package/web/dist/assets/{app-hMd5uyvT.js → app-DhbXrOFp.js} +1 -1
  19. package/web/dist/assets/{main-DT492iHt.js → main-BTb7f--C.js} +2 -2
  20. package/web/dist/assets/styles-eZ2IzXEL.js +16 -0
  21. package/web/dist/index.html +2 -2
  22. package/web/dist/sw.js +2 -2
  23. package/web/dist/vuvodesk-build-evidence.json +51 -51
  24. package/web/dist/assets/styles-Ba1tPFHg.js +0 -16
@@ -17,14 +17,15 @@ import {
17
17
  BoundedSegmentedBuffer,
18
18
  createBoundedAgentBinaryIngressLane
19
19
  } from './transport/agent-binary-ingress.js';
20
- import {
21
- REMOTE_CLIPBOARD_FILE_ACTIONS,
20
+ import {
21
+ REMOTE_CLIPBOARD_FILE_ACTIONS,
22
22
  REMOTE_CLIPBOARD_LIMITS,
23
23
  REMOTE_CLIPBOARD_PROTOCOL,
24
24
  normalizeRemoteClipboardCommandResult,
25
25
  normalizeRemoteClipboardManifest,
26
26
  remoteClipboardContentNeedsFileTransfer
27
- } from './remote-clipboard-contract.mjs';
27
+ } from './remote-clipboard-contract.mjs';
28
+ import { liveProfileSatisfiesDemand } from './shared-wall-profile-contract.mjs';
28
29
 
29
30
  const DEFAULT_REMOTE_HUB_PORT = 5197;
30
31
  const DEFAULT_REMOTE_HUB_HOST = '0.0.0.0';
@@ -11276,8 +11277,22 @@ export function createRemoteHub(options = {}) {
11276
11277
  === normalized.streamPurpose;
11277
11278
  }
11278
11279
 
11279
- function sharedLiveProfileResult(device, activeLiveStream, normalized, extra = {}) {
11280
- return {
11280
+ function sharedLiveProfileResult(
11281
+ device,
11282
+ activeLiveStream,
11283
+ normalized,
11284
+ extra = {},
11285
+ subscriberRequestedProfile = null
11286
+ ) {
11287
+ const requested = subscriberRequestedProfile && typeof subscriberRequestedProfile === 'object'
11288
+ ? {
11289
+ fps: clampNumber(subscriberRequestedProfile.fps, 1, normalized.streamPurpose === 'control' ? 60 : 30, normalized.fps),
11290
+ maxWidth: clampNumber(subscriberRequestedProfile.maxWidth, 320, 3840, normalized.maxWidth),
11291
+ maxHeight: clampNumber(subscriberRequestedProfile.maxHeight, 180, 2160, normalized.maxHeight),
11292
+ quality: clampNumber(subscriberRequestedProfile.quality, 20, 95, normalized.quality)
11293
+ }
11294
+ : normalized;
11295
+ return {
11281
11296
  ok: true,
11282
11297
  commandId: activeLiveStream.commandId,
11283
11298
  sessionId: device.sessionId,
@@ -11290,12 +11305,12 @@ export function createRemoteHub(options = {}) {
11290
11305
  captureGeneration: Number(activeLiveStream.captureGeneration || 0),
11291
11306
  ready: liveStreamHasCurrentFrame(activeLiveStream),
11292
11307
  reused: true,
11293
- sharedProfileReused: true,
11294
- requestedProfile: {
11295
- fps: normalized.fps,
11296
- maxWidth: normalized.maxWidth,
11297
- maxHeight: normalized.maxHeight,
11298
- quality: normalized.quality
11308
+ sharedProfileReused: true,
11309
+ requestedProfile: {
11310
+ fps: requested.fps,
11311
+ maxWidth: requested.maxWidth,
11312
+ maxHeight: requested.maxHeight,
11313
+ quality: requested.quality
11299
11314
  },
11300
11315
  effectiveProfile: {
11301
11316
  fps: Number(activeLiveStream.fps || normalized.fps),
@@ -11506,10 +11521,11 @@ export function createRemoteHub(options = {}) {
11506
11521
  const pendingDescriptor = getPendingLiveStreamDescriptor(activeLiveStream);
11507
11522
  if (pendingDescriptor?.commandId) {
11508
11523
  const pendingMatchesRequest = liveStreamMatchesOptions(pendingDescriptor, normalized);
11509
- const pendingMatchesSharedOwner = options.forceRestart !== true
11510
- && options.reuseExisting === true
11511
- && options.reuseSharedExisting === true
11512
- && liveStreamMatchesOwnerIdentity(pendingDescriptor, normalized);
11524
+ const pendingMatchesSharedOwner = options.forceRestart !== true
11525
+ && options.reuseExisting === true
11526
+ && options.reuseSharedExisting === true
11527
+ && liveStreamMatchesOwnerIdentity(pendingDescriptor, normalized)
11528
+ && liveProfileSatisfiesDemand(pendingDescriptor, normalized);
11513
11529
  if ((pendingMatchesRequest || pendingMatchesSharedOwner)
11514
11530
  && liveStreamReplacementStillPending(activeLiveStream)) {
11515
11531
  emitRemoteEvent('RemoteLiveStreamRestartPending', device, {
@@ -11519,10 +11535,10 @@ export function createRemoteHub(options = {}) {
11519
11535
  reason: safeString(options.restartReason || 'duplicate-restart', 128)
11520
11536
  });
11521
11537
  if (pendingMatchesSharedOwner && !pendingMatchesRequest) {
11522
- return sharedLiveProfileResult(device, pendingDescriptor, normalized, {
11523
- ready: false,
11524
- pending: true
11525
- });
11538
+ return sharedLiveProfileResult(device, pendingDescriptor, normalized, {
11539
+ ready: false,
11540
+ pending: true
11541
+ }, options.subscriberRequestedProfile);
11526
11542
  }
11527
11543
  return {
11528
11544
  ok: true,
@@ -11599,10 +11615,11 @@ export function createRemoteHub(options = {}) {
11599
11615
  }
11600
11616
  if (activeLiveStream
11601
11617
  && options.forceRestart !== true
11602
- && options.reuseExisting === true
11603
- && options.reuseSharedExisting === true
11604
- && liveStreamMatchesOwnerIdentity(activeLiveStream, normalized)
11605
- && liveStreamIsReusable(activeLiveStream)) {
11618
+ && options.reuseExisting === true
11619
+ && options.reuseSharedExisting === true
11620
+ && liveStreamMatchesOwnerIdentity(activeLiveStream, normalized)
11621
+ && liveStreamIsReusable(activeLiveStream)
11622
+ && liveProfileSatisfiesDemand(activeLiveStream, normalized)) {
11606
11623
  emitRemoteEvent('RemoteLiveStreamSharedProfileReused', device, {
11607
11624
  streamId,
11608
11625
  commandId: activeLiveStream.commandId,
@@ -11614,7 +11631,13 @@ export function createRemoteHub(options = {}) {
11614
11631
  effectiveMaxWidth: Number(activeLiveStream.maxWidth || normalized.maxWidth),
11615
11632
  effectiveMaxHeight: Number(activeLiveStream.maxHeight || normalized.maxHeight)
11616
11633
  });
11617
- return sharedLiveProfileResult(device, activeLiveStream, normalized);
11634
+ return sharedLiveProfileResult(
11635
+ device,
11636
+ activeLiveStream,
11637
+ normalized,
11638
+ {},
11639
+ options.subscriberRequestedProfile
11640
+ );
11618
11641
  }
11619
11642
  if (activeLiveStream
11620
11643
  && options.forceRestart !== true
package/hub/src/server.js CHANGED
@@ -35,6 +35,7 @@ import {
35
35
  isReusedLiveStreamFrameReady
36
36
  } from './live-stream-monitor-contract.js';
37
37
  import { createLiveCaptureTransitionRetryCoordinator } from './live-capture-transition-retry.mjs';
38
+ import { selectSharedWallSourceProfile } from './shared-wall-profile-contract.mjs';
38
39
  import {
39
40
  createPlanDeviceAccessSnapshot,
40
41
  partitionPlanDeviceIds,
@@ -449,6 +450,25 @@ function handleRemoteHubEvent(type, event) {
449
450
  .scheduleAfterConfirmedStop(liveStreamEventDeviceId);
450
451
  }
451
452
  }
453
+ if (type === 'RemoteLiveStreamRestartFailed') {
454
+ const failedDeviceId = String(event?.deviceId || event?.device?.deviceId || '').trim();
455
+ const retainedOwner = event?.device?.activeLiveStream;
456
+ if (String(retainedOwner?.streamPurpose || '').trim().toLowerCase() === 'wall') {
457
+ rebindSharedWallSubscribersToOwner(failedDeviceId, {
458
+ ...retainedOwner,
459
+ sessionId: event?.device?.sessionId || ''
460
+ }, {
461
+ effectiveProfile: {
462
+ fps: Number(retainedOwner?.fps || 0),
463
+ maxWidth: Number(retainedOwner?.maxWidth || 0),
464
+ maxHeight: Number(retainedOwner?.maxHeight || 0),
465
+ quality: Number(retainedOwner?.quality || 0)
466
+ },
467
+ readySent: true,
468
+ reason: 'shared-wall-profile-upgrade-failed'
469
+ });
470
+ }
471
+ }
452
472
  if (type === 'RemoteDeviceConnected' || type === 'RemoteDeviceDisconnected') {
453
473
  connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
454
474
  refreshPlanDeviceAccess(type === 'RemoteDeviceConnected' ? 'device-connected' : 'device-disconnected');
@@ -2591,7 +2611,7 @@ function hasOtherFrameStreamOwner(ws, deviceId, streamId, streamPurpose = '') {
2591
2611
  return false;
2592
2612
  }
2593
2613
 
2594
- function hasOtherFrameStreamDemand(ws, deviceId, streamPurpose = '') {
2614
+ function hasOtherFrameStreamDemand(ws, deviceId, streamPurpose = '') {
2595
2615
  const normalizedPurpose = String(streamPurpose || '').trim().toLowerCase();
2596
2616
  if (!deviceId || !normalizedPurpose) {
2597
2617
  return false;
@@ -2608,8 +2628,135 @@ function hasOtherFrameStreamDemand(ws, deviceId, streamPurpose = '') {
2608
2628
  return true;
2609
2629
  }
2610
2630
  }
2611
- return false;
2612
- }
2631
+ return false;
2632
+ }
2633
+
2634
+ function frameClientMonitorIndex(ws, deviceId, liveOptions = ws?.liveDeskLiveOptions || {}) {
2635
+ return Object.prototype.hasOwnProperty.call(liveOptions.monitorSelections || {}, deviceId)
2636
+ ? normalizeMonitorIndex(liveOptions.monitorSelections[deviceId])
2637
+ : normalizeMonitorIndex(liveOptions.monitorIndex);
2638
+ }
2639
+
2640
+ function resolveSharedWallSourceDemand(ws, deviceId, liveOptions, monitorIndex) {
2641
+ const requestedMode = String(liveOptions?.frameMode || liveOptions?.mode || '').trim().toLowerCase();
2642
+ const requestedPurpose = String(liveOptions?.streamPurpose || '').trim().toLowerCase();
2643
+ const requestedMonitorIndex = normalizeMonitorIndex(monitorIndex);
2644
+ const demands = [{
2645
+ fps: liveOptions?.fps,
2646
+ maxWidth: liveOptions?.maxWidth,
2647
+ maxHeight: liveOptions?.maxHeight,
2648
+ quality: liveOptions?.quality
2649
+ }];
2650
+ if (requestedPurpose === 'wall') {
2651
+ for (const candidate of frameClients) {
2652
+ const candidateOptions = candidate?.liveDeskLiveOptions;
2653
+ if (candidate === ws
2654
+ || candidate?.readyState !== candidate?.OPEN
2655
+ || candidate?.liveDeskAutoStart !== true
2656
+ || !(candidate.liveDeskDeviceIds instanceof Set)
2657
+ || !candidate.liveDeskDeviceIds.has(deviceId)
2658
+ || String(candidateOptions?.streamPurpose || '').trim().toLowerCase() !== 'wall'
2659
+ || String(candidateOptions?.frameMode || candidateOptions?.mode || '').trim().toLowerCase()
2660
+ !== requestedMode
2661
+ || frameClientMonitorIndex(candidate, deviceId, candidateOptions) !== requestedMonitorIndex) {
2662
+ continue;
2663
+ }
2664
+ demands.push({
2665
+ fps: candidateOptions.fps,
2666
+ maxWidth: candidateOptions.maxWidth,
2667
+ maxHeight: candidateOptions.maxHeight,
2668
+ quality: candidateOptions.quality
2669
+ });
2670
+ }
2671
+ }
2672
+ return {
2673
+ ...selectSharedWallSourceProfile(demands),
2674
+ // Keep the strongest healthy generation until the final Wall viewer
2675
+ // releases it. This prevents a lone late low-rate refresh from undoing a
2676
+ // 30 fps upgrade and makes start order irrelevant.
2677
+ preserveDominantProfile: requestedPurpose === 'wall'
2678
+ };
2679
+ }
2680
+
2681
+ function rebindSharedWallSubscribersToOwner(deviceId, owner, {
2682
+ effectiveProfile = null,
2683
+ readySent = false,
2684
+ reason = 'shared-wall-profile-owner-changed'
2685
+ } = {}) {
2686
+ const normalizedDeviceId = String(deviceId || '').trim();
2687
+ const sessionId = String(owner?.sessionId || owner?.device?.sessionId || '').trim();
2688
+ const streamId = String(owner?.streamId || '').trim();
2689
+ const commandId = String(owner?.commandId || '').trim();
2690
+ const captureGeneration = Number(owner?.captureGeneration || 0);
2691
+ const monitorIndex = normalizeMonitorIndex(owner?.monitorIndex);
2692
+ const frameMode = String(owner?.frameMode || owner?.mode || '').trim().toLowerCase();
2693
+ const streamPurpose = String(owner?.streamPurpose || '').trim().toLowerCase();
2694
+ if (!normalizedDeviceId
2695
+ || !sessionId
2696
+ || !streamId
2697
+ || !commandId
2698
+ || !Number.isSafeInteger(captureGeneration)
2699
+ || captureGeneration <= 0
2700
+ || streamPurpose !== 'wall') {
2701
+ return 0;
2702
+ }
2703
+ let rebound = 0;
2704
+ const candidates = new Set([
2705
+ ...(frameClientsByDeviceId.get(normalizedDeviceId) || []),
2706
+ ...frameWildcardClients
2707
+ ]);
2708
+ for (const candidate of candidates) {
2709
+ const candidateOptions = candidate?.liveDeskLiveOptions;
2710
+ if (candidate?.readyState !== candidate?.OPEN
2711
+ || candidate?.liveDeskAutoStart !== true
2712
+ || !(candidate.liveDeskDeviceIds instanceof Set)
2713
+ || !candidate.liveDeskDeviceIds.has(normalizedDeviceId)
2714
+ || String(candidateOptions?.streamPurpose || '').trim().toLowerCase() !== 'wall'
2715
+ || String(candidateOptions?.frameMode || candidateOptions?.mode || '').trim().toLowerCase()
2716
+ !== frameMode
2717
+ || frameClientMonitorIndex(candidate, normalizedDeviceId, candidateOptions) !== monitorIndex) {
2718
+ continue;
2719
+ }
2720
+ const previousBinding = candidate.liveDeskExpectedStreamBindingsByDeviceId instanceof Map
2721
+ ? candidate.liveDeskExpectedStreamBindingsByDeviceId.get(normalizedDeviceId)
2722
+ : null;
2723
+ if (!previousBinding || previousBinding.readOnlyControlBorrow === true) continue;
2724
+ const nextBinding = {
2725
+ deviceId: normalizedDeviceId,
2726
+ sessionId,
2727
+ streamId,
2728
+ streamPurpose: 'wall',
2729
+ commandId,
2730
+ captureGeneration,
2731
+ monitorIndex,
2732
+ readOnlyControlBorrow: false,
2733
+ presentationPurpose: '',
2734
+ effectiveProfile,
2735
+ readySent: readySent === true
2736
+ };
2737
+ const previousIdentity = frameLaneBindingIdentity(previousBinding);
2738
+ const nextIdentity = replaceExpectedFrameBindingForClient(
2739
+ candidate,
2740
+ normalizedDeviceId,
2741
+ nextBinding
2742
+ );
2743
+ if (!nextIdentity || nextIdentity === previousIdentity) continue;
2744
+ candidate.liveDeskStreamIdsByDeviceId?.set?.(normalizedDeviceId, streamId);
2745
+ candidate.liveDeskSharedProfileUpgradeCount = Math.max(
2746
+ 0,
2747
+ Number(candidate.liveDeskSharedProfileUpgradeCount || 0)
2748
+ ) + 1;
2749
+ rebound += 1;
2750
+ }
2751
+ if (rebound > 0) {
2752
+ console.log(
2753
+ `[VuvoDesk Hub] rebound ${rebound} Wall browser lane(s) device=${normalizedDeviceId} `
2754
+ + `generation=${captureGeneration} profile=${Number(effectiveProfile?.fps || owner?.fps || 0)}fps `
2755
+ + `reason=${String(reason || 'shared-wall-profile-owner-changed')}`
2756
+ );
2757
+ }
2758
+ return rebound;
2759
+ }
2613
2760
 
2614
2761
  function frameStreamStopKey(deviceId, streamId, streamPurpose) {
2615
2762
  return `${deviceId}\u0000${streamId}\u0000${streamPurpose}`;
@@ -3004,8 +3151,9 @@ function snapshotFrameLaneResourceHealth() {
3004
3151
  maxWidth: Math.max(0, Number(ws.liveDeskLiveOptions.maxWidth || 0)),
3005
3152
  maxHeight: Math.max(0, Number(ws.liveDeskLiveOptions.maxHeight || 0)),
3006
3153
  quality: Math.max(0, Number(ws.liveDeskLiveOptions.quality || 0))
3007
- } : null,
3008
- sharedProfileReuseCount: Math.max(0, Number(ws.liveDeskSharedProfileReuseCount || 0)),
3154
+ } : null,
3155
+ sharedProfileReuseCount: Math.max(0, Number(ws.liveDeskSharedProfileReuseCount || 0)),
3156
+ sharedProfileUpgradeCount: Math.max(0, Number(ws.liveDeskSharedProfileUpgradeCount || 0)),
3009
3157
  queueDepth: Math.max(0, Number(lane?.queue?.length || 0)),
3010
3158
  queueLimit: frameClientQueueLimit(ws),
3011
3159
  queueHighWaterPackets: Math.max(0, Number(lane?.queuedPacketsHighWater || 0)),
@@ -3810,12 +3958,33 @@ function startFrameSubscriptionLive(
3810
3958
  skipped.push({ deviceId, reason: device?.connected ? 'live-unavailable' : 'not-connected' });
3811
3959
  continue;
3812
3960
  }
3813
- const monitorIndex = Object.prototype.hasOwnProperty.call(liveOptions.monitorSelections || {}, deviceId)
3814
- ? liveOptions.monitorSelections[deviceId]
3815
- : liveOptions.monitorIndex;
3961
+ const monitorIndex = Object.prototype.hasOwnProperty.call(liveOptions.monitorSelections || {}, deviceId)
3962
+ ? liveOptions.monitorSelections[deviceId]
3963
+ : liveOptions.monitorIndex;
3964
+ const sharedWallSourceProfile = resolveSharedWallSourceDemand(
3965
+ ws,
3966
+ deviceId,
3967
+ liveOptions,
3968
+ monitorIndex
3969
+ );
3970
+ const sourceLiveOptions = sharedWallSourceProfile.preserveDominantProfile
3971
+ ? {
3972
+ ...liveOptions,
3973
+ fps: sharedWallSourceProfile.fps,
3974
+ maxWidth: sharedWallSourceProfile.maxWidth,
3975
+ maxHeight: sharedWallSourceProfile.maxHeight,
3976
+ quality: sharedWallSourceProfile.quality
3977
+ }
3978
+ : liveOptions;
3816
3979
  const result = remoteHub.startLiveStream(deviceId, {
3817
- ...liveOptions,
3980
+ ...sourceLiveOptions,
3818
3981
  monitorIndex,
3982
+ subscriberRequestedProfile: {
3983
+ fps: liveOptions.fps,
3984
+ maxWidth: liveOptions.maxWidth,
3985
+ maxHeight: liveOptions.maxHeight,
3986
+ quality: liveOptions.quality
3987
+ },
3819
3988
  reuseExisting: liveOptions.forceRestart !== true
3820
3989
  && (liveOptions.reuseExisting === true || reason === 'subscribe' || reason === 'watchdog'),
3821
3990
  // Wall capture is one shared native encoder per device. Two open browser
@@ -3825,7 +3994,8 @@ function startFrameSubscriptionLive(
3825
3994
  // and forth every time either view refreshes its subscription.
3826
3995
  reuseSharedExisting: liveOptions.forceRestart !== true
3827
3996
  && String(liveOptions.streamPurpose || '').trim().toLowerCase() === 'wall'
3828
- && hasOtherFrameStreamDemand(ws, deviceId, 'wall'),
3997
+ && (hasOtherFrameStreamDemand(ws, deviceId, 'wall')
3998
+ || sharedWallSourceProfile.preserveDominantProfile === true),
3829
3999
  silentReuse: reason === 'watchdog' && liveOptions.forceRestart !== true
3830
4000
  });
3831
4001
  if (liveOptions.forceRestart === true) {
@@ -3834,8 +4004,25 @@ function startFrameSubscriptionLive(
3834
4004
  `[VuvoDesk Hub] browser frame recovery client=${String(ws.liveDeskFrameClientId || '-')} device=${deviceId} purpose=${String(liveOptions.streamPurpose || 'wall')} reason=${String(liveOptions.restartReason || reason || 'browser-stale')} token=${token ? token.slice(0, 12) : '-'} result=${result?.ok ? 'accepted' : String(result?.error || 'failed')} session=${String(result?.sessionId || device?.sessionId || '').slice(0, 8)} stream=${String(result?.streamId || '-')} generation=${Number(result?.captureGeneration || 0)} reused=${result?.reused === true}`
3835
4005
  );
3836
4006
  }
3837
- if (result?.ok) {
3838
- if (result.sharedProfileReused === true) {
4007
+ if (result?.ok) {
4008
+ const effectiveWallProfile = String(result.streamPurpose || liveOptions.streamPurpose || '')
4009
+ .trim().toLowerCase() === 'wall'
4010
+ ? {
4011
+ fps: Number(result.effectiveProfile?.fps || sourceLiveOptions.fps || result.fps || 0),
4012
+ maxWidth: Number(result.effectiveProfile?.maxWidth || sourceLiveOptions.maxWidth || 0),
4013
+ maxHeight: Number(result.effectiveProfile?.maxHeight || sourceLiveOptions.maxHeight || 0),
4014
+ quality: Number(result.effectiveProfile?.quality || sourceLiveOptions.quality || 0)
4015
+ }
4016
+ : result.effectiveProfile || null;
4017
+ if (result.reused !== true
4018
+ && String(result.streamPurpose || '').trim().toLowerCase() === 'wall') {
4019
+ rebindSharedWallSubscribersToOwner(deviceId, result, {
4020
+ effectiveProfile: effectiveWallProfile,
4021
+ readySent: false,
4022
+ reason: 'shared-wall-profile-upgraded'
4023
+ });
4024
+ }
4025
+ if (result.sharedProfileReused === true) {
3839
4026
  ws.liveDeskSharedProfileReuseCount = Math.max(
3840
4027
  0,
3841
4028
  Number(ws.liveDeskSharedProfileReuseCount || 0)
@@ -3852,7 +4039,7 @@ function startFrameSubscriptionLive(
3852
4039
  monitorIndex: Number(result.monitorIndex || 0),
3853
4040
  readOnlyControlBorrow: result.readOnlyControlBorrow === true,
3854
4041
  presentationPurpose: result.presentationPurpose === 'wall' ? 'wall' : '',
3855
- effectiveProfile: result.effectiveProfile || null,
4042
+ effectiveProfile: effectiveWallProfile,
3856
4043
  readySent: false
3857
4044
  };
3858
4045
  const activeStream = device?.activeLiveStream;
@@ -0,0 +1,40 @@
1
+ function finiteProfileNumber(value, fallback = 0) {
2
+ const numeric = Number(value);
3
+ return Number.isFinite(numeric) && numeric > 0 ? numeric : fallback;
4
+ }
5
+
6
+ export function selectSharedWallSourceProfile(demands = []) {
7
+ const profiles = Array.isArray(demands) ? demands.filter(Boolean) : [];
8
+ if (profiles.length === 0) {
9
+ return Object.freeze({
10
+ fps: 0,
11
+ maxWidth: 0,
12
+ maxHeight: 0,
13
+ quality: 0,
14
+ demandCount: 0
15
+ });
16
+ }
17
+ const selected = profiles.reduce((profile, demand) => ({
18
+ fps: Math.max(profile.fps, finiteProfileNumber(demand.fps)),
19
+ maxWidth: Math.max(profile.maxWidth, finiteProfileNumber(demand.maxWidth)),
20
+ maxHeight: Math.max(profile.maxHeight, finiteProfileNumber(demand.maxHeight)),
21
+ quality: Math.max(profile.quality, finiteProfileNumber(demand.quality))
22
+ }), {
23
+ fps: 0,
24
+ maxWidth: 0,
25
+ maxHeight: 0,
26
+ quality: 0
27
+ });
28
+ return Object.freeze({
29
+ ...selected,
30
+ demandCount: profiles.length
31
+ });
32
+ }
33
+
34
+ export function liveProfileSatisfiesDemand(activeProfile, requestedProfile) {
35
+ if (!activeProfile || !requestedProfile) return false;
36
+ return finiteProfileNumber(activeProfile.fps) >= finiteProfileNumber(requestedProfile.fps)
37
+ && finiteProfileNumber(activeProfile.maxWidth) >= finiteProfileNumber(requestedProfile.maxWidth)
38
+ && finiteProfileNumber(activeProfile.maxHeight) >= finiteProfileNumber(requestedProfile.maxHeight)
39
+ && finiteProfileNumber(activeProfile.quality) >= finiteProfileNumber(requestedProfile.quality);
40
+ }
@@ -0,0 +1,38 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import {
5
+ liveProfileSatisfiesDemand,
6
+ selectSharedWallSourceProfile
7
+ } from './shared-wall-profile-contract.mjs';
8
+
9
+ test('shared Wall source keeps the strongest field from every current viewer', () => {
10
+ assert.deepEqual(selectSharedWallSourceProfile([
11
+ { fps: 5, maxWidth: 960, maxHeight: 540, quality: 68 },
12
+ { fps: 30, maxWidth: 1920, maxHeight: 1080, quality: 75 }
13
+ ]), {
14
+ fps: 30,
15
+ maxWidth: 1920,
16
+ maxHeight: 1080,
17
+ quality: 75,
18
+ demandCount: 2
19
+ });
20
+ assert.deepEqual(selectSharedWallSourceProfile([
21
+ { fps: 5, maxWidth: 3840, maxHeight: 2160, quality: 70 },
22
+ { fps: 30, maxWidth: 1920, maxHeight: 1080, quality: 80 }
23
+ ]), {
24
+ fps: 30,
25
+ maxWidth: 3840,
26
+ maxHeight: 2160,
27
+ quality: 80,
28
+ demandCount: 2
29
+ });
30
+ });
31
+
32
+ test('a shared source is reused only when it satisfies every requested profile field', () => {
33
+ const high = { fps: 30, maxWidth: 1920, maxHeight: 1080, quality: 75 };
34
+ const low = { fps: 5, maxWidth: 960, maxHeight: 540, quality: 68 };
35
+ assert.equal(liveProfileSatisfiesDemand(high, low), true);
36
+ assert.equal(liveProfileSatisfiesDemand(low, high), false);
37
+ assert.equal(liveProfileSatisfiesDemand({ ...high, fps: 5 }, high), false);
38
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.656",
3
+ "version": "0.1.657",
4
4
  "livedeskClientVersion": "0.1.271",
5
5
  "buildFlavor": "production",
6
6
  "description": "VuvoDesk Hub and client launcher",
package/web/dist/app.html CHANGED
@@ -52,13 +52,13 @@
52
52
  .vuvodesk-static-boot [hidden] { display: none; }
53
53
  @keyframes vuvodesk-static-boot-spin { to { transform: rotate(360deg); } }
54
54
  </style>
55
- <script type="module" crossorigin src="/assets/app-hMd5uyvT.js" data-vuvodesk-entry></script>
55
+ <script type="module" crossorigin src="/assets/app-DhbXrOFp.js" data-vuvodesk-entry></script>
56
56
  <link rel="modulepreload" crossorigin href="/assets/icons-CacEbnZu.js">
57
57
  <link rel="modulepreload" crossorigin href="/assets/react-Cfedt3N5.js">
58
- <link rel="modulepreload" crossorigin href="/assets/styles-Ba1tPFHg.js">
58
+ <link rel="modulepreload" crossorigin href="/assets/styles-eZ2IzXEL.js">
59
59
  <link rel="modulepreload" crossorigin href="/assets/supabase-C7qjtLN3.js">
60
60
  <link rel="modulepreload" crossorigin href="/assets/frame-lifecycle-esOdP-EY.js">
61
- <link rel="modulepreload" crossorigin href="/assets/App-DK9GZcvU.js">
61
+ <link rel="modulepreload" crossorigin href="/assets/App-ARrqCA9B.js">
62
62
  <link rel="stylesheet" crossorigin href="/assets/styles-DDKP4MTD.css">
63
63
  <link rel="stylesheet" crossorigin href="/assets/App-DFLBNvIi.css">
64
64
  </head>
@@ -4,7 +4,7 @@
4
4
  "short_name": "VuvoDesk",
5
5
  "description": "Monitor and control your VuvoDesk computers from your phone.",
6
6
  "lang": "en",
7
- "start_url": "/app?pwa=0.1.656",
7
+ "start_url": "/app?pwa=0.1.657",
8
8
  "scope": "/",
9
9
  "display": "standalone",
10
10
  "orientation": "any",
@@ -1 +1 @@
1
- import{h as x,j as t}from"./styles-Ba1tPFHg.js";import{a as c,a5 as A,k as q,a6 as I,a7 as z}from"./icons-CacEbnZu.js";import"./react-Cfedt3N5.js";const E={enabled:!0};function w(a){return{enabled:a?.enabled===!0}}async function L(a,p,f){let l=null;for(let s=0;s<2;s+=1){const g=await x("/api/settings",{signal:f},p);if(g.settings.agent?.enabled===a)return;try{await x("/api/settings",{method:"PATCH",signal:f,body:JSON.stringify({revision:g.revision,agent:{enabled:a}})},p);return}catch(i){if(l=i,s!==0||!String(i instanceof Error?i.message:i).startsWith("409 "))throw i}}throw l}function F({hubUrl:a,showToast:p}){const[f,l]=c.useState(E),[s,g]=c.useState(null),[i,m]=c.useState(""),[j,v]=c.useState(""),[y,d]=c.useState(""),b=c.useRef(0),u=c.useRef(null);c.useEffect(()=>{let e=!0;u.current?.abort();const n=new AbortController;u.current=n;const h=++b.current;return g(null),l(E),m(""),d(""),v(""),x("/api/settings/agent",{signal:n.signal},a).then(o=>{!e||n.signal.aborted||h!==b.current||(g(o.settings),l(w(o.settings)))}).catch(o=>{e&&!n.signal.aborted&&h===b.current&&d(o instanceof Error?o.message:String(o))}),()=>{e=!1,n.abort(),u.current===n&&(u.current=null)}},[a]);const k=(e,n)=>l(h=>({...h,[e]:n})),C=e=>{g(e),l(w(e))},S=()=>({generation:b.current,controller:u.current}),r=e=>e.generation===b.current&&e.controller!==null&&e.controller===u.current&&!e.controller.signal.aborted,R=async()=>{if(!s){d("Agent settings are not available yet.");return}const e=S();if(r(e)){m("save"),d(""),v("");try{if(await L(f.enabled,a,e.controller.signal),!r(e))return;const n=await x("/api/settings/agent",{signal:e.controller.signal},a);if(!r(e))return;C(n.settings),v("Codex Agent settings saved.")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},D=async()=>{const e=S();if(r(e)){m("test"),d(""),v("");try{const n=await x("/api/settings/agent/test",{method:"POST",signal:e.controller.signal,body:JSON.stringify({})},a);if(!r(e))return;const h=await x("/api/settings/agent",{signal:e.controller.signal},a);if(!r(e))return;C(h.settings);const o=Number.isFinite(n.connection?.latencyMs)?Math.max(0,Math.round(n.connection.latencyMs)):null;p(`Codex Agent connection verified${o!==null?` in ${o} ms.`:"."}`,"success")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},T=s?.codexInstallation==="installed"&&s.codexAuth==="signed-in",N=s!==null;return t.jsxs("div",{className:"settings-tab-content agent-settings-content",children:[t.jsxs("section",{className:"settings-section-card agent-enable-card",children:[t.jsxs("label",{className:"settings-toggle agent-enable-toggle",children:[t.jsx("input",{type:"checkbox",checked:f.enabled,onChange:e=>k("enabled",e.target.checked),disabled:!N||i!==""}),t.jsx("span",{children:"Enable Codex Agent"})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," Enabled by default. Agent commands use only the Codex account signed in on this Hub."]})]}),y&&t.jsx("div",{className:"agent-settings-alert error",role:"alert",children:y}),j&&t.jsx("div",{className:"agent-settings-alert success",role:"status",children:j}),t.jsxs("section",{className:"settings-section-card",children:[t.jsxs("div",{className:"settings-section-heading",children:[t.jsxs("div",{children:[t.jsx("span",{children:"Connection"}),t.jsx("h3",{children:"Codex SDK / CLI"})]}),t.jsx(q,{size:20})]}),t.jsxs("div",{className:"agent-connection-row",children:[t.jsx("span",{className:`agent-connection-dot ${T?"connected":s?.codexAuth==="not-signed-in"?"unavailable":""}`}),t.jsx("span",{children:s?.codexInstallation==="not-installed"?"Codex SDK not installed":s?.codexAuth==="signed-in"?"Codex CLI signed in":s?.codexAuth==="not-signed-in"?"Codex CLI sign-in required":"Codex status not tested"}),t.jsxs("button",{className:"settings-reset",onClick:()=>{D()},disabled:i!=="",type:"button",children:[t.jsx(I,{size:15})," ",i==="test"?"Testing":"Test connection"]})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," The Hub uses the installed Codex SDK / CLI to plan approved commands and run them through VuvoDesk Agent tools on selected computers. VuvoDesk never asks for separate credentials."]})]}),t.jsx("div",{className:"settings-action-row agent-settings-actions",children:t.jsxs("button",{className:"text-button primary",onClick:()=>{R()},disabled:!N||i!=="",type:"button",children:[t.jsx(z,{size:15})," ",i==="save"?"Saving":"Save Agent settings"]})})]})}export{F as AgentSettingsTab};
1
+ import{h as x,j as t}from"./styles-eZ2IzXEL.js";import{a as c,a5 as A,k as q,a6 as I,a7 as z}from"./icons-CacEbnZu.js";import"./react-Cfedt3N5.js";const E={enabled:!0};function w(a){return{enabled:a?.enabled===!0}}async function L(a,p,f){let l=null;for(let s=0;s<2;s+=1){const g=await x("/api/settings",{signal:f},p);if(g.settings.agent?.enabled===a)return;try{await x("/api/settings",{method:"PATCH",signal:f,body:JSON.stringify({revision:g.revision,agent:{enabled:a}})},p);return}catch(i){if(l=i,s!==0||!String(i instanceof Error?i.message:i).startsWith("409 "))throw i}}throw l}function F({hubUrl:a,showToast:p}){const[f,l]=c.useState(E),[s,g]=c.useState(null),[i,m]=c.useState(""),[j,v]=c.useState(""),[y,d]=c.useState(""),b=c.useRef(0),u=c.useRef(null);c.useEffect(()=>{let e=!0;u.current?.abort();const n=new AbortController;u.current=n;const h=++b.current;return g(null),l(E),m(""),d(""),v(""),x("/api/settings/agent",{signal:n.signal},a).then(o=>{!e||n.signal.aborted||h!==b.current||(g(o.settings),l(w(o.settings)))}).catch(o=>{e&&!n.signal.aborted&&h===b.current&&d(o instanceof Error?o.message:String(o))}),()=>{e=!1,n.abort(),u.current===n&&(u.current=null)}},[a]);const k=(e,n)=>l(h=>({...h,[e]:n})),C=e=>{g(e),l(w(e))},S=()=>({generation:b.current,controller:u.current}),r=e=>e.generation===b.current&&e.controller!==null&&e.controller===u.current&&!e.controller.signal.aborted,R=async()=>{if(!s){d("Agent settings are not available yet.");return}const e=S();if(r(e)){m("save"),d(""),v("");try{if(await L(f.enabled,a,e.controller.signal),!r(e))return;const n=await x("/api/settings/agent",{signal:e.controller.signal},a);if(!r(e))return;C(n.settings),v("Codex Agent settings saved.")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},D=async()=>{const e=S();if(r(e)){m("test"),d(""),v("");try{const n=await x("/api/settings/agent/test",{method:"POST",signal:e.controller.signal,body:JSON.stringify({})},a);if(!r(e))return;const h=await x("/api/settings/agent",{signal:e.controller.signal},a);if(!r(e))return;C(h.settings);const o=Number.isFinite(n.connection?.latencyMs)?Math.max(0,Math.round(n.connection.latencyMs)):null;p(`Codex Agent connection verified${o!==null?` in ${o} ms.`:"."}`,"success")}catch(n){r(e)&&d(n instanceof Error?n.message:String(n))}finally{r(e)&&m("")}}},T=s?.codexInstallation==="installed"&&s.codexAuth==="signed-in",N=s!==null;return t.jsxs("div",{className:"settings-tab-content agent-settings-content",children:[t.jsxs("section",{className:"settings-section-card agent-enable-card",children:[t.jsxs("label",{className:"settings-toggle agent-enable-toggle",children:[t.jsx("input",{type:"checkbox",checked:f.enabled,onChange:e=>k("enabled",e.target.checked),disabled:!N||i!==""}),t.jsx("span",{children:"Enable Codex Agent"})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," Enabled by default. Agent commands use only the Codex account signed in on this Hub."]})]}),y&&t.jsx("div",{className:"agent-settings-alert error",role:"alert",children:y}),j&&t.jsx("div",{className:"agent-settings-alert success",role:"status",children:j}),t.jsxs("section",{className:"settings-section-card",children:[t.jsxs("div",{className:"settings-section-heading",children:[t.jsxs("div",{children:[t.jsx("span",{children:"Connection"}),t.jsx("h3",{children:"Codex SDK / CLI"})]}),t.jsx(q,{size:20})]}),t.jsxs("div",{className:"agent-connection-row",children:[t.jsx("span",{className:`agent-connection-dot ${T?"connected":s?.codexAuth==="not-signed-in"?"unavailable":""}`}),t.jsx("span",{children:s?.codexInstallation==="not-installed"?"Codex SDK not installed":s?.codexAuth==="signed-in"?"Codex CLI signed in":s?.codexAuth==="not-signed-in"?"Codex CLI sign-in required":"Codex status not tested"}),t.jsxs("button",{className:"settings-reset",onClick:()=>{D()},disabled:i!=="",type:"button",children:[t.jsx(I,{size:15})," ",i==="test"?"Testing":"Test connection"]})]}),t.jsxs("p",{className:"settings-help",children:[t.jsx(A,{size:12})," The Hub uses the installed Codex SDK / CLI to plan approved commands and run them through VuvoDesk Agent tools on selected computers. VuvoDesk never asks for separate credentials."]})]}),t.jsx("div",{className:"settings-action-row agent-settings-actions",children:t.jsxs("button",{className:"text-button primary",onClick:()=>{R()},disabled:!N||i!=="",type:"button",children:[t.jsx(z,{size:15})," ",i==="save"?"Saving":"Save Agent settings"]})})]})}export{F as AgentSettingsTab};
@@ -1,4 +1,4 @@
1
- import{j as t,e as ht,h as F}from"./styles-Ba1tPFHg.js";import{a as d,m as yt,n as Ce,o as vt,l as ee,X as xe,p as wt,q as kt,s as Je,B as bt,t as Ct,u as Se,S as H,v as xt,w as St,x as _e,b as jt,y as At,k as Ze,z as Rt,N as Nt,F as Pt,H as It,I as Tt,J as Et,U as ue,c as Xe}from"./icons-CacEbnZu.js";import"./react-Cfedt3N5.js";const Ot=d.forwardRef(function({value:s,onChange:n,onSubmit:r,onOpenSuggestedTasks:i,suggestedOpen:f=!1,suggestedTaskCount:c,disabled:v=!1,placeholder:S="Ask VuvoDesk to work across your machines...",compact:g=!1},x){return t.jsxs("form",{className:"agents-composer",onSubmit:w=>{w.preventDefault(),r()},children:[t.jsx("button",{className:`agents-suggested-trigger ${f?"active":""}`,type:"button","aria-label":c===void 0?"Open Suggested tasks":`Open Suggested tasks (${c} available)`,"aria-expanded":f,onClick:i,disabled:v,children:t.jsx(yt,{size:17})}),t.jsx("textarea",{ref:x,value:s,onChange:w=>n(w.target.value.slice(0,4e3)),placeholder:S,"aria-label":"Ask VuvoDesk",rows:g?1:2,disabled:v}),t.jsxs("button",{className:"agents-run-button",type:"submit",disabled:v||!s.trim(),children:[v?t.jsx(Ce,{className:"spin",size:16}):t.jsx(vt,{size:16}),v?"Running":"Run"]})]})}),$t=["read","processControl","serviceControl","applicationControl","fileRead","fileWrite","fileDelete","shell","script","softwareInstall","network","systemPower","systemConfiguration","userAccount"];function de(e){return e.deviceName?.trim()||e.hostname?.trim()||e.deviceId}function pe(e){return e.connected!==!0?!1:e.synthetic===!0?!0:!e.channels||e.channels.control===!0}function Dt(e){return e==="running"||e==="completed"||e==="failed"||e==="cancelled"?e:"queued"}function _t(e){switch(e){case"process.list":return"Process list";case"system.health":return"System health";case"gpu.status":return"GPU status";case"disk.status":return"Disk status";case"service.status":return"Service status";case"diagnostics.collect":return"Diagnostics";case"process.control":return"Process control";case"service.control":return"Service control";case"application.launch":return"Launch application";case"application.close":return"Close application";case"file.read":return"Read file";case"file.write":return"Write file";case"file.delete":return"Delete file";case"file.list":return"List directory";case"command.run":return"Run command";case"script.run":return"Run script";case"software.install":return"Install software";case"network.status":return"Network status";case"system.power":return"Power action";case"system.configure":return"System configuration";case"logs.collect":return"Collect logs"}}const zt=64*1024,ze="[Earlier log output hidden by PWA display limit.]",Qe=new TextEncoder,Mt=new TextDecoder("utf-8",{fatal:!0}),Me=16*1024,Lt=200,Le="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Ft="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Bt="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|credential|password|secret|cookie)",Ut=/\b(?:sk-(?:proj-|ant-)?[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{8,}|(?:AKIA|ASIA)[A-Z0-9]{16}|AIza[A-Za-z0-9_-]{35}|npm_[A-Za-z0-9]{8,}|xox[a-z]-[A-Za-z0-9-]{8,}|(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,})\b/g;function qt(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function Fe(e){return Qe.encode(e).byteLength}function Be(e,s){const n=Qe.encode(e);if(n.byteLength<=s)return{value:e,truncated:!1};let r=n.byteLength-s;for(;r<n.byteLength&&(n[r]&192)===128;)r+=1;let i=Mt.decode(n.subarray(r));const f=i.indexOf(`
1
+ import{j as t,e as ht,h as F}from"./styles-eZ2IzXEL.js";import{a as d,m as yt,n as Ce,o as vt,l as ee,X as xe,p as wt,q as kt,s as Je,B as bt,t as Ct,u as Se,S as H,v as xt,w as St,x as _e,b as jt,y as At,k as Ze,z as Rt,N as Nt,F as Pt,H as It,I as Tt,J as Et,U as ue,c as Xe}from"./icons-CacEbnZu.js";import"./react-Cfedt3N5.js";const Ot=d.forwardRef(function({value:s,onChange:n,onSubmit:r,onOpenSuggestedTasks:i,suggestedOpen:f=!1,suggestedTaskCount:c,disabled:v=!1,placeholder:S="Ask VuvoDesk to work across your machines...",compact:g=!1},x){return t.jsxs("form",{className:"agents-composer",onSubmit:w=>{w.preventDefault(),r()},children:[t.jsx("button",{className:`agents-suggested-trigger ${f?"active":""}`,type:"button","aria-label":c===void 0?"Open Suggested tasks":`Open Suggested tasks (${c} available)`,"aria-expanded":f,onClick:i,disabled:v,children:t.jsx(yt,{size:17})}),t.jsx("textarea",{ref:x,value:s,onChange:w=>n(w.target.value.slice(0,4e3)),placeholder:S,"aria-label":"Ask VuvoDesk",rows:g?1:2,disabled:v}),t.jsxs("button",{className:"agents-run-button",type:"submit",disabled:v||!s.trim(),children:[v?t.jsx(Ce,{className:"spin",size:16}):t.jsx(vt,{size:16}),v?"Running":"Run"]})]})}),$t=["read","processControl","serviceControl","applicationControl","fileRead","fileWrite","fileDelete","shell","script","softwareInstall","network","systemPower","systemConfiguration","userAccount"];function de(e){return e.deviceName?.trim()||e.hostname?.trim()||e.deviceId}function pe(e){return e.connected!==!0?!1:e.synthetic===!0?!0:!e.channels||e.channels.control===!0}function Dt(e){return e==="running"||e==="completed"||e==="failed"||e==="cancelled"?e:"queued"}function _t(e){switch(e){case"process.list":return"Process list";case"system.health":return"System health";case"gpu.status":return"GPU status";case"disk.status":return"Disk status";case"service.status":return"Service status";case"diagnostics.collect":return"Diagnostics";case"process.control":return"Process control";case"service.control":return"Service control";case"application.launch":return"Launch application";case"application.close":return"Close application";case"file.read":return"Read file";case"file.write":return"Write file";case"file.delete":return"Delete file";case"file.list":return"List directory";case"command.run":return"Run command";case"script.run":return"Run script";case"software.install":return"Install software";case"network.status":return"Network status";case"system.power":return"Power action";case"system.configure":return"System configuration";case"logs.collect":return"Collect logs"}}const zt=64*1024,ze="[Earlier log output hidden by PWA display limit.]",Qe=new TextEncoder,Mt=new TextDecoder("utf-8",{fatal:!0}),Me=16*1024,Lt=200,Le="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Ft="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|private[_-]?key|connection[_-]?string|credential|password|passwd|secret|cookie|set-cookie)",Bt="(?:access[_-]?(?:token|key)|refresh[_-]?token|id[_-]?token|pair[_-]?(?:token|key)|token|authorization|api[_-]?key|credential|password|secret|cookie)",Ut=/\b(?:sk-(?:proj-|ant-)?[A-Za-z0-9_-]{8,}|gh[pousr]_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{8,}|(?:AKIA|ASIA)[A-Z0-9]{16}|AIza[A-Za-z0-9_-]{35}|npm_[A-Za-z0-9]{8,}|xox[a-z]-[A-Za-z0-9-]{8,}|(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,})\b/g;function qt(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function Fe(e){return Qe.encode(e).byteLength}function Be(e,s){const n=Qe.encode(e);if(n.byteLength<=s)return{value:e,truncated:!1};let r=n.byteLength-s;for(;r<n.byteLength&&(n[r]&192)===128;)r+=1;let i=Mt.decode(n.subarray(r));const f=i.indexOf(`
2
2
  `);return f>=0&&f<i.length-1&&(i=i.slice(f+1)),{value:i,truncated:!0}}function et(e){return e.replace(/-----BEGIN [^-\r\n]*PRIVATE KEY-----[\s\S]*?-----END [^-\r\n]*PRIVATE KEY-----/gi,"[REDACTED PRIVATE KEY]").replace(/\b(?:proxy-)?authorization\s*[:=]\s*[^\r\n]*|\b(?:set-)?cookie\s*[:=]\s*[^\r\n]*/gi,"credential-header=[REDACTED]").replace(/\bBearer\s+(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi,"Bearer [REDACTED]").replace(new RegExp(`("${Le}"\\s*:\\s*")[^"\\r\\n]*(")`,"gi"),"$1[REDACTED]$2").replace(new RegExp(`('${Le}'\\s*:\\s*')[^'\\r\\n]*(')`,"gi"),"$1[REDACTED]$2").replace(new RegExp(`((?:[A-Za-z0-9.-]+[_-])?${Ft}\\s*[:=]\\s*)(?!\\[REDACTED\\])[^\\s,;&]+`,"gi"),"$1[REDACTED]").replace(new RegExp(`([?&]${Bt}=)[^&\\s]+`,"gi"),"$1[REDACTED]").replace(/\b(?:eyJ|[A-Za-z0-9_-]{8,}\.)[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,"[REDACTED TOKEN]").replace(Ut,"[REDACTED KEY]")}function Gt(e){return typeof e!="string"?"source unknown":et(e).replace(/[\u0000-\u001f\u007f]/g," ").trim().slice(0,64)||"source unknown"}function Ue(e){return typeof e=="number"&&Number.isSafeInteger(e)&&e>=0?Math.floor(e):null}function Vt(e){if(typeof e!="string"||e.length===0)return{output:"",displayTruncated:!1};const s=Be(e.replace(/\r\n?/g,`
3
3
  `),zt);let n=et(s.value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g," ").trim(),r=s.truncated;const i=n?n.split(`
4
4
  `):[];if(i.length>Lt-1&&(n=i.slice(-199).join(`