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
|
@@ -25,7 +25,7 @@ export function isKnownLiveDeskClientAgentProcess(processName, commandLine, exec
|
|
|
25
25
|
// Linux TASK_COMM_LEN exposes a 15-character `comm`, so the native apphost
|
|
26
26
|
// appears as `livedesk-client`. Accept that truncated name only when an
|
|
27
27
|
// independently resolved executable path, or argv[0], proves the canonical
|
|
28
|
-
// packaged
|
|
28
|
+
// packaged VuvoDesk Fast binary. A matching path elsewhere in argv is not
|
|
29
29
|
// sufficient.
|
|
30
30
|
const isTruncatedLinuxFast = /^livedesk-client$/i.test(normalizedName);
|
|
31
31
|
const isKnownNodeAgent = /(?:node_modules\/(?:livedesk\/client|@livedesk\/client)|packages\/(?:livedesk\/client|client))\/bin\/livedesk-client-node\.js(?:\s|["']|$)/i.test(normalizedCommandLine);
|
package/bootstrap/device-role.js
CHANGED
|
@@ -112,7 +112,7 @@ export function writeRoleCache(resolved, options = {}) {
|
|
|
112
112
|
const role = normalizeRole(resolved?.role);
|
|
113
113
|
const deviceId = normalizeDeviceId(resolved?.deviceId);
|
|
114
114
|
if (!role || !deviceId) {
|
|
115
|
-
throw new Error('A valid device role and device id are required to write the
|
|
115
|
+
throw new Error('A valid device role and device id are required to write the VuvoDesk role cache.');
|
|
116
116
|
}
|
|
117
117
|
const paths = getRoleStatePaths(options);
|
|
118
118
|
writeJson(paths.rolePath, {
|
|
@@ -89,7 +89,7 @@ function safePathSegment(value, fallback = 'unknown') {
|
|
|
89
89
|
export class LegacyClientUpdateSupersededError extends Error {
|
|
90
90
|
constructor(expectedOperationId, actualOperationId) {
|
|
91
91
|
super(
|
|
92
|
-
`
|
|
92
|
+
`VuvoDesk Client update ${expectedOperationId || 'unknown'} was superseded by `
|
|
93
93
|
+ `${actualOperationId || 'another operation'}.`
|
|
94
94
|
);
|
|
95
95
|
this.name = 'LegacyClientUpdateSupersededError';
|
|
@@ -114,7 +114,7 @@ export function assertLegacyClientUpdateDeadline(
|
|
|
114
114
|
) {
|
|
115
115
|
const deadlineEpochMs = Number(config?.updateDeadlineEpochMs);
|
|
116
116
|
if (!Number.isSafeInteger(deadlineEpochMs) || deadlineEpochMs <= 0) {
|
|
117
|
-
const error = new Error('
|
|
117
|
+
const error = new Error('VuvoDesk Client update has no valid absolute deadline.');
|
|
118
118
|
error.code = DEADLINE_EXPIRED_CODE;
|
|
119
119
|
error.deadlineExpired = true;
|
|
120
120
|
throw error;
|
|
@@ -122,7 +122,7 @@ export function assertLegacyClientUpdateDeadline(
|
|
|
122
122
|
const currentEpochMs = Number(now());
|
|
123
123
|
if (currentEpochMs < deadlineEpochMs) return deadlineEpochMs - currentEpochMs;
|
|
124
124
|
const error = new Error(
|
|
125
|
-
`
|
|
125
|
+
`VuvoDesk Client update deadline expired before ${checkpoint} `
|
|
126
126
|
+ `(deadline=${new Date(deadlineEpochMs).toISOString()}).`
|
|
127
127
|
);
|
|
128
128
|
error.code = DEADLINE_EXPIRED_CODE;
|
|
@@ -140,14 +140,14 @@ export function getLegacyClientUpdateNeutralCwd(stateDir, operationId) {
|
|
|
140
140
|
|
|
141
141
|
function prepareNeutralCwd(cwd) {
|
|
142
142
|
const configuredCwd = String(cwd || '').trim();
|
|
143
|
-
if (!configuredCwd) throw new Error('
|
|
143
|
+
if (!configuredCwd) throw new Error('VuvoDesk update neutral working directory is unavailable');
|
|
144
144
|
const neutralCwd = resolve(configuredCwd);
|
|
145
145
|
mkdirSync(neutralCwd, { recursive: true });
|
|
146
146
|
const unexpectedEntry = readdirSync(neutralCwd)[0];
|
|
147
147
|
if (unexpectedEntry) {
|
|
148
148
|
const shadowPath = join(neutralCwd, unexpectedEntry);
|
|
149
149
|
throw new Error(
|
|
150
|
-
`
|
|
150
|
+
`VuvoDesk update neutral working directory is shadowed by ${shadowPath}: ${neutralCwd}`
|
|
151
151
|
);
|
|
152
152
|
}
|
|
153
153
|
return neutralCwd;
|
|
@@ -472,7 +472,7 @@ export function readLegacyProcessArguments(pid, platform = process.platform) {
|
|
|
472
472
|
if (platform === 'darwin' || platform === 'linux' || platform === 'freebsd') {
|
|
473
473
|
return processArgumentsFromPs(processId);
|
|
474
474
|
}
|
|
475
|
-
throw new Error(`
|
|
475
|
+
throw new Error(`VuvoDesk cannot snapshot Client arguments on ${platform}`);
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
function readOptionValue(args, index, joinUntilFlag) {
|
|
@@ -579,7 +579,7 @@ function captureRestartConfiguration(config, env, readArguments = readLegacyProc
|
|
|
579
579
|
agentArgs = readArguments(config.agentPid);
|
|
580
580
|
} catch (error) {
|
|
581
581
|
throw new Error(
|
|
582
|
-
`
|
|
582
|
+
`VuvoDesk could not freeze the current Client options before update; `
|
|
583
583
|
+ `the existing Client was left running. ${error?.message || error}`
|
|
584
584
|
);
|
|
585
585
|
}
|
|
@@ -637,7 +637,7 @@ export function resolveExactLocalRestoreLauncher(config, env, frozen) {
|
|
|
637
637
|
};
|
|
638
638
|
}
|
|
639
639
|
throw new Error(
|
|
640
|
-
`
|
|
640
|
+
`VuvoDesk could not freeze an exact local restore artifact for `
|
|
641
641
|
+ `${config.currentProductVersion}/${config.currentAgentVersion}; the existing Client was left running.`
|
|
642
642
|
);
|
|
643
643
|
}
|
|
@@ -716,7 +716,7 @@ export function withLegacyClientUpdateStateLock(
|
|
|
716
716
|
if (error?.code !== 'EEXIST') throw error;
|
|
717
717
|
if (removeAbandonedUpdateStateLock(lockPath, aliveImpl, now)) continue;
|
|
718
718
|
if (now() >= deadline) {
|
|
719
|
-
throw new Error(`Timed out waiting for the
|
|
719
|
+
throw new Error(`Timed out waiting for the VuvoDesk update state lock: ${lockPath}`);
|
|
720
720
|
}
|
|
721
721
|
waitForUpdateStateLock(Math.min(Math.max(1, pollMs), Math.max(1, deadline - now())));
|
|
722
722
|
}
|
|
@@ -1008,7 +1008,7 @@ function resolveNpxInvocation(args, env = process.env) {
|
|
|
1008
1008
|
: [env.LIVEDESK_NPX_EXECUTABLE, join(nodeDirectory, 'npx'), 'npx'];
|
|
1009
1009
|
const npx = executableCandidates.find(candidate => candidate && (!candidate.includes('/') && !candidate.includes('\\') || existsSync(candidate)))
|
|
1010
1010
|
|| executableCandidates.at(-1);
|
|
1011
|
-
if (!npx) throw new Error('
|
|
1011
|
+
if (!npx) throw new Error('VuvoDesk npx executable was not found');
|
|
1012
1012
|
if (process.platform !== 'win32') return { command: npx, args };
|
|
1013
1013
|
|
|
1014
1014
|
const quote = value => `"${String(value).replaceAll('"', '""')}"`;
|
|
@@ -1021,7 +1021,7 @@ function resolveNpxInvocation(args, env = process.env) {
|
|
|
1021
1021
|
export function prefetchExactNpxRestore(config, env = process.env, options = {}) {
|
|
1022
1022
|
const version = cleanVersion(config?.currentProductVersion);
|
|
1023
1023
|
if (!version) {
|
|
1024
|
-
throw new Error('
|
|
1024
|
+
throw new Error('VuvoDesk cannot prefetch an exact restore without the current product version.');
|
|
1025
1025
|
}
|
|
1026
1026
|
const neutralCwd = prepareNeutralCwd(config?.neutralCwd);
|
|
1027
1027
|
const packageSpec = `livedesk@${version}`;
|
|
@@ -1057,7 +1057,7 @@ export function prefetchExactNpxRestore(config, env = process.env, options = {})
|
|
|
1057
1057
|
const output = `${result?.stdout || ''}\n${result?.stderr || ''}`.trim();
|
|
1058
1058
|
if (result?.error || result?.status !== 0) {
|
|
1059
1059
|
throw new Error(
|
|
1060
|
-
`
|
|
1060
|
+
`VuvoDesk could not prefetch exact restore package livedesk@${version}; `
|
|
1061
1061
|
+ `the existing Client was left running. `
|
|
1062
1062
|
+ `${result?.error?.message || output || `exit=${result?.status ?? 'unknown'}`}`
|
|
1063
1063
|
);
|
|
@@ -1068,7 +1068,7 @@ export function prefetchExactNpxRestore(config, env = process.env, options = {})
|
|
|
1068
1068
|
.filter(Boolean);
|
|
1069
1069
|
if (!reportedVersions.includes(version)) {
|
|
1070
1070
|
throw new Error(
|
|
1071
|
-
`
|
|
1071
|
+
`VuvoDesk exact restore preflight returned an unexpected version for livedesk@${version}; `
|
|
1072
1072
|
+ `the existing Client was left running. Output: ${output || '(empty)'}`
|
|
1073
1073
|
);
|
|
1074
1074
|
}
|
|
@@ -1147,7 +1147,7 @@ function hasLegacyConnectedOutput(outputPath, deviceId) {
|
|
|
1147
1147
|
const escapedDeviceId = String(deviceId || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1148
1148
|
if (!escapedDeviceId) return false;
|
|
1149
1149
|
return new RegExp(
|
|
1150
|
-
`Connected to
|
|
1150
|
+
`Connected to VuvoDesk Hub as [^\\r\\n]*\\(${escapedDeviceId}\\)(?:\\s+via\\s+\\S+)?`
|
|
1151
1151
|
).test(output);
|
|
1152
1152
|
}
|
|
1153
1153
|
|
|
@@ -1246,7 +1246,7 @@ async function terminateReplacementTree(child) {
|
|
|
1246
1246
|
}
|
|
1247
1247
|
const result = await drainOwnedWindowsAgentTreeUntilStopped(child, {
|
|
1248
1248
|
reportTerminationFailure: message => console.error(
|
|
1249
|
-
`[
|
|
1249
|
+
`[VuvoDesk update] ${message}`
|
|
1250
1250
|
)
|
|
1251
1251
|
});
|
|
1252
1252
|
if (result?.ok === false) {
|
|
@@ -228,7 +228,7 @@ export function createRoleBootstrapServer(options = {}) {
|
|
|
228
228
|
const filePath = resolve(webDist, requested);
|
|
229
229
|
if (!filePath.startsWith(`${webDist}${process.platform === 'win32' ? '\\' : '/'}`) || !existsSync(filePath)) {
|
|
230
230
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
231
|
-
res.end('
|
|
231
|
+
res.end('VuvoDesk bootstrap web build is not available. Run npm run build.');
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
234
234
|
res.writeHead(200, { 'Content-Type': requested.endsWith('.js') ? 'text/javascript; charset=utf-8' : requested.endsWith('.css') ? 'text/css; charset=utf-8' : 'text/html; charset=utf-8', 'Cache-Control': requested === 'index.html' ? 'no-store' : 'public, max-age=3600' });
|
|
@@ -253,7 +253,7 @@ export function createRoleBootstrapServer(options = {}) {
|
|
|
253
253
|
return;
|
|
254
254
|
}
|
|
255
255
|
if (url.pathname === '/api/health') {
|
|
256
|
-
sendJson(req, res, 200, { ok: true, product: 'LiveDesk', role: 'bootstrap' }, port);
|
|
256
|
+
sendJson(req, res, 200, { ok: true, product: 'LiveDesk', role: 'bootstrap' }, port);
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
259
|
if (url.pathname === '/api/bootstrap/status' || url.pathname === '/api/runtime/status' || url.pathname === '/api/runtime') {
|
|
@@ -48,14 +48,14 @@ function releaseOwnedLock(path, payload) {
|
|
|
48
48
|
function updateOwnedLockRole(path, payload, nextRole) {
|
|
49
49
|
const role = String(nextRole || '').trim().toLowerCase();
|
|
50
50
|
if (!['hub', 'client'].includes(role)) {
|
|
51
|
-
throw new Error(`Unsupported
|
|
51
|
+
throw new Error(`Unsupported VuvoDesk runtime-lock role: ${role || '<empty>'}.`);
|
|
52
52
|
}
|
|
53
53
|
const current = readLock(path);
|
|
54
54
|
if (Number(current?.pid) !== Number(payload?.pid)
|
|
55
55
|
|| current?.ownerToken !== payload?.ownerToken
|
|
56
56
|
|| current?.ownerInstanceMarker !== payload?.ownerInstanceMarker
|
|
57
57
|
|| current?.ownerStartOrder !== payload?.ownerStartOrder) {
|
|
58
|
-
throw new Error('
|
|
58
|
+
throw new Error('VuvoDesk runtime-lock ownership changed before its role could be updated.');
|
|
59
59
|
}
|
|
60
60
|
if (current.role === role) {
|
|
61
61
|
payload.role = role;
|
|
@@ -80,7 +80,7 @@ function updateOwnedLockRole(path, payload, nextRole) {
|
|
|
80
80
|
|| revalidated?.ownerInstanceMarker !== payload.ownerInstanceMarker
|
|
81
81
|
|| revalidated?.ownerStartOrder !== payload.ownerStartOrder
|
|
82
82
|
|| revalidated?.role !== current.role) {
|
|
83
|
-
throw new Error('
|
|
83
|
+
throw new Error('VuvoDesk runtime-lock ownership changed during its role update.');
|
|
84
84
|
}
|
|
85
85
|
// The path remains continuously present: contenders observe either the old
|
|
86
86
|
// live role or the new live role, never a missing-lock acquisition window.
|
|
@@ -278,7 +278,7 @@ export function acquireRuntimeLock({
|
|
|
278
278
|
acquired: false,
|
|
279
279
|
path,
|
|
280
280
|
existing: existing || existingTakeover,
|
|
281
|
-
error: new Error('
|
|
281
|
+
error: new Error('VuvoDesk runtime lock takeover is already in progress.')
|
|
282
282
|
};
|
|
283
283
|
}
|
|
284
284
|
}
|
|
@@ -154,15 +154,15 @@ export function startRuntimeShutdownWatcher({
|
|
|
154
154
|
pollMs = DEFAULT_RUNTIME_SHUTDOWN_POLL_MS,
|
|
155
155
|
now = () => Date.now(),
|
|
156
156
|
reportError = error => console.warn(
|
|
157
|
-
`[
|
|
157
|
+
`[VuvoDesk] Cooperative shutdown mailbox warning: ${error?.message || error}`
|
|
158
158
|
)
|
|
159
159
|
} = {}) {
|
|
160
160
|
const exactOwner = normalizeOwner(owner);
|
|
161
161
|
if (!exactOwner) {
|
|
162
|
-
throw new Error('A cooperative
|
|
162
|
+
throw new Error('A cooperative VuvoDesk shutdown watcher requires an exact runtime-lock owner.');
|
|
163
163
|
}
|
|
164
164
|
if (typeof onShutdown !== 'function') {
|
|
165
|
-
throw new Error('A cooperative
|
|
165
|
+
throw new Error('A cooperative VuvoDesk shutdown watcher requires an onShutdown handler.');
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
const paths = getRuntimeShutdownPaths(stateDir);
|
|
@@ -132,7 +132,7 @@ export function sendUpdateHostRequest({
|
|
|
132
132
|
const target = String(endpoint || '').trim();
|
|
133
133
|
const secret = String(token || '').trim();
|
|
134
134
|
if (!target || !/^[a-f0-9]{64}$/i.test(secret)) {
|
|
135
|
-
return Promise.reject(new Error('
|
|
135
|
+
return Promise.reject(new Error('VuvoDesk Update Host endpoint or token is unavailable.'));
|
|
136
136
|
}
|
|
137
137
|
const request = JSON.stringify({
|
|
138
138
|
protocolVersion: UPDATE_HOST_PROTOCOL_VERSION,
|
|
@@ -140,7 +140,7 @@ export function sendUpdateHostRequest({
|
|
|
140
140
|
...message
|
|
141
141
|
});
|
|
142
142
|
if (Buffer.byteLength(request, 'utf8') > UPDATE_HOST_MAX_MESSAGE_BYTES) {
|
|
143
|
-
return Promise.reject(new Error('
|
|
143
|
+
return Promise.reject(new Error('VuvoDesk Update Host request exceeded its bounded message size.'));
|
|
144
144
|
}
|
|
145
145
|
const boundedTimeoutMs = clampMilliseconds(
|
|
146
146
|
timeoutMs,
|
|
@@ -163,13 +163,13 @@ export function sendUpdateHostRequest({
|
|
|
163
163
|
else resolveRequest(value);
|
|
164
164
|
};
|
|
165
165
|
const timeout = setTimeout(() => {
|
|
166
|
-
finish(new Error(`
|
|
166
|
+
finish(new Error(`VuvoDesk Update Host request timed out after ${boundedTimeoutMs}ms.`));
|
|
167
167
|
}, boundedTimeoutMs);
|
|
168
168
|
socket.once('connect', () => socket.write(`${request}\n`));
|
|
169
169
|
socket.on('data', chunk => {
|
|
170
170
|
responseText += chunk;
|
|
171
171
|
if (Buffer.byteLength(responseText, 'utf8') > UPDATE_HOST_MAX_MESSAGE_BYTES) {
|
|
172
|
-
finish(new Error('
|
|
172
|
+
finish(new Error('VuvoDesk Update Host response exceeded its bounded message size.'));
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
socket.once('error', error => finish(error));
|
|
@@ -178,7 +178,7 @@ export function sendUpdateHostRequest({
|
|
|
178
178
|
try {
|
|
179
179
|
const response = JSON.parse(responseText || '{}');
|
|
180
180
|
if (response?.ok === false) {
|
|
181
|
-
const error = new Error(String(response.error || '
|
|
181
|
+
const error = new Error(String(response.error || 'VuvoDesk Update Host rejected the request.'));
|
|
182
182
|
error.code = String(response.code || 'LIVEDESK_UPDATE_HOST_REJECTED');
|
|
183
183
|
error.response = response;
|
|
184
184
|
finish(error);
|
|
@@ -186,7 +186,7 @@ export function sendUpdateHostRequest({
|
|
|
186
186
|
}
|
|
187
187
|
finish(null, response);
|
|
188
188
|
} catch (error) {
|
|
189
|
-
finish(new Error(`
|
|
189
|
+
finish(new Error(`VuvoDesk Update Host returned invalid JSON: ${error.message}`));
|
|
190
190
|
}
|
|
191
191
|
});
|
|
192
192
|
});
|
|
@@ -309,7 +309,7 @@ export async function ensureUpdateHost({
|
|
|
309
309
|
return {
|
|
310
310
|
available: false,
|
|
311
311
|
disabled: false,
|
|
312
|
-
error: `
|
|
312
|
+
error: `VuvoDesk Update Host entry is unavailable: ${entry || '<empty>'}`,
|
|
313
313
|
paths
|
|
314
314
|
};
|
|
315
315
|
}
|
|
@@ -360,7 +360,7 @@ export async function ensureUpdateHost({
|
|
|
360
360
|
return {
|
|
361
361
|
available: false,
|
|
362
362
|
disabled: false,
|
|
363
|
-
error: `
|
|
363
|
+
error: `VuvoDesk Update Host did not become ready within ${boundedStartTimeoutMs}ms.`,
|
|
364
364
|
paths,
|
|
365
365
|
spawnedPid: Number(child?.pid || 0),
|
|
366
366
|
observedStatus: lastStatus
|
|
@@ -396,7 +396,7 @@ function currentHostConnection(hostResult) {
|
|
|
396
396
|
|
|
397
397
|
export async function submitUpdateHostJob(hostResult, job, options = {}) {
|
|
398
398
|
if (!hostResult?.available) {
|
|
399
|
-
throw new Error(hostResult?.error || '
|
|
399
|
+
throw new Error(hostResult?.error || 'VuvoDesk Update Host is unavailable.');
|
|
400
400
|
}
|
|
401
401
|
const connection = currentHostConnection(hostResult);
|
|
402
402
|
return sendUpdateHostRequest({
|
|
@@ -37,7 +37,7 @@ function decodeStateDir() {
|
|
|
37
37
|
const sourceVersion = argumentValue('--source-version');
|
|
38
38
|
const replacePid = Number(argumentValue('--replace-pid') || 0);
|
|
39
39
|
const stateDir = decodeStateDir();
|
|
40
|
-
if (!stateDir) throw new Error('
|
|
40
|
+
if (!stateDir) throw new Error('VuvoDesk Update Host requires a state directory.');
|
|
41
41
|
const paths = getUpdateHostPaths(stateDir);
|
|
42
42
|
const workingDirectory = prepareUpdateHostWorkingDirectory(paths.stateDir);
|
|
43
43
|
process.chdir(workingDirectory);
|
|
@@ -396,7 +396,7 @@ function startJob(value) {
|
|
|
396
396
|
const validatedJob = validateJob(value);
|
|
397
397
|
if (currentJob && ['accepted', 'running'].includes(currentJob.state)) {
|
|
398
398
|
if (currentJob.jobId === validatedJob.jobId && currentJob.operationId === validatedJob.operationId) return currentJob;
|
|
399
|
-
const error = new Error(`
|
|
399
|
+
const error = new Error(`VuvoDesk Update Host is busy with ${currentJob.operationId}.`);
|
|
400
400
|
error.code = 'LIVEDESK_UPDATE_HOST_BUSY';
|
|
401
401
|
throw error;
|
|
402
402
|
}
|
|
@@ -458,11 +458,11 @@ function send(socket, payload) {
|
|
|
458
458
|
async function handleMessage(socket, message) {
|
|
459
459
|
requestCount += 1;
|
|
460
460
|
if (message?.protocolVersion !== UPDATE_HOST_PROTOCOL_VERSION) {
|
|
461
|
-
send(socket, { ok: false, code: 'LIVEDESK_UPDATE_HOST_PROTOCOL_MISMATCH', error: '
|
|
461
|
+
send(socket, { ok: false, code: 'LIVEDESK_UPDATE_HOST_PROTOCOL_MISMATCH', error: 'VuvoDesk Update Host protocol mismatch.' });
|
|
462
462
|
return;
|
|
463
463
|
}
|
|
464
464
|
if (!tokensMatch(message?.token)) {
|
|
465
|
-
send(socket, { ok: false, code: 'LIVEDESK_UPDATE_HOST_UNAUTHORIZED', error: '
|
|
465
|
+
send(socket, { ok: false, code: 'LIVEDESK_UPDATE_HOST_UNAUTHORIZED', error: 'VuvoDesk Update Host token mismatch.' });
|
|
466
466
|
return;
|
|
467
467
|
}
|
|
468
468
|
switch (String(message?.type || '')) {
|
|
@@ -498,7 +498,7 @@ async function handleMessage(socket, message) {
|
|
|
498
498
|
}
|
|
499
499
|
case 'stop-host': {
|
|
500
500
|
if (currentJob && ['accepted', 'running'].includes(currentJob.state)) {
|
|
501
|
-
send(socket, { ok: false, code: 'LIVEDESK_UPDATE_HOST_BUSY', error: '
|
|
501
|
+
send(socket, { ok: false, code: 'LIVEDESK_UPDATE_HOST_BUSY', error: 'VuvoDesk Update Host cannot stop during an update.' });
|
|
502
502
|
return;
|
|
503
503
|
}
|
|
504
504
|
stopping = true;
|
|
@@ -508,7 +508,7 @@ async function handleMessage(socket, message) {
|
|
|
508
508
|
return;
|
|
509
509
|
}
|
|
510
510
|
default:
|
|
511
|
-
send(socket, { ok: false, code: 'LIVEDESK_UPDATE_HOST_UNKNOWN_REQUEST', error: 'Unknown
|
|
511
|
+
send(socket, { ok: false, code: 'LIVEDESK_UPDATE_HOST_UNKNOWN_REQUEST', error: 'Unknown VuvoDesk Update Host request.' });
|
|
512
512
|
}
|
|
513
513
|
}
|
|
514
514
|
|
package/client/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# VuvoDesk Client
|
|
2
2
|
|
|
3
|
-
For normal users, run the unified
|
|
3
|
+
For normal users, run the unified VuvoDesk application:
|
|
4
4
|
|
|
5
5
|
```powershell
|
|
6
6
|
npx -y --prefer-online livedesk@latest
|
|
@@ -20,7 +20,7 @@ aligned.
|
|
|
20
20
|
The default flow lets the local Client runtime own Google authorization,
|
|
21
21
|
callback exchange, and saved authentication as one path. The browser only
|
|
22
22
|
shows progress; it does not maintain a second session that must be transferred
|
|
23
|
-
back to the Client.
|
|
23
|
+
back to the Client. VuvoDesk then waits for the active Hub published by the
|
|
24
24
|
same account and connects locally. If the Hub is not
|
|
25
25
|
ready yet, the client waits for a Hub-online event and polls the registry at
|
|
26
26
|
most every six seconds as a fallback. Missing a wake event can never leave a
|
|
@@ -34,10 +34,10 @@ On Windows, check **Start with Windows** on the connection page to reconnect thi
|
|
|
34
34
|
client automatically after reboot. The startup entry reuses the saved Google
|
|
35
35
|
session and only opens the connection page again if sign-in is needed.
|
|
36
36
|
|
|
37
|
-
On the Hub computer, open the
|
|
37
|
+
On the Hub computer, open the VuvoDesk dashboard, sign in, and run **Sync
|
|
38
38
|
Server** once. The dashboard transfers the signed-in session to the local Hub;
|
|
39
39
|
the Hub process then keeps its own local address and private pair token
|
|
40
|
-
refreshed in the
|
|
40
|
+
refreshed in the VuvoDesk Supabase registry. The dashboard can be closed after
|
|
41
41
|
that while the Hub remains running.
|
|
42
42
|
|
|
43
43
|
Supabase Auth must allow the CLI callback URL:
|
|
@@ -55,12 +55,12 @@ can receive safe task-only instructions. The C# RemoteFast engine also supports
|
|
|
55
55
|
keyboard/mouse control on Windows, macOS, and Linux/X11, file transfer, and
|
|
56
56
|
system-audio playback from Windows, macOS, and Linux. Linux captures the default
|
|
57
57
|
output through PipeWire `pw-record` or PulseAudio `parec`. macOS control requires Accessibility
|
|
58
|
-
permission for the terminal app that started
|
|
59
|
-
system-audio capture require Screen Recording permission.
|
|
58
|
+
permission for the terminal app that started VuvoDesk; screen streaming and
|
|
59
|
+
system-audio capture require Screen Recording permission. VuvoDesk checks these
|
|
60
60
|
permissions without opening repeated system dialogs and prints the matching
|
|
61
61
|
System Settings path when access is missing; after granting access, restart the
|
|
62
62
|
client. When file transfer is enabled by the Hub, received files are saved
|
|
63
|
-
to `Desktop/
|
|
63
|
+
to `Desktop/VuvoDeskFiles` unless the Hub sets another destination folder.
|
|
64
64
|
|
|
65
65
|
By default, the launcher uses the packaged C# RemoteFast engine when supported.
|
|
66
66
|
It falls back to the Node engine only when a compatible RemoteFast runtime is
|
|
@@ -80,7 +80,7 @@ npx -y --prefer-online livedesk@latest client 3
|
|
|
80
80
|
npx -y --prefer-online livedesk@latest client --no-thumbnail
|
|
81
81
|
npx -y --prefer-online livedesk@latest client --no-live
|
|
82
82
|
npx -y --prefer-online livedesk@latest client --no-audio
|
|
83
|
-
npx -y --prefer-online livedesk@latest client --files-dir "D:\
|
|
83
|
+
npx -y --prefer-online livedesk@latest client --files-dir "D:\VuvoDeskFiles"
|
|
84
84
|
npx -y --prefer-online livedesk@latest client --engine node
|
|
85
85
|
npx -y --prefer-online livedesk@latest client --engine fast --trace-frames
|
|
86
86
|
npx -y --prefer-online livedesk@latest client --auth-port 5200
|
|
@@ -91,7 +91,7 @@ Frame pipeline roadmap:
|
|
|
91
91
|
|
|
92
92
|
- Mode 1: `mode1-jpeg` - current test path using screen capture, resize, and JPEG binary frames.
|
|
93
93
|
- Mode 2: `mode2-lzo` - cross-platform wall path using independent RGB565LE frames capped at 320x180 and compressed as LZO1X blocks. The wall defaults to 8 fps. macOS and Linux keep one persistent local capture helper per client process. Legacy `mode2-lz4` settings migrate to this mode.
|
|
94
|
-
- Mode 3: `mode3-h264-hw` - OS-specific H.264 path. Windows tries Media Foundation/NVENC/QSV/AMF, macOS prefers ScreenCaptureKit plus VideoToolbox, and Linux tries NVENC/VAAPI/QSV before falling back to the bundled ffmpeg `libx264` encoder. Linux users do not need to install ffmpeg separately when starting
|
|
94
|
+
- Mode 3: `mode3-h264-hw` - OS-specific H.264 path. Windows tries Media Foundation/NVENC/QSV/AMF, macOS prefers ScreenCaptureKit plus VideoToolbox, and Linux tries NVENC/VAAPI/QSV before falling back to the bundled ffmpeg `libx264` encoder. Linux users do not need to install ffmpeg separately when starting VuvoDesk through the published npm package. macOS emits one startup key frame and then an approximately half-second recovery GOP. The launcher uses bundled ffmpeg for fallback paths unless `LIVEDESK_FFMPEG` points to a custom binary. On macOS, set `LIVEDESK_FFMPEG_AVFOUNDATION_INPUT` only when the ScreenCaptureKit helper is unavailable and the AVFoundation fallback input is not `1:none`.
|
|
95
95
|
|
|
96
96
|
Legacy mode names such as `remote-fast` and `remote-quality` are treated as
|
|
97
97
|
Mode 1 aliases.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## lzokay
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
VuvoDesk's LZO1X block encoder and decoder follow the instruction layout of
|
|
6
6
|
[lzokay](https://github.com/AxioDL/lzokay), Copyright (c) 2018 Jack Andersen.
|
|
7
7
|
|
|
8
8
|
The MIT License
|
|
@@ -19,5 +19,5 @@ export function formatClientVersionBanner(env = process.env) {
|
|
|
19
19
|
const launcherVersion = String(env.LIVEDESK_NPM_LAUNCHER_VERSION || '').trim();
|
|
20
20
|
const productName = launcherName || 'livedesk';
|
|
21
21
|
const productVersion = launcherVersion || readClientPackageVersion();
|
|
22
|
-
return `[
|
|
22
|
+
return `[VuvoDesk Client] ${productName}@${productVersion}`;
|
|
23
23
|
}
|