@livedesk/client 0.1.170 → 0.1.171

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.
@@ -50,6 +50,9 @@ async function writeClientUpdateState(stage, details = {}) {
50
50
  const statePath = getClientUpdateStatePath();
51
51
  const operationId = String(details.operationId || process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
52
52
  if (!operationId) return;
53
+ const attemptId = stage === 'scheduled'
54
+ ? ''
55
+ : String(details.attemptId || process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim();
53
56
  let previous = null;
54
57
  try {
55
58
  previous = JSON.parse(await fs.readFile(statePath, 'utf8'));
@@ -61,6 +64,18 @@ async function writeClientUpdateState(stage, details = {}) {
61
64
  && stage === 'scheduled'
62
65
  && ['connected', 'failed', 'restored'].includes(String(previous.stage || ''));
63
66
  if (previous?.operationId && previous.operationId !== operationId && !replacesTerminalOperation) return;
67
+ if (!replacesTerminalOperation
68
+ && previous?.attemptId
69
+ && attemptId
70
+ && previous.attemptId !== attemptId) {
71
+ return;
72
+ }
73
+ if (!replacesTerminalOperation
74
+ && previous?.stage === 'connected'
75
+ && previous?.restartVerified === true
76
+ && stage !== 'connected') {
77
+ return;
78
+ }
64
79
  const targetProductVersion = String(
65
80
  details.targetProductVersion
66
81
  || process.env.LIVEDESK_CLIENT_UPDATE_TARGET_PRODUCT_VERSION
@@ -87,7 +102,7 @@ async function writeClientUpdateState(stage, details = {}) {
87
102
  agentVersion: AGENT_VERSION,
88
103
  agentPid: process.pid,
89
104
  launcherPid: Number(process.env.LIVEDESK_CLIENT_PARENT_PID || 0) || 0,
90
- attemptId: String(details.attemptId || process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim(),
105
+ attemptId,
91
106
  updatedAt: new Date().toISOString(),
92
107
  ...details
93
108
  };
@@ -111,7 +126,7 @@ async function waitForClientUpdateShutdownSignal(operationId, timeoutMs = 150_00
111
126
  await wait(200);
112
127
  continue;
113
128
  }
114
- if (state.stage === 'waiting-for-shutdown') return true;
129
+ if (state.stage === 'preflight-ready' || state.stage === 'waiting-for-shutdown') return true;
115
130
  if (state.stage === 'failed') {
116
131
  console.error(`LiveDesk client update preparation failed: ${state.error || 'unknown error'}`);
117
132
  return false;
@@ -1432,55 +1447,27 @@ function remotePolicyAllows(options, command) {
1432
1447
 
1433
1448
  function buildClientUpdateBootstrapScript() {
1434
1449
  return [
1435
- 'const { spawn, spawnSync } = require("node:child_process");',
1450
+ 'const { spawn } = require("node:child_process");',
1436
1451
  'const fs = require("node:fs");',
1437
1452
  'const path = require("node:path");',
1438
- 'const crypto = require("node:crypto");',
1439
- 'const waitPid = Number(process.env.LIVEDESK_UPDATE_WAIT_PID || 0);',
1440
- 'const agentPid = Number(process.env.LIVEDESK_UPDATE_AGENT_PID || 0);',
1441
1453
  'const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || "").trim();',
1442
1454
  'const statePath = String(process.env.LIVEDESK_CLIENT_UPDATE_STATE_PATH || path.join(require("node:os").homedir(), ".livedesk", "client-update.json"));',
1443
- 'const logPath = statePath.replace(/\\.json$/i, "") + ".log";',
1444
1455
  'const cleanVersion = value => String(value || "").trim().replace(/^v/i, "");',
1445
- 'const versionPattern = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/;',
1446
1456
  'const targetProductVersion = cleanVersion(process.env.LIVEDESK_CLIENT_UPDATE_TARGET_PRODUCT_VERSION || process.env.LIVEDESK_CLIENT_UPDATE_TARGET_VERSION);',
1447
- 'const targetAgentVersion = cleanVersion(process.env.LIVEDESK_CLIENT_UPDATE_TARGET_AGENT_VERSION || process.env.LIVEDESK_CLIENT_UPDATE_TARGET_VERSION || targetProductVersion);',
1448
- 'const currentProductVersion = cleanVersion(process.env.LIVEDESK_CLIENT_UPDATE_CURRENT_PRODUCT_VERSION || process.env.LIVEDESK_NPM_LAUNCHER_VERSION);',
1449
- 'const currentAgentVersion = cleanVersion(process.env.LIVEDESK_CLIENT_UPDATE_CURRENT_AGENT_VERSION || process.env.LIVEDESK_CLIENT_PACKAGE_VERSION || currentProductVersion);',
1450
- 'for (const [label, version] of [["target product", targetProductVersion], ["target Agent", targetAgentVersion], ["current product", currentProductVersion], ["current Agent", currentAgentVersion]]) { if (!versionPattern.test(version)) throw new Error("Invalid LiveDesk " + label + " update version: " + version); }',
1451
- 'const packageSpec = "livedesk@" + targetProductVersion;',
1452
- 'const localLauncher = String(process.env.LIVEDESK_CLIENT_UPDATE_LOCAL_LAUNCHER || process.env.LIVEDESK_UNIFIED_LAUNCHER_ENTRY || "").trim();',
1453
- 'const preservedArgs = (() => { try { const decoded = JSON.parse(Buffer.from(String(process.env.LIVEDESK_CLIENT_UPDATE_ARGS_BASE64 || ""), "base64").toString("utf8")); return Array.isArray(decoded) ? decoded.map(value => String(value)) : []; } catch { return []; } })();',
1454
- 'const proofTimeoutMs = Math.max(500, Number(process.env.LIVEDESK_CLIENT_UPDATE_PROOF_TIMEOUT_MS || 30000));',
1455
- 'const proofPollMs = Math.max(25, Number(process.env.LIVEDESK_CLIENT_UPDATE_PROOF_POLL_MS || 200));',
1456
- 'const killGraceMs = Math.max(50, Number(process.env.LIVEDESK_CLIENT_UPDATE_KILL_GRACE_MS || 1500));',
1457
- 'const targetLaunchLimit = 3;',
1458
- 'const restoreLaunchLimit = 2;',
1457
+ 'const versionPattern = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/;',
1459
1458
  'const readState = () => { try { return JSON.parse(fs.readFileSync(statePath, "utf8")); } catch { return {}; } };',
1460
- 'const writeState = (stage, details = {}) => { fs.mkdirSync(path.dirname(statePath), { recursive: true }); const previous = readState(); const replacesTerminalOperation = previous.operationId && previous.operationId !== operationId && stage === "preparing-package" && ["connected", "failed", "restored"].includes(String(previous.stage || "")); if (previous.operationId && previous.operationId !== operationId && !replacesTerminalOperation) return false; const base = replacesTerminalOperation ? {} : previous; const next = { ...base, operationId, stage, targetVersion: targetAgentVersion, targetProductVersion, targetAgentVersion, currentProductVersion, currentAgentVersion, supervisorPid: process.pid, updatedAt: new Date().toISOString(), ...details }; const temporary = statePath + "." + process.pid + "." + Math.random().toString(16).slice(2) + ".tmp"; fs.writeFileSync(temporary, JSON.stringify(next, null, 2), { encoding: "utf8", mode: 0o600 }); fs.renameSync(temporary, statePath); return true; };',
1461
- 'const log = message => { try { fs.mkdirSync(path.dirname(logPath), { recursive: true }); fs.appendFileSync(logPath, new Date().toISOString() + " " + message + "\\n", "utf8"); } catch {} };',
1462
- 'const isAlive = pid => { try { process.kill(pid, 0); return true; } catch (error) { return error?.code !== "ESRCH"; } };',
1463
- 'const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));',
1464
- 'const waitForExit = async (pid, timeoutMs) => { const deadline = Date.now() + timeoutMs; while (pid > 1 && Date.now() < deadline) { if (!isAlive(pid)) return true; await sleep(250); } return pid <= 1 || !isAlive(pid); };',
1465
- 'const killProcessTree = async pid => { if (!Number.isInteger(pid) || pid <= 1 || pid === process.pid) return; if (process.platform === "win32") { spawnSync("taskkill.exe", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" }); } else { try { process.kill(-pid, "SIGTERM"); } catch { try { process.kill(pid, "SIGTERM"); } catch {} } await sleep(killGraceMs); try { process.kill(-pid, "SIGKILL"); } catch { try { process.kill(pid, "SIGKILL"); } catch {} } } await waitForExit(pid, killGraceMs); };',
1466
- 'const ensureExit = async (pid, label) => { if (pid <= 1 || await waitForExit(pid, 20000)) return; log(label + " pid=" + pid + " did not exit gracefully; terminating its process tree."); await killProcessTree(pid); if (isAlive(pid)) throw new Error(label + " process " + pid + " did not exit before restart."); };',
1459
+ 'const writeFailure = error => { try { fs.mkdirSync(path.dirname(statePath), { recursive: true }); const previous = readState(); if (previous.operationId && previous.operationId !== operationId) return; const now = new Date().toISOString(); const next = { ...previous, operationId, stage: "failed", targetProductVersion, restartVerified: false, error: String(error?.message || error).slice(0, 4000), failedAt: now, updatedAt: now }; const temporary = statePath + "." + process.pid + ".handoff.tmp"; fs.writeFileSync(temporary, JSON.stringify(next, null, 2), { encoding: "utf8", mode: 0o600 }); fs.renameSync(temporary, statePath); } catch {} process.stderr.write("LiveDesk update handoff failed: " + (error?.message || error) + "\\n"); process.exitCode = 1; };',
1460
+ 'const monitorPreflight = child => { let settled = false; const finish = (error, preserveFailure = false) => { if (settled) return; settled = true; clearInterval(poll); clearTimeout(timeout); child.unref(); if (preserveFailure) process.exitCode = 1; else if (error) writeFailure(error); }; const inspect = () => { const state = readState(); if (state.operationId !== operationId) return; if (state.stage === "preflight-ready" || state.stage === "waiting-for-shutdown") finish(); else if (state.stage === "failed") finish(null, true); }; const poll = setInterval(inspect, 100); const timeout = setTimeout(() => { try { child.kill(); } catch {} finish(new Error("Timed out waiting for exact-package update preflight.")); }, Math.max(1000, Number(process.env.LIVEDESK_CLIENT_UPDATE_HANDOFF_TIMEOUT_MS || 140000))); child.once("exit", (code, signal) => { inspect(); if (!settled) finish(new Error("Exact-package update supervisor exited before preflight (code=" + (code ?? "none") + ", signal=" + (signal || "none") + ").")); }); inspect(); };',
1461
+ 'if (!operationId || !versionPattern.test(targetProductVersion)) { writeFailure(new Error("Invalid LiveDesk exact-package update handoff.")); } else {',
1462
+ 'const env = { ...process.env, LIVEDESK_UPDATE_STARTER_PID: String(process.pid) };',
1467
1463
  'const nodeDir = path.dirname(process.execPath);',
1468
- 'const npxCandidates = process.platform === "win32" ? [process.env.LIVEDESK_NPX_EXECUTABLE, path.join(nodeDir, "npx.cmd"), path.join(nodeDir, "npx.exe"), "npx.cmd"] : [process.env.LIVEDESK_NPX_EXECUTABLE, path.join(nodeDir, "npx"), "npx"];',
1469
- 'const npx = npxCandidates.find(candidate => candidate && (!path.isAbsolute(candidate) || fs.existsSync(candidate)));',
1470
- 'if (!npx) { writeState("failed", { error: "LiveDesk npx executable was not found." }); throw new Error("LiveDesk npx executable was not found."); }',
1471
- 'const npmExecPath = String(process.env.npm_execpath || process.env.NPM_EXECPATH || "").trim();',
1472
- 'const npxCliCandidates = [process.env.LIVEDESK_NPX_CLI_PATH, npmExecPath ? path.join(path.dirname(npmExecPath), "npx-cli.js") : "", path.join(nodeDir, "node_modules", "npm", "bin", "npx-cli.js")].filter(Boolean);',
1473
- 'const npxCli = npxCliCandidates.find(candidate => fs.existsSync(candidate));',
1464
+ 'const npxCli = [env.LIVEDESK_NPX_CLI_PATH, env.npm_execpath ? path.join(path.dirname(env.npm_execpath), "npx-cli.js") : "", env.LIVEDESK_NPX_EXECUTABLE ? path.join(path.dirname(env.LIVEDESK_NPX_EXECUTABLE), "node_modules", "npm", "bin", "npx-cli.js") : "", path.join(nodeDir, "node_modules", "npm", "bin", "npx-cli.js")].find(value => value && fs.existsSync(value));',
1465
+ 'const npx = env.LIVEDESK_NPX_EXECUTABLE || (process.platform === "win32" ? "npx.cmd" : "npx");',
1466
+ 'const args = ["-y", "--prefer-online", "livedesk@" + targetProductVersion, "--internal-legacy-client-update"];',
1474
1467
  'const quoteCmd = value => "\\"" + String(value).replaceAll("%", "%%").replaceAll("\\"", "\\"\\"") + "\\"";',
1475
- 'const invocation = npxArgs => npxCli ? { command: process.execPath, args: [npxCli, ...npxArgs] } : process.platform === "win32" ? { command: process.env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", "call " + quoteCmd(npx) + " " + npxArgs.map(quoteCmd).join(" ")] } : { command: npx, args: npxArgs };',
1476
- 'const runToExit = (npxArgs, timeoutMs) => new Promise((resolve, reject) => { const call = invocation(npxArgs); const child = spawn(call.command, call.args, { env: process.env, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] }); let stdout = ""; let stderr = ""; let settled = false; child.stdout?.on("data", chunk => { stdout = (stdout + chunk.toString()).slice(-65536); }); child.stderr?.on("data", chunk => { stderr = (stderr + chunk.toString()).slice(-65536); }); const timer = setTimeout(() => { if (settled) return; settled = true; void killProcessTree(child.pid || 0).finally(() => reject(new Error("LiveDesk package preparation timed out."))); }, timeoutMs); child.once("error", error => { if (settled) return; settled = true; clearTimeout(timer); reject(error); }); child.once("exit", (code, signal) => { if (settled) return; settled = true; clearTimeout(timer); resolve({ code: code ?? -1, signal, stdout, stderr }); }); });',
1477
- 'const attempts = [];',
1478
- 'const attemptIdFor = (kind, number) => operationId + "-" + kind + "-" + number + "-" + crypto.randomBytes(4).toString("hex");',
1479
- 'const updateAttempt = (attempt, patch) => { Object.assign(attempt, patch, { updatedAt: new Date().toISOString() }); return attempts.map(item => ({ ...item })); };',
1480
- 'const killAttempt = async (restartPid, attemptId) => { const state = readState(); const pids = new Set([Number(restartPid || 0)]); if (state.operationId === operationId && state.attemptId === attemptId) { pids.add(Number(state.launcherPid || 0)); pids.add(Number(state.agentPid || 0)); } for (const pid of pids) await killProcessTree(pid); };',
1481
- 'const launchAndVerify = async ({ kind, number, expectedProductVersion, expectedAgentVersion, call }) => { const attemptId = attemptIdFor(kind, number); const attempt = { attemptId, kind, number, status: "launching", expectedProductVersion, expectedAgentVersion, startedAt: new Date().toISOString() }; attempts.push(attempt); const attemptEnv = { ...process.env, LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID: attemptId, LIVEDESK_CLIENT_UPDATE_ATTEMPT_KIND: kind, LIVEDESK_CLIENT_UPDATE_ATTEMPT_NUMBER: String(number), LIVEDESK_CLIENT_UPDATE_OPERATION_ID: operationId, LIVEDESK_CLIENT_UPDATE_TARGET_PRODUCT_VERSION: targetProductVersion, LIVEDESK_CLIENT_UPDATE_TARGET_AGENT_VERSION: targetAgentVersion, LIVEDESK_CLIENT_UPDATE_TARGET_VERSION: targetAgentVersion }; const logFd = fs.openSync(logPath, "a"); let child; try { child = spawn(call.command, call.args, { cwd: process.env.LIVEDESK_CLIENT_UPDATE_CWD || process.cwd(), env: attemptEnv, detached: true, windowsHide: true, stdio: ["ignore", logFd, logFd] }); await new Promise((resolve, reject) => { child.once("error", reject); child.once("spawn", resolve); }); } finally { fs.closeSync(logFd); } const restartPid = Number(child?.pid || 0); updateAttempt(attempt, { status: "waiting-for-connected-proof", restartPid }); writeState(kind + "-launched", { attemptId, attemptKind: kind, attemptNumber: number, restartPid, attempts: attempts.map(item => ({ ...item })), restartVerified: false, error: "" }); let exitResult = null; child.once("exit", (code, signal) => { exitResult = { code, signal }; }); const deadline = Date.now() + proofTimeoutMs; let proof = null; let failure = ""; while (Date.now() < deadline) { const state = readState(); if (state.operationId === operationId && state.attemptId === attemptId && state.stage === "connected") { const observedProduct = cleanVersion(state.productVersion); const observedAgent = cleanVersion(state.agentVersion); if (observedProduct === expectedProductVersion && observedAgent === expectedAgentVersion) { proof = state; break; } failure = "Connected version mismatch expected product " + expectedProductVersion + " / Agent " + expectedAgentVersion + ", observed product " + (observedProduct || "unknown") + " / Agent " + (observedAgent || "unknown") + "."; break; } if (exitResult) { failure = "Launcher exited before exact connected proof (code=" + (exitResult.code ?? "none") + ", signal=" + (exitResult.signal || "none") + ")."; break; } await sleep(proofPollMs); } if (proof) { updateAttempt(attempt, { status: "connected", connectedAt: proof.connectedAt || new Date().toISOString(), productVersion: cleanVersion(proof.productVersion), agentVersion: cleanVersion(proof.agentVersion) }); if (child && !exitResult) child.unref(); writeState(kind === "target" ? "connected" : "restored", { attemptId, attemptKind: kind, attemptNumber: number, restartPid, launcherPid: Number(proof.launcherPid || 0), agentPid: Number(proof.agentPid || 0), productVersion: cleanVersion(proof.productVersion), agentVersion: cleanVersion(proof.agentVersion), attempts: attempts.map(item => ({ ...item })), restartVerified: kind === "target", restored: kind === "restore", connectedAt: proof.connectedAt || new Date().toISOString(), error: kind === "restore" ? "Target update failed; previous local Client was restored." : "" }); return true; } failure = failure || "Timed out waiting for exact connected proof for " + attemptId + "."; await killAttempt(restartPid, attemptId); updateAttempt(attempt, { status: "failed", failedAt: new Date().toISOString(), killed: true, error: failure }); writeState(kind + "-attempt-failed", { attemptId, attemptKind: kind, attemptNumber: number, restartPid, attempts: attempts.map(item => ({ ...item })), restartVerified: false, error: failure }); log(attemptId + " failed: " + failure); return false; };',
1482
- 'process.env.LIVEDESK_SKIP_BROWSER_OPEN = "1";',
1483
- '(async () => { writeState("preparing-package", { packageSpec, attempts: [], error: "" }); log("preparing " + packageSpec); const prepared = await runToExit(["-y", "--prefer-online", packageSpec, "--version"], 120000); if (prepared.code !== 0) throw new Error("LiveDesk package preparation failed (exit=" + prepared.code + "): " + (prepared.stderr || prepared.stdout).trim().slice(-2000)); const stdoutVersions = prepared.stdout.split(/\\r?\\n/).map(line => line.trim()).filter(line => versionPattern.test(line)); if (!stdoutVersions.includes(targetProductVersion)) throw new Error("Prepared LiveDesk stdout did not report requested product version " + targetProductVersion + ". stdout=" + prepared.stdout.trim().slice(-1000)); writeState("waiting-for-shutdown", { observedVersion: targetProductVersion, observedProductVersion: targetProductVersion, preparedAt: new Date().toISOString(), attempts: [], error: "" }); log("package ready product=" + targetProductVersion + "; waiting for current runtime shutdown"); await ensureExit(agentPid, "Agent"); await ensureExit(waitPid, "Launcher"); const roleArgs = ["--force-role", "client", "--no-login", "--no-open", ...preservedArgs]; const targetCall = invocation(["-y", "--prefer-online", packageSpec, ...roleArgs]); for (let number = 1; number <= targetLaunchLimit; number += 1) { if (await launchAndVerify({ kind: "target", number, expectedProductVersion: targetProductVersion, expectedAgentVersion: targetAgentVersion, call: targetCall })) { log("target connected with exact product " + targetProductVersion + " / Agent " + targetAgentVersion); return; } } writeState("restoring", { attempts: attempts.map(item => ({ ...item })), error: "All target launch attempts failed; restoring previous local Client." }); const restoreCall = localLauncher && fs.existsSync(localLauncher) ? { command: process.execPath, args: [localLauncher, ...roleArgs] } : invocation(["-y", "--prefer-offline", "livedesk@" + currentProductVersion, ...roleArgs]); for (let number = 1; number <= restoreLaunchLimit; number += 1) { if (await launchAndVerify({ kind: "restore", number, expectedProductVersion: currentProductVersion, expectedAgentVersion: currentAgentVersion, call: restoreCall })) { log("previous local Client restored at product " + currentProductVersion + " / Agent " + currentAgentVersion); process.exitCode = 1; return; } } throw new Error("Target update failed after 3 attempts and previous local Client restoration failed after 2 attempts."); })().catch(error => { const message = error?.stack || error?.message || String(error); log("failed: " + message); try { writeState("failed", { attempts: attempts.map(item => ({ ...item })), error: String(error?.message || error).slice(0, 4000), failedAt: new Date().toISOString(), restartVerified: false }); } catch {} process.exitCode = 1; });',
1468
+ 'const invocation = npxCli ? { command: process.execPath, args: [npxCli, ...args] } : process.platform === "win32" ? { command: env.ComSpec || "cmd.exe", args: ["/d", "/s", "/c", "call " + quoteCmd(npx) + " " + args.map(quoteCmd).join(" ")] } : { command: npx, args };',
1469
+ 'if (!npxCli && path.isAbsolute(npx) && !fs.existsSync(npx)) { writeFailure(new Error("LiveDesk npx executable was not found: " + npx)); } else { try { const child = spawn(invocation.command, invocation.args, { env, detached: true, stdio: "ignore", windowsHide: true }); child.once("error", writeFailure); child.once("spawn", () => monitorPreflight(child)); } catch (error) { writeFailure(error); } }',
1470
+ '}',
1484
1471
  ''
1485
1472
  ].join('\n');
1486
1473
  }
@@ -603,8 +603,15 @@ function readClientUpdateState() {
603
603
  function writeClientUpdateRuntimeStage(stage, details = {}) {
604
604
  const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
605
605
  if (!operationId) return;
606
+ const attemptId = String(process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim();
606
607
  const previous = readClientUpdateState();
607
608
  if (previous?.operationId && previous.operationId !== operationId) return;
609
+ if (previous?.attemptId && attemptId && previous.attemptId !== attemptId) return;
610
+ if (previous?.stage === 'connected'
611
+ && previous?.restartVerified === true
612
+ && stage !== 'connected') {
613
+ return;
614
+ }
608
615
  const targetVersion = String(process.env.LIVEDESK_CLIENT_UPDATE_TARGET_VERSION || '').trim().replace(/^v/i, '');
609
616
  const productVersion = String(process.env.LIVEDESK_NPM_LAUNCHER_VERSION || readPackageVersion()).trim();
610
617
  const state = {
@@ -615,6 +622,7 @@ function writeClientUpdateRuntimeStage(stage, details = {}) {
615
622
  productVersion,
616
623
  agentVersion: readPackageVersion(),
617
624
  launcherPid: process.pid,
625
+ attemptId,
618
626
  updatedAt: new Date().toISOString(),
619
627
  ...details
620
628
  };
@@ -3592,9 +3600,10 @@ function buildClientAgentEnvironment(prepared) {
3592
3600
  env.LIVEDESK_NPX_CLI_PATH = npxCliPath;
3593
3601
  }
3594
3602
  env.LIVEDESK_CLIENT_MANAGER = String(prepared?.manager || env.LIVEDESK_CLIENT_MANAGER || '');
3595
- env.LIVEDESK_CLIENT_PAIR_TOKEN = String(prepared?.pair || env.LIVEDESK_CLIENT_PAIR_TOKEN || '');
3596
- env.LIVEDESK_CLIENT_NAME = String(prepared?.name || env.LIVEDESK_CLIENT_NAME || '');
3603
+ env.LIVEDESK_CLIENT_PAIR_TOKEN = String(prepared?.pair || env.LIVEDESK_CLIENT_PAIR_TOKEN || '');
3604
+ env.LIVEDESK_CLIENT_NAME = String(prepared?.name || env.LIVEDESK_CLIENT_NAME || '');
3597
3605
  env.LIVEDESK_CLIENT_SLOT = String(prepared?.slot || env.LIVEDESK_CLIENT_SLOT || '');
3606
+ env.LIVEDESK_DEVICE_ID = String(prepared?.deviceId || env.LIVEDESK_DEVICE_ID || '');
3598
3607
  env.LIVEDESK_CLIENT_ENGINE = String(prepared?.engine || env.LIVEDESK_CLIENT_ENGINE || 'auto');
3599
3608
  env.LIVEDESK_CLIENT_UPDATE_STATE_PATH = CLIENT_UPDATE_STATE_PATH;
3600
3609
  env.LIVEDESK_CLIENT_UPDATE_ARGS_BASE64 = Buffer
@@ -3805,16 +3814,19 @@ function resolveFastLaunch(runtime) {
3805
3814
  }
3806
3815
 
3807
3816
  function spawnAgent(command, args, env = process.env, onStart = null) {
3808
- const child = spawn(command, args, {
3809
- env,
3810
- stdio: 'inherit',
3811
- windowsHide: true
3812
- });
3813
- activeAgentProcess = child;
3814
- writeClientUpdateRuntimeStage('agent-started', {
3815
- agentPid: Number(child.pid || 0),
3817
+ // Publish only a pre-spawn stage. A very fast child can reach Hub welcome
3818
+ // before spawn() returns; a post-spawn write would then erase its one-shot
3819
+ // connected proof.
3820
+ writeClientUpdateRuntimeStage('agent-launching', {
3821
+ agentPid: 0,
3816
3822
  restartVerified: false
3817
3823
  });
3824
+ const child = spawn(command, args, {
3825
+ env,
3826
+ stdio: 'inherit',
3827
+ windowsHide: true
3828
+ });
3829
+ activeAgentProcess = child;
3818
3830
  if (typeof onStart === 'function') {
3819
3831
  onStart(child);
3820
3832
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.170",
3
+ "version": "0.1.171",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,10 +39,10 @@
39
39
  "ws": "^8.18.3"
40
40
  },
41
41
  "optionalDependencies": {
42
- "@livedesk/fast-linux-x64": "0.1.374",
43
- "@livedesk/fast-osx-arm64": "0.1.376",
44
- "@livedesk/fast-osx-x64": "0.1.376",
45
- "@livedesk/fast-win-x64": "0.1.374"
42
+ "@livedesk/fast-linux-x64": "0.1.377",
43
+ "@livedesk/fast-osx-arm64": "0.1.377",
44
+ "@livedesk/fast-osx-x64": "0.1.377",
45
+ "@livedesk/fast-win-x64": "0.1.377"
46
46
  },
47
47
  "publishConfig": {
48
48
  "access": "public"