livedesk 0.1.448 → 0.1.450
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/bootstrap/legacy-client-update.js +1 -7
- package/client/bin/livedesk-client-node.js +66 -187
- package/client/bin/livedesk-client.js +383 -136
- package/client/package.json +5 -6
- package/hub/package.json +1 -1
- package/hub/src/agents/agent-manager.js +0 -9
- package/hub/src/agents/agent-settings.js +4 -34
- package/hub/src/agents/codex-agent-runtime.js +12 -14
- package/hub/src/remote-hub.js +19 -6
- package/hub/src/server.js +13 -11
- package/hub/src/transport/udp-hub-transport.js +22 -22
- package/hub/src/transport/udp-protocol.js +51 -26
- package/package.json +8 -9
- package/web/dist/assets/icons-CCS0Avce.js +1 -0
- package/web/dist/assets/index-vBxXkd_R.js +25 -0
- package/web/dist/assets/{react-BOnb-QRJ.js → react-Cg5haagh.js} +1 -1
- package/web/dist/index.html +3 -3
- package/web/dist/assets/icons-CTHUbfAT.js +0 -1
- package/web/dist/assets/index-DKPdDVYW.js +0 -25
|
@@ -39,6 +39,9 @@ const DEFAULT_RELAY_PORT = 5199;
|
|
|
39
39
|
const DEFAULT_AUTH_CALLBACK_HOST = '127.0.0.1';
|
|
40
40
|
const DEFAULT_AUTH_CALLBACK_PORT = 5179;
|
|
41
41
|
const ENDPOINT_PROBE_TIMEOUT_MS = 900;
|
|
42
|
+
const FRESH_REGISTRY_TIMEOUT_MS = 4000;
|
|
43
|
+
const FRESH_REGISTRY_TIMEOUT_MIN_MS = 25;
|
|
44
|
+
const FRESH_REGISTRY_TIMEOUT_MAX_MS = 10000;
|
|
42
45
|
// Hub-online delivery is an optimization, not a correctness dependency. A
|
|
43
46
|
// browser, proxy, or suspended network can miss the wake event, so registry
|
|
44
47
|
// polling must stay interactive instead of backing off to multi-minute waits.
|
|
@@ -49,6 +52,7 @@ const EXIT_INVALID_PAIR_TOKEN = 23;
|
|
|
49
52
|
const EXIT_CLIENT_UPDATE = 42;
|
|
50
53
|
const SESSION_REFRESH_SKEW_SECONDS = 60;
|
|
51
54
|
const HUB_TARGET_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
55
|
+
const HUB_TARGET_CACHE_FUTURE_SKEW_MS = 5 * 60 * 1000;
|
|
52
56
|
const WINDOWS_STARTUP_SCRIPT_NAME = 'LiveDesk Desktop.vbs';
|
|
53
57
|
const WINDOWS_STARTUP_LEGACY_SCRIPT_NAME = 'LiveDesk Client.vbs';
|
|
54
58
|
const WINDOWS_STARTUP_LEGACY_CMD_NAME = 'LiveDesk Client.cmd';
|
|
@@ -67,6 +71,11 @@ const FAST_PREFLIGHT_CACHE_PATH = join(UNIFIED_CLIENT_STATE_DIR, 'fast-preflight
|
|
|
67
71
|
const CLIENT_SLOT_PATH = join(UNIFIED_CLIENT_STATE_DIR, 'device-slot.json');
|
|
68
72
|
const CLIENT_UPDATE_STATE_PATH = process.env.LIVEDESK_CLIENT_UPDATE_STATE_PATH
|
|
69
73
|
|| join(UNIFIED_CLIENT_STATE_DIR, 'client-update.json');
|
|
74
|
+
// Migration-only compatibility: releases before 0.1.204 could persist these
|
|
75
|
+
// Client-side model options in startup/update commands. Consume them without
|
|
76
|
+
// forwarding so an upgrade starts normally, but never restores that feature.
|
|
77
|
+
const RETIRED_CLIENT_MODEL_SINGLE_OPTIONS = new Set(['--ai', '--no-ai', '--fake-ai']);
|
|
78
|
+
const RETIRED_CLIENT_MODEL_VALUE_OPTION = '--ai-model';
|
|
70
79
|
let activeAgentProcess = null;
|
|
71
80
|
let roleRestartRequest = null;
|
|
72
81
|
let agentRestartRequest = null;
|
|
@@ -301,8 +310,8 @@ Options:
|
|
|
301
310
|
--version Show the agent version.
|
|
302
311
|
--help Show this help.
|
|
303
312
|
|
|
304
|
-
Auto uses C# RemoteFast when supported and falls back to Node
|
|
305
|
-
|
|
313
|
+
Auto uses C# RemoteFast when supported and falls back to Node only when a
|
|
314
|
+
packaged RemoteFast runtime is unavailable.
|
|
306
315
|
Enable Windows auto-start from the connection page when this client signs in.
|
|
307
316
|
`.trimStart());
|
|
308
317
|
}
|
|
@@ -394,15 +403,30 @@ export function parseLauncherArgs(argv) {
|
|
|
394
403
|
let slot = process.env.LIVEDESK_CLIENT_SLOT || process.env.MINDEXEC_REMOTE_SLOT || '';
|
|
395
404
|
let authPort = normalizePort(process.env.LIVEDESK_CLIENT_RUNTIME_PORT || process.env.LIVEDESK_CLIENT_AUTH_PORT) || DEFAULT_AUTH_CALLBACK_PORT;
|
|
396
405
|
let command = 'connect';
|
|
397
|
-
let
|
|
398
|
-
let fakeThumbnail = false;
|
|
406
|
+
let fakeThumbnail = false;
|
|
399
407
|
let startupRun = isTruthy(process.env.LIVEDESK_ROLE_TRANSITION);
|
|
400
|
-
let openBrowserOnStart = !isTruthy(process.env.LIVEDESK_SKIP_BROWSER_OPEN);
|
|
401
|
-
let updateWaitPid = 0;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
408
|
+
let openBrowserOnStart = !isTruthy(process.env.LIVEDESK_SKIP_BROWSER_OPEN);
|
|
409
|
+
let updateWaitPid = 0;
|
|
410
|
+
let retiredModelOptionsDiscarded = 0;
|
|
411
|
+
|
|
412
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
413
|
+
const arg = argv[index];
|
|
414
|
+
if (RETIRED_CLIENT_MODEL_SINGLE_OPTIONS.has(arg)) {
|
|
415
|
+
retiredModelOptionsDiscarded += 1;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
if (arg === RETIRED_CLIENT_MODEL_VALUE_OPTION) {
|
|
419
|
+
retiredModelOptionsDiscarded += 1;
|
|
420
|
+
if (index + 1 < argv.length && !String(argv[index + 1] || '').startsWith('-')) {
|
|
421
|
+
index += 1;
|
|
422
|
+
}
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (String(arg || '').startsWith(`${RETIRED_CLIENT_MODEL_VALUE_OPTION}=`)) {
|
|
426
|
+
retiredModelOptionsDiscarded += 1;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (arg && !arg.startsWith('-')) {
|
|
406
430
|
const lowerArg = arg.toLowerCase();
|
|
407
431
|
const shortcutSlot = normalizeSlotNumber(arg);
|
|
408
432
|
if (lowerArg === 'connect') {
|
|
@@ -512,11 +536,8 @@ export function parseLauncherArgs(argv) {
|
|
|
512
536
|
if (arg === '--version') {
|
|
513
537
|
version = true;
|
|
514
538
|
}
|
|
515
|
-
if (arg === '--
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
if (arg === '--fake-thumbnail') {
|
|
519
|
-
fakeThumbnail = true;
|
|
539
|
+
if (arg === '--fake-thumbnail') {
|
|
540
|
+
fakeThumbnail = true;
|
|
520
541
|
}
|
|
521
542
|
forwarded.push(arg);
|
|
522
543
|
}
|
|
@@ -539,13 +560,13 @@ export function parseLauncherArgs(argv) {
|
|
|
539
560
|
deviceId,
|
|
540
561
|
slot,
|
|
541
562
|
authPort,
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
};
|
|
548
|
-
}
|
|
563
|
+
fakeThumbnail,
|
|
564
|
+
startupRun,
|
|
565
|
+
openBrowserOnStart,
|
|
566
|
+
updateWaitPid,
|
|
567
|
+
retiredModelOptionsDiscarded
|
|
568
|
+
};
|
|
569
|
+
}
|
|
549
570
|
|
|
550
571
|
function normalizePort(value) {
|
|
551
572
|
const port = Number(String(value || '').trim());
|
|
@@ -854,13 +875,15 @@ const CLIENT_UPDATE_SINGLE_FLAGS = new Set([
|
|
|
854
875
|
'--no-open',
|
|
855
876
|
'--exit-on-disconnect',
|
|
856
877
|
'--exit-on-invalid-pair',
|
|
857
|
-
'--once'
|
|
878
|
+
'--once',
|
|
879
|
+
...RETIRED_CLIENT_MODEL_SINGLE_OPTIONS
|
|
858
880
|
]);
|
|
859
881
|
const CLIENT_UPDATE_VALUE_FLAGS = new Set([
|
|
860
882
|
'--update-wait-pid',
|
|
861
883
|
// Pairing credentials stay in the inherited private environment and must
|
|
862
884
|
// never be copied onto a replacement process command line.
|
|
863
|
-
'--pair'
|
|
885
|
+
'--pair',
|
|
886
|
+
RETIRED_CLIENT_MODEL_VALUE_OPTION
|
|
864
887
|
]);
|
|
865
888
|
|
|
866
889
|
export function buildClientUpdateRestartArgs(prepared = {}) {
|
|
@@ -874,6 +897,9 @@ export function buildClientUpdateRestartArgs(prepared = {}) {
|
|
|
874
897
|
if (arg.startsWith('--pair=')) {
|
|
875
898
|
continue;
|
|
876
899
|
}
|
|
900
|
+
if (arg.startsWith(`${RETIRED_CLIENT_MODEL_VALUE_OPTION}=`)) {
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
877
903
|
if (CLIENT_UPDATE_VALUE_FLAGS.has(arg)) {
|
|
878
904
|
index += 1;
|
|
879
905
|
continue;
|
|
@@ -945,7 +971,9 @@ export function normalizeCachedHubTarget(value, expectedOwnerKey, now = Date.now
|
|
|
945
971
|
return null;
|
|
946
972
|
}
|
|
947
973
|
const updatedAt = Date.parse(String(cached.updatedAt || ''));
|
|
948
|
-
if (!Number.isFinite(updatedAt)
|
|
974
|
+
if (!Number.isFinite(updatedAt)
|
|
975
|
+
|| updatedAt > now + HUB_TARGET_CACHE_FUTURE_SKEW_MS
|
|
976
|
+
|| now - updatedAt > HUB_TARGET_CACHE_MAX_AGE_MS) {
|
|
949
977
|
return null;
|
|
950
978
|
}
|
|
951
979
|
const pair = String(cached.pair || '').trim();
|
|
@@ -1109,13 +1137,63 @@ function isTransientNetworkError(error) {
|
|
|
1109
1137
|
return /fetch failed|network|dns|socket|connection|timeout|getaddrinfo/i.test(getNestedErrorMessage(error));
|
|
1110
1138
|
}
|
|
1111
1139
|
|
|
1112
|
-
function formatDiscoveryError(error) {
|
|
1113
|
-
if (isTransientNetworkError(error)) {
|
|
1114
|
-
const code = getNestedErrorCode(error);
|
|
1115
|
-
return `Network is not ready yet${code ? ` (${code})` : ''}. Waiting for DNS/Wi-Fi after sleep.`;
|
|
1116
|
-
}
|
|
1117
|
-
return error instanceof Error ? error.message : String(error);
|
|
1118
|
-
}
|
|
1140
|
+
function formatDiscoveryError(error) {
|
|
1141
|
+
if (isTransientNetworkError(error)) {
|
|
1142
|
+
const code = getNestedErrorCode(error);
|
|
1143
|
+
return `Network is not ready yet${code ? ` (${code})` : ''}. Waiting for DNS/Wi-Fi after sleep.`;
|
|
1144
|
+
}
|
|
1145
|
+
return error instanceof Error ? error.message : String(error);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function createHubDiscoveryError(code, message, cause = null) {
|
|
1149
|
+
const error = new Error(message);
|
|
1150
|
+
error.code = String(code || '').trim();
|
|
1151
|
+
if (cause) {
|
|
1152
|
+
error.cause = cause;
|
|
1153
|
+
}
|
|
1154
|
+
return error;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
export function canUseCachedRelayAfterRegistryError(error) {
|
|
1158
|
+
const code = getNestedErrorCode(error).toLowerCase();
|
|
1159
|
+
if ([
|
|
1160
|
+
'hub-registry-missing',
|
|
1161
|
+
'hub-registry-expired',
|
|
1162
|
+
'hub-registry-timeout',
|
|
1163
|
+
'pin-not-found',
|
|
1164
|
+
'pin-expired',
|
|
1165
|
+
'resolver-unavailable'
|
|
1166
|
+
].includes(code)) {
|
|
1167
|
+
return true;
|
|
1168
|
+
}
|
|
1169
|
+
// Semantic registry failures must never be reclassified as network errors
|
|
1170
|
+
// merely because their message mentions a "connection token".
|
|
1171
|
+
if (code.startsWith('hub-registry-')
|
|
1172
|
+
|| code.startsWith('pin-registry-')
|
|
1173
|
+
|| ['invalid-pin', 'rate-limited', 'request-too-large'].includes(code)) {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
return isTransientNetworkError(error);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
async function normalizePinResolverInvocationError(error) {
|
|
1180
|
+
if (isTransientNetworkError(error)) {
|
|
1181
|
+
return error;
|
|
1182
|
+
}
|
|
1183
|
+
const response = error?.context;
|
|
1184
|
+
const status = Number(response?.status || 0);
|
|
1185
|
+
let reason = '';
|
|
1186
|
+
try {
|
|
1187
|
+
const payload = await response?.clone?.().json?.();
|
|
1188
|
+
reason = String(payload?.reason || '').trim().toLowerCase();
|
|
1189
|
+
} catch {
|
|
1190
|
+
// HTTP status still gives a conservative semantic classification below.
|
|
1191
|
+
}
|
|
1192
|
+
if (!reason && status === 404) reason = 'pin-not-found';
|
|
1193
|
+
if (!reason && status === 503) reason = 'resolver-unavailable';
|
|
1194
|
+
if (!reason) return error;
|
|
1195
|
+
return createHubDiscoveryError(reason, reason, error);
|
|
1196
|
+
}
|
|
1119
1197
|
|
|
1120
1198
|
async function recoverRotatedSession(supabase, previousSession) {
|
|
1121
1199
|
for (const delayMs of [100, 250, 500]) {
|
|
@@ -2618,54 +2696,72 @@ function readRequestBody(req, maxBytes = 4096) {
|
|
|
2618
2696
|
});
|
|
2619
2697
|
}
|
|
2620
2698
|
|
|
2621
|
-
async function resolveManagerFromPin(supabase, pin, options = {}) {
|
|
2699
|
+
export async function resolveManagerFromPin(supabase, pin, options = {}) {
|
|
2622
2700
|
const normalizedPin = normalizePairingPin(pin);
|
|
2623
2701
|
if (!normalizedPin) {
|
|
2624
2702
|
throw new Error('Enter a 6-digit LiveDesk PIN.');
|
|
2625
2703
|
}
|
|
2626
2704
|
const cacheOwnerKey = hubCacheOwnerForPin(normalizedPin);
|
|
2627
|
-
const
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2705
|
+
const resolved = await resolveManagerWithCachedRecovery(cacheOwnerKey, {
|
|
2706
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
2707
|
+
relayEndpoint: options.relayEndpoint,
|
|
2708
|
+
probeEndpoint: options.probeEndpoint,
|
|
2709
|
+
resolveFreshTarget: async () => {
|
|
2710
|
+
const { data, error } = await supabase.functions.invoke('livedesk-resolve-remote-host', {
|
|
2711
|
+
body: { pin: normalizedPin }
|
|
2712
|
+
});
|
|
2713
|
+
if (error) {
|
|
2714
|
+
throw await normalizePinResolverInvocationError(error);
|
|
2715
|
+
}
|
|
2716
|
+
const result = Array.isArray(data) ? data[0] : data;
|
|
2717
|
+
if (!result?.ok) {
|
|
2718
|
+
const reason = String(result?.reason || 'pin-not-found').trim().toLowerCase();
|
|
2719
|
+
throw createHubDiscoveryError(reason, reason);
|
|
2720
|
+
}
|
|
2721
|
+
if (!result.pair_token) {
|
|
2722
|
+
throw createHubDiscoveryError(
|
|
2723
|
+
'pin-registry-pair-token-missing',
|
|
2724
|
+
'The PIN matched a Hub record without a private connection token.'
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2727
|
+
const endpointCandidates = normalizeEndpointCandidates(result);
|
|
2728
|
+
if (endpointCandidates.length === 0) {
|
|
2729
|
+
throw createHubDiscoveryError(
|
|
2730
|
+
'pin-registry-endpoints-missing',
|
|
2731
|
+
'The PIN matched a Hub record without a reachable address.'
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
const target = await chooseManagerConnectionTarget(endpointCandidates, {
|
|
2735
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
2736
|
+
probeEndpoint: options.probeEndpoint
|
|
2737
|
+
});
|
|
2738
|
+
if (!target) {
|
|
2739
|
+
throw createHubDiscoveryError(
|
|
2740
|
+
'pin-direct-unreachable',
|
|
2741
|
+
`No reachable LiveDesk Hub endpoint yet. Checked ${endpointCandidates.join(', ')}.`
|
|
2742
|
+
);
|
|
2743
|
+
}
|
|
2744
|
+
return {
|
|
2745
|
+
manager: target.manager,
|
|
2746
|
+
pair: result.pair_token,
|
|
2747
|
+
hubDeviceId: String(result.node_id || '').trim(),
|
|
2748
|
+
endpointCandidates,
|
|
2749
|
+
directReachable: target.directReachable,
|
|
2750
|
+
connectionTransport: target.connectionTransport,
|
|
2751
|
+
discoverySource: 'supabase'
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2652
2754
|
});
|
|
2653
|
-
if (
|
|
2654
|
-
|
|
2755
|
+
if (resolved.discoverySource === 'supabase') {
|
|
2756
|
+
writeCachedHubTarget(cacheOwnerKey, resolved);
|
|
2655
2757
|
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
endpointCandidates,
|
|
2662
|
-
directReachable: target.directReachable,
|
|
2663
|
-
connectionTransport: target.connectionTransport,
|
|
2664
|
-
discoverySource: 'supabase'
|
|
2665
|
-
};
|
|
2666
|
-
writeCachedHubTarget(cacheOwnerKey, resolved);
|
|
2758
|
+
console.log(resolved.connectionTransport === 'relay-fallback' && resolved.discoverySource === 'cache'
|
|
2759
|
+
? 'Fresh PIN registry lookup was unavailable; using the saved PIN-bound identity for the bounded encrypted relay path.'
|
|
2760
|
+
: resolved.discoverySource === 'cache'
|
|
2761
|
+
? `Fresh PIN registry lookup was unavailable; using the responding saved direct Hub endpoint ${resolved.manager}.`
|
|
2762
|
+
: `Found current PIN-bound LiveDesk Hub at ${resolved.manager}.`);
|
|
2667
2763
|
return resolved;
|
|
2668
|
-
}
|
|
2764
|
+
}
|
|
2669
2765
|
|
|
2670
2766
|
async function waitForManagerFromSavedPin(supabase, pin, options = {}) {
|
|
2671
2767
|
const configuredIntervalMs = Number(options.intervalMs || 0);
|
|
@@ -2680,7 +2776,8 @@ async function waitForManagerFromSavedPin(supabase, pin, options = {}) {
|
|
|
2680
2776
|
attempts += 1;
|
|
2681
2777
|
try {
|
|
2682
2778
|
return await resolveManagerFromPin(supabase, pin, {
|
|
2683
|
-
allowRelayFallback: options.allowRelayFallback === true
|
|
2779
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
2780
|
+
relayEndpoint: options.relayEndpoint
|
|
2684
2781
|
});
|
|
2685
2782
|
} catch (err) {
|
|
2686
2783
|
if (shouldStop()) {
|
|
@@ -2834,9 +2931,11 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
2834
2931
|
}
|
|
2835
2932
|
dashboardState.message = 'Saved LiveDesk PIN found. Waiting for the Hub to become reachable.';
|
|
2836
2933
|
try {
|
|
2837
|
-
const resolved = await waitForManagerFromSavedPin(supabase, savedPin, {
|
|
2838
|
-
|
|
2839
|
-
|
|
2934
|
+
const resolved = await waitForManagerFromSavedPin(supabase, savedPin, {
|
|
2935
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
2936
|
+
relayEndpoint: options.relayEndpoint,
|
|
2937
|
+
shouldStop: () => completed || dashboardState.loggedOut
|
|
2938
|
+
});
|
|
2840
2939
|
if (!resolved) {
|
|
2841
2940
|
return;
|
|
2842
2941
|
}
|
|
@@ -3066,7 +3165,8 @@ async function startConnectionChoiceServer(supabase, options = {}) {
|
|
|
3066
3165
|
pin = normalizePairingPin(pin);
|
|
3067
3166
|
try {
|
|
3068
3167
|
const resolved = await resolveManagerFromPin(supabase, pin, {
|
|
3069
|
-
allowRelayFallback: options.allowRelayFallback === true
|
|
3168
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
3169
|
+
relayEndpoint: options.relayEndpoint
|
|
3070
3170
|
});
|
|
3071
3171
|
writeSavedPin(pin);
|
|
3072
3172
|
applyStartupPreference(pendingStartup, startupArgs);
|
|
@@ -3229,7 +3329,8 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3229
3329
|
resolvePin: pin => {
|
|
3230
3330
|
if (!supabase) throw new Error('supabase-session-required');
|
|
3231
3331
|
return resolveManagerFromPin(supabase, pin, {
|
|
3232
|
-
allowRelayFallback: options.allowRelayFallback === true
|
|
3332
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
3333
|
+
relayEndpoint: options.relayEndpoint
|
|
3233
3334
|
});
|
|
3234
3335
|
},
|
|
3235
3336
|
changeRole: async (role, snapshot) => {
|
|
@@ -3328,7 +3429,8 @@ async function chooseClientConnection(supabase, options = {}) {
|
|
|
3328
3429
|
startupArgs: options.startupArgs,
|
|
3329
3430
|
savedSession: options.savedSession,
|
|
3330
3431
|
savedPin: options.savedPin,
|
|
3331
|
-
allowRelayFallback: options.allowRelayFallback === true
|
|
3432
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
3433
|
+
relayEndpoint: options.relayEndpoint
|
|
3332
3434
|
});
|
|
3333
3435
|
if (options.openBrowser !== false) {
|
|
3334
3436
|
console.log('Opening LiveDesk connection page...');
|
|
@@ -3506,10 +3608,16 @@ export async function chooseManagerConnectionTarget(candidates, options = {}) {
|
|
|
3506
3608
|
}
|
|
3507
3609
|
|
|
3508
3610
|
export async function resolveManagerFromCachedTarget(ownerKey, options = {}) {
|
|
3509
|
-
const cached = options
|
|
3611
|
+
const cached = Object.prototype.hasOwnProperty.call(options, 'cachedTarget')
|
|
3612
|
+
? normalizeCachedHubTarget(options.cachedTarget, ownerKey, options.now)
|
|
3613
|
+
: readCachedHubTarget(ownerKey);
|
|
3510
3614
|
if (!cached) return null;
|
|
3511
|
-
const
|
|
3512
|
-
|
|
3615
|
+
const target = await chooseManagerConnectionTarget(cached.endpointCandidates, {
|
|
3616
|
+
allowRelayFallback: false,
|
|
3617
|
+
probeEndpoint: options.probeEndpoint
|
|
3618
|
+
});
|
|
3619
|
+
if (!target) return null;
|
|
3620
|
+
const manager = target.manager;
|
|
3513
3621
|
const resolved = {
|
|
3514
3622
|
manager,
|
|
3515
3623
|
pair: cached.pair,
|
|
@@ -3518,6 +3626,8 @@ export async function resolveManagerFromCachedTarget(ownerKey, options = {}) {
|
|
|
3518
3626
|
manager,
|
|
3519
3627
|
...cached.endpointCandidates.filter(endpoint => endpoint !== manager)
|
|
3520
3628
|
],
|
|
3629
|
+
directReachable: target.directReachable,
|
|
3630
|
+
connectionTransport: target.connectionTransport,
|
|
3521
3631
|
discoverySource: 'cache'
|
|
3522
3632
|
};
|
|
3523
3633
|
if (options.persist !== false) {
|
|
@@ -3526,6 +3636,86 @@ export async function resolveManagerFromCachedTarget(ownerKey, options = {}) {
|
|
|
3526
3636
|
return resolved;
|
|
3527
3637
|
}
|
|
3528
3638
|
|
|
3639
|
+
export function resolveManagerFromCachedRelayFallback(ownerKey, options = {}) {
|
|
3640
|
+
if (options.allowRelayFallback !== true) return null;
|
|
3641
|
+
const cached = Object.prototype.hasOwnProperty.call(options, 'cachedTarget')
|
|
3642
|
+
? normalizeCachedHubTarget(options.cachedTarget, ownerKey, options.now)
|
|
3643
|
+
: readCachedHubTarget(ownerKey);
|
|
3644
|
+
if (!cached) return null;
|
|
3645
|
+
const normalizedOwner = String(cached.ownerKey || '');
|
|
3646
|
+
const accountIdentityComplete = normalizedOwner.startsWith('user:')
|
|
3647
|
+
&& normalizedOwner.length > 'user:'.length
|
|
3648
|
+
&& Boolean(cached.hubDeviceId);
|
|
3649
|
+
// The public PIN resolver intentionally does not expose node_id. Its
|
|
3650
|
+
// previous signed pair/endpoints remain bound to the exact saved six-digit
|
|
3651
|
+
// PIN owner in the private, finite-lifetime cache.
|
|
3652
|
+
const pinIdentityComplete = /^pin:[0-9]{6}$/.test(normalizedOwner);
|
|
3653
|
+
const relayEndpoint = String(options.relayEndpoint || '').trim().replace(/^tcp:\/\//i, '');
|
|
3654
|
+
if ((!accountIdentityComplete && !pinIdentityComplete) || !parseManagerEndpoint(relayEndpoint)) {
|
|
3655
|
+
return null;
|
|
3656
|
+
}
|
|
3657
|
+
return {
|
|
3658
|
+
manager: cached.endpointCandidates[0],
|
|
3659
|
+
pair: cached.pair,
|
|
3660
|
+
hubDeviceId: cached.hubDeviceId,
|
|
3661
|
+
endpointCandidates: [...cached.endpointCandidates],
|
|
3662
|
+
directReachable: false,
|
|
3663
|
+
connectionTransport: 'relay-fallback',
|
|
3664
|
+
discoverySource: 'cache'
|
|
3665
|
+
};
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
export async function resolveManagerWithCachedRecovery(ownerKey, options = {}) {
|
|
3669
|
+
const cached = Object.prototype.hasOwnProperty.call(options, 'cachedTarget')
|
|
3670
|
+
? normalizeCachedHubTarget(options.cachedTarget, ownerKey, options.now)
|
|
3671
|
+
: readCachedHubTarget(ownerKey);
|
|
3672
|
+
try {
|
|
3673
|
+
if (typeof options.resolveFreshTarget !== 'function') {
|
|
3674
|
+
throw createHubDiscoveryError('fresh-registry-resolver-required', 'Fresh Hub registry resolver is required.');
|
|
3675
|
+
}
|
|
3676
|
+
const configuredFreshTimeoutMs = Number(options.freshTimeoutMs);
|
|
3677
|
+
const freshTimeoutMs = Number.isFinite(configuredFreshTimeoutMs)
|
|
3678
|
+
? Math.max(FRESH_REGISTRY_TIMEOUT_MIN_MS, Math.min(FRESH_REGISTRY_TIMEOUT_MAX_MS, configuredFreshTimeoutMs))
|
|
3679
|
+
: FRESH_REGISTRY_TIMEOUT_MS;
|
|
3680
|
+
let timeoutHandle = null;
|
|
3681
|
+
const fresh = await Promise.race([
|
|
3682
|
+
Promise.resolve().then(() => options.resolveFreshTarget()),
|
|
3683
|
+
new Promise((_, reject) => {
|
|
3684
|
+
timeoutHandle = setTimeout(() => reject(createHubDiscoveryError(
|
|
3685
|
+
'hub-registry-timeout',
|
|
3686
|
+
`Fresh LiveDesk Hub registry lookup exceeded ${freshTimeoutMs}ms.`
|
|
3687
|
+
)), freshTimeoutMs);
|
|
3688
|
+
})
|
|
3689
|
+
]).finally(() => clearTimeout(timeoutHandle));
|
|
3690
|
+
if (!fresh?.manager || !fresh?.pair) {
|
|
3691
|
+
throw createHubDiscoveryError('fresh-registry-target-invalid', 'Fresh Hub registry target is incomplete.');
|
|
3692
|
+
}
|
|
3693
|
+
return {
|
|
3694
|
+
...fresh,
|
|
3695
|
+
discoverySource: fresh.discoverySource || 'supabase'
|
|
3696
|
+
};
|
|
3697
|
+
} catch (error) {
|
|
3698
|
+
if (canUseCachedRelayAfterRegistryError(error)) {
|
|
3699
|
+
const direct = await resolveManagerFromCachedTarget(ownerKey, {
|
|
3700
|
+
cachedTarget: cached,
|
|
3701
|
+
probeEndpoint: options.probeEndpoint,
|
|
3702
|
+
// A TCP listener is not pair-token authentication. Only a
|
|
3703
|
+
// fresh signed registry result may extend cache trust.
|
|
3704
|
+
persist: false
|
|
3705
|
+
});
|
|
3706
|
+
if (direct) return direct;
|
|
3707
|
+
const relay = resolveManagerFromCachedRelayFallback(ownerKey, {
|
|
3708
|
+
cachedTarget: cached,
|
|
3709
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
3710
|
+
relayEndpoint: options.relayEndpoint,
|
|
3711
|
+
now: options.now
|
|
3712
|
+
});
|
|
3713
|
+
if (relay) return relay;
|
|
3714
|
+
}
|
|
3715
|
+
throw error;
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3529
3719
|
export function getDiscoveryRetryDelay(attempt, configuredIntervalMs = 0, random = Math.random) {
|
|
3530
3720
|
const configured = Number(configuredIntervalMs);
|
|
3531
3721
|
if (Number.isFinite(configured) && configured > 0) {
|
|
@@ -3577,34 +3767,50 @@ function normalizeEndpointCandidates(target) {
|
|
|
3577
3767
|
return [...new Set(rawCandidates.map(value => String(value || '').trim()).filter(Boolean))];
|
|
3578
3768
|
}
|
|
3579
3769
|
|
|
3580
|
-
async function resolveManagerFromSupabase(supabase, options = {}) {
|
|
3770
|
+
export async function resolveManagerFromSupabase(supabase, options = {}) {
|
|
3581
3771
|
await refreshSessionIfNeeded(supabase);
|
|
3582
3772
|
const { data, error } = await supabase
|
|
3583
3773
|
.from('livedesk_remote_host_targets')
|
|
3584
3774
|
.select('node_id, endpoint, endpoint_candidates, pair_token, active, expires_at, updated_at, manager_version')
|
|
3585
3775
|
.eq('product_key', 'livedesk')
|
|
3586
3776
|
.maybeSingle();
|
|
3587
|
-
if (error) {
|
|
3588
|
-
throw error;
|
|
3589
|
-
}
|
|
3590
|
-
if (!data?.active) {
|
|
3591
|
-
throw
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
}
|
|
3596
|
-
if (
|
|
3597
|
-
throw
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3777
|
+
if (error) {
|
|
3778
|
+
throw error;
|
|
3779
|
+
}
|
|
3780
|
+
if (!data?.active) {
|
|
3781
|
+
throw createHubDiscoveryError(
|
|
3782
|
+
'hub-registry-missing',
|
|
3783
|
+
'No active LiveDesk Hub was found for this Google account. Start LiveDesk Hub on the screen wall computer and sign in there first.'
|
|
3784
|
+
);
|
|
3785
|
+
}
|
|
3786
|
+
if (data.expires_at && Date.parse(data.expires_at) <= Date.now()) {
|
|
3787
|
+
throw createHubDiscoveryError(
|
|
3788
|
+
'hub-registry-expired',
|
|
3789
|
+
'The LiveDesk Hub record is expired. Open the Hub screen again while signed in.'
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
if (!data.pair_token) {
|
|
3793
|
+
throw createHubDiscoveryError(
|
|
3794
|
+
'hub-registry-pair-token-missing',
|
|
3795
|
+
'The LiveDesk Hub record is missing its private connection token.'
|
|
3796
|
+
);
|
|
3797
|
+
}
|
|
3798
|
+
const endpointCandidates = normalizeEndpointCandidates(data);
|
|
3799
|
+
if (endpointCandidates.length === 0) {
|
|
3800
|
+
throw createHubDiscoveryError(
|
|
3801
|
+
'hub-registry-endpoints-missing',
|
|
3802
|
+
'The LiveDesk Hub record does not contain a reachable local address.'
|
|
3803
|
+
);
|
|
3804
|
+
}
|
|
3603
3805
|
const target = await chooseManagerConnectionTarget(endpointCandidates, {
|
|
3604
|
-
allowRelayFallback: options.allowRelayFallback === true
|
|
3806
|
+
allowRelayFallback: options.allowRelayFallback === true,
|
|
3807
|
+
probeEndpoint: options.probeEndpoint
|
|
3605
3808
|
});
|
|
3606
3809
|
if (!target) {
|
|
3607
|
-
throw
|
|
3810
|
+
throw createHubDiscoveryError(
|
|
3811
|
+
'hub-direct-unreachable',
|
|
3812
|
+
`No reachable LiveDesk Hub endpoint yet. Checked ${endpointCandidates.join(', ')}.`
|
|
3813
|
+
);
|
|
3608
3814
|
}
|
|
3609
3815
|
return {
|
|
3610
3816
|
manager: target.manager,
|
|
@@ -3647,6 +3853,7 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
|
|
|
3647
3853
|
let attempts = 0;
|
|
3648
3854
|
let lastMessage = '';
|
|
3649
3855
|
let wakeListener = null;
|
|
3856
|
+
let initialError = options.initialError || null;
|
|
3650
3857
|
console.log('Waiting for a LiveDesk Hub. LiveDesk is listening for Hub-online events with adaptive registry retries as a fallback.');
|
|
3651
3858
|
try {
|
|
3652
3859
|
while (true) {
|
|
@@ -3655,6 +3862,11 @@ async function waitForManagerFromSupabase(supabase, options = {}) {
|
|
|
3655
3862
|
}
|
|
3656
3863
|
attempts += 1;
|
|
3657
3864
|
try {
|
|
3865
|
+
if (initialError) {
|
|
3866
|
+
const error = initialError;
|
|
3867
|
+
initialError = null;
|
|
3868
|
+
throw error;
|
|
3869
|
+
}
|
|
3658
3870
|
return await resolveManagerFromSupabase(supabase, {
|
|
3659
3871
|
allowRelayFallback: options.allowRelayFallback === true
|
|
3660
3872
|
});
|
|
@@ -3730,7 +3942,8 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3730
3942
|
choice = { type: 'google', session: savedSession };
|
|
3731
3943
|
} else if ((parsed.startupRun || existingConnectionPage) && savedPin) {
|
|
3732
3944
|
const resolved = await waitForManagerFromSavedPin(supabase, savedPin, {
|
|
3733
|
-
allowRelayFallback
|
|
3945
|
+
allowRelayFallback,
|
|
3946
|
+
relayEndpoint: parsed.relay
|
|
3734
3947
|
});
|
|
3735
3948
|
choice = { type: 'pin', ...resolved };
|
|
3736
3949
|
} else if (existingConnectionPage && previousChoice) {
|
|
@@ -3747,8 +3960,9 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3747
3960
|
savedSession,
|
|
3748
3961
|
savedPin,
|
|
3749
3962
|
allowRelayFallback,
|
|
3963
|
+
relayEndpoint: parsed.relay,
|
|
3750
3964
|
openBrowser: parsed.openBrowserOnStart
|
|
3751
|
-
});
|
|
3965
|
+
});
|
|
3752
3966
|
}
|
|
3753
3967
|
connectionPage = existingConnectionPage || choice.connectionPage || null;
|
|
3754
3968
|
if (choice.type === 'pin') {
|
|
@@ -3770,26 +3984,39 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3770
3984
|
: 'Connected by LiveDesk PIN.');
|
|
3771
3985
|
} else {
|
|
3772
3986
|
const cacheOwnerKey = hubCacheOwnerForSession(choice.session);
|
|
3773
|
-
let resolved = await resolveManagerFromCachedTarget(cacheOwnerKey);
|
|
3774
3987
|
let session = choice.session;
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3988
|
+
let initialDiscoveryError = null;
|
|
3989
|
+
let resolved = null;
|
|
3990
|
+
try {
|
|
3991
|
+
resolved = await resolveManagerWithCachedRecovery(cacheOwnerKey, {
|
|
3992
|
+
allowRelayFallback,
|
|
3993
|
+
relayEndpoint: parsed.relay,
|
|
3994
|
+
resolveFreshTarget: async () => {
|
|
3995
|
+
// The signed registry is authoritative for Hub replacement
|
|
3996
|
+
// and pair-token rotation. Cache direct/relay recovery is
|
|
3997
|
+
// considered only after this one current lookup reports
|
|
3998
|
+
// an explicitly recoverable availability failure.
|
|
3999
|
+
try {
|
|
4000
|
+
session = await activateSupabaseSession(supabase, choice.session);
|
|
4001
|
+
} catch (error) {
|
|
4002
|
+
if (!isRefreshTokenAlreadyUsedError(error)) throw error;
|
|
4003
|
+
session = readSavedSessionFromFile() || choice.session;
|
|
4004
|
+
console.warn('[LiveDesk Client] Another auth owner rotated the refresh token. Keeping the runtime alive while the saved session catches up.');
|
|
4005
|
+
}
|
|
4006
|
+
return await resolveManagerFromSupabase(supabase, {
|
|
4007
|
+
allowRelayFallback
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
});
|
|
4011
|
+
} catch (error) {
|
|
4012
|
+
initialDiscoveryError = error;
|
|
4013
|
+
}
|
|
4014
|
+
discoverySource = resolved?.discoverySource || 'supabase';
|
|
4015
|
+
if (resolved?.discoverySource === 'cache') {
|
|
3779
4016
|
writeSavedSessionToFile(session);
|
|
3780
|
-
console.log(
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
// Activate the accepted browser session only when registry
|
|
3784
|
-
// discovery is needed; a reachable account-bound cache can
|
|
3785
|
-
// start the Agent without a Supabase round trip.
|
|
3786
|
-
try {
|
|
3787
|
-
session = await activateSupabaseSession(supabase, choice.session);
|
|
3788
|
-
} catch (error) {
|
|
3789
|
-
if (!isRefreshTokenAlreadyUsedError(error)) throw error;
|
|
3790
|
-
session = readSavedSessionFromFile() || choice.session;
|
|
3791
|
-
console.warn('[LiveDesk Client] Another auth owner rotated the refresh token. Keeping the runtime alive while the saved session catches up.');
|
|
3792
|
-
}
|
|
4017
|
+
console.log(resolved.connectionTransport === 'relay-fallback'
|
|
4018
|
+
? `Fresh Hub registry lookup was unavailable; using saved account-bound Hub identity ${resolved.hubDeviceId} for the bounded encrypted relay path.`
|
|
4019
|
+
: `Fresh Hub registry lookup was unavailable; using the responding saved direct Hub endpoint ${resolved.manager}.`);
|
|
3793
4020
|
}
|
|
3794
4021
|
choice.session = session;
|
|
3795
4022
|
const email = session?.user?.email ? ` as ${session.user.email}` : '';
|
|
@@ -3797,18 +4024,20 @@ async function prepareLoginConnection(parsed, existingConnectionPage = null) {
|
|
|
3797
4024
|
if (!resolved) {
|
|
3798
4025
|
resolved = await waitForManagerFromSupabase(supabase, {
|
|
3799
4026
|
allowRelayFallback,
|
|
4027
|
+
initialError: initialDiscoveryError,
|
|
3800
4028
|
shouldStop: () => Boolean(roleRestartRequest?.role)
|
|
3801
4029
|
});
|
|
4030
|
+
discoverySource = 'supabase';
|
|
3802
4031
|
}
|
|
3803
4032
|
if (!resolved && roleRestartRequest?.role) {
|
|
3804
|
-
return {
|
|
3805
|
-
...parsed,
|
|
3806
|
-
manager,
|
|
3807
|
-
pair,
|
|
3808
|
-
slot: normalizeSlotNumber(parsed.slot),
|
|
3809
|
-
connectionPage,
|
|
3810
|
-
forwarded,
|
|
3811
|
-
rediscoverOnDisconnect: shouldLogin,
|
|
4033
|
+
return {
|
|
4034
|
+
...parsed,
|
|
4035
|
+
manager,
|
|
4036
|
+
pair,
|
|
4037
|
+
slot: normalizeSlotNumber(parsed.slot),
|
|
4038
|
+
connectionPage,
|
|
4039
|
+
forwarded,
|
|
4040
|
+
rediscoverOnDisconnect: shouldLogin,
|
|
3812
4041
|
rediscoverOnInvalidPair: shouldLogin
|
|
3813
4042
|
};
|
|
3814
4043
|
}
|
|
@@ -4233,6 +4462,18 @@ export function buildFastArgs(args, fakeThumbnail, transport = 'auto', relay = '
|
|
|
4233
4462
|
const forwarded = [];
|
|
4234
4463
|
for (let index = 0; index < args.length; index += 1) {
|
|
4235
4464
|
const arg = args[index];
|
|
4465
|
+
if (RETIRED_CLIENT_MODEL_SINGLE_OPTIONS.has(arg)) {
|
|
4466
|
+
continue;
|
|
4467
|
+
}
|
|
4468
|
+
if (arg === RETIRED_CLIENT_MODEL_VALUE_OPTION) {
|
|
4469
|
+
if (index + 1 < args.length && !String(args[index + 1] || '').startsWith('-')) {
|
|
4470
|
+
index += 1;
|
|
4471
|
+
}
|
|
4472
|
+
continue;
|
|
4473
|
+
}
|
|
4474
|
+
if (String(arg || '').startsWith(`${RETIRED_CLIENT_MODEL_VALUE_OPTION}=`)) {
|
|
4475
|
+
continue;
|
|
4476
|
+
}
|
|
4236
4477
|
if (arg === '--transport' || arg === '--relay') {
|
|
4237
4478
|
index += 1;
|
|
4238
4479
|
continue;
|
|
@@ -4241,8 +4482,8 @@ export function buildFastArgs(args, fakeThumbnail, transport = 'auto', relay = '
|
|
|
4241
4482
|
forwarded.push('--fake-frame');
|
|
4242
4483
|
continue;
|
|
4243
4484
|
}
|
|
4244
|
-
if (arg === '--tasks' || arg === '--no-tasks'
|
|
4245
|
-
continue;
|
|
4485
|
+
if (arg === '--tasks' || arg === '--no-tasks') {
|
|
4486
|
+
continue;
|
|
4246
4487
|
}
|
|
4247
4488
|
forwarded.push(arg);
|
|
4248
4489
|
}
|
|
@@ -4446,15 +4687,15 @@ function spawnAgent(command, args, env = process.env, onStart = null) {
|
|
|
4446
4687
|
});
|
|
4447
4688
|
}
|
|
4448
4689
|
|
|
4449
|
-
function shouldUseFast(parsed) {
|
|
4450
|
-
if (parsed.engine === 'node') {
|
|
4451
|
-
return false;
|
|
4690
|
+
function shouldUseFast(parsed) {
|
|
4691
|
+
if (parsed.engine === 'node') {
|
|
4692
|
+
return false;
|
|
4452
4693
|
}
|
|
4453
4694
|
if (parsed.engine === 'fast') {
|
|
4454
4695
|
return true;
|
|
4455
4696
|
}
|
|
4456
|
-
return
|
|
4457
|
-
}
|
|
4697
|
+
return true;
|
|
4698
|
+
}
|
|
4458
4699
|
|
|
4459
4700
|
function shouldTryFast(parsed, runtime) {
|
|
4460
4701
|
if (parsed.engine === 'fast') {
|
|
@@ -4480,6 +4721,12 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
|
|
|
4480
4721
|
console.warn('[LiveDesk] @livedesk/client is deprecated. Use the unified livedesk package or LiveDesk Desktop.');
|
|
4481
4722
|
}
|
|
4482
4723
|
const parsed = parseLauncherArgs(argv);
|
|
4724
|
+
if (parsed.retiredModelOptionsDiscarded > 0) {
|
|
4725
|
+
console.warn(
|
|
4726
|
+
'[LiveDesk Client] Ignored retired Client model-runtime options. '
|
|
4727
|
+
+ 'Approved AI computer commands are handled by the Hub Codex Agent.'
|
|
4728
|
+
);
|
|
4729
|
+
}
|
|
4483
4730
|
if (parsed.updateWaitPid) {
|
|
4484
4731
|
await waitForProcessExit(parsed.updateWaitPid);
|
|
4485
4732
|
}
|