livedesk 0.1.454 → 0.1.456

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/README.md CHANGED
@@ -51,7 +51,7 @@ Pass `--no-clean` to disable the startup cleanup explicitly. If a child Hub
51
51
  still loses a startup race with `EADDRINUSE`, the launcher performs one cleanup
52
52
  and startup retry, then exits without looping.
53
53
 
54
- ## Client compatibility
54
+ ## Client compatibility
55
55
 
56
56
  ```powershell
57
57
  npx -y --prefer-online livedesk@latest client 3
@@ -61,9 +61,36 @@ The `client` form remains as a legacy compatibility alias. It records the
61
61
  Client role and uses the same unified runtime lock. The client signs in with
62
62
  Google, discovers the active Hub, and connects to the wall. On Windows, enable
63
63
  **Start with Windows** on the connection page to reconnect automatically after
64
- reboot; the generated startup command uses the unified launcher.
65
-
66
- ## Frame modes
64
+ reboot; the generated startup command uses the unified launcher.
65
+
66
+ ## macOS physical diagnostic
67
+
68
+ On a Mac with two connected displays, stop every running LiveDesk runtime,
69
+ show visibly different content on the two displays, grant Screen Recording and
70
+ Accessibility permissions, then run:
71
+
72
+ ```bash
73
+ npx -y --prefer-online livedesk@latest diagnose mac-physical
74
+ ```
75
+
76
+ This command branches before role resolution, state migration, and the normal
77
+ runtime lock. It refuses to overlap an existing LiveDesk runtime or capture
78
+ helper, and normal macOS startup reciprocally refuses an active diagnostic
79
+ lease. It starts only an isolated loopback Hub and temporary RemoteFast Client
80
+ and stops only the exact children it created. Ctrl+C and termination signals
81
+ join the same owned cleanup before exit. The default JSON report is written to
82
+ one final path after cleanup, and run/cleanup errors always produce a failed
83
+ result. It is written under `~/.livedesk/diagnostics`. To select a path relative
84
+ to the directory where the command was invoked:
85
+
86
+ ```bash
87
+ npx -y --prefer-online livedesk@latest diagnose mac-physical --report ./mac-result.json
88
+ ```
89
+
90
+ Use `diagnose mac-physical --help` to inspect requirements without starting a
91
+ runtime or changing cached role/account state.
92
+
93
+ ## Frame modes
67
94
 
68
95
  - Mode 2: independent 320x180 RGB565+LZO tiles for large, stable walls.
69
96
  - Mode 3: direct hardware H.264 for focused remote control up to 4K.
package/bin/livedesk.js CHANGED
@@ -34,6 +34,7 @@ import {
34
34
  import { runLegacyClientUpdateSupervisorCli } from '../bootstrap/legacy-client-update.js';
35
35
  import { transferSavedClientSessionToHub } from '../bootstrap/hub-auth-handoff.js';
36
36
  import { readWindowsOwnedProcessManifest } from '../client/src/runtime/windows-owned-process-manifest.js';
37
+ import { inspectMacDiagnosticLease } from '../diagnostics/livedesk-mac-physical-isolation.mjs';
37
38
 
38
39
  const require = createRequire(import.meta.url);
39
40
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -143,6 +144,22 @@ function isPidAlive(pid) {
143
144
  }
144
145
  }
145
146
 
147
+ async function assertNoActiveMacPhysicalDiagnosticLease() {
148
+ if (os.platform() !== 'darwin') return;
149
+ const lease = await inspectMacDiagnosticLease();
150
+ if (lease.status === 'missing' || lease.status === 'stale') return;
151
+ if (lease.status === 'active') {
152
+ throw new Error(
153
+ `Mac physical diagnostic is running (pid ${lease.pid}); normal LiveDesk startup was not started. `
154
+ + 'Wait for its owned cleanup to finish or stop the diagnostic with Ctrl+C.'
155
+ );
156
+ }
157
+ throw new Error(
158
+ `Mac physical diagnostic ownership is unreadable at ${lease.path}; normal LiveDesk startup was not started `
159
+ + 'to avoid overlapping a capture runtime.'
160
+ );
161
+ }
162
+
146
163
  function canClaimHubUpdateResult(current, next) {
147
164
  const currentOperationId = String(current?.operationId || '');
148
165
  const nextOperationId = String(next?.operationId || '');
@@ -312,15 +329,18 @@ function printHelp() {
312
329
  process.stdout.write(`
