livedesk 0.1.501 → 0.1.503

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 (80) hide show
  1. package/bin/livedesk.js +199 -196
  2. package/bootstrap/client-process-identity.js +74 -74
  3. package/bootstrap/device-role.js +224 -224
  4. package/bootstrap/hub-auth-handoff.js +3 -3
  5. package/bootstrap/legacy-client-update.js +1806 -1806
  6. package/bootstrap/npm-update-cache.mjs +158 -158
  7. package/bootstrap/port-inspection.js +10 -10
  8. package/bootstrap/role-bootstrap-server.js +102 -102
  9. package/bootstrap/runtime-lock.js +368 -368
  10. package/bootstrap/runtime-shutdown.js +414 -414
  11. package/bootstrap/update-host-client.mjs +442 -431
  12. package/bootstrap/update-host.mjs +607 -520
  13. package/client/README.md +20 -20
  14. package/client/THIRD_PARTY_NOTICES.md +26 -26
  15. package/client/bin/client-version.js +23 -23
  16. package/client/bin/livedesk-client-fast.js +23 -23
  17. package/client/bin/livedesk-client-node.js +65 -65
  18. package/client/bin/livedesk-client-update-bootstrap.cjs +319 -319
  19. package/client/package.json +6 -6
  20. package/client/src/runtime/agent-process-lifecycle.js +860 -860
  21. package/client/src/runtime/client-runtime-server.js +10 -10
  22. package/client/src/runtime/hub-wake-listener.js +159 -159
  23. package/client/src/runtime/linux-video-acceleration.js +390 -390
  24. package/client/src/runtime/windows-owned-process-manifest.js +402 -402
  25. package/client/tests/client-version.test.mjs +27 -27
  26. package/diagnostics/livedesk-mac-physical-gate-core.mjs +1844 -1844
  27. package/diagnostics/livedesk-mac-physical-isolation.mjs +354 -354
  28. package/diagnostics/livedesk-transport-physical-core.mjs +702 -702
  29. package/diagnostics/livedesk-transport-physical.mjs +567 -567
  30. package/electron/auth-config.mjs +10 -10
  31. package/electron/bounded-desktop-log.mjs +609 -609
  32. package/electron/electron-builder.e2e.yml +8 -8
  33. package/electron/electron-builder.win.yml +38 -38
  34. package/electron/electron-builder.yml +4 -4
  35. package/electron/icon.svg +11 -11
  36. package/electron/oauth-pkce.mjs +143 -143
  37. package/electron/preload.cjs +13 -13
  38. package/electron/runtime-process-owner.mjs +287 -287
  39. package/electron/update-feed.json +4 -4
  40. package/hub/package.json +2 -2
  41. package/hub/src/agents/agent-device-scope.js +29 -29
  42. package/hub/src/agents/agent-manager.js +103 -103
  43. package/hub/src/agents/agent-permission-store.js +78 -78
  44. package/hub/src/agents/agent-runtime-error.js +9 -9
  45. package/hub/src/agents/agent-settings.js +71 -71
  46. package/hub/src/agents/codex-agent-runtime.js +594 -594
  47. package/hub/src/agents/codex-mcp-server.js +100 -100
  48. package/hub/src/filesystem/directory-reader.js +187 -187
  49. package/hub/src/filesystem/hub-filesystem.js +78 -78
  50. package/hub/src/filesystem/path-registry.js +78 -78
  51. package/hub/src/filesystem/roots.js +115 -115
  52. package/hub/src/frame-packet-contract.mjs +427 -427
  53. package/hub/src/http/hub-ui-session.js +119 -119
  54. package/hub/src/live-capture-transition-retry.mjs +297 -297
  55. package/hub/src/live-desk-update.js +88 -88
  56. package/hub/src/live-stream-monitor-contract.js +38 -38
  57. package/hub/src/lzo1x.js +109 -109
  58. package/hub/src/mode4-atlas-pool.js +137 -137
  59. package/hub/src/mode4-atlas-sizing.js +32 -32
  60. package/hub/src/mode4-atlas-worker.js +599 -599
  61. package/hub/src/mode4-atlas.js +306 -306
  62. package/hub/src/remote-audio-subscription-contract.mjs +109 -109
  63. package/hub/src/remote-audio-subscription-contract.test.mjs +72 -72
  64. package/hub/src/remote-hub.js +140 -111
  65. package/hub/src/server.js +190 -175
  66. package/hub/src/settings/effective-device-policy.js +16 -16
  67. package/hub/src/transport/udp-protocol.js +453 -453
  68. package/package.json +7 -7
  69. package/runtime-core/package.json +1 -1
  70. package/runtime-core/src/auth-config.js +48 -48
  71. package/runtime-core/src/auth-http.js +29 -29
  72. package/runtime-core/src/auth-session.js +36 -36
  73. package/runtime-core/src/role-transition-supervisor.js +384 -384
  74. package/runtime-core/src/windows-owned-process-tree.js +580 -580
  75. package/scripts/sync-web-dist.js +115 -115
  76. package/web/dist/_headers +2 -2
  77. package/web/dist/assets/{index-PHlwNiVH.js → index-DqXhTKiG.js} +1 -1
  78. package/web/dist/favicon.svg +11 -11
  79. package/web/dist/index.html +16 -16
  80. package/web/dist/livedesk-build-evidence.json +14 -14
package/bin/livedesk.js CHANGED
@@ -31,16 +31,16 @@ import {
31
31
  isKnownLiveDeskCaptureHelperProcess,
32
32
  isKnownLiveDeskClientAgentProcess
33
33
  } from '../bootstrap/client-process-identity.js';
