@livedesk/hub 0.1.49 → 0.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @livedesk/hub
2
2
 
3
- Local LiveDesk Hub runtime.
3
+ Local VuvoDesk Hub runtime.
4
4
 
5
5
  This package is used by the `livedesk` launcher package. Most users should run:
6
6
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.49",
4
- "description": "LiveDesk local Hub API and browser frame bridge",
3
+ "version": "0.1.50",
4
+ "description": "VuvoDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
7
7
  "files": [
@@ -11,12 +11,12 @@
11
11
  "scripts": {
12
12
  "dev": "node src/server.js",
13
13
  "start": "node src/server.js",
14
- "check": "node --check src/server.js && node --check src/remote-hub.js && node --check src/remote-clipboard-contract.mjs && node --check src/console-relay.js",
14
+ "check": "node --check src/server.js && node --check src/remote-hub.js && node --check src/remote-clipboard-contract.mjs && node --check src/console-relay.js",
15
15
  "prepublishOnly": "node ../../scripts/livedesk-release-git-gate.mjs"
16
16
  },
17
17
  "dependencies": {
18
18
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
19
- "@livedesk/runtime-core": "0.1.5",
19
+ "@livedesk/runtime-core": "0.1.6",
20
20
  "@openai/codex-sdk": "0.145.0",
21
21
  "cors": "^2.8.5",
22
22
  "express": "^4.21.2",
@@ -87,12 +87,12 @@ export function createAgentManager({
87
87
  const source = input && typeof input === 'object' ? input : {};
88
88
  const results = Array.isArray(source.results) ? source.results : [];
89
89
  return {
90
- summary: String(results.length ? `${source.completed || 0} completed, ${source.failed || 0} failed.` : 'LiveDesk Agent run completed.').slice(0, 1200)
90
+ summary: String(results.length ? `${source.completed || 0} completed, ${source.failed || 0} failed.` : 'VuvoDesk Agent run completed.').slice(0, 1200)
91
91
  };
92
92
  },
93
93
  async startRun(input) {
94
94
  if (!runtime) {
95
- throw new AgentRuntimeError('agent-codex-not-active', 'Codex SDK is not available for LiveDesk Agent commands.', { status: 409 });
95
+ throw new AgentRuntimeError('agent-codex-not-active', 'Codex SDK is not available for VuvoDesk Agent commands.', { status: 409 });
96
96
  }
97
97
  return runtime.start(input);
98
98
  },
@@ -164,7 +164,7 @@ function isSafeAutoRelativePath(value) {
164
164
  function safeAutoConstraint(tool, args, mode) {
165
165
  if (mode !== 'safe-auto') return '';
166
166
  if (tool.category === 'fileWrite' && !isSafeAutoRelativePath(args?.path)) {
167
- return 'Safe Auto file writes are limited to relative paths inside the LiveDesk files directory.';
167
+ return 'Safe Auto file writes are limited to relative paths inside the VuvoDesk files directory.';
168
168
  }
169
169
  if (tool.category === 'fileWrite') {
170
170
  const filePath = String(args?.path || '').trim().replaceAll('\\', '/').toLowerCase();
@@ -175,7 +175,7 @@ function safeAutoConstraint(tool, args, mode) {
175
175
  if (tool.category === 'script') {
176
176
  const scriptPath = String(args?.path || '').trim().toLowerCase();
177
177
  if (!isSafeAutoRelativePath(scriptPath) || !scriptPath.replaceAll('\\', '/').startsWith('scripts/') || !/\.(ps1|psm1|sh|bash|py|js|mjs|cmd|bat)$/.test(scriptPath)) {
178
- return 'Safe Auto scripts must be registered relative script files inside the LiveDesk files directory.';
178
+ return 'Safe Auto scripts must be registered relative script files inside the VuvoDesk files directory.';
179
179
  }
180
180
  }
181
181
  return '';
@@ -27,7 +27,7 @@ const readTaskPermission = { ask: 'allow', safeAuto: 'allow', fullAccess: 'allow
27
27
  export const AGENT_TOOL_DEFINITIONS = Object.freeze([
28
28
  {
29
29
  name: 'livedesk.list_devices',
30
- description: 'List connected LiveDesk Clients. This is read-only.',
30
+ description: 'List connected VuvoDesk Clients. This is read-only.',
31
31
  category: 'read',
32
32
  readOnly: true,
33
33
  mutating: false,
@@ -105,7 +105,7 @@ export const AGENT_TOOL_DEFINITIONS = Object.freeze([
105
105
  },
106
106
  {
107
107
  name: 'livedesk.collect_diagnostics',
108
- description: 'Collect the existing safe LiveDesk diagnostics payload from the selected Clients.',
108
+ description: 'Collect the existing safe VuvoDesk diagnostics payload from the selected Clients.',
109
109
  category: 'read',
110
110
  readOnly: true,
111
111
  mutating: false,
@@ -207,8 +207,8 @@ export const AGENT_TOOL_DEFINITIONS = Object.freeze([
207
207
  inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, recursive: { type: 'boolean' } }, ['path']),
208
208
  defaultPermission: taskPermission
209
209
  },
210
- {
211
- name: 'livedesk.list_directory',
210
+ {
211
+ name: 'livedesk.list_directory',
212
212
  description: 'List bounded file metadata from a directory on the selected Clients.',
213
213
  category: 'fileRead',
214
214
  readOnly: true,
@@ -218,37 +218,37 @@ export const AGENT_TOOL_DEFINITIONS = Object.freeze([
218
218
  supportsBatch: true,
219
219
  supportedPlatforms: ['windows', 'macos', 'linux'],
220
220
  inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, recursive: { type: 'boolean' }, maxEntries: { type: 'integer', minimum: 1, maximum: 500 } }, ['path']),
221
- defaultPermission: readTaskPermission
222
- },
223
- {
224
- name: 'livedesk.search_files',
225
- description: 'Search file and folder names under one bounded Client directory without reading file contents.',
226
- category: 'fileRead',
227
- readOnly: true,
228
- mutating: false,
229
- risk: 'low',
230
- reversible: true,
231
- supportsBatch: true,
232
- supportedPlatforms: ['windows', 'macos', 'linux'],
233
- inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, query: { type: 'string', minLength: 1, maxLength: 160 }, maxResults: { type: 'integer', minimum: 1, maximum: 200 }, maxDepth: { type: 'integer', minimum: 0, maximum: 8 }, maxScannedEntries: { type: 'integer', minimum: 1, maximum: 5000 } }, ['path', 'query']),
234
- defaultPermission: readTaskPermission
235
- },
236
- {
237
- name: 'livedesk.create_directory',
238
- description: 'Create a directory on the selected Clients. Safe Auto remains limited to the LiveDesk files directory.',
239
- category: 'fileWrite',
240
- readOnly: false,
241
- mutating: true,
242
- risk: 'medium',
243
- reversible: true,
244
- supportsBatch: true,
245
- supportedPlatforms: ['windows', 'macos', 'linux'],
246
- inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 } }, ['path']),
247
- defaultPermission: taskPermission
248
- },
249
- {
250
- name: 'livedesk.run_command',
251
- description: 'Run a bounded PowerShell, cmd, sh, bash, zsh, or pwsh command on one selected Client. Choose the shell explicitly when syntax matters. This is an audited high-risk action.',
221
+ defaultPermission: readTaskPermission
222
+ },
223
+ {
224
+ name: 'livedesk.search_files',
225
+ description: 'Search file and folder names under one bounded Client directory without reading file contents.',
226
+ category: 'fileRead',
227
+ readOnly: true,
228
+ mutating: false,
229
+ risk: 'low',
230
+ reversible: true,
231
+ supportsBatch: true,
232
+ supportedPlatforms: ['windows', 'macos', 'linux'],
233
+ inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 }, query: { type: 'string', minLength: 1, maxLength: 160 }, maxResults: { type: 'integer', minimum: 1, maximum: 200 }, maxDepth: { type: 'integer', minimum: 0, maximum: 8 }, maxScannedEntries: { type: 'integer', minimum: 1, maximum: 5000 } }, ['path', 'query']),
234
+ defaultPermission: readTaskPermission
235
+ },
236
+ {
237
+ name: 'livedesk.create_directory',
238
+ description: 'Create a directory on the selected Clients. Safe Auto remains limited to the VuvoDesk files directory.',
239
+ category: 'fileWrite',
240
+ readOnly: false,
241
+ mutating: true,
242
+ risk: 'medium',
243
+ reversible: true,
244
+ supportsBatch: true,
245
+ supportedPlatforms: ['windows', 'macos', 'linux'],
246
+ inputSchema: withRequiredInput({ path: { type: 'string', minLength: 1, maxLength: 600 } }, ['path']),
247
+ defaultPermission: taskPermission
248
+ },
249
+ {
250
+ name: 'livedesk.run_command',
251
+ description: 'Run a bounded PowerShell, cmd, sh, bash, zsh, or pwsh command on one selected Client. Choose the shell explicitly when syntax matters. This is an audited high-risk action.',
252
252
  category: 'shell',
253
253
  readOnly: false,
254
254
  mutating: true,
@@ -256,7 +256,7 @@ export const AGENT_TOOL_DEFINITIONS = Object.freeze([
256
256
  reversible: false,
257
257
  supportsBatch: false,
258
258
  supportedPlatforms: ['windows', 'macos', 'linux'],
259
- inputSchema: withRequiredInput({ command: { type: 'string', minLength: 1, maxLength: 16000 }, shell: { type: 'string', enum: ['auto', 'powershell', 'pwsh', 'cmd', 'sh', 'bash', 'zsh'] }, workingDirectory: { type: 'string', maxLength: 600 }, timeoutMs: { type: 'integer', minimum: 1000, maximum: 300000 } }, ['command']),
259
+ inputSchema: withRequiredInput({ command: { type: 'string', minLength: 1, maxLength: 16000 }, shell: { type: 'string', enum: ['auto', 'powershell', 'pwsh', 'cmd', 'sh', 'bash', 'zsh'] }, workingDirectory: { type: 'string', maxLength: 600 }, timeoutMs: { type: 'integer', minimum: 1000, maximum: 300000 } }, ['command']),
260
260
  defaultPermission: taskPermission
261
261
  },
262
262
  {
@@ -75,9 +75,9 @@ function normalizeCodexError(error) {
75
75
  const message = safeText(error?.message, 800) || 'Codex run failed.';
76
76
  if (error instanceof AgentRuntimeError && error.code !== 'codex-run-failed') return error;
77
77
  if (/out of credits|usage limit|rate limit|too many requests/i.test(message)) return new AgentRuntimeError('codex-usage-limit-reached', 'The Codex workspace has no remaining usage.', { status: 402, retryable: true });
78
- if (/refresh[_ -]?token[_ -]?(?:already[_ -]?)?used|token refresh|refresh credential/i.test(message)) return new AgentRuntimeError('codex-auth-stale', 'The saved Codex sign-in is stale. LiveDesk refreshed its Codex authentication bridge and will retry.', { status: 401, retryable: true });
78
+ if (/refresh[_ -]?token[_ -]?(?:already[_ -]?)?used|token refresh|refresh credential/i.test(message)) return new AgentRuntimeError('codex-auth-stale', 'The saved Codex sign-in is stale. VuvoDesk refreshed its Codex authentication bridge and will retry.', { status: 401, retryable: true });
79
79
  if (/not authenticated|not logged in|authentication|unauthorized|login required/i.test(message)) return new AgentRuntimeError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
80
- if (/user cancelled MCP tool call/i.test(message)) return new AgentRuntimeError('codex-mcp-tool-rejected', 'Codex rejected the LiveDesk MCP tool before it reached the Hub.', { status: 502 });
80
+ if (/user cancelled MCP tool call/i.test(message)) return new AgentRuntimeError('codex-mcp-tool-rejected', 'Codex rejected the VuvoDesk MCP tool before it reached the Hub.', { status: 502 });
81
81
  return new AgentRuntimeError('codex-run-failed', message, { status: 502, retryable: true });
82
82
  }
83
83
 
@@ -180,7 +180,7 @@ async function ensureCodexHome(codexHome, globalCodexHome, { preferGlobalAuth =
180
180
  const resolvedHome = path.resolve(codexHome);
181
181
  const globalHome = path.resolve(globalCodexHome);
182
182
  if (!resolvedHome || resolvedHome === globalHome) {
183
- throw new AgentRuntimeError('codex-isolation-unavailable', 'Codex requires a dedicated LiveDesk home.', { status: 503 });
183
+ throw new AgentRuntimeError('codex-isolation-unavailable', 'Codex requires a dedicated VuvoDesk home.', { status: 503 });
184
184
  }
185
185
  try {
186
186
  await mkdir(resolvedHome, { recursive: true, mode: 0o700 });
@@ -628,7 +628,7 @@ export function createCodexAgentRuntime({
628
628
 
629
629
  async function loadCodex() {
630
630
  if (!codexModulePromise) codexModulePromise = import('@openai/codex-sdk').catch(error => {
631
- const wrapped = new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed in this LiveDesk package.', { status: 503 });
631
+ const wrapped = new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed in this VuvoDesk package.', { status: 503 });
632
632
  wrapped.cause = error;
633
633
  throw wrapped;
634
634
  });
@@ -723,13 +723,13 @@ export function createCodexAgentRuntime({
723
723
 
724
724
  function promptFor({ instruction, deviceIds, permissionPolicy }) {
725
725
  return [
726
- 'You are the LiveDesk Agent orchestrator.',
727
- 'Use only the registered livedesk.* MCP tools exposed by the LiveDesk server.',
726
+ 'You are the VuvoDesk Agent orchestrator.',
727
+ 'Use only the registered livedesk.* MCP tools exposed by the VuvoDesk server.',
728
728
  'Never use arbitrary MCP servers, change permissions, forge approvals, request credentials, or invent a tool result.',
729
729
  `The Hub has fixed this run to permission mode ${permissionPolicy?.mode || 'ask'} and enforces the policy independently of your instructions.`,
730
730
  'Use only the selected connected device IDs below. If the Hub asks for user approval, wait for that approval result and do not work around it.',
731
731
  'You may perform multiple safe read-only checks when the request requires a sequence. For example, find Clients missing a named process, then check a related service only on those Clients.',
732
- 'If one read-only result omits a fact the user requested, do not treat that missing field as proof that the fact is unavailable. Try another applicable registered LiveDesk read-only tool.',
732
+ 'If one read-only result omits a fact the user requested, do not treat that missing field as proof that the fact is unavailable. Try another applicable registered VuvoDesk read-only tool.',
733
733
  'If only a high-risk registered tool such as livedesk.run_command can obtain the missing fact, use it only when necessary, one selected device per call, and let the Hub approval policy ask the user. If approval is denied or no applicable tool exists, explain that exact limit.',
734
734
  `Selected device IDs: ${JSON.stringify(deviceIds)}`,
735
735
  'Return a concise Korean or English summary grounded only in tool results. Do not invent results.',
@@ -754,7 +754,7 @@ export function createCodexAgentRuntime({
754
754
  timeoutTimer.unref?.();
755
755
  await mkdir(workspace, { recursive: true });
756
756
  if (activeCount > settings.maxConcurrentRequests) {
757
- throw new AgentRuntimeError('agent-concurrency-limit', 'The LiveDesk Agent concurrency limit is reached.', { status: 429, retryable: true });
757
+ throw new AgentRuntimeError('agent-concurrency-limit', 'The VuvoDesk Agent concurrency limit is reached.', { status: 429, retryable: true });
758
758
  }
759
759
  if (run.abortController.signal.aborted) {
760
760
  if (run.abortReason === 'timeout') throw codexAbortError('codex-run-timeout', 'The Codex task exceeded its timeout.');
@@ -763,7 +763,7 @@ export function createCodexAgentRuntime({
763
763
  const status = await getStatus();
764
764
  if (!status.installed) throw new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
765
765
  if (status.authenticated !== 'signed-in') throw new AgentRuntimeError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
766
- reportProgress(run, 'preparing-tools', 'Codex is ready. Preparing the LiveDesk tools for this Client.');
766
+ reportProgress(run, 'preparing-tools', 'Codex is ready. Preparing the VuvoDesk tools for this Client.');
767
767
  session = createMcpSession({
768
768
  runId: run.runId,
769
769
  signal: run.abortController.signal,
@@ -805,7 +805,7 @@ export function createCodexAgentRuntime({
805
805
  for await (const event of streamed.events) {
806
806
  emit(run, event);
807
807
  if (event.type === 'item.started' && event.item?.type === 'mcp_tool_call') {
808
- reportProgress(run, 'tool-running', `Running ${safeText(event.item.tool, 120) || 'a LiveDesk tool'}.`, {
808
+ reportProgress(run, 'tool-running', `Running ${safeText(event.item.tool, 120) || 'a VuvoDesk tool'}.`, {
809
809
  toolName: safeText(event.item.tool, 120)
810
810
  });
811
811
  }
@@ -815,17 +815,17 @@ export function createCodexAgentRuntime({
815
815
  run.abortReason = 'turn-limit';
816
816
  run.abortController.abort();
817
817
  session.cancel();
818
- throw new AgentRuntimeError('codex-turn-limit-reached', 'Codex exceeded the LiveDesk turn limit.', { status: 409 });
818
+ throw new AgentRuntimeError('codex-turn-limit-reached', 'Codex exceeded the VuvoDesk turn limit.', { status: 409 });
819
819
  }
820
820
  }
821
821
  if (run.toolCallCount > run.permissionPolicy.maxToolCalls) {
822
822
  run.abortReason = 'tool-limit';
823
823
  run.abortController.abort();
824
824
  session.cancel();
825
- throw new AgentRuntimeError('codex-tool-limit-reached', 'Codex exceeded the LiveDesk tool-call limit.', { status: 409 });
825
+ throw new AgentRuntimeError('codex-tool-limit-reached', 'Codex exceeded the VuvoDesk tool-call limit.', { status: 409 });
826
826
  }
827
827
  if (event.type === 'item.completed' && event.item?.type === 'mcp_tool_call' && event.item?.status === 'failed') {
828
- throw new Error(safeText(event.item?.error?.message, 800) || `LiveDesk tool ${safeText(event.item?.tool, 120) || 'call'} failed.`);
828
+ throw new Error(safeText(event.item?.error?.message, 800) || `VuvoDesk tool ${safeText(event.item?.tool, 120) || 'call'} failed.`);
829
829
  }
830
830
  if (event.type === 'item.completed' && event.item?.type === 'agent_message') run.finalResponse = safeText(event.item.text, 4000);
831
831
  if (event.type === 'turn.completed') turnCompleted = true;
@@ -885,12 +885,12 @@ export function createCodexAgentRuntime({
885
885
  } else if (run.abortReason === 'tool-limit') {
886
886
  run.status = 'failed';
887
887
  run.error = 'codex-tool-limit-reached';
888
- run.errorMessage = 'Codex exceeded the LiveDesk tool-call limit.';
888
+ run.errorMessage = 'Codex exceeded the VuvoDesk tool-call limit.';
889
889
  run.errorStage = run.stage || 'unknown';
890
890
  } else if (run.abortReason === 'turn-limit') {
891
891
  run.status = 'failed';
892
892
  run.error = 'codex-turn-limit-reached';
893
- run.errorMessage = 'Codex exceeded the LiveDesk turn limit.';
893
+ run.errorMessage = 'Codex exceeded the VuvoDesk turn limit.';
894
894
  run.errorStage = run.stage || 'unknown';
895
895
  } else if (isAbortError(error) || run.abortController.signal.aborted) {
896
896
  run.status = 'cancelled';
@@ -934,7 +934,7 @@ export function createCodexAgentRuntime({
934
934
  if (!settings.enabled) throw new AgentRuntimeError('agent-disabled', 'Codex Agent is disabled in Settings.', { status: 409 });
935
935
  assertSecurityConfiguration();
936
936
  pruneRuns();
937
- if (runs.size >= MAX_RUNS) throw new AgentRuntimeError('agent-run-limit', 'The LiveDesk Agent run history is full.', { status: 429, retryable: true });
937
+ if (runs.size >= MAX_RUNS) throw new AgentRuntimeError('agent-run-limit', 'The VuvoDesk Agent run history is full.', { status: 429, retryable: true });
938
938
  const permissionPolicy = freezeAgentPermissionPolicy(input.permissionPolicy && typeof input.permissionPolicy === 'object'
939
939
  ? input.permissionPolicy
940
940
  : createAgentPermissionPolicy({ mode: input.permissionMode || 'safe-auto', deviceIds, maxToolCalls: settings.codexMaxToolCalls }));
@@ -13,7 +13,7 @@ const toolDefinitions = AGENT_TOOL_DEFINITIONS.map(tool => ({
13
13
  readOnlyHint: tool.readOnly === true,
14
14
  // Codex exec cannot service an interactive MCP approval prompt. Device
15
15
  // mutations are proposals at this boundary and remain fail-closed behind
16
- // LiveDesk's own signed permission policy and Hub approval workflow.
16
+ // VuvoDesk's own signed permission policy and Hub approval workflow.
17
17
  destructiveHint: false,
18
18
  idempotentHint: tool.readOnly === true || tool.reversible === true,
19
19
  openWorldHint: false
@@ -29,14 +29,14 @@ function errorResponse(id, code, message) {
29
29
  }
30
30
 
31
31
  async function callHub(name, args) {
32
- if (!hubUrl || !token) throw new Error('LiveDesk MCP session is not configured.');
32
+ if (!hubUrl || !token) throw new Error('VuvoDesk MCP session is not configured.');
33
33
  const result = await fetch(`${hubUrl}/api/internal/agent-mcp/tool`, {
34
34
  method: 'POST',
35
35
  headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
36
36
  body: JSON.stringify({ name, arguments: args && typeof args === 'object' ? args : {} })
37
37
  });
38
38
  const body = await result.json().catch(() => ({}));
39
- if (!result.ok || body.ok !== true) throw new Error(String(body.error || `LiveDesk tool failed (${result.status}).`));
39
+ if (!result.ok || body.ok !== true) throw new Error(String(body.error || `VuvoDesk tool failed (${result.status}).`));
40
40
  return body.result;
41
41
  }
42
42
 
@@ -63,7 +63,7 @@ async function handle(message) {
63
63
  if (method === 'tools/call') {
64
64
  const name = String(message?.params?.name || '');
65
65
  if (!toolDefinitions.some(tool => tool.name === name)) {
66
- errorResponse(id, -32602, 'Unknown LiveDesk tool.');
66
+ errorResponse(id, -32602, 'Unknown VuvoDesk tool.');
67
67
  return;
68
68
  }
69
69
  try {
@@ -75,7 +75,7 @@ async function handle(message) {
75
75
  });
76
76
  } catch (error) {
77
77
  response(id, {
78
- content: [{ type: 'text', text: String(error?.message || 'LiveDesk tool failed.') }],
78
+ content: [{ type: 'text', text: String(error?.message || 'VuvoDesk tool failed.') }],
79
79
  isError: true
80
80
  });
81
81
  }
@@ -98,26 +98,26 @@ async function mountedRoots() {
98
98
  return roots;
99
99
  }
100
100
 
101
- export async function discoverFilesystemRoots() {
102
- const isolatedTestRoot = process.env.LIVEDESK_TEST_MODE === '1'
103
- ? normalizeRootPath(process.env.LIVEDESK_TEST_FILESYSTEM_ROOT)
104
- : '';
105
- if (isolatedTestRoot) {
106
- const rootStat = await fs.stat(isolatedTestRoot);
107
- if (!rootStat.isDirectory()) {
108
- throw new Error('LIVEDESK_TEST_FILESYSTEM_ROOT must be a directory.');
109
- }
110
- return [{
111
- path: isolatedTestRoot,
112
- name: 'Test drive',
113
- displayPath: isolatedTestRoot,
114
- type: 'drive',
115
- driveType: 'fixed',
116
- ...await statfsCapacity(isolatedTestRoot),
117
- hasChildren: true
118
- }];
119
- }
120
- const roots = process.platform === 'win32' ? await windowsRoots() : await mountedRoots();
101
+ export async function discoverFilesystemRoots() {
102
+ const isolatedTestRoot = process.env.LIVEDESK_TEST_MODE === '1'
103
+ ? normalizeRootPath(process.env.LIVEDESK_TEST_FILESYSTEM_ROOT)
104
+ : '';
105
+ if (isolatedTestRoot) {
106
+ const rootStat = await fs.stat(isolatedTestRoot);
107
+ if (!rootStat.isDirectory()) {
108
+ throw new Error('LIVEDESK_TEST_FILESYSTEM_ROOT must be a directory.');
109
+ }
110
+ return [{
111
+ path: isolatedTestRoot,
112
+ name: 'Test drive',
113
+ displayPath: isolatedTestRoot,
114
+ type: 'drive',
115
+ driveType: 'fixed',
116
+ ...await statfsCapacity(isolatedTestRoot),
117
+ hasChildren: true
118
+ }];
119
+ }
120
+ const roots = process.platform === 'win32' ? await windowsRoots() : await mountedRoots();
121
121
  if (roots.length > 0) return roots;
122
122
  const fallback = process.platform === 'win32' ? `${process.env.SystemDrive || 'C:'}\\` : os.homedir();
123
123
  return [{
@@ -44,7 +44,7 @@ export class HubSharedFolders {
44
44
  if (folder?.id && folder?.sourcePath) this.folders.set(folder.id, { ...folder, manifestVersion: MANIFEST_VERSION });
45
45
  }
46
46
  } catch (error) {
47
- if (error?.code !== 'ENOENT') console.warn(`[LiveDesk Hub] shared folder store unavailable: ${error?.message || error}`);
47
+ if (error?.code !== 'ENOENT') console.warn(`[VuvoDesk Hub] shared folder store unavailable: ${error?.message || error}`);
48
48
  }
49
49
  }
50
50
 
@@ -111,7 +111,7 @@ export class HubSharedFolders {
111
111
  if (this.syncing.has(folder.id)) throw new Error('sync-already-running');
112
112
  const source = await this.filesystem.registerPersistedFolder(folder.sourcePath, folder.displayPath);
113
113
  if (!source) throw new Error('sync-source-unavailable');
114
- folder.remoteDirectory = String(remoteDirectory || folder.remoteDirectory || 'Desktop/LiveDeskFiles').replace(/[\0\r\n\t]/g, ' ').trim().slice(0, 600);
114
+ folder.remoteDirectory = String(remoteDirectory || folder.remoteDirectory || 'Desktop/VuvoDeskFiles').replace(/[\0\r\n\t]/g, ' ').trim().slice(0, 600);
115
115
  const scan = await this.filesystem.scan([source.id]);
116
116
  folder.fileCount = scan.files.length;
117
117
  folder.totalBytes = scan.totalBytes;
@@ -79,14 +79,14 @@ function buildLegacyPackageSupervisorStarterSource() {
79
79
  "const{spawn}=require('node:child_process'),f=require('node:fs'),p=require('node:path'),o=require('node:os'),c=JSON.parse(Buffer.from(process.env.C,'base64')),e={...process.env,LIVEDESK_UPDATE_STARTER_PID:String(process.pid),LIVEDESK_UPDATE_ORIGINAL_CWD:process.cwd()},w=p.join(o.tmpdir(),'livedesk-update-cwd','client-'+c[0]),k=p.join(o.tmpdir(),'livedesk-update-cache','client-'+c[0]),u=e.LIVEDESK_NPX_EXECUTABLE,s=process.platform,j=process.execPath,n=u||(s==='win32'?'npx.cmd':'npx'),x=[e.LIVEDESK_NPX_CLI_PATH,e.npm_execpath&&p.join(p.dirname(e.npm_execpath),'npx-cli.js'),u&&p.join(p.dirname(u),'node_modules/npm/bin/npx-cli.js'),p.join(p.dirname(j),'node_modules/npm/bin/npx-cli.js')].find(v=>v&&f.existsSync(v)),a=['-y','--prefer-online','--prefix',w,'--workspaces=false','livedesk@'+c[6],'--internal-legacy-client-update'],z=String.fromCharCode(32),q=v=>'\"'+String(v).replaceAll('\"','\"\"')+'\"';if(!(+c[7]>0))process.exit(1);c[7]=String(Date.now()+Number(c[7]));e.C=Buffer.from(JSON.stringify(c)).toString('base64');f.mkdirSync(w,{recursive:true});if(f.readdirSync(w)[0])process.exit(1);f.mkdirSync(k,{recursive:true});Object.keys(e).forEach(n=>/^(INIT_CWD|npm_config_(cache|local_prefix|workspaces?|include_workspace_root))$/i.test(n)&&Reflect.deleteProperty(e,n));Object.assign(e,{INIT_CWD:w,npm_config_cache:k,npm_config_local_prefix:w,npm_config_workspaces:'false',npm_config_include_workspace_root:'false',LIVEDESK_UPDATE_NEUTRAL_CWD:w});e.i=x?{c:j,a:[x,...a]}:s==='win32'?{c:e.ComSpec||'cmd.exe',a:['/d','/s','/c','call'+z+q(n)+z+a.map(q).join(z)]}:{c:n,a};e.r=spawn(e.i.c,e.i.a,{cwd:w,env:e,detached:true,stdio:'ignore',windowsHide:true});",
80
80
  "e.r.on('error',()=>process.exit(1));e.r.unref();"
81
81
  ].join('');
82
- if (source.includes(' ')) throw new Error('LiveDesk legacy starter source must not contain spaces.');
82
+ if (source.includes(' ')) throw new Error('VuvoDesk legacy starter source must not contain spaces.');
83
83
  return source;
84
84
  }
85
85
 
86
86
  function assertLegacyCommandLength(command, platform) {
87
87
  if (command.length > LIVE_DESK_LEGACY_COMMAND_MAX_LENGTH) {
88
88
  throw new Error(
89
- `LiveDesk legacy ${platform} update command is ${command.length} characters; `
89
+ `VuvoDesk legacy ${platform} update command is ${command.length} characters; `
90
90
  + `the compatibility limit is ${LIVE_DESK_LEGACY_COMMAND_MAX_LENGTH}.`
91
91
  );
92
92
  }
@@ -112,10 +112,10 @@ function buildLegacyPlatformCommand(platform, payload) {
112
112
  "$ErrorActionPreference='Stop'",
113
113
  '$n=[string]$env:LIVEDESK_NODE_EXECUTABLE',
114
114
  'if(-not $n){$n=(Get-Command node.exe -ErrorAction SilentlyContinue).Source}',
115
- "if(-not $n){throw 'LiveDesk node executable was not found.'}",
115
+ "if(-not $n){throw 'VuvoDesk node executable was not found.'}",
116
116
  "$j=\"eval(Buffer.from(process.env.S,'base64').toString('utf8'))\"",
117
117
  "$p=Start-Process -FilePath $n -ArgumentList @('-e',$j) -WindowStyle Hidden -PassThru",
118
- "if(-not $p){throw 'LiveDesk update starter failed.'}"
118
+ "if(-not $p){throw 'VuvoDesk update starter failed.'}"
119
119
  ].join(';');
120
120
  const command = windows
121
121
  ? `set C=${configBase64}&set S=${starterBase64}&powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encodePowerShell(powerShellStarter)}`
@@ -159,7 +159,7 @@ async function fetchLatestPackage(packageName, fetchImpl) {
159
159
  }
160
160
 
161
161
  export async function fetchLatestLiveDeskRelease(fetchImpl = globalThis.fetch) {
162
- if (typeof fetchImpl !== 'function') throw new Error('fetch is unavailable for LiveDesk update checks.');
162
+ if (typeof fetchImpl !== 'function') throw new Error('fetch is unavailable for VuvoDesk update checks.');
163
163
  const [manager, client] = await Promise.all([
164
164
  fetchLatestPackage('livedesk', fetchImpl),
165
165
  fetchLatestPackage('@livedesk/client', fetchImpl)
@@ -379,7 +379,7 @@ export function createLiveDeskUpdateManager({
379
379
  const failRun = (message) => {
380
380
  if (!run) return;
381
381
  run.state = 'failed';
382
- run.error = String(message || 'LiveDesk update failed.');
382
+ run.error = String(message || 'VuvoDesk update failed.');
383
383
  for (const target of run.targets) {
384
384
  if (target.state === 'completed') continue;
385
385
  target.state = 'failed';
@@ -428,7 +428,7 @@ export function createLiveDeskUpdateManager({
428
428
 
429
429
  const requestRestart = () => {
430
430
  if (!restartSupported) {
431
- failRun('Hub launcher restart is unavailable. Start LiveDesk through npx livedesk@latest.');
431
+ failRun('Hub launcher restart is unavailable. Start VuvoDesk through npx livedesk@latest.');
432
432
  return false;
433
433
  }
434
434
  const result = requestHubRestart?.({
@@ -686,7 +686,7 @@ export function createLiveDeskUpdateManager({
686
686
  };
687
687
  }
688
688
  const release = latestRelease || await checkLatest();
689
- if (!release) return { ok: false, error: checkError || 'LiveDesk update check failed.', ...getStatus() };
689
+ if (!release) return { ok: false, error: checkError || 'VuvoDesk update check failed.', ...getStatus() };
690
690
  const managerNeedsUpdate = compareVersions(release.latestManagerVersion, currentManagerVersion) > 0;
691
691
  const clientPackageNeedsUpdate = compareVersions(release.latestClientVersion, currentClientVersion) > 0;
692
692
  const connected = connectedClientDevices();
@@ -700,7 +700,7 @@ export function createLiveDeskUpdateManager({
700
700
  return { ok: true, state: 'clients-updated', ...getStatus() };
701
701
  }
702
702
  if (needsHubRestart && !restartSupported) {
703
- return { ok: false, error: 'Hub launcher restart is unavailable. Start LiveDesk through npx livedesk@latest.', ...getStatus() };
703
+ return { ok: false, error: 'Hub launcher restart is unavailable. Start VuvoDesk through npx livedesk@latest.', ...getStatus() };
704
704
  }
705
705
  const targets = outdatedClients;
706
706
  run = {