313
330
  LiveDesk
314
331
 
315
- Usage:
316
- npx -y --prefer-online livedesk@latest
317
- npx -y --prefer-online livedesk@latest --force-role hub
318
- npx -y --prefer-online livedesk@latest --force-role client
319
-
320
- Commands:
321
- No command Resolve the signed-in device role and start the matching runtime.
322
- hub Legacy alias for --force-role hub.
323
- client Legacy alias for --force-role client.
332
+ Usage:
333
+ npx -y --prefer-online livedesk@latest
334
+ npx -y --prefer-online livedesk@latest --force-role hub
335
+ npx -y --prefer-online livedesk@latest --force-role client
336
+ npx -y --prefer-online livedesk@latest diagnose mac-physical
337
+
338
+ Commands:
339
+ No command Resolve the signed-in device role and start the matching runtime.
340
+ hub Legacy alias for --force-role hub.
341
+ client Legacy alias for --force-role client.
342
+ diagnose mac-physical
343
+ Run the isolated two-display macOS capture/control physical gate.
324
344
 
325
345
  Hub options:
326
346
  --no-open Do not open the browser automatically.
@@ -342,10 +362,63 @@ Common:
342
362
  Do not ask for a role on first run; fail if no role is known.
343
363
  --help Show this help.
344
364
  --version Show version.