34
- import { runLegacyClientUpdateSupervisorCli } from '../bootstrap/legacy-client-update.js';
35
- import { transferSavedClientSessionToHub } from '../bootstrap/hub-auth-handoff.js';
36
- import {
37
- buildUpdateHostWorkerEnvironment,
38
- cancelUpdateHostJob,
39
- ensureUpdateHost,
40
- getUpdateHostJob,
41
- submitUpdateHostJob,
42
- updateHostEnvironment
43
- } from '../bootstrap/update-host-client.mjs';
34
+ import { runLegacyClientUpdateSupervisorCli } from '../bootstrap/legacy-client-update.js';
35
+ import { transferSavedClientSessionToHub } from '../bootstrap/hub-auth-handoff.js';
36
+ import {
37
+ buildUpdateHostWorkerEnvironment,
38
+ cancelUpdateHostJob,
39
+ ensureUpdateHost,
40
+ getUpdateHostJob,
41
+ submitUpdateHostJob,
42
+ updateHostEnvironment
43
+ } from '../bootstrap/update-host-client.mjs';
44
44
  import {
45
45
  consumeDeadWindowsOwnedProcessManifest,
46
46
  readDeadWindowsOwnedProcessManifest,
@@ -228,80 +228,80 @@ function readBoundedMilliseconds(value, fallback, minimum = 1) {
228
228
  return Number.isFinite(parsed) && parsed >= minimum ? Math.round(parsed) : fallback;
229
229
  }
230
230
 
231
- async function cancelUnclaimedHubUpdateHostJob(updateHost, jobId, reason) {
232
- try {
233
- await cancelUpdateHostJob(updateHost, jobId, reason);
234
- } catch (error) {
235
- const job = await getUpdateHostJob(updateHost, jobId).catch(() => null);
236
- if (job && ['accepted', 'running', 'cancelling'].includes(String(job.state || ''))) {
237
- throw new Error(
238
- `Update Host job ${jobId} could not be cancelled: ${error instanceof Error ? error.message : String(error)}`
239
- );
240
- }
241
- }
242
- }
243
-
244
- async function waitForHubRestartUpdateHostClaim(updateHost, jobId, expected) {
245
- const claimDeadline = Date.now() + expected.timeoutMs;
246
- let matchingClaimSince = 0;
247
- let workerPid = 0;
248
- while (Date.now() < claimDeadline) {
249
- const job = await getUpdateHostJob(updateHost, jobId, {
250
- timeoutMs: Math.min(2_000, Math.max(250, claimDeadline - Date.now()))
251
- });
252
- if (!job) {
253
- throw new Error(`LiveDesk Update Host lost job ${jobId} before handoff.`);
254
- }
255
- if (['failed', 'cancelled'].includes(String(job.state || ''))) {
256
- throw new Error(
257
- `LiveDesk Update Host job ${jobId} failed before handoff: ${job.error || `state=${job.state}`}`
258
- );
259
- }
260
- workerPid = Number(job.workerPid || 0);
261
- const claim = readJsonFile(expected.resultPath);
262
- if (
263
- job.state === 'running'
264
- && workerPid > 1
265
- && claim?.operationId === expected.operationId
266
- && claim?.stage === 'waiting-for-old-launcher'
267
- && claim?.outcome === 'in-progress'
268
- && Number(claim?.supervisorPid || 0) === workerPid
269
- && Number(claim?.launcherPid || 0) === expected.launcherPid
270
- && claim?.targetVersion === expected.targetVersion
271
- && claim?.handoffToken === expected.handoffToken
272
- ) {
273
- if (!matchingClaimSince) matchingClaimSince = Date.now();
274
- if (Date.now() - matchingClaimSince >= expected.stabilityMs) {
275
- await delay(HUB_UPDATE_SUPERVISOR_HANDSHAKE_POLL_MS);
276
- const stableJob = await getUpdateHostJob(updateHost, jobId, { timeoutMs: 2_000 });
277
- const stableClaim = readJsonFile(expected.resultPath);
278
- if (
279
- stableJob?.state === 'running'
280
- && Number(stableJob?.workerPid || 0) === workerPid
281
- && stableClaim?.operationId === expected.operationId
282
- && stableClaim?.stage === 'waiting-for-old-launcher'
283
- && stableClaim?.outcome === 'in-progress'
284
- && Number(stableClaim?.supervisorPid || 0) === workerPid
285
- && Number(stableClaim?.launcherPid || 0) === expected.launcherPid
286
- && stableClaim?.targetVersion === expected.targetVersion
287
- && stableClaim?.handoffToken === expected.handoffToken
288
- ) {
289
- return stableClaim;
290
- }
291
- matchingClaimSince = 0;
292
- }
293
- } else {
294
- matchingClaimSince = 0;
295
- }
296
- await delay(Math.max(50, HUB_UPDATE_SUPERVISOR_HANDSHAKE_POLL_MS));
297
- }
298
- throw new Error(
299
- `LiveDesk Update Host job ${jobId} did not publish an atomic handoff for `
300
- + `operation ${expected.operationId} within ${expected.timeoutMs}ms.`
301
- );
302
- }
303
-
304
- function writeJsonAtomic(filePath, value) {
231
+ async function cancelUnclaimedHubUpdateHostJob(updateHost, jobId, reason) {
232
+ try {
233
+ await cancelUpdateHostJob(updateHost, jobId, reason);
234
+ } catch (error) {
235
+ const job = await getUpdateHostJob(updateHost, jobId).catch(() => null);
236
+ if (job && ['accepted', 'running', 'cancelling'].includes(String(job.state || ''))) {
237
+ throw new Error(
238
+ `Update Host job ${jobId} could not be cancelled: ${error instanceof Error ? error.message : String(error)}`
239
+ );
240
+ }
241
+ }
242
+ }
243
+
244
+ async function waitForHubRestartUpdateHostClaim(updateHost, jobId, expected) {
245
+ const claimDeadline = Date.now() + expected.timeoutMs;
246
+ let matchingClaimSince = 0;
247
+ let workerPid = 0;
248
+ while (Date.now() < claimDeadline) {
249
+ const job = await getUpdateHostJob(updateHost, jobId, {
250
+ timeoutMs: Math.min(2_000, Math.max(250, claimDeadline - Date.now()))
251
+ });
252
+ if (!job) {
253
+ throw new Error(`LiveDesk Update Host lost job ${jobId} before handoff.`);
254
+ }
255
+ if (['failed', 'cancelled'].includes(String(job.state || ''))) {
256
+ throw new Error(
257
+ `LiveDesk Update Host job ${jobId} failed before handoff: ${job.error || `state=${job.state}`}`
258
+ );
259
+ }
260
+ workerPid = Number(job.workerPid || 0);
261
+ const claim = readJsonFile(expected.resultPath);
262
+ if (
263
+ job.state === 'running'
264
+ && workerPid > 1
265
+ && claim?.operationId === expected.operationId
266
+ && claim?.stage === 'waiting-for-old-launcher'
267
+ && claim?.outcome === 'in-progress'
268
+ && Number(claim?.supervisorPid || 0) === workerPid
269
+ && Number(claim?.launcherPid || 0) === expected.launcherPid
270
+ && claim?.targetVersion === expected.targetVersion
271
+ && claim?.handoffToken === expected.handoffToken
272
+ ) {
273
+ if (!matchingClaimSince) matchingClaimSince = Date.now();
274
+ if (Date.now() - matchingClaimSince >= expected.stabilityMs) {
275
+ await delay(HUB_UPDATE_SUPERVISOR_HANDSHAKE_POLL_MS);
276
+ const stableJob = await getUpdateHostJob(updateHost, jobId, { timeoutMs: 2_000 });
277
+ const stableClaim = readJsonFile(expected.resultPath);
278
+ if (
279
+ stableJob?.state === 'running'
280
+ && Number(stableJob?.workerPid || 0) === workerPid
281
+ && stableClaim?.operationId === expected.operationId
282
+ && stableClaim?.stage === 'waiting-for-old-launcher'
283
+ && stableClaim?.outcome === 'in-progress'
284
+ && Number(stableClaim?.supervisorPid || 0) === workerPid
285
+ && Number(stableClaim?.launcherPid || 0) === expected.launcherPid
286
+ && stableClaim?.targetVersion === expected.targetVersion
287
+ && stableClaim?.handoffToken === expected.handoffToken
288
+ ) {
289
+ return stableClaim;
290
+ }
291
+ matchingClaimSince = 0;
292
+ }
293
+ } else {
294
+ matchingClaimSince = 0;
295
+ }
296
+ await delay(Math.max(50, HUB_UPDATE_SUPERVISOR_HANDSHAKE_POLL_MS));
297
+ }
298
+ throw new Error(
299
+ `LiveDesk Update Host job ${jobId} did not publish an atomic handoff for `
300
+ + `operation ${expected.operationId} within ${expected.timeoutMs}ms.`
301
+ );
302
+ }
303
+
304
+ function writeJsonAtomic(filePath, value) {
305
305
  if (!filePath) return;
306
306
  mkdirSync(dirname(filePath), { recursive: true });
307
307
  const previous = readJsonFile(filePath);
@@ -2445,7 +2445,7 @@ function buildHubRestartBootstrapScript() {
2445
2445
  ].join('\n');
2446
2446
  }
2447
2447
 
2448
- async function runManager(args, resolvedRole = null, runtimeLock = null, updateHost = null) {
2448
+ async function runManager(args, resolvedRole = null, runtimeLock = null, updateHost = null) {
2449
2449
  const runtimeOwner = runtimeLock?.payload || null;
2450
2450
  const options = parseManagerArgs(args);
2451
2451
  const httpPort = normalizePort(
@@ -2493,7 +2493,7 @@ async function runManager(args, resolvedRole = null, runtimeLock = null, updateH
2493
2493
  LIVEDESK_HUB_HTTP_PORT: String(httpPort),
2494
2494
  LIVEDESK_MANAGER_VERSION: readVersion(),
2495
2495
  LIVEDESK_CLIENT_PACKAGE_VERSION: readClientVersion(),
2496
- LIVEDESK_HUB_UPDATE_REQUEST_PATH: updateHost?.available ? updateRequestPath : '',
2496
+ LIVEDESK_HUB_UPDATE_REQUEST_PATH: updateHost?.available ? updateRequestPath : '',
2497
2497
  LIVEDESK_HUB_UPDATE_RESULT_PATH: hubUpdateResultPath,
2498
2498
  LIVEDESK_UDP_ENABLED: String(process.env.LIVEDESK_UDP_ENABLED || '1'),
2499
2499
  LIVEDESK_UDP_PREFER_P2P: String(process.env.LIVEDESK_UDP_PREFER_P2P || '0'),
@@ -2621,10 +2621,10 @@ async function runManager(args, resolvedRole = null, runtimeLock = null, updateH
2621
2621
  process.on(signal, handler);
2622
2622
  }
2623
2623
 
2624
- const restartWithLatest = async request => {
2625
- if (!updateHost?.available) {
2626
- throw new Error('The independent LiveDesk Update Host is unavailable. The unchanged Hub remains running.');
2627
- }
2624
+ const restartWithLatest = async request => {
2625
+ if (!updateHost?.available) {
2626
+ throw new Error('The independent LiveDesk Update Host is unavailable. The unchanged Hub remains running.');
2627
+ }
2628
2628
  const requestedVersion = normalizeExactUpdateVersion(request?.latestVersion);
2629
2629
  if (!requestedVersion) {
2630
2630
  throw new Error('The Hub update target version must be one exact semantic version.');
@@ -2641,9 +2641,9 @@ async function runManager(args, resolvedRole = null, runtimeLock = null, updateH
2641
2641
  );
2642
2642
  const packageTarget = `livedesk@${requestedVersion}`;
2643
2643
  const nextArgs = [
2644
- '-y',
2645
- '--prefer-offline',
2646
- '--prefix',
2644
+ '-y',
2645
+ '--prefer-offline',
2646
+ '--prefix',
2647
2647
  neutralCwd,
2648
2648
  '--workspaces=false',
2649
2649
  packageTarget,
@@ -2681,82 +2681,82 @@ async function runManager(args, resolvedRole = null, runtimeLock = null, updateH
2681
2681
  reportLauncherUpdate(`[LiveDesk Hub] Could not persist the restart supervisor state: ${error instanceof Error ? error.message : String(error)}.`, true);
2682
2682
  throw error;
2683
2683
  }
2684
- const workerEnvironment = buildUpdateHostWorkerEnvironment(process.env, {
2685
- LIVEDESK_RESTART_WAIT_PID: String(process.pid),
2686
- LIVEDESK_RESTART_PREVIOUS_RUNTIME_PID: String(request?.pid || 0),
2687
- LIVEDESK_RESTART_OPERATION_ID: operationId,
2688
- LIVEDESK_RESTART_STARTED_AT: restartStartedAt,
2689
- LIVEDESK_RESTART_HANDOFF_TOKEN: handoffToken,
2690
- LIVEDESK_RESTART_RESULT_PATH: hubUpdateResultPath,
2691
- LIVEDESK_RESTART_COMMAND: restartInvocation.command,
2692
- LIVEDESK_RESTART_ARGS_BASE64: Buffer.from(JSON.stringify(restartInvocation.args), 'utf8').toString('base64'),
2693
- LIVEDESK_RESTART_FALLBACK_COMMAND: fallbackInvocation.command,
2694
- LIVEDESK_RESTART_FALLBACK_ARGS_BASE64: Buffer.from(JSON.stringify(fallbackInvocation.args), 'utf8').toString('base64'),
2695
- LIVEDESK_RESTART_EXPECTED_VERSION: requestedVersion,
2696
- LIVEDESK_RESTART_FALLBACK_VERSION: readVersion(),
2697
- LIVEDESK_RESTART_HEALTH_URL: hubProbeBaseUrl,
2698
- LIVEDESK_RESTART_REMOTE_HOST: '127.0.0.1',
2699
- LIVEDESK_RESTART_REMOTE_PORT: String(remotePort),
2700
- LIVEDESK_RESTART_NEUTRAL_CWD: neutralCwd,
2701
- LIVEDESK_RESTART_FALLBACK_CWD: originalCwd,
2702
- LIVEDESK_UPDATE_ORIGINAL_CWD: originalCwd,
2703
- LIVEDESK_RESTART_LOG_PATH: hubLogPath
2704
- });
2705
- const hostJobId = `hub-${safeUpdatePathSegment(operationId)}`;
2706
- let hostJob;
2707
- try {
2708
- const submitted = await submitUpdateHostJob(updateHost, {
2709
- jobId: hostJobId,
2710
- kind: 'hub-update',
2711
- operationId,
2712
- command: process.execPath,
2713
- args: ['-e', buildHubRestartBootstrapScript()],
2714
- cwd: neutralCwd,
2715
- env: workerEnvironment,
2716
- resultPath: hubUpdateResultPath,
2717
- requestedAt: restartStartedAt,
2718
- expiresAt: new Date(Date.parse(restartStartedAt) + 90 * 60_000).toISOString()
2719
- }, { timeoutMs: handshakeTimeoutMs });
2720
- hostJob = submitted?.job || null;
2721
- if (!hostJob || Number(hostJob.workerPid || 0) <= 1) {
2722
- throw new Error('LiveDesk Update Host accepted the request without a worker identity.');
2723
- }
2724
- } catch (error) {
2725
- throw new Error(`LiveDesk Update Host rejected the Hub replacement: ${error instanceof Error ? error.message : String(error)}`);
2726
- }
2727
- try {
2728
- await waitForHubRestartUpdateHostClaim(updateHost, hostJobId, {
2729
- operationId,
2730
- targetVersion: requestedVersion,
2731
- resultPath: hubUpdateResultPath,
2684
+ const workerEnvironment = buildUpdateHostWorkerEnvironment(process.env, {
2685
+ LIVEDESK_RESTART_WAIT_PID: String(process.pid),
2686
+ LIVEDESK_RESTART_PREVIOUS_RUNTIME_PID: String(request?.pid || 0),
2687
+ LIVEDESK_RESTART_OPERATION_ID: operationId,
2688
+ LIVEDESK_RESTART_STARTED_AT: restartStartedAt,
2689
+ LIVEDESK_RESTART_HANDOFF_TOKEN: handoffToken,
2690
+ LIVEDESK_RESTART_RESULT_PATH: hubUpdateResultPath,
2691
+ LIVEDESK_RESTART_COMMAND: restartInvocation.command,
2692
+ LIVEDESK_RESTART_ARGS_BASE64: Buffer.from(JSON.stringify(restartInvocation.args), 'utf8').toString('base64'),
2693
+ LIVEDESK_RESTART_FALLBACK_COMMAND: fallbackInvocation.command,
2694
+ LIVEDESK_RESTART_FALLBACK_ARGS_BASE64: Buffer.from(JSON.stringify(fallbackInvocation.args), 'utf8').toString('base64'),
2695
+ LIVEDESK_RESTART_EXPECTED_VERSION: requestedVersion,
2696
+ LIVEDESK_RESTART_FALLBACK_VERSION: readVersion(),
2697
+ LIVEDESK_RESTART_HEALTH_URL: hubProbeBaseUrl,
2698
+ LIVEDESK_RESTART_REMOTE_HOST: '127.0.0.1',
2699
+ LIVEDESK_RESTART_REMOTE_PORT: String(remotePort),
2700
+ LIVEDESK_RESTART_NEUTRAL_CWD: neutralCwd,
2701
+ LIVEDESK_RESTART_FALLBACK_CWD: originalCwd,
2702
+ LIVEDESK_UPDATE_ORIGINAL_CWD: originalCwd,
2703
+ LIVEDESK_RESTART_LOG_PATH: hubLogPath
2704
+ });
2705
+ const hostJobId = `hub-${safeUpdatePathSegment(operationId)}`;
2706
+ let hostJob;
2707
+ try {
2708
+ const submitted = await submitUpdateHostJob(updateHost, {
2709
+ jobId: hostJobId,
2710
+ kind: 'hub-update',
2711
+ operationId,
2712
+ command: process.execPath,
2713
+ args: ['-e', buildHubRestartBootstrapScript()],
2714
+ cwd: neutralCwd,
2715
+ env: workerEnvironment,
2716
+ resultPath: hubUpdateResultPath,
2717
+ requestedAt: restartStartedAt,
2718
+ expiresAt: new Date(Date.parse(restartStartedAt) + 90 * 60_000).toISOString()
2719
+ }, { timeoutMs: handshakeTimeoutMs });
2720
+ hostJob = submitted?.job || null;
2721
+ if (!hostJob || Number(hostJob.workerPid || 0) <= 1) {
2722
+ throw new Error('LiveDesk Update Host accepted the request without a worker identity.');
2723
+ }
2724
+ } catch (error) {
2725
+ throw new Error(`LiveDesk Update Host rejected the Hub replacement: ${error instanceof Error ? error.message : String(error)}`);
2726
+ }
2727
+ try {
2728
+ await waitForHubRestartUpdateHostClaim(updateHost, hostJobId, {
2729
+ operationId,
2730
+ targetVersion: requestedVersion,
2731
+ resultPath: hubUpdateResultPath,
2732
2732
  launcherPid: process.pid,
2733
2733
  handoffToken,
2734
2734
  timeoutMs: handshakeTimeoutMs,
2735
2735
  stabilityMs: HUB_UPDATE_SUPERVISOR_CLAIM_STABILITY_MS
2736
2736
  });
2737
- } catch (handshakeError) {
2738
- let stopError = null;
2739
- try {
2740
- await cancelUnclaimedHubUpdateHostJob(updateHost, hostJobId, 'Hub handoff was not proven.');
2741
- } catch (error) {
2742
- stopError = error;
2743
- }
2737
+ } catch (handshakeError) {
2738
+ let stopError = null;
2739
+ try {
2740
+ await cancelUnclaimedHubUpdateHostJob(updateHost, hostJobId, 'Hub handoff was not proven.');
2741
+ } catch (error) {
2742
+ stopError = error;
2743
+ }
2744
2744
  const error = new Error(
2745
2745
  `Hub restart supervisor handoff failed: `
2746
2746
  + `${handshakeError instanceof Error ? handshakeError.message : String(handshakeError)}`
2747
2747
  + `${stopError ? ` ${stopError.message}` : ''}`
2748
2748
  );
2749
- error.supervisorPid = Number(hostJob?.workerPid || 0);
2750
- error.updateHostPid = Number(updateHost?.host?.pid || 0);
2751
- error.handoffToken = handoffToken;
2752
- error.handoffCancelled = true;
2753
- throw error;
2754
- }
2755
- reportLauncherUpdate(
2756
- `[LiveDesk Hub] Update handoff claimed target=${requestedVersion} `
2757
- + `updateHostPid=${updateHost.host?.pid || 0} workerPid=${hostJob.workerPid || 0} operation=${operationId}. `
2758
- + `The current launcher will now exit. Restart diagnostics: ${hubLogPath}`
2759
- );
2749
+ error.supervisorPid = Number(hostJob?.workerPid || 0);
2750
+ error.updateHostPid = Number(updateHost?.host?.pid || 0);
2751
+ error.handoffToken = handoffToken;
2752
+ error.handoffCancelled = true;
2753
+ throw error;
2754
+ }
2755
+ reportLauncherUpdate(
2756
+ `[LiveDesk Hub] Update handoff claimed target=${requestedVersion} `
2757
+ + `updateHostPid=${updateHost.host?.pid || 0} workerPid=${hostJob.workerPid || 0} operation=${operationId}. `
2758
+ + `The current launcher will now exit. Restart diagnostics: ${hubLogPath}`
2759
+ );
2760
2760
  process.exit(0);
2761
2761
  };
2762
2762
 
@@ -3034,19 +3034,22 @@ async function runManager(args, resolvedRole = null, runtimeLock = null, updateH
3034
3034
  updatePollId = setInterval(pollUpdateRequest, HUB_UPDATE_POLL_MS);
3035
3035
 
3036
3036
  const hubReady = waitForManager(hubProbeBaseUrl);
3037
- const hubStartup = hubReady.then(async ok => {
3037
+ const hubStartup = hubReady.then(ok => {
3038
3038
  if (!ok) {
3039
3039
  return false;
3040
3040
  }
3041
- const handoff = await transferSavedClientSessionToHub(hubProbeBaseUrl, MANAGER_STATE_DIR);
3042
- if (handoff.ok) {
3043
- reportLauncherUpdate('[LiveDesk Hub] Saved Client sign-in adopted by the Hub.');
3044
- if (!handoff.hostTargetOk) {
3045
- reportLauncherUpdate('[LiveDesk Hub] Sign-in was adopted; host publication will continue through lease renewal.', true);
3041
+ void transferSavedClientSessionToHub(hubProbeBaseUrl, MANAGER_STATE_DIR).then(handoff => {
3042
+ if (handoff.ok) {
3043
+ reportLauncherUpdate('[LiveDesk Hub] Saved Client sign-in adopted by the Hub.');
3044
+ if (!handoff.hostTargetOk) {
3045
+ reportLauncherUpdate('[LiveDesk Hub] Sign-in was adopted; host publication will continue through lease renewal.', true);
3046
+ }
3047
+ } else if (!handoff.skipped) {
3048
+ reportLauncherUpdate(`[LiveDesk Hub] Saved Client sign-in could not be adopted: ${handoff.reason}. Continue sign-in in the Hub page.`, true);
3046
3049
  }
3047
- } else if (!handoff.skipped) {
3048
- reportLauncherUpdate(`[LiveDesk Hub] Saved Client sign-in could not be adopted: ${handoff.reason}. Continue sign-in in the Hub page.`, true);
3049
- }
3050
+ }).catch(error => {
3051
+ reportLauncherUpdate(`[LiveDesk Hub] Saved Client sign-in could not be adopted: ${error instanceof Error ? error.message : String(error)}. Continue sign-in in the Hub page.`, true);
3052
+ });
3050
3053
  return true;
3051
3054
  });
3052
3055
 
@@ -3285,30 +3288,30 @@ async function main() {
3285
3288
  });
3286
3289
  }
3287
3290
  }
3288
- if (!lock.acquired) {
3289
- reportExistingRuntime(lock.existing || { role: resolvedRole.role, pid: 'unknown' });
3290
- return;
3291
- }
3292
- process.once('exit', lock.release);
3293
-
3294
- const updateHost = await ensureUpdateHost({
3295
- stateDir: MANAGER_STATE_DIR,
3296
- entryPath: resolve(packageRoot, 'bootstrap', 'update-host.mjs'),
3297
- sourceVersion: readVersion(),
3298
- env: process.env
3299
- });
3300
- Object.assign(process.env, updateHostEnvironment(updateHost));
3301
- if (updateHost.available) {
3302
- console.log(
3303
- `[LiveDesk] Independent Update Host ready pid=${updateHost.host?.pid || 'unknown'} `
3304
- + `source=${updateHost.host?.sourceVersion || readVersion()}.`
3305
- );
3306
- } else if (!updateHost.disabled) {
3307
- console.warn(
3308
- `[LiveDesk] Independent Update Host is unavailable; updates are disabled for this run: `
3309
- + `${updateHost.error || 'unknown error'}`
3310
- );
3311
- }
3291
+ if (!lock.acquired) {
3292
+ reportExistingRuntime(lock.existing || { role: resolvedRole.role, pid: 'unknown' });
3293
+ return;
3294
+ }
3295
+ process.once('exit', lock.release);
3296
+
3297
+ const updateHost = await ensureUpdateHost({
3298
+ stateDir: MANAGER_STATE_DIR,
3299
+ entryPath: resolve(packageRoot, 'bootstrap', 'update-host.mjs'),
3300
+ sourceVersion: readVersion(),
3301
+ env: process.env
3302
+ });
3303
+ Object.assign(process.env, updateHostEnvironment(updateHost));
3304
+ if (updateHost.available) {
3305
+ console.log(
3306
+ `[LiveDesk] Independent Update Host ready pid=${updateHost.host?.pid || 'unknown'} `
3307
+ + `source=${updateHost.host?.sourceVersion || readVersion()}.`
3308
+ );
3309
+ } else if (!updateHost.disabled) {
3310
+ console.warn(
3311
+ `[LiveDesk] Independent Update Host is unavailable; updates are disabled for this run: `
3312
+ + `${updateHost.error || 'unknown error'}`
3313
+ );
3314
+ }
3312
3315
 
3313
3316
  // Only the contender that won the replacement lock may clean residual
3314
3317
  // listener ports. A simultaneous losing launcher must never kill the newly
@@ -3352,16 +3355,16 @@ async function main() {
3352
3355
  await runClient(runtimeArgs, resolvedRole, lock);
3353
3356
  return;
3354
3357
  }
3355
- if (command === 'hub' || command === 'manager') {
3356
- await runManager(runtimeArgs, resolvedRole, lock, updateHost);
3358
+ if (command === 'hub' || command === 'manager') {
3359
+ await runManager(runtimeArgs, resolvedRole, lock, updateHost);
3357
3360
  return;
3358
3361
  }
3359
3362
  if (resolvedRole.role === 'client') {
3360
3363
  await runClient(runtimeArgs, resolvedRole, lock);
3361
3364
  return;
3362
3365
  }
3363
- await runManager(runtimeArgs, resolvedRole, lock, updateHost);
3364
- }
3366
+ await runManager(runtimeArgs, resolvedRole, lock, updateHost);
3367
+ }
3365
3368
 
3366
3369
  main().catch(error => {
3367
3370
  console.error(error?.message || error);
@@ -1,74 +1,74 @@
1
- function firstCommandArgument(commandLine) {
2
- const value = String(commandLine || '').trim();
3
- if (!value) return '';
4
- const quote = value[0] === '"' || value[0] === "'" ? value[0] : '';
5
- if (!quote) return value.split(/\s+/, 1)[0] || '';
6
- const end = value.indexOf(quote, 1);
7
- return end > 0 ? value.slice(1, end) : '';
8
- }
9
-
10
- function normalizeExecutablePath(value) {
11
- return String(value || '').trim().replaceAll('\\', '/').replace(/\s+\(deleted\)$/i, '');
12
- }
13
-
14
- const BUNDLED_FAST_ROOT = '(?:node_modules/(?:livedesk/client|@livedesk/client)|packages/(?:livedesk/client|client))/fast/[^/\\s]+';
15
- const OPTIONAL_FAST_ROOT = 'node_modules/@livedesk/fast-(?:linux-x64|osx-arm64|osx-x64|win-x64)/fast';
16
- const FAST_ROOT = `(?:${BUNDLED_FAST_ROOT}|${OPTIONAL_FAST_ROOT})`;
17
- const KNOWN_FAST_EXECUTABLE_PATH = new RegExp(`${FAST_ROOT}/livedesk-client-fast(?:\\.exe)?$`, 'i');
18
-
19
- export function isKnownLiveDeskClientAgentProcess(processName, commandLine, executablePath = '') {
20
- const normalizedName = String(processName || '').trim().replaceAll('\\', '/').split('/').pop() || '';
21
- const normalizedCommandLine = String(commandLine || '').replaceAll('\\', '/');
22
- const isNode = /^(?:node|node\.exe)$/i.test(normalizedName);
23
- const isDotnet = /^(?:dotnet|dotnet\.exe)$/i.test(normalizedName);
24
- const isFast = /^livedesk-client-fast(?:\.exe)?$/i.test(normalizedName);
25
- // Linux TASK_COMM_LEN exposes a 15-character `comm`, so the native apphost
26
- // appears as `livedesk-client`. Accept that truncated name only when an
27
- // independently resolved executable path, or argv[0], proves the canonical
28
- // packaged LiveDesk Fast binary. A matching path elsewhere in argv is not
29
- // sufficient.
30
- const isTruncatedLinuxFast = /^livedesk-client$/i.test(normalizedName);
31
- const isKnownNodeAgent = /(?:node_modules\/(?:livedesk\/client|@livedesk\/client)|packages\/(?:livedesk\/client|client))\/bin\/livedesk-client-node\.js(?:\s|["']|$)/i.test(normalizedCommandLine);
32
- const isKnownFastExecutable = new RegExp(
33
- `${FAST_ROOT}/livedesk-client-fast(?:\\.exe)?(?:\\s|["']|$)`,
34
- 'i'
35
- ).test(normalizedCommandLine);
36
- const isKnownFastDll = new RegExp(
37
- `${FAST_ROOT}/livedesk-client-fast\\.dll(?:\\s|["']|$)`,
38
- 'i'
39
- ).test(normalizedCommandLine);
40
- const resolvedExecutable = normalizeExecutablePath(executablePath);
41
- const argvExecutable = normalizeExecutablePath(firstCommandArgument(normalizedCommandLine));
42
- const hasStrictFastExecutableProof = KNOWN_FAST_EXECUTABLE_PATH.test(resolvedExecutable)
43
- || KNOWN_FAST_EXECUTABLE_PATH.test(argvExecutable);
44
- return (isNode && isKnownNodeAgent)
45
- || (isFast && isKnownFastExecutable)
46
- || (isTruncatedLinuxFast && hasStrictFastExecutableProof)
47
- || (isDotnet && isKnownFastDll);
48
- }
49
-
50
- export function isKnownLiveDeskCaptureHelperProcess(processName, commandLine, executablePath = '') {
51
- const normalizedName = String(processName || '').trim().replaceAll('\\', '/').split('/').pop() || '';
52
- const normalizedCommandLine = String(commandLine || '').replaceAll('\\', '/');
53
- const normalizedExecutablePath = normalizeExecutablePath(executablePath);
54
- const argvExecutable = normalizeExecutablePath(firstCommandArgument(normalizedCommandLine));
55
- const videoHelperPath = /(?:^|\/)\.livedesk\/helpers\/macos-sck-h264\/livedesk-sck-h264$/i;
56
- const audioHelperPath = /(?:^|\/)\.livedesk\/helpers\/macos-sck-audio\/livedesk-sck-audio$/i;
57
- const knownVideoCommand = /(?:^|\/)\.livedesk\/helpers\/macos-sck-h264\/livedesk-sck-h264(?:\s|["']|$)/i
58
- .test(normalizedCommandLine);
59
- const knownAudioCommand = /(?:^|\/)\.livedesk\/helpers\/macos-sck-audio\/livedesk-sck-audio(?:\s|["']|$)/i
60
- .test(normalizedCommandLine);
61
- const strictVideoExecutableProof = videoHelperPath.test(normalizedExecutablePath)
62
- || videoHelperPath.test(argvExecutable);
63
- const strictAudioExecutableProof = audioHelperPath.test(normalizedExecutablePath)
64
- || audioHelperPath.test(argvExecutable);
65
- const knownRawCaptureCommand = new RegExp(
66
- `^(?:"[^"]*"|'[^']*'|\\S+)\\s+["']?\\S*${FAST_ROOT}/livedesk-raw-capture-helper\\.mjs(?:["']|\\s|$)`,
67
- 'i'
68
- ).test(normalizedCommandLine);
69
- return (/^livedesk-sck-h264$/i.test(normalizedName) && knownVideoCommand)
70
- || (/^livedesk-sck-h2$/i.test(normalizedName) && strictVideoExecutableProof)
71
- || (/^livedesk-sck-audio$/i.test(normalizedName) && knownAudioCommand)
72
- || (/^livedesk-sck-aud$/i.test(normalizedName) && strictAudioExecutableProof)
73
- || (/^(?:node|node\.exe)$/i.test(normalizedName) && knownRawCaptureCommand);
74
- }
1
+ function firstCommandArgument(commandLine) {
2
+ const value = String(commandLine || '').trim();
3
+ if (!value) return '';
4
+ const quote = value[0] === '"' || value[0] === "'" ? value[0] : '';
5
+ if (!quote) return value.split(/\s+/, 1)[0] || '';
6
+ const end = value.indexOf(quote, 1);
7
+ return end > 0 ? value.slice(1, end) : '';
8
+ }
9
+
10
+ function normalizeExecutablePath(value) {
11
+ return String(value || '').trim().replaceAll('\\', '/').replace(/\s+\(deleted\)$/i, '');
12
+ }
13
+
14
+ const BUNDLED_FAST_ROOT = '(?:node_modules/(?:livedesk/client|@livedesk/client)|packages/(?:livedesk/client|client))/fast/[^/\\s]+';
15
+ const OPTIONAL_FAST_ROOT = 'node_modules/@livedesk/fast-(?:linux-x64|osx-arm64|osx-x64|win-x64)/fast';
16
+ const FAST_ROOT = `(?:${BUNDLED_FAST_ROOT}|${OPTIONAL_FAST_ROOT})`;
17
+ const KNOWN_FAST_EXECUTABLE_PATH = new RegExp(`${FAST_ROOT}/livedesk-client-fast(?:\\.exe)?$`, 'i');
18
+
19
+ export function isKnownLiveDeskClientAgentProcess(processName, commandLine, executablePath = '') {
20
+ const normalizedName = String(processName || '').trim().replaceAll('\\', '/').split('/').pop() || '';
21
+ const normalizedCommandLine = String(commandLine || '').replaceAll('\\', '/');
22
+ const isNode = /^(?:node|node\.exe)$/i.test(normalizedName);
23
+ const isDotnet = /^(?:dotnet|dotnet\.exe)$/i.test(normalizedName);
24
+ const isFast = /^livedesk-client-fast(?:\.exe)?$/i.test(normalizedName);
25
+ // Linux TASK_COMM_LEN exposes a 15-character `comm`, so the native apphost
26
+ // appears as `livedesk-client`. Accept that truncated name only when an
27
+ // independently resolved executable path, or argv[0], proves the canonical
28
+ // packaged LiveDesk Fast binary. A matching path elsewhere in argv is not
29
+ // sufficient.
30
+ const isTruncatedLinuxFast = /^livedesk-client$/i.test(normalizedName);
31
+ const isKnownNodeAgent = /(?:node_modules\/(?:livedesk\/client|@livedesk\/client)|packages\/(?:livedesk\/client|client))\/bin\/livedesk-client-node\.js(?:\s|["']|$)/i.test(normalizedCommandLine);
32
+ const isKnownFastExecutable = new RegExp(
33
+ `${FAST_ROOT}/livedesk-client-fast(?:\\.exe)?(?:\\s|["']|$)`,
34
+ 'i'
35
+ ).test(normalizedCommandLine);
36
+ const isKnownFastDll = new RegExp(
37
+ `${FAST_ROOT}/livedesk-client-fast\\.dll(?:\\s|["']|$)`,
38
+ 'i'
39
+ ).test(normalizedCommandLine);
40
+ const resolvedExecutable = normalizeExecutablePath(executablePath);
41
+ const argvExecutable = normalizeExecutablePath(firstCommandArgument(normalizedCommandLine));
42
+ const hasStrictFastExecutableProof = KNOWN_FAST_EXECUTABLE_PATH.test(resolvedExecutable)
43
+ || KNOWN_FAST_EXECUTABLE_PATH.test(argvExecutable);
44
+ return (isNode && isKnownNodeAgent)
45
+ || (isFast && isKnownFastExecutable)
46
+ || (isTruncatedLinuxFast && hasStrictFastExecutableProof)
47
+ || (isDotnet && isKnownFastDll);
48
+ }
49
+
50
+ export function isKnownLiveDeskCaptureHelperProcess(processName, commandLine, executablePath = '') {
51
+ const normalizedName = String(processName || '').trim().replaceAll('\\', '/').split('/').pop() || '';
52
+ const normalizedCommandLine = String(commandLine || '').replaceAll('\\', '/');
53
+ const normalizedExecutablePath = normalizeExecutablePath(executablePath);
54
+ const argvExecutable = normalizeExecutablePath(firstCommandArgument(normalizedCommandLine));
55
+ const videoHelperPath = /(?:^|\/)\.livedesk\/helpers\/macos-sck-h264\/livedesk-sck-h264$/i;
56
+ const audioHelperPath = /(?:^|\/)\.livedesk\/helpers\/macos-sck-audio\/livedesk-sck-audio$/i;
57
+ const knownVideoCommand = /(?:^|\/)\.livedesk\/helpers\/macos-sck-h264\/livedesk-sck-h264(?:\s|["']|$)/i
58
+ .test(normalizedCommandLine);
59
+ const knownAudioCommand = /(?:^|\/)\.livedesk\/helpers\/macos-sck-audio\/livedesk-sck-audio(?:\s|["']|$)/i
60
+ .test(normalizedCommandLine);
61
+ const strictVideoExecutableProof = videoHelperPath.test(normalizedExecutablePath)
62
+ || videoHelperPath.test(argvExecutable);
63
+ const strictAudioExecutableProof = audioHelperPath.test(normalizedExecutablePath)
64
+ || audioHelperPath.test(argvExecutable);
65
+ const knownRawCaptureCommand = new RegExp(
66
+ `^(?:"[^"]*"|'[^']*'|\\S+)\\s+["']?\\S*${FAST_ROOT}/livedesk-raw-capture-helper\\.mjs(?:["']|\\s|$)`,
67
+ 'i'
68
+ ).test(normalizedCommandLine);
69
+ return (/^livedesk-sck-h264$/i.test(normalizedName) && knownVideoCommand)
70
+ || (/^livedesk-sck-h2$/i.test(normalizedName) && strictVideoExecutableProof)
71
+ || (/^livedesk-sck-audio$/i.test(normalizedName) && knownAudioCommand)
72
+ || (/^livedesk-sck-aud$/i.test(normalizedName) && strictAudioExecutableProof)
73
+ || (/^(?:node|node\.exe)$/i.test(normalizedName) && knownRawCaptureCommand);
74
+ }