livedesk 0.1.591 → 0.1.593
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 +8 -8
- package/bin/livedesk.js +115 -112
- package/bootstrap/client-process-identity.js +1 -1
- package/bootstrap/device-role.js +1 -1
- package/bootstrap/legacy-client-update.js +15 -15
- package/bootstrap/role-bootstrap-server.js +2 -2
- package/bootstrap/runtime-lock.js +4 -4
- package/bootstrap/runtime-shutdown.js +3 -3
- package/bootstrap/update-host-client.mjs +9 -9
- package/bootstrap/update-host.mjs +6 -6
- package/client/README.md +10 -10
- package/client/THIRD_PARTY_NOTICES.md +1 -1
- package/client/bin/client-version.js +1 -1
- package/client/bin/livedesk-client-node.js +102 -102
- package/client/bin/livedesk-client-update-bootstrap.cjs +12 -12
- package/client/bin/livedesk-client.js +136 -122
- package/client/package.json +7 -7
- package/client/src/runtime/agent-process-lifecycle.js +10 -10
- package/client/src/runtime/agent-shell.js +66 -66
- package/client/src/runtime/client-runtime-server.js +12 -11
- package/client/src/runtime/linux-video-acceleration.js +1 -1
- package/client/src/runtime/windows-owned-process-manifest.js +9 -9
- package/client/tests/client-version.test.mjs +2 -2
- package/diagnostics/livedesk-mac-physical-gate-core.mjs +5 -5
- package/diagnostics/livedesk-mode3-capture-smoke.mjs +7 -7
- package/diagnostics/livedesk-transport-physical.mjs +3 -3
- package/electron/auth-session-owner.mjs +24 -24
- package/electron/desktop-clipboard-owner.mjs +382 -382
- package/electron/electron-builder.e2e.yml +1 -1
- package/electron/electron-builder.mac-arm64.yml +1 -1
- package/electron/electron-builder.mac-x64.yml +1 -1
- package/electron/electron-builder.win-dev.yml +1 -1
- package/electron/electron-builder.win.yml +2 -2
- package/electron/electron-builder.yml +2 -2
- package/electron/main.mjs +175 -174
- package/electron/preload.cjs +13 -13
- package/electron/runtime-role-transition.mjs +17 -17
- package/hub/package.json +4 -4
- package/hub/src/agents/agent-manager.js +2 -2
- package/hub/src/agents/agent-permissions.js +2 -2
- package/hub/src/agents/agent-tool-registry.js +36 -36
- package/hub/src/agents/codex-agent-runtime.js +16 -16
- package/hub/src/agents/codex-mcp-server.js +5 -5
- package/hub/src/filesystem/roots.js +20 -20
- package/hub/src/filesystem/shared-folders.js +2 -2
- package/hub/src/live-desk-update.js +9 -9
- package/hub/src/remote-clipboard-contract.mjs +482 -482
- package/hub/src/remote-hub.js +1025 -1025
- package/hub/src/server.js +146 -146
- package/hub/src/settings/effective-device-policy.js +31 -31
- package/hub/src/settings/settings-schema.js +2 -2
- package/package.json +7 -7
- package/runtime-core/package.json +2 -2
- package/runtime-core/src/auth-config.js +1 -1
- package/scripts/sync-web-dist.js +9 -9
- package/web/dist/app.webmanifest +4 -4
- package/web/dist/assets/LiveDeskApp-Djx1CsIp.js +178 -0
- package/web/dist/assets/{icons-CebEqIp7.js → icons-DwJmccVC.js} +1 -1
- package/web/dist/assets/{index-BPgqiKB-.js → index-DjIbOQxl.js} +6 -6
- package/web/dist/assets/{react-BHQWCi9c.js → react-DOZUPHF8.js} +1 -1
- package/web/dist/assets/supabase-C7qjtLN3.js +29 -0
- package/web/dist/desktop-auth-callback.html +4 -4
- package/web/dist/desktop-auth-callback.js +13 -13
- package/web/dist/index.html +11 -10
- package/web/dist/livedesk-build-evidence.json +30 -30
- package/web/dist/offline.html +2 -2
- package/web/dist/sw.js +11 -11
- package/web/dist/assets/LiveDeskApp-BnYbdi9v.js +0 -178
- package/web/dist/assets/supabase-C0Z0FS3K.js +0 -29
package/client/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livedesk/client",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.252",
|
|
4
|
+
"description": "VuvoDesk local remote client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"client": "bin/livedesk-client.js",
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
38
38
|
"ffmpeg-static": "^5.3.0",
|
|
39
|
-
"@livedesk/runtime-core": "0.1.
|
|
39
|
+
"@livedesk/runtime-core": "0.1.6",
|
|
40
40
|
"@supabase/supabase-js": "^2.110.0",
|
|
41
41
|
"node-screenshots": "^0.2.8",
|
|
42
42
|
"ws": "^8.18.3"
|
|
43
43
|
},
|
|
44
44
|
"optionalDependencies": {
|
|
45
|
-
"@livedesk/fast-linux-x64": "0.1.
|
|
46
|
-
"@livedesk/fast-osx-arm64": "0.1.
|
|
47
|
-
"@livedesk/fast-osx-x64": "0.1.
|
|
48
|
-
"@livedesk/fast-win-x64": "0.1.
|
|
45
|
+
"@livedesk/fast-linux-x64": "0.1.448",
|
|
46
|
+
"@livedesk/fast-osx-arm64": "0.1.448",
|
|
47
|
+
"@livedesk/fast-osx-x64": "0.1.448",
|
|
48
|
+
"@livedesk/fast-win-x64": "0.1.448"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
|
@@ -217,7 +217,7 @@ export function createWindowsProcessTreeTracker(child, {
|
|
|
217
217
|
waitImpl = waitMilliseconds,
|
|
218
218
|
publishTrackedRecords = null,
|
|
219
219
|
reportPublisherError = error => console.warn(
|
|
220
|
-
`[
|
|
220
|
+
`[VuvoDesk Client] Windows owned-process manifest warning: ${error?.message || error}`
|
|
221
221
|
)
|
|
222
222
|
} = {}) {
|
|
223
223
|
const rootPid = Number(child?.pid || 0);
|
|
@@ -501,7 +501,7 @@ export async function drainOwnedWindowsAgentTree(child, {
|
|
|
501
501
|
: `Exact Windows Agent tree retained pid(s): ${remaining.map(record => record.pid).join(', ')}`
|
|
502
502
|
);
|
|
503
503
|
reportTerminationFailure(
|
|
504
|
-
`[
|
|
504
|
+
`[VuvoDesk Client] Exact Windows Agent tree could not be drained after `
|
|
505
505
|
+ `${attempts} bounded attempt(s): ${error?.message || error}`
|
|
506
506
|
);
|
|
507
507
|
return { ok: false, error, remaining };
|
|
@@ -549,7 +549,7 @@ export async function drainOwnedWindowsAgentTreeUntilStopped(child, {
|
|
|
549
549
|
return result;
|
|
550
550
|
}
|
|
551
551
|
reportTerminationFailure(
|
|
552
|
-
`[
|
|
552
|
+
`[VuvoDesk Client] Exact Windows Agent tree is not drained yet `
|
|
553
553
|
+ `(attempt ${attempt}); the launcher will remain alive and retry.`
|
|
554
554
|
);
|
|
555
555
|
await waitImpl(Math.max(100, Number(retryForeverMs) || 1000));
|
|
@@ -639,7 +639,7 @@ export async function drainOwnedUnixAgentTree(child, {
|
|
|
639
639
|
signalOwnedGroup(gracefulSignal);
|
|
640
640
|
} catch (error) {
|
|
641
641
|
reportTerminationFailure(
|
|
642
|
-
`[
|
|
642
|
+
`[VuvoDesk Client] Exited Agent group pid=${processId} rejected ${gracefulSignal} `
|
|
643
643
|
+ `(${error?.message || error}); forced cleanup will continue.`
|
|
644
644
|
);
|
|
645
645
|
}
|
|
@@ -660,7 +660,7 @@ export async function drainOwnedUnixAgentTree(child, {
|
|
|
660
660
|
if (!failureReported) {
|
|
661
661
|
failureReported = true;
|
|
662
662
|
reportTerminationFailure(
|
|
663
|
-
`[
|
|
663
|
+
`[VuvoDesk Client] Exited Agent group pid=${processId} rejected SIGKILL `
|
|
664
664
|
+ `(${error?.message || error}). The launcher will not start a replacement until the group drains.`
|
|
665
665
|
);
|
|
666
666
|
}
|
|
@@ -669,7 +669,7 @@ export async function drainOwnedUnixAgentTree(child, {
|
|
|
669
669
|
if (!failureReported) {
|
|
670
670
|
failureReported = true;
|
|
671
671
|
reportTerminationFailure(
|
|
672
|
-
`[
|
|
672
|
+
`[VuvoDesk Client] Exited Agent group pid=${processId} still has capture descendants after SIGKILL. `
|
|
673
673
|
+ 'The launcher will not start a replacement until the group drains.'
|
|
674
674
|
);
|
|
675
675
|
}
|
|
@@ -767,7 +767,7 @@ export function installAgentTerminationHandlers({
|
|
|
767
767
|
return;
|
|
768
768
|
}
|
|
769
769
|
reportTerminationFailure(
|
|
770
|
-
`[
|
|
770
|
+
`[VuvoDesk Client] Terminal shutdown retained an exact Windows Agent tree: `
|
|
771
771
|
+ `${result.error?.message || 'bounded drain failed'}. Retrying before exit.`
|
|
772
772
|
);
|
|
773
773
|
child.livedeskTreeStopPromise = null;
|
|
@@ -799,7 +799,7 @@ export function installAgentTerminationHandlers({
|
|
|
799
799
|
signalAgentTree(child, signal, platform, signalProcess);
|
|
800
800
|
} catch (error) {
|
|
801
801
|
reportTerminationFailure(
|
|
802
|
-
`[
|
|
802
|
+
`[VuvoDesk Client] Agent tree pid=${processId || 'unknown'} could not receive ${signal} `
|
|
803
803
|
+ `(${error?.message || error}). Forced cleanup will continue.`
|
|
804
804
|
);
|
|
805
805
|
}
|
|
@@ -816,7 +816,7 @@ export function installAgentTerminationHandlers({
|
|
|
816
816
|
if (!hardFailureReported) {
|
|
817
817
|
hardFailureReported = true;
|
|
818
818
|
reportTerminationFailure(
|
|
819
|
-
`[
|
|
819
|
+
`[VuvoDesk Client] Agent tree pid=${processId || 'unknown'} rejected SIGKILL `
|
|
820
820
|
+ `(${error?.message || error}). The launcher will remain alive and retry.`
|
|
821
821
|
);
|
|
822
822
|
}
|
|
@@ -826,7 +826,7 @@ export function installAgentTerminationHandlers({
|
|
|
826
826
|
if (!hardFailureReported) {
|
|
827
827
|
hardFailureReported = true;
|
|
828
828
|
reportTerminationFailure(
|
|
829
|
-
`[
|
|
829
|
+
`[VuvoDesk Client] Agent tree pid=${processId || 'unknown'} is still alive after SIGKILL. `
|
|
830
830
|
+ 'The launcher will remain alive and keep retrying so capture descendants are not orphaned.'
|
|
831
831
|
);
|
|
832
832
|
}
|
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
|
|
3
|
-
export const AGENT_SHELL_NAMES = Object.freeze([
|
|
4
|
-
'auto',
|
|
5
|
-
'powershell',
|
|
6
|
-
'pwsh',
|
|
7
|
-
'cmd',
|
|
8
|
-
'sh',
|
|
9
|
-
'bash',
|
|
10
|
-
'zsh'
|
|
11
|
-
]);
|
|
12
|
-
|
|
13
|
-
function unixShellPath(shell, platform, fileExists) {
|
|
14
|
-
if (shell === 'sh') return '/bin/sh';
|
|
15
|
-
if (shell === 'bash') return '/bin/bash';
|
|
16
|
-
if (shell === 'zsh') return '/bin/zsh';
|
|
17
|
-
if (shell === 'pwsh') return 'pwsh';
|
|
18
|
-
if (shell === 'powershell' || shell === 'cmd') {
|
|
19
|
-
throw new Error(`${shell} is not available on ${platform}.`);
|
|
20
|
-
}
|
|
21
|
-
if (platform === 'darwin' && fileExists('/bin/zsh')) return '/bin/zsh';
|
|
22
|
-
if (fileExists('/bin/bash')) return '/bin/bash';
|
|
23
|
-
return '/bin/sh';
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function resolveAgentShellCommand({
|
|
27
|
-
platform = process.platform,
|
|
28
|
-
shell = 'auto',
|
|
29
|
-
command = '',
|
|
30
|
-
fileExists = existsSync
|
|
31
|
-
} = {}) {
|
|
32
|
-
const normalizedShell = String(shell || 'auto').trim().toLowerCase();
|
|
33
|
-
if (!AGENT_SHELL_NAMES.includes(normalizedShell)) {
|
|
34
|
-
throw new Error(`Unsupported shell: ${normalizedShell || 'empty'}.`);
|
|
35
|
-
}
|
|
36
|
-
if (platform === 'win32') {
|
|
37
|
-
if (['sh', 'bash', 'zsh'].includes(normalizedShell)) {
|
|
38
|
-
throw new Error(`${normalizedShell} is not available on Windows.`);
|
|
39
|
-
}
|
|
40
|
-
if (normalizedShell === 'cmd') {
|
|
41
|
-
return {
|
|
42
|
-
shell: 'cmd',
|
|
43
|
-
executable: process.env.ComSpec || 'cmd.exe',
|
|
44
|
-
args: ['/d', '/s', '/c', String(command)]
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const powershellCore = normalizedShell === 'pwsh';
|
|
48
|
-
return {
|
|
49
|
-
shell: powershellCore ? 'pwsh' : 'powershell',
|
|
50
|
-
executable: powershellCore ? 'pwsh.exe' : 'powershell.exe',
|
|
51
|
-
args: ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', String(command)]
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const executable = unixShellPath(normalizedShell, platform, fileExists);
|
|
56
|
-
if (executable.startsWith('/') && !fileExists(executable)) {
|
|
57
|
-
throw new Error(`${normalizedShell} is not installed at ${executable}.`);
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
shell: normalizedShell === 'auto'
|
|
61
|
-
? executable.endsWith('/zsh') ? 'zsh' : executable.endsWith('/bash') ? 'bash' : 'sh'
|
|
62
|
-
: normalizedShell,
|
|
63
|
-
executable,
|
|
64
|
-
args: ['-lc', String(command)]
|
|
65
|
-
};
|
|
66
|
-
}
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
export const AGENT_SHELL_NAMES = Object.freeze([
|
|
4
|
+
'auto',
|
|
5
|
+
'powershell',
|
|
6
|
+
'pwsh',
|
|
7
|
+
'cmd',
|
|
8
|
+
'sh',
|
|
9
|
+
'bash',
|
|
10
|
+
'zsh'
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function unixShellPath(shell, platform, fileExists) {
|
|
14
|
+
if (shell === 'sh') return '/bin/sh';
|
|
15
|
+
if (shell === 'bash') return '/bin/bash';
|
|
16
|
+
if (shell === 'zsh') return '/bin/zsh';
|
|
17
|
+
if (shell === 'pwsh') return 'pwsh';
|
|
18
|
+
if (shell === 'powershell' || shell === 'cmd') {
|
|
19
|
+
throw new Error(`${shell} is not available on ${platform}.`);
|
|
20
|
+
}
|
|
21
|
+
if (platform === 'darwin' && fileExists('/bin/zsh')) return '/bin/zsh';
|
|
22
|
+
if (fileExists('/bin/bash')) return '/bin/bash';
|
|
23
|
+
return '/bin/sh';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveAgentShellCommand({
|
|
27
|
+
platform = process.platform,
|
|
28
|
+
shell = 'auto',
|
|
29
|
+
command = '',
|
|
30
|
+
fileExists = existsSync
|
|
31
|
+
} = {}) {
|
|
32
|
+
const normalizedShell = String(shell || 'auto').trim().toLowerCase();
|
|
33
|
+
if (!AGENT_SHELL_NAMES.includes(normalizedShell)) {
|
|
34
|
+
throw new Error(`Unsupported shell: ${normalizedShell || 'empty'}.`);
|
|
35
|
+
}
|
|
36
|
+
if (platform === 'win32') {
|
|
37
|
+
if (['sh', 'bash', 'zsh'].includes(normalizedShell)) {
|
|
38
|
+
throw new Error(`${normalizedShell} is not available on Windows.`);
|
|
39
|
+
}
|
|
40
|
+
if (normalizedShell === 'cmd') {
|
|
41
|
+
return {
|
|
42
|
+
shell: 'cmd',
|
|
43
|
+
executable: process.env.ComSpec || 'cmd.exe',
|
|
44
|
+
args: ['/d', '/s', '/c', String(command)]
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const powershellCore = normalizedShell === 'pwsh';
|
|
48
|
+
return {
|
|
49
|
+
shell: powershellCore ? 'pwsh' : 'powershell',
|
|
50
|
+
executable: powershellCore ? 'pwsh.exe' : 'powershell.exe',
|
|
51
|
+
args: ['-NoLogo', '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', String(command)]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const executable = unixShellPath(normalizedShell, platform, fileExists);
|
|
56
|
+
if (executable.startsWith('/') && !fileExists(executable)) {
|
|
57
|
+
throw new Error(`${normalizedShell} is not installed at ${executable}.`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
shell: normalizedShell === 'auto'
|
|
61
|
+
? executable.endsWith('/zsh') ? 'zsh' : executable.endsWith('/bash') ? 'bash' : 'sh'
|
|
62
|
+
: normalizedShell,
|
|
63
|
+
executable,
|
|
64
|
+
args: ['-lc', String(command)]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -20,8 +20,9 @@ const SUPABASE_URL = process.env.LIVEDESK_SUPABASE_URL || 'https://otbyfkjxrkngv
|
|
|
20
20
|
const SUPABASE_PUBLISHABLE_KEY = process.env.LIVEDESK_SUPABASE_PUBLISHABLE_KEY || 'sb_publishable_NpUs0RDJH2YnllsqTKO6TQ_1jTdSsNQ';
|
|
21
21
|
const CLIENT_STATE_DIR = process.env.LIVEDESK_STATE_DIR || (process.env.LIVEDESK_UNIFIED_RUNTIME === '1' ? join(os.homedir(), '.livedesk') : join(os.homedir(), '.livedesk-client'));
|
|
22
22
|
const CLIENT_AUTH_PATH = join(CLIENT_STATE_DIR, 'auth.json');
|
|
23
|
-
const DEFAULT_TRUSTED_WEB_ORIGINS = Object.freeze([
|
|
24
|
-
'https://
|
|
23
|
+
const DEFAULT_TRUSTED_WEB_ORIGINS = Object.freeze([
|
|
24
|
+
'https://vuvodesk.com',
|
|
25
|
+
'https://livedesk.pages.dev',
|
|
25
26
|
'http://127.0.0.1:5173',
|
|
26
27
|
'http://localhost:5173',
|
|
27
28
|
'http://127.0.0.1:4173',
|
|
@@ -1050,7 +1051,7 @@ function isTrustedLocalRequest(req, host, port, options = {}) {
|
|
|
1050
1051
|
|
|
1051
1052
|
// Chromium can mark modulepreload/stylesheet requests as cross-site. Static
|
|
1052
1053
|
// assets are not privileged. An opaque Origin (`null`) is never accepted for
|
|
1053
|
-
// an API route because it cannot distinguish the
|
|
1054
|
+
// an API route because it cannot distinguish the VuvoDesk shell from an
|
|
1054
1055
|
// attacker-controlled file, sandbox, or data page.
|
|
1055
1056
|
if (options.allowStaticCrossSite === true) {
|
|
1056
1057
|
return true;
|
|
@@ -1107,7 +1108,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1107
1108
|
const initialChoiceMessage = normalizeString(
|
|
1108
1109
|
options.initialChoiceMessage,
|
|
1109
1110
|
1000
|
|
1110
|
-
) || 'Existing
|
|
1111
|
+
) || 'Existing VuvoDesk credentials accepted. Starting the Client.';
|
|
1111
1112
|
const trustedWebOrigins = createTrustedWebOrigins(port);
|
|
1112
1113
|
const webDist = resolve(String(options.webDist || process.env.LIVEDESK_WEB_DIST || '').trim() || join(process.cwd(), 'apps', 'web', 'dist'));
|
|
1113
1114
|
const deviceId = normalizeString(options.deviceId || process.env.LIVEDESK_DEVICE_ID, 160);
|
|
@@ -1334,7 +1335,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1334
1335
|
state.lastError = '';
|
|
1335
1336
|
if (state.message.startsWith('Client authentication needs attention:')) {
|
|
1336
1337
|
state.message = state.agent.state === 'running'
|
|
1337
|
-
? 'Connected to the
|
|
1338
|
+
? 'Connected to the VuvoDesk Hub.'
|
|
1338
1339
|
: normalizeString(message, 1000) || 'Client credentials accepted. Finding the Hub.';
|
|
1339
1340
|
}
|
|
1340
1341
|
}
|
|
@@ -1453,7 +1454,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1453
1454
|
: pathname.replace(/^\//, '');
|
|
1454
1455
|
const filePath = resolve(webDist, requested);
|
|
1455
1456
|
if (!filePath.startsWith(`${webDist}${process.platform === 'win32' ? '\\' : '/'}`) || !existsSync(filePath)) {
|
|
1456
|
-
sendText(res, 404, '
|
|
1457
|
+
sendText(res, 404, 'VuvoDesk web build is not available. Run npm run build.');
|
|
1457
1458
|
return;
|
|
1458
1459
|
}
|
|
1459
1460
|
try {
|
|
@@ -1500,7 +1501,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1500
1501
|
return;
|
|
1501
1502
|
}
|
|
1502
1503
|
if (pathname === '/api/health') {
|
|
1503
|
-
respondJson(200, { ok: true, product: 'LiveDesk', role: 'client', timestamp: new Date().toISOString() });
|
|
1504
|
+
respondJson(200, { ok: true, product: 'LiveDesk', role: 'client', timestamp: new Date().toISOString() });
|
|
1504
1505
|
return;
|
|
1505
1506
|
}
|
|
1506
1507
|
if (pathname === '/api/client/diagnostics') {
|
|
@@ -1582,7 +1583,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1582
1583
|
};
|
|
1583
1584
|
if (!writeSavedSession(session)) throw new Error('refresh-token-required');
|
|
1584
1585
|
state.auth.persisted = true;
|
|
1585
|
-
complete({ type: 'google', session }, 'Signed in. Finding the
|
|
1586
|
+
complete({ type: 'google', session }, 'Signed in. Finding the VuvoDesk Hub.');
|
|
1586
1587
|
res.writeHead(303, { Location: '/', 'Cache-Control': 'no-store' });
|
|
1587
1588
|
res.end();
|
|
1588
1589
|
} catch (error) {
|
|
@@ -1629,7 +1630,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1629
1630
|
respondJson(500, { ok: false, error: message });
|
|
1630
1631
|
return;
|
|
1631
1632
|
}
|
|
1632
|
-
complete({ type: 'google', session }, 'Signed in. Finding the
|
|
1633
|
+
complete({ type: 'google', session }, 'Signed in. Finding the VuvoDesk Hub.');
|
|
1633
1634
|
respondJson(200, { ok: true, authenticated: true, persisted: true, role: 'client' });
|
|
1634
1635
|
return;
|
|
1635
1636
|
}
|
|
@@ -1812,7 +1813,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1812
1813
|
state.manager = '';
|
|
1813
1814
|
state.agent.state = 'reconnecting';
|
|
1814
1815
|
state.lastError = '';
|
|
1815
|
-
state.message = 'Refreshing the
|
|
1816
|
+
state.message = 'Refreshing the VuvoDesk Hub discovery now.';
|
|
1816
1817
|
runtime.update({ state: RuntimeState.RESOLVING_ROLE }, 'client-reconnect-requested');
|
|
1817
1818
|
runtime.emit('client.reconnect.requested', { deviceId, previousManager: previousManager || '' });
|
|
1818
1819
|
await options.onReconnect?.();
|
|
@@ -1895,7 +1896,7 @@ export function createClientRuntimeServer(options = {}) {
|
|
|
1895
1896
|
const hydratedSession = await hydrateClientAccountProfile(normalizedSession);
|
|
1896
1897
|
if (!writeSavedSession(hydratedSession)) throw new Error('refresh-token-required');
|
|
1897
1898
|
state.auth.persisted = true;
|
|
1898
|
-
complete({ type: 'google', session: hydratedSession }, 'Saved sign-in found. Finding the
|
|
1899
|
+
complete({ type: 'google', session: hydratedSession }, 'Saved sign-in found. Finding the VuvoDesk Hub.');
|
|
1899
1900
|
} catch (error) {
|
|
1900
1901
|
recordAuthAttempt('rejected', `session-persistence-failed:${normalizeString(error?.message || error, 160)}`);
|
|
1901
1902
|
}
|
|
@@ -385,6 +385,6 @@ export async function installLinuxVideoAcceleration(currentStatus, options = {})
|
|
|
385
385
|
...refreshed,
|
|
386
386
|
busy: false,
|
|
387
387
|
restartAgent: true,
|
|
388
|
-
message: 'VAAPI hardware video is ready. Restarting the
|
|
388
|
+
message: 'VAAPI hardware video is ready. Restarting the VuvoDesk Agent…'
|
|
389
389
|
};
|
|
390
390
|
}
|
|
@@ -135,13 +135,13 @@ export function writeWindowsOwnedProcessManifest({
|
|
|
135
135
|
if (!exactOwner) {
|
|
136
136
|
return {
|
|
137
137
|
ok: false,
|
|
138
|
-
error: resultError('An exact Windows
|
|
138
|
+
error: resultError('An exact Windows VuvoDesk launcher owner is required.')
|
|
139
139
|
};
|
|
140
140
|
}
|
|
141
141
|
if (!exactRecords) {
|
|
142
142
|
return {
|
|
143
143
|
ok: false,
|
|
144
|
-
error: resultError('Every Windows
|
|
144
|
+
error: resultError('Every Windows VuvoDesk process record requires exact PID and CreationDate identity.')
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
if (!Number.isFinite(nowMs)) {
|
|
@@ -177,7 +177,7 @@ function readExactWindowsOwnedProcessManifest({
|
|
|
177
177
|
return {
|
|
178
178
|
ok: false,
|
|
179
179
|
records: [],
|
|
180
|
-
error: resultError('An exact Windows
|
|
180
|
+
error: resultError('An exact Windows VuvoDesk launcher owner is required.')
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
183
|
if (!Number.isFinite(nowMs)
|
|
@@ -210,7 +210,7 @@ function readExactWindowsOwnedProcessManifest({
|
|
|
210
210
|
ok: false,
|
|
211
211
|
records: [],
|
|
212
212
|
updatedAt,
|
|
213
|
-
error: resultError('Unsupported Windows
|
|
213
|
+
error: resultError('Unsupported Windows VuvoDesk process-manifest version.')
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
if (!ownersMatch(parsed?.owner, exactOwner)) {
|
|
@@ -218,7 +218,7 @@ function readExactWindowsOwnedProcessManifest({
|
|
|
218
218
|
ok: false,
|
|
219
219
|
records: [],
|
|
220
220
|
updatedAt,
|
|
221
|
-
error: resultError('Windows
|
|
221
|
+
error: resultError('Windows VuvoDesk process manifest belongs to another launcher generation.')
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
if (!Number.isFinite(updatedAtMs)
|
|
@@ -228,7 +228,7 @@ function readExactWindowsOwnedProcessManifest({
|
|
|
228
228
|
ok: false,
|
|
229
229
|
records: [],
|
|
230
230
|
updatedAt,
|
|
231
|
-
error: resultError('Windows
|
|
231
|
+
error: resultError('Windows VuvoDesk process manifest is stale or has an invalid timestamp.')
|
|
232
232
|
};
|
|
233
233
|
}
|
|
234
234
|
const exactRecords = normalizeRecords(parsed?.records);
|
|
@@ -237,7 +237,7 @@ function readExactWindowsOwnedProcessManifest({
|
|
|
237
237
|
ok: false,
|
|
238
238
|
records: [],
|
|
239
239
|
updatedAt,
|
|
240
|
-
error: resultError('Windows
|
|
240
|
+
error: resultError('Windows VuvoDesk process manifest contains an invalid process identity.')
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
243
|
return {
|
|
@@ -327,7 +327,7 @@ export function consumeDeadWindowsOwnedProcessManifest({
|
|
|
327
327
|
ok: false,
|
|
328
328
|
path,
|
|
329
329
|
consumed: false,
|
|
330
|
-
error: resultError('Windows
|
|
330
|
+
error: resultError('Windows VuvoDesk process manifest changed after its exact process set was drained.')
|
|
331
331
|
};
|
|
332
332
|
}
|
|
333
333
|
|
|
@@ -374,7 +374,7 @@ export function consumeDeadWindowsOwnedProcessManifest({
|
|
|
374
374
|
quarantinePath: restored ? undefined : quarantinePath,
|
|
375
375
|
consumed: false,
|
|
376
376
|
error: resultError(
|
|
377
|
-
'Windows
|
|
377
|
+
'Windows VuvoDesk process manifest changed during consumption; '
|
|
378
378
|
+ `${restored ? 'the moved generation was restored.' : 'the moved generation was preserved.'}`
|
|
379
379
|
+ `${restoreError?.message ? ` ${restoreError.message}` : ''}`
|
|
380
380
|
)
|
|
@@ -15,13 +15,13 @@ test('prints the public launcher version in the user-facing banner', () => {
|
|
|
15
15
|
LIVEDESK_NPM_LAUNCHER_NAME: 'livedesk',
|
|
16
16
|
LIVEDESK_NPM_LAUNCHER_VERSION: '9.8.7'
|
|
17
17
|
}),
|
|
18
|
-
'[
|
|
18
|
+
'[VuvoDesk Client] livedesk@9.8.7'
|
|
19
19
|
);
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
test('uses the product name for direct client launches', () => {
|
|
23
23
|
assert.equal(
|
|
24
24
|
formatClientVersionBanner({}),
|
|
25
|
-
`[
|
|
25
|
+
`[VuvoDesk Client] livedesk@${packageInfo.version}`
|
|
26
26
|
);
|
|
27
27
|
});
|
|
@@ -67,7 +67,7 @@ export const MAC_ACTIVE_MOTION_RESOURCE_BUDGET = Object.freeze({
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
// Cold helper prime is not allowed a relaxed "temporary" resource envelope.
|
|
70
|
-
// If source compilation exceeds the normal
|
|
70
|
+
// If source compilation exceeds the normal VuvoDesk topology or load ceiling,
|
|
71
71
|
// the release must ship a lighter/prebuilt helper rather than hiding the cost.
|
|
72
72
|
export const MAC_STARTUP_PRIME_RESOURCE_BUDGET = Object.freeze({
|
|
73
73
|
profile: 'cold-sck-helper-prime-to-first-wall-frame',
|
|
@@ -395,7 +395,7 @@ function classifyMacLiveDeskRuntimeRecord(record) {
|
|
|
395
395
|
return 'hub-runtime';
|
|
396
396
|
}
|
|
397
397
|
if (
|
|
398
|
-
/(?:^|\/)
|
|
398
|
+
/(?:^|\/)VuvoDesk\.app\/Contents\/MacOS\/VuvoDesk(?:\s|$|["'])/i.test(commandLine)
|
|
399
399
|
) {
|
|
400
400
|
return 'desktop-runtime';
|
|
401
401
|
}
|
|
@@ -491,7 +491,7 @@ function normalizedOwnedPids(values) {
|
|
|
491
491
|
|
|
492
492
|
/**
|
|
493
493
|
* Returns a report-safe process sample. Command lines and executable paths are
|
|
494
|
-
* used only in-memory for strict
|
|
494
|
+
* used only in-memory for strict VuvoDesk/helper identity and never returned.
|
|
495
495
|
*/
|
|
496
496
|
export function buildMacDiagnosticResourceSample(
|
|
497
497
|
psOutput,
|
|
@@ -508,9 +508,9 @@ export function buildMacDiagnosticResourceSample(
|
|
|
508
508
|
const ownedObserverPids = normalizedOwnedPids(observerPids);
|
|
509
509
|
const helpers = records.filter(isMacCaptureHelperRecord);
|
|
510
510
|
// The public `npx livedesk diagnose mac-physical` coordinator is itself
|
|
511
|
-
// classified as a unified
|
|
511
|
+
// classified as a unified VuvoDesk runtime. Exclude only that exact,
|
|
512
512
|
// caller-owned PID from ownership/drain counts. Descendants and neighboring
|
|
513
|
-
//
|
|
513
|
+
// VuvoDesk runtimes remain visible and therefore continue to fail closed.
|
|
514
514
|
const liveDeskRecords = identifyMacLiveDeskRuntimeRecords(records, {
|
|
515
515
|
ignorePids: [normalizedDiagnosticPid]
|
|
516
516
|
});
|
|
@@ -51,7 +51,7 @@ function optionalFiniteNumber(value) {
|
|
|
51
51
|
return Number.isFinite(parsed) ? parsed : null;
|
|
52
52
|
}
|
|
53
53
|
if (macPhysicalGate && !isMacPhysicalGate) {
|
|
54
|
-
const message = 'Mac physical diagnostic requires macOS hardware; no
|
|
54
|
+
const message = 'Mac physical diagnostic requires macOS hardware; no VuvoDesk process was started.';
|
|
55
55
|
if (publicMacPhysicalDiagnostic) {
|
|
56
56
|
console.error(message);
|
|
57
57
|
process.exit(2);
|
|
@@ -66,7 +66,7 @@ if (!isWindows && !isMacPhysicalGate) {
|
|
|
66
66
|
if (isMacPhysicalGate && !macActiveMotionOperatorConfirmed) {
|
|
67
67
|
console.error(
|
|
68
68
|
'Mac physical diagnostic requires --confirm-active-motion after continuous visible motion '
|
|
69
|
-
+ 'has been started on both tested displays; no
|
|
69
|
+
+ 'has been started on both tested displays; no VuvoDesk process was started.'
|
|
70
70
|
);
|
|
71
71
|
process.exit(2);
|
|
72
72
|
}
|
|
@@ -82,10 +82,10 @@ const rootDir = existsSync(repoHubServerPath) ? candidateRepoRoot : packageRoot;
|
|
|
82
82
|
const hubServerPath = existsSync(repoHubServerPath) ? repoHubServerPath : packagedHubServerPath;
|
|
83
83
|
const unifiedLauncherPath = path.resolve(packageRoot, 'bin', 'livedesk.js');
|
|
84
84
|
if (isMacPhysicalGate && !existsSync(unifiedLauncherPath)) {
|
|
85
|
-
throw new Error(`
|
|
85
|
+
throw new Error(`VuvoDesk diagnostic unified launcher is missing: ${unifiedLauncherPath}`);
|
|
86
86
|
}
|
|
87
87
|
if (!isMacPhysicalGate && !existsSync(hubServerPath)) {
|
|
88
|
-
throw new Error(`
|
|
88
|
+
throw new Error(`VuvoDesk diagnostic Hub runtime is missing: ${hubServerPath}`);
|
|
89
89
|
}
|
|
90
90
|
const clientRuntimeId = isWindows
|
|
91
91
|
? 'win-x64'
|
|
@@ -119,7 +119,7 @@ function resolveClientPath() {
|
|
|
119
119
|
// The actionable error below covers a missing optional platform package.
|
|
120
120
|
}
|
|
121
121
|
throw new Error(
|
|
122
|
-
`
|
|
122
|
+
`VuvoDesk diagnostic runtime ${platformPackage} is unavailable. `
|
|
123
123
|
+ 'Re-run with `npx -y --prefer-online livedesk@latest diagnose mac-physical '
|
|
124
124
|
+ '--confirm-active-motion`.'
|
|
125
125
|
);
|
|
@@ -440,8 +440,8 @@ function assertNoConflictingMacLiveDeskRuntimes() {
|
|
|
440
440
|
.map(processInfo => `${processInfo.kind} pid=${processInfo.pid} process=${processInfo.commandName}`)
|
|
441
441
|
.join(', ');
|
|
442
442
|
throw new Error(
|
|
443
|
-
`Mac physical diagnostic refused to overlap an existing
|
|
444
|
-
+ 'Stop
|
|
443
|
+
`Mac physical diagnostic refused to overlap an existing VuvoDesk runtime: ${summary}. `
|
|
444
|
+
+ 'Stop VuvoDesk, then retry. No existing process was stopped.'
|
|
445
445
|
);
|
|
446
446
|
}
|
|
447
447
|
|
|
@@ -539,8 +539,8 @@ export async function runTransportPhysicalDiagnosticCli(argv = []) {
|
|
|
539
539
|
return { status: 'help' };
|
|
540
540
|
}
|
|
541
541
|
const { report, reportPath } = await runTransportPhysicalDiagnostic(args);
|
|
542
|
-
console.log(`[
|
|
543
|
-
console.log(`[
|
|
542
|
+
console.log(`[VuvoDesk] transport-physical ${report.result.status}: ${report.result.summary}`);
|
|
543
|
+
console.log(`[VuvoDesk] redacted report: ${reportPath}`);
|
|
544
544
|
if (report.result.status !== 'passed') process.exitCode = 2;
|
|
545
545
|
return { status: report.result.status, report, reportPath };
|
|
546
546
|
}
|
|
@@ -551,7 +551,7 @@ if (isMain) {
|
|
|
551
551
|
runTransportPhysicalDiagnosticCli(process.argv.slice(2))
|
|
552
552
|
.catch(error => {
|
|
553
553
|
console.error(
|
|
554
|
-
`[
|
|
554
|
+
`[VuvoDesk] transport-physical failed: ${genericTransportPhysicalError(error)}`);
|
|
555
555
|
process.exitCode = 1;
|
|
556
556
|
});
|
|
557
557
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
export const DESKTOP_AUTH_REFRESH_SKEW_SECONDS = 5 * 60;
|
|
2
2
|
export const DESKTOP_AUTH_RETRY_MS = 15_000;
|
|
3
3
|
export const DESKTOP_AUTH_MINIMUM_TIMER_MS = 30_000;
|
|
4
|
-
export const DESKTOP_AUTH_INVALID_CONFIRMATIONS = 3;
|
|
5
|
-
|
|
6
|
-
const DEFINITIVE_REFRESH_REJECTION_CODES = new Set([
|
|
7
|
-
'invalid_grant',
|
|
8
|
-
'refresh_token_already_used',
|
|
9
|
-
'refresh_token_not_found',
|
|
10
|
-
'session_expired',
|
|
11
|
-
'session_not_found',
|
|
12
|
-
'user_not_found'
|
|
13
|
-
]);
|
|
4
|
+
export const DESKTOP_AUTH_INVALID_CONFIRMATIONS = 3;
|
|
5
|
+
|
|
6
|
+
const DEFINITIVE_REFRESH_REJECTION_CODES = new Set([
|
|
7
|
+
'invalid_grant',
|
|
8
|
+
'refresh_token_already_used',
|
|
9
|
+
'refresh_token_not_found',
|
|
10
|
+
'session_expired',
|
|
11
|
+
'session_not_found',
|
|
12
|
+
'user_not_found'
|
|
13
|
+
]);
|
|
14
14
|
|
|
15
15
|
export function desktopSessionNeedsRefresh(
|
|
16
16
|
session,
|
|
@@ -44,20 +44,20 @@ export function desktopSessionRefreshDelayMs(
|
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export function isPermanentDesktopAuthRefreshFailure(error) {
|
|
48
|
-
const status = Number(error?.authStatus || 0);
|
|
49
|
-
if (status !== 400 && status !== 401 && status !== 403) return false;
|
|
50
|
-
|
|
51
|
-
// Status alone is not logout authority. A captive portal, provider edge,
|
|
52
|
-
// corporate proxy, or temporarily inconsistent auth replica can all return
|
|
53
|
-
// 403 while the saved refresh token remains valid. Supabase provides a
|
|
54
|
-
// stable refresh/session error code for an actual credential rejection; old
|
|
55
|
-
// GoTrue releases are covered by the narrow legacy message fallback.
|
|
56
|
-
const code = String(error?.authCode || '').trim().toLowerCase();
|
|
57
|
-
if (DEFINITIVE_REFRESH_REJECTION_CODES.has(code)) return true;
|
|
58
|
-
const message = String(error?.message || '');
|
|
59
|
-
return /invalid refresh token|refresh token (?:was )?(?:not found|already used)/i.test(message);
|
|
60
|
-
}
|
|
47
|
+
export function isPermanentDesktopAuthRefreshFailure(error) {
|
|
48
|
+
const status = Number(error?.authStatus || 0);
|
|
49
|
+
if (status !== 400 && status !== 401 && status !== 403) return false;
|
|
50
|
+
|
|
51
|
+
// Status alone is not logout authority. A captive portal, provider edge,
|
|
52
|
+
// corporate proxy, or temporarily inconsistent auth replica can all return
|
|
53
|
+
// 403 while the saved refresh token remains valid. Supabase provides a
|
|
54
|
+
// stable refresh/session error code for an actual credential rejection; old
|
|
55
|
+
// GoTrue releases are covered by the narrow legacy message fallback.
|
|
56
|
+
const code = String(error?.authCode || '').trim().toLowerCase();
|
|
57
|
+
if (DEFINITIVE_REFRESH_REJECTION_CODES.has(code)) return true;
|
|
58
|
+
const message = String(error?.message || '');
|
|
59
|
+
return /invalid refresh token|refresh token (?:was )?(?:not found|already used)/i.test(message);
|
|
60
|
+
}
|
|
61
61
|
|
|
62
62
|
export function selectFreshestDesktopSession(currentSession, incomingSession) {
|
|
63
63
|
if (!currentSession) return incomingSession || null;
|