345
- `.trimStart());
346
- }
347
-
348
- function printClientHelp() {
365
+ `.trimStart());
366
+ }
367
+
368
+ function printMacPhysicalDiagnosticHelp() {
369
+ process.stdout.write(`
370
+ LiveDesk macOS physical diagnostic
371
+
372
+ Usage:
373
+ npx -y --prefer-online livedesk@latest diagnose mac-physical
374
+ npx -y --prefer-online livedesk@latest diagnose mac-physical --report ./mac-result.json
375
+
376
+ Requirements:
377
+ macOS with two connected displays showing visibly different content.
378
+ Screen Recording permission for the terminal/LiveDesk capture helper.
379
+ Accessibility permission for the terminal/LiveDesk input helper.
380
+ Stop every existing LiveDesk Hub, Client, and desktop runtime first.
381
+
382
+ Behavior:
383
+ Starts one isolated loopback Hub and one temporary RemoteFast Client.
384
+ Refuses to overlap a running LiveDesk runtime or capture helper.
385
+ Stops only the exact child processes created by this diagnostic.
386
+ Writes the default report under ~/.livedesk/diagnostics.
387
+
388
+ Options:
389
+ --report <path> Write the JSON report relative to the invocation directory.
390
+ --monitor <n> Initial zero-based display index. Default: 0.
391
+ --width <px> Requested capture width. Default: 1920.
392
+ --height <px> Requested capture height. Default: 1080.
393
+ --help Show this help without resolving a role or acquiring a runtime lock.
394
+ `.trimStart());
395
+ }
396
+
397
+ async function runDiagnosticCommand(args) {
398
+ const name = String(args[0] || '').trim().toLowerCase();
399
+ if (!name || name === '--help' || name === '-h' || name === 'help') {
400
+ printMacPhysicalDiagnosticHelp();
401
+ return;
402
+ }
403
+ if (name !== 'mac-physical') {
404
+ throw new Error(`Unknown LiveDesk diagnostic: ${name}. Supported diagnostic: mac-physical.`);
405
+ }
406
+ if (args.slice(1).some(argument => ['--help', '-h', 'help'].includes(String(argument).toLowerCase()))) {
407
+ printMacPhysicalDiagnosticHelp();
408
+ return;
409
+ }
410
+ const diagnosticEntry = resolve(
411
+ packageRoot,
412
+ 'diagnostics',
413
+ 'livedesk-mode3-capture-smoke.mjs'
414
+ );
415
+ if (!existsSync(diagnosticEntry)) {
416
+ throw new Error(`LiveDesk mac-physical diagnostic is missing from this package: ${diagnosticEntry}`);
417
+ }
418
+ await import(pathToFileURL(diagnosticEntry).href);
419
+ }
420
+
421
+ function printClientHelp() {
349
422
  process.stdout.write(`
350
423
  LiveDesk Client
351
424
 
@@ -2847,8 +2920,14 @@ async function runDesktopRoleBootstrap() {
2847
2920
 
2848
2921
  async function main() {
2849
2922
  const rawArgs = process.argv.slice(2);
2923
+ const rawCommand = String(rawArgs[0] || '').trim().toLowerCase();
2924
+ if (rawCommand === 'diagnose') {
2925
+ await runDiagnosticCommand(rawArgs.slice(1));
2926
+ return;
2927
+ }
2850
2928
  restoreUpdateOriginalCwd();
2851
2929
  if (rawArgs.includes('--internal-legacy-client-update')) {
2930
+ await assertNoActiveMacPhysicalDiagnosticLease();
2852
2931
  await runLegacyClientUpdateSupervisorCli();
2853
2932
  return;
2854
2933
  }
@@ -2870,11 +2949,12 @@ async function main() {
2870
2949
  printHelp();
2871
2950
  return;
2872
2951
  }
2873
- if (command === '--version' || command === '-v' || command === 'version') {
2874
- console.log(readVersion());
2875
- return;
2876
- }
2877
- const explicitRoleCommand = command === 'hub' || command === 'manager' || command === 'client';
2952
+ if (command === '--version' || command === '-v' || command === 'version') {
2953
+ console.log(readVersion());
2954
+ return;
2955
+ }
2956
+ await assertNoActiveMacPhysicalDiagnosticLease();
2957
+ const explicitRoleCommand = command === 'hub' || command === 'manager' || command === 'client';
2878
2958
  let resolvedRole;
2879
2959
  let runtimeArgs = args;
2880
2960
  if (explicitRoleCommand) {
@@ -1432,9 +1432,11 @@ function remotePolicyAllows(options, command) {
1432
1432
  const policy = options.effectivePolicy;
1433
1433
  if (!policy || typeof policy !== 'object') return { ok: true };
1434
1434
  if (policy.accessMode === 'block-remote-access') return { ok: false, error: 'remote-access-blocked-by-settings' };
1435
- const required = command === 'input.control'
1436
- ? ['allowControl']
1437
- : command.startsWith('file.transfer')
1435
+ const required = command === 'input.control'
1436
+ ? ['allowControl']
1437
+ : command === 'system.power'
1438
+ ? ['allowPowerActions']
1439
+ : command.startsWith('file.transfer')
1438
1440
  ? ['allowFileTransfer']
1439
1441
  : command === 'audio.start'
1440
1442
  ? ['allowRemoteAudio']
@@ -2044,8 +2046,8 @@ function connectOnce(options, deviceId) {
2044
2046
 
2045
2047
  const message = JSON.parse(line);
2046
2048
  if (message.type === 'welcome') {
2047
- options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
2048
- ? message.effectivePolicy
2049
+ options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
2050
+ ? message.effectivePolicy
2049
2051
  : null;
2050
2052
  console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
2051
2053
  markClientUpdateConnected();
@@ -2058,13 +2060,17 @@ function connectOnce(options, deviceId) {
2058
2060
  return;
2059
2061
  }
2060
2062
 
2061
- heartbeatTimer = setInterval(() => {
2062
- writeJsonLine(socket, {
2063
- type: 'status',
2064
- status: getStatus(options)
2065
- });
2066
- }, options.heartbeatMs);
2067
- } else if (message.type === 'command') {
2063
+ heartbeatTimer = setInterval(() => {
2064
+ writeJsonLine(socket, {
2065
+ type: 'status',
2066
+ status: getStatus(options)
2067
+ });
2068
+ }, options.heartbeatMs);
2069
+ } else if (message.type === 'policy.update') {
2070
+ options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
2071
+ ? message.effectivePolicy
2072
+ : options.effectivePolicy;
2073
+ } else if (message.type === 'command') {
2068
2074
  handleRemoteCommand(socket, options, message, () => {
2069
2075
  frameSeq += 1;
2070
2076
  return frameSeq;
@@ -4795,8 +4795,8 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
4795
4795
  const parsed = parseLauncherArgs(argv);
4796
4796
  if (parsed.retiredModelOptionsDiscarded > 0) {
4797
4797
  console.warn(
4798
- '[LiveDesk Client] Ignored retired Client model-runtime options. '
4799
- + 'Approved AI computer commands are handled by the Hub Codex Agent.'
4798
+ '[LiveDesk Client] Ignored retired Client options. '
4799
+ + 'Computer commands are handled by the Hub Codex Agent.'
4800
4800
  );
4801
4801
  }
4802
4802
  if (parsed.updateWaitPid) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.209",
3
+ "version": "0.1.211",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,12 +9,12 @@
9
9
  "livedesk-client-node": "bin/livedesk-client-node.js",
10
10
  "livedesk-client-fast": "bin/livedesk-client-fast.js"
11
11
  },
12
- "files": [
13
- "bin/",
14
- "src/",
15
- "README.md",
16
- "THIRD_PARTY_NOTICES.md"
17
- ],
12
+ "files": [
13
+ "bin/",
14
+ "src/",
15
+ "README.md",
16
+ "THIRD_PARTY_NOTICES.md"
17
+ ],
18
18
  "scripts": {
19
19
  "check": "node --check bin/client-version.js && node --check bin/livedesk-client.js && node --check bin/livedesk-client-node.js && node --check bin/livedesk-client-fast.js",
20
20
  "test:version": "node --test tests/client-version.test.mjs",
@@ -31,21 +31,21 @@
31
31
  "engines": {
32
32
  "node": ">=20"
33
33
  },
34
- "dependencies": {
35
- "@ffmpeg-installer/ffmpeg": "^1.1.0",
36
- "ffmpeg-static": "^5.3.0",
37
- "@livedesk/runtime-core": "0.1.0",
38
- "@supabase/supabase-js": "^2.110.0",
39
- "node-screenshots": "^0.2.8",
40
- "ws": "^8.18.3"
41
- },
42
- "optionalDependencies": {
43
- "@livedesk/fast-linux-x64": "0.1.414",
44
- "@livedesk/fast-osx-arm64": "0.1.414",
45
- "@livedesk/fast-osx-x64": "0.1.414",
46
- "@livedesk/fast-win-x64": "0.1.414"
47
- },
48
- "publishConfig": {
34
+ "dependencies": {
35
+ "@ffmpeg-installer/ffmpeg": "^1.1.0",
36
+ "ffmpeg-static": "^5.3.0",
37
+ "@livedesk/runtime-core": "0.1.0",
38
+ "@supabase/supabase-js": "^2.110.0",
39
+ "node-screenshots": "^0.2.8",
40
+ "ws": "^8.18.3"
41
+ },
42
+ "optionalDependencies": {
43
+ "@livedesk/fast-linux-x64": "0.1.415",
44
+ "@livedesk/fast-osx-arm64": "0.1.415",
45
+ "@livedesk/fast-osx-x64": "0.1.415",
46
+ "@livedesk/fast-win-x64": "0.1.415"
47
+ },
48
+ "publishConfig": {
49
49
  "access": "public"
50
50
  }
51
51
  }