livedesk 0.1.448 → 0.1.449

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.
@@ -47,9 +47,6 @@ const PRESERVED_SINGLE_FLAGS = new Set([
47
47
  '--no-audio',
48
48
  '--tasks',
49
49
  '--no-tasks',
50
- '--ai',
51
- '--no-ai',
52
- '--fake-ai',
53
50
  '--fake-thumbnail',
54
51
  '--trace-frames'
55
52
  ]);
@@ -57,7 +54,6 @@ const PRESERVED_VALUE_FLAGS = new Set([
57
54
  '--name',
58
55
  '--heartbeat',
59
56
  '--files-dir',
60
- '--ai-model',
61
57
  '--transport'
62
58
  ]);
63
59
  const JOINED_VALUE_FLAGS = new Set(['--name', '--files-dir']);
@@ -71,9 +67,7 @@ const SETTING_GROUPS = new Map([
71
67
  ['--audio', 'audio'],
72
68
  ['--no-audio', 'audio'],
73
69
  ['--tasks', 'tasks'],
74
- ['--no-tasks', 'tasks'],
75
- ['--ai', 'ai'],
76
- ['--no-ai', 'ai']
70
+ ['--no-tasks', 'tasks']
77
71
  ]);
78
72
 
79
73
  function cleanVersion(value) {
@@ -19,15 +19,13 @@ const AGENT_VERSION = (() => {
19
19
  const PRODUCT_VERSION = String(process.env.LIVEDESK_NPM_LAUNCHER_VERSION || AGENT_VERSION).trim() || AGENT_VERSION;
20
20
  const DEFAULT_MANAGER = '127.0.0.1:5197';
21
21
  const DEFAULT_HEARTBEAT_MS = 5000;
22
- const DEFAULT_RECONNECT_MS = 5000;
23
- const EXIT_INVALID_PAIR_TOKEN = 23;
24
- const EXIT_CLIENT_UPDATE = 42;
25
- const DEFAULT_AI_MODEL = 'gpt-5.4-mini';
26
- const DEFAULT_LIVE_FPS = 30;
27
- const MAX_LIVE_FPS = 30;
28
- const MAX_FRAME_BASE64_CHARS = 3 * 1024 * 1024;
29
- const MAX_AI_OUTPUT_CHARS = 6000;
30
- const MAX_FILE_TRANSFER_FILES = 24;
22
+ const DEFAULT_RECONNECT_MS = 5000;
23
+ const EXIT_INVALID_PAIR_TOKEN = 23;
24
+ const EXIT_CLIENT_UPDATE = 42;
25
+ const DEFAULT_LIVE_FPS = 30;
26
+ const MAX_LIVE_FPS = 30;
27
+ const MAX_FRAME_BASE64_CHARS = 3 * 1024 * 1024;
28
+ const MAX_FILE_TRANSFER_FILES = 24;
31
29
  const MAX_FILE_TRANSFER_BYTES = 24 * 1024 * 1024;
32
30
  const MAX_AGENT_OUTPUT_CHARS = 32000;
33
31
  const launchedProcessRegistry = new Map();
@@ -281,14 +279,10 @@ Options:
281
279
  --no-thumbnail Disable thumbnail capture capability.
282
280
  --live Enable focused live screen streaming. Default on.
283
281
  --no-live Disable focused live screen streaming.
284
- --tasks Enable safe remote task inbox. Default on.
285
- --no-tasks Disable remote task dispatch capability.
286
- --files-dir <path> Default folder for received files. Default: ~/Desktop/LiveDeskFiles.
287
- --ai Enable OpenAI-backed remote AI assist tasks.
288
- --no-ai Disable OpenAI-backed remote AI assist tasks.
289
- --ai-model <model> OpenAI model for AI assist. Default: ${DEFAULT_AI_MODEL}
290
- --fake-ai Use deterministic AI assist responses for smoke tests.
291
- --fake-thumbnail Use generated thumbnail frames for smoke tests.
282
+ --tasks Enable safe remote task inbox. Default on.
283
+ --no-tasks Disable remote task dispatch capability.
284
+ --files-dir <path> Default folder for received files. Default: ~/Desktop/LiveDeskFiles.
285
+ --fake-thumbnail Use generated thumbnail frames for smoke tests.
292
286
  --exit-on-disconnect Exit after Hub disconnect. Useful for tests.
293
287
  --exit-on-invalid-pair Exit when the Hub rejects the pair token.
294
288
  --once Connect, send one status packet, and exit after welcome.
@@ -328,14 +322,10 @@ function parseArgs(argv) {
328
322
  heartbeatMs: DEFAULT_HEARTBEAT_MS,
329
323
  deviceId: '',
330
324
  thumbnailEnabled: defaultDesktopCaptureEnabled && !isFalsy(process.env.LIVEDESK_CLIENT_THUMBNAIL ?? process.env.MINDEXEC_REMOTE_THUMBNAIL),
331
- liveEnabled: defaultDesktopCaptureEnabled && !isFalsy(process.env.LIVEDESK_CLIENT_LIVE ?? process.env.MINDEXEC_REMOTE_LIVE),
332
- taskEnabled: !isFalsy(process.env.LIVEDESK_CLIENT_TASKS ?? process.env.MINDEXEC_REMOTE_TASKS),
333
- filesDir: process.env.LIVEDESK_CLIENT_FILES_DIR || process.env.MINDEXEC_REMOTE_FILES_DIR || '',
334
- aiEnabled: isTruthy(process.env.LIVEDESK_CLIENT_AI || process.env.MINDEXEC_REMOTE_AI),
335
- aiModel: process.env.LIVEDESK_CLIENT_AI_MODEL || process.env.MINDEXEC_REMOTE_AI_MODEL || process.env.OPENAI_MODEL || DEFAULT_AI_MODEL,
336
- openAiApiKey: process.env.OPENAI_API_KEY || '',
337
- fakeAi: isTruthy(process.env.LIVEDESK_CLIENT_FAKE_AI || process.env.MINDEXEC_REMOTE_FAKE_AI),
338
- fakeThumbnail: isTruthy(process.env.LIVEDESK_CLIENT_FAKE_THUMBNAIL || process.env.MINDEXEC_REMOTE_FAKE_THUMBNAIL),
325
+ liveEnabled: defaultDesktopCaptureEnabled && !isFalsy(process.env.LIVEDESK_CLIENT_LIVE ?? process.env.MINDEXEC_REMOTE_LIVE),
326
+ taskEnabled: !isFalsy(process.env.LIVEDESK_CLIENT_TASKS ?? process.env.MINDEXEC_REMOTE_TASKS),
327
+ filesDir: process.env.LIVEDESK_CLIENT_FILES_DIR || process.env.MINDEXEC_REMOTE_FILES_DIR || '',
328
+ fakeThumbnail: isTruthy(process.env.LIVEDESK_CLIENT_FAKE_THUMBNAIL || process.env.MINDEXEC_REMOTE_FAKE_THUMBNAIL),
339
329
  exitOnDisconnect: false,
340
330
  exitOnInvalidPair: false,
341
331
  once: false,
@@ -387,26 +377,11 @@ function parseArgs(argv) {
387
377
  case '--no-tasks':
388
378
  result.taskEnabled = false;
389
379
  break;
390
- case '--files-dir':
391
- result.filesDir = args[++index] || result.filesDir;
392
- break;
393
- case '--ai':
394
- result.aiEnabled = true;
395
- result.taskEnabled = true;
396
- break;
397
- case '--no-ai':
398
- result.aiEnabled = false;
399
- break;
400
- case '--ai-model':
401
- result.aiModel = args[++index] || result.aiModel;
402
- break;
403
- case '--fake-ai':
404
- result.fakeAi = true;
405
- result.aiEnabled = true;
406
- result.taskEnabled = true;
407
- break;
408
- case '--fake-thumbnail':
409
- result.fakeThumbnail = true;
380
+ case '--files-dir':
381
+ result.filesDir = args[++index] || result.filesDir;
382
+ break;
383
+ case '--fake-thumbnail':
384
+ result.fakeThumbnail = true;
410
385
  result.thumbnailEnabled = true;
411
386
  break;
412
387
  case '--exit-on-disconnect':
@@ -586,7 +561,7 @@ function getStatus(options = {}) {
586
561
  usedMemRatio,
587
562
  platform: os.platform(),
588
563
  release: os.release(),
589
- role: options.aiEnabled ? 'Agent worker' : options.taskEnabled ? 'Task worker' : 'Local machine',
564
+ role: options.taskEnabled ? 'Command-ready Client' : 'Local machine',
590
565
  cpu: {
591
566
  cores: cpuCores,
592
567
  model: cpus[0]?.model || '',
@@ -609,9 +584,9 @@ function getStatus(options = {}) {
609
584
  gpu: acceleratorStatus.gpu,
610
585
  npu: acceleratorStatus.npu,
611
586
  network: getNetworkStatus(),
612
- workload: {
613
- role: options.aiEnabled ? 'Agent worker' : options.taskEnabled ? 'Task worker' : 'Local machine',
614
- status: 'idle',
587
+ workload: {
588
+ role: options.taskEnabled ? 'Command-ready Client' : 'Local machine',
589
+ status: 'idle',
615
590
  title: 'idle'
616
591
  },
617
592
  timestamp: new Date().toISOString()
@@ -941,95 +916,17 @@ async function captureThumbnailFrame(options, payload, frameSeq) {
941
916
  return await captureScreenFrame(options, payload, frameSeq, 'thumbnail');
942
917
  }
943
918
 
944
- function normalizeApprovalLevel(value) {
945
- const level = String(value || 'task-only').trim().toLowerCase();
946
- return level === 'ai-assist' ? 'ai-assist' : 'task-only';
947
- }
948
-
949
- function buildAiPrompt(options, payload, instruction) {
950
- const title = String(payload?.title || 'Remote AI task').trim();
951
- return [
952
- 'You are the LiveDesk client AI assistant running on a controlled remote computer.',
953
- 'Complete the LiveDesk Hub task as a text-only assistant.',
954
- 'Do not claim you used shell commands, file writes, browser automation, keyboard input, or mouse input.',
955
- 'If the task requires external side effects, explain what would be needed and stop.',
956
- '',
957
- `Device: ${options.name} (${os.hostname()}, ${os.platform()} ${os.release()}, ${os.arch()})`,
958
- `Task title: ${title}`,
959
- '',
960
- 'Hub instruction:',
961
- instruction
962
- ].join('\n');
963
- }
964
-
965
- function extractResponseText(response) {
966
- if (typeof response?.output_text === 'string' && response.output_text.trim()) {
967
- return response.output_text.trim();
968
- }
969
-
970
- const parts = [];
971
- if (Array.isArray(response?.output)) {
972
- for (const item of response.output) {
973
- if (!Array.isArray(item?.content)) {
974
- continue;
975
- }
976
-
977
- for (const content of item.content) {
978
- if (typeof content?.text === 'string') {
979
- parts.push(content.text);
980
- }
981
- if (typeof content?.output_text === 'string') {
982
- parts.push(content.output_text);
983
- }
984
- }
985
- }
986
- }
987
-
988
- return parts.join('\n').trim();
989
- }
990
-
991
- async function runAiAssistTask(options, payload, instruction) {
992
- if (!options.aiEnabled) {
993
- throw new Error('AI assist capability is disabled. Start the agent with --ai to enable it.');
994
- }
995
-
996
- if (options.fakeAi) {
997
- return {
998
- text: [
999
- `Fake AI assist completed on ${options.name}.`,
1000
- '',
1001
- `Instruction: ${instruction.slice(0, 500)}`,
1002
- '',
1003
- 'No shell, file, input, browser, or persistent side effects were performed.'
1004
- ].join('\n'),
1005
- model: 'fake-ai',
1006
- responseId: `fake-ai-${String(payload?.taskId || crypto.randomUUID()).slice(0, 96)}`
1007
- };
1008
- }
1009
-
1010
- if (!options.openAiApiKey) {
1011
- throw new Error('OPENAI_API_KEY is required for --ai remote tasks.');
1012
- }
1013
-
1014
- const openaiModule = await import('openai');
1015
- const OpenAI = openaiModule.default || openaiModule.OpenAI;
1016
- const client = new OpenAI({ apiKey: options.openAiApiKey });
1017
- const model = String(payload?.model || options.aiModel || DEFAULT_AI_MODEL).trim() || DEFAULT_AI_MODEL;
1018
- const response = await client.responses.create({
1019
- model,
1020
- input: buildAiPrompt(options, payload, instruction)
1021
- });
1022
- const text = extractResponseText(response);
1023
- if (!text) {
1024
- throw new Error('AI assist returned no text output.');
1025
- }
1026
-
1027
- return {
1028
- text: text.slice(0, MAX_AI_OUTPUT_CHARS),
1029
- model,
1030
- responseId: response?.id || ''
1031
- };
1032
- }
919
+ const RETIRED_CLIENT_AGENT_TASK_ERROR = 'agent-ai-assist-retired';
920
+
921
+ function getRetiredClientAgentTaskError(payload = {}) {
922
+ const source = payload && typeof payload === 'object' ? payload : {};
923
+ const approvalLevel = String(source.approvalLevel || '').trim().toLowerCase();
924
+ const hasModelSelection = ['model', 'aiModel', 'aiProvider', 'provider']
925
+ .some(key => Object.prototype.hasOwnProperty.call(source, key));
926
+ return approvalLevel === 'ai-assist' || hasModelSelection
927
+ ? RETIRED_CLIENT_AGENT_TASK_ERROR
928
+ : '';
929
+ }
1033
930
 
1034
931
  function clampInteger(value, min, max, fallback) {
1035
932
  const number = Number(value);
@@ -1846,10 +1743,25 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
1846
1743
  error: 'remote task capability is disabled'
1847
1744
  });
1848
1745
  return;
1849
- }
1850
-
1851
- const payload = message.payload || {};
1852
- const instruction = String(payload.instruction || '').replace(/\0/g, '').trim().slice(0, 4000);
1746
+ }
1747
+
1748
+ const payload = message.payload || {};
1749
+ const retiredTaskError = getRetiredClientAgentTaskError(payload);
1750
+ if (retiredTaskError) {
1751
+ writeJsonLine(socket, {
1752
+ type: 'command.result',
1753
+ commandId: message.commandId,
1754
+ error: retiredTaskError,
1755
+ result: {
1756
+ ok: false,
1757
+ status: 'failed',
1758
+ error: retiredTaskError,
1759
+ completedAt: new Date().toISOString()
1760
+ }
1761
+ });
1762
+ return;
1763
+ }
1764
+ const instruction = String(payload.instruction || '').replace(/\0/g, '').trim().slice(0, 4000);
1853
1765
  if (!instruction) {
1854
1766
  writeJsonLine(socket, {
1855
1767
  type: 'command.result',
@@ -1859,43 +1771,13 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
1859
1771
  return;
1860
1772
  }
1861
1773
 
1862
- const approvalLevel = normalizeApprovalLevel(payload.approvalLevel);
1863
- const completedAt = new Date().toISOString();
1864
- const title = String(payload.title || instruction.split(/\r?\n/)[0] || 'Remote task')
1865
- .replace(/[\r\n\t]/g, ' ')
1866
- .trim()
1867
- .slice(0, 120);
1868
- if (approvalLevel === 'ai-assist') {
1869
- try {
1870
- const aiResult = await runAiAssistTask(options, payload, instruction);
1871
- writeJsonLine(socket, {
1872
- type: 'command.result',
1873
- commandId: message.commandId,
1874
- result: {
1875
- kind: 'agent.task',
1876
- mode: 'ai-assist',
1877
- taskId: String(payload.taskId || '').slice(0, 128),
1878
- title,
1879
- status: 'completed',
1880
- summary: aiResult.text,
1881
- sideEffects: 'none',
1882
- model: aiResult.model,
1883
- responseId: aiResult.responseId,
1884
- instructionPreview: instruction.slice(0, 320),
1885
- completedAt: new Date().toISOString()
1886
- }
1887
- });
1888
- } catch (error) {
1889
- writeJsonLine(socket, {
1890
- type: 'command.result',
1891
- commandId: message.commandId,
1892
- error: error?.message || String(error)
1893
- });
1894
- }
1895
- return;
1896
- }
1897
-
1898
- writeJsonLine(socket, {
1774
+ const completedAt = new Date().toISOString();
1775
+ const title = String(payload.title || instruction.split(/\r?\n/)[0] || 'Remote task')
1776
+ .replace(/[\r\n\t]/g, ' ')
1777
+ .trim()
1778
+ .slice(0, 120);
1779
+
1780
+ writeJsonLine(socket, {
1899
1781
  type: 'command.result',
1900
1782
  commandId: message.commandId,
1901
1783
  result: {
@@ -2140,14 +2022,11 @@ function connectOnce(options, deviceId) {
2140
2022
  taskDispatch: options.taskEnabled,
2141
2023
  clientUpdate: true,
2142
2024
  productVersion: PRODUCT_VERSION,
2143
- agentApproval: options.taskEnabled,
2144
- agentAudit: options.taskEnabled,
2145
- agentTools: [...NODE_AGENT_OPERATIONS],
2146
- elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
2147
- aiAssist: options.taskEnabled && options.aiEnabled && (options.fakeAi || !!options.openAiApiKey),
2148
- aiModel: options.aiModel,
2149
- aiProvider: options.fakeAi ? 'fake' : (options.openAiApiKey ? 'openai' : ''),
2150
- externalEffects: options.taskEnabled
2025
+ agentApproval: options.taskEnabled,
2026
+ agentAudit: options.taskEnabled,
2027
+ agentTools: [...NODE_AGENT_OPERATIONS],
2028
+ elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
2029
+ externalEffects: options.taskEnabled
2151
2030
  }
2152
2031
  });
2153
2032
  });
@@ -67,6 +67,11 @@ const FAST_PREFLIGHT_CACHE_PATH = join(UNIFIED_CLIENT_STATE_DIR, 'fast-preflight
67
67
  const CLIENT_SLOT_PATH = join(UNIFIED_CLIENT_STATE_DIR, 'device-slot.json');
68
68
  const CLIENT_UPDATE_STATE_PATH = process.env.LIVEDESK_CLIENT_UPDATE_STATE_PATH
69
69
  || join(UNIFIED_CLIENT_STATE_DIR, 'client-update.json');
70
+ // Migration-only compatibility: releases before 0.1.204 could persist these
71
+ // Client-side model options in startup/update commands. Consume them without
72
+ // forwarding so an upgrade starts normally, but never restores that feature.
73
+ const RETIRED_CLIENT_MODEL_SINGLE_OPTIONS = new Set(['--ai', '--no-ai', '--fake-ai']);
74
+ const RETIRED_CLIENT_MODEL_VALUE_OPTION = '--ai-model';
70
75
  let activeAgentProcess = null;
71
76
  let roleRestartRequest = null;
72
77
  let agentRestartRequest = null;
@@ -301,8 +306,8 @@ Options:
301
306
  --version Show the agent version.
302
307
  --help Show this help.
303
308
 
304
- Auto uses C# RemoteFast when supported and falls back to Node for AI assist or
305
- when a packaged RemoteFast runtime is unavailable.
309
+ Auto uses C# RemoteFast when supported and falls back to Node only when a
310
+ packaged RemoteFast runtime is unavailable.
306
311
  Enable Windows auto-start from the connection page when this client signs in.
307
312
  `.trimStart());
308
313
  }
@@ -394,15 +399,30 @@ export function parseLauncherArgs(argv) {
394
399
  let slot = process.env.LIVEDESK_CLIENT_SLOT || process.env.MINDEXEC_REMOTE_SLOT || '';
395
400
  let authPort = normalizePort(process.env.LIVEDESK_CLIENT_RUNTIME_PORT || process.env.LIVEDESK_CLIENT_AUTH_PORT) || DEFAULT_AUTH_CALLBACK_PORT;
396
401
  let command = 'connect';
397
- let nodeOnlyFeature = false;
398
- let fakeThumbnail = false;
402
+ let fakeThumbnail = false;
399
403
  let startupRun = isTruthy(process.env.LIVEDESK_ROLE_TRANSITION);
400
- let openBrowserOnStart = !isTruthy(process.env.LIVEDESK_SKIP_BROWSER_OPEN);
401
- let updateWaitPid = 0;
402
-
403
- for (let index = 0; index < argv.length; index += 1) {
404
- const arg = argv[index];
405
- if (arg && !arg.startsWith('-')) {
404
+ let openBrowserOnStart = !isTruthy(process.env.LIVEDESK_SKIP_BROWSER_OPEN);
405
+ let updateWaitPid = 0;
406
+ let retiredModelOptionsDiscarded = 0;
407
+
408
+ for (let index = 0; index < argv.length; index += 1) {
409
+ const arg = argv[index];
410
+ if (RETIRED_CLIENT_MODEL_SINGLE_OPTIONS.has(arg)) {
411
+ retiredModelOptionsDiscarded += 1;
412
+ continue;
413
+ }
414
+ if (arg === RETIRED_CLIENT_MODEL_VALUE_OPTION) {
415
+ retiredModelOptionsDiscarded += 1;
416
+ if (index + 1 < argv.length && !String(argv[index + 1] || '').startsWith('-')) {
417
+ index += 1;
418
+ }
419
+ continue;
420
+ }
421
+ if (String(arg || '').startsWith(`${RETIRED_CLIENT_MODEL_VALUE_OPTION}=`)) {
422
+ retiredModelOptionsDiscarded += 1;
423
+ continue;
424
+ }
425
+ if (arg && !arg.startsWith('-')) {
406
426
  const lowerArg = arg.toLowerCase();
407
427
  const shortcutSlot = normalizeSlotNumber(arg);
408
428
  if (lowerArg === 'connect') {
@@ -512,11 +532,8 @@ export function parseLauncherArgs(argv) {
512
532
  if (arg === '--version') {
513
533
  version = true;
514
534
  }
515
- if (arg === '--ai' || arg === '--fake-ai' || arg === '--ai-model' || arg === '--no-ai') {
516
- nodeOnlyFeature = true;
517
- }
518
- if (arg === '--fake-thumbnail') {
519
- fakeThumbnail = true;
535
+ if (arg === '--fake-thumbnail') {
536
+ fakeThumbnail = true;
520
537
  }
521
538
  forwarded.push(arg);
522
539
  }
@@ -539,13 +556,13 @@ export function parseLauncherArgs(argv) {
539
556
  deviceId,
540
557
  slot,
541
558
  authPort,
542
- nodeOnlyFeature,
543
- fakeThumbnail,
544
- startupRun,
545
- openBrowserOnStart,
546
- updateWaitPid
547
- };
548
- }
559
+ fakeThumbnail,
560
+ startupRun,
561
+ openBrowserOnStart,
562
+ updateWaitPid,
563
+ retiredModelOptionsDiscarded
564
+ };
565
+ }
549
566
 
550
567
  function normalizePort(value) {
551
568
  const port = Number(String(value || '').trim());
@@ -854,13 +871,15 @@ const CLIENT_UPDATE_SINGLE_FLAGS = new Set([
854
871
  '--no-open',
855
872
  '--exit-on-disconnect',
856
873
  '--exit-on-invalid-pair',
857
- '--once'
874
+ '--once',
875
+ ...RETIRED_CLIENT_MODEL_SINGLE_OPTIONS
858
876
  ]);
859
877
  const CLIENT_UPDATE_VALUE_FLAGS = new Set([
860
878
  '--update-wait-pid',
861
879
  // Pairing credentials stay in the inherited private environment and must
862
880
  // never be copied onto a replacement process command line.
863
- '--pair'
881
+ '--pair',
882
+ RETIRED_CLIENT_MODEL_VALUE_OPTION
864
883
  ]);
865
884
 
866
885
  export function buildClientUpdateRestartArgs(prepared = {}) {
@@ -874,6 +893,9 @@ export function buildClientUpdateRestartArgs(prepared = {}) {
874
893
  if (arg.startsWith('--pair=')) {
875
894
  continue;
876
895
  }
896
+ if (arg.startsWith(`${RETIRED_CLIENT_MODEL_VALUE_OPTION}=`)) {
897
+ continue;
898
+ }
877
899
  if (CLIENT_UPDATE_VALUE_FLAGS.has(arg)) {
878
900
  index += 1;
879
901
  continue;
@@ -4233,6 +4255,18 @@ export function buildFastArgs(args, fakeThumbnail, transport = 'auto', relay = '
4233
4255
  const forwarded = [];
4234
4256
  for (let index = 0; index < args.length; index += 1) {
4235
4257
  const arg = args[index];
4258
+ if (RETIRED_CLIENT_MODEL_SINGLE_OPTIONS.has(arg)) {
4259
+ continue;
4260
+ }
4261
+ if (arg === RETIRED_CLIENT_MODEL_VALUE_OPTION) {
4262
+ if (index + 1 < args.length && !String(args[index + 1] || '').startsWith('-')) {
4263
+ index += 1;
4264
+ }
4265
+ continue;
4266
+ }
4267
+ if (String(arg || '').startsWith(`${RETIRED_CLIENT_MODEL_VALUE_OPTION}=`)) {
4268
+ continue;
4269
+ }
4236
4270
  if (arg === '--transport' || arg === '--relay') {
4237
4271
  index += 1;
4238
4272
  continue;
@@ -4241,8 +4275,8 @@ export function buildFastArgs(args, fakeThumbnail, transport = 'auto', relay = '
4241
4275
  forwarded.push('--fake-frame');
4242
4276
  continue;
4243
4277
  }
4244
- if (arg === '--tasks' || arg === '--no-tasks' || arg === '--no-ai') {
4245
- continue;
4278
+ if (arg === '--tasks' || arg === '--no-tasks') {
4279
+ continue;
4246
4280
  }
4247
4281
  forwarded.push(arg);
4248
4282
  }
@@ -4446,15 +4480,15 @@ function spawnAgent(command, args, env = process.env, onStart = null) {
4446
4480
  });
4447
4481
  }
4448
4482
 
4449
- function shouldUseFast(parsed) {
4450
- if (parsed.engine === 'node') {
4451
- return false;
4483
+ function shouldUseFast(parsed) {
4484
+ if (parsed.engine === 'node') {
4485
+ return false;
4452
4486
  }
4453
4487
  if (parsed.engine === 'fast') {
4454
4488
  return true;
4455
4489
  }
4456
- return !parsed.nodeOnlyFeature;
4457
- }
4490
+ return true;
4491
+ }
4458
4492
 
4459
4493
  function shouldTryFast(parsed, runtime) {
4460
4494
  if (parsed.engine === 'fast') {
@@ -4480,6 +4514,12 @@ export async function runClientRuntime(argv = process.argv.slice(2)) {
4480
4514
  console.warn('[LiveDesk] @livedesk/client is deprecated. Use the unified livedesk package or LiveDesk Desktop.');
4481
4515
  }
4482
4516
  const parsed = parseLauncherArgs(argv);
4517
+ if (parsed.retiredModelOptionsDiscarded > 0) {
4518
+ console.warn(
4519
+ '[LiveDesk Client] Ignored retired Client model-runtime options. '
4520
+ + 'Approved AI computer commands are handled by the Hub Codex Agent.'
4521
+ );
4522
+ }
4483
4523
  if (parsed.updateWaitPid) {
4484
4524
  await waitForProcessExit(parsed.updateWaitPid);
4485
4525
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.203",
3
+ "version": "0.1.204",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,7 +37,6 @@
37
37
  "@livedesk/runtime-core": "0.1.0",
38
38
  "@supabase/supabase-js": "^2.110.0",
39
39
  "node-screenshots": "^0.2.8",
40
- "openai": "^6.42.0",
41
40
  "ws": "^8.18.3"
42
41
  },
43
42
  "optionalDependencies": {
package/hub/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "LiveDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -94,15 +94,6 @@ export function createAgentManager({
94
94
  async deleteApiKey() {
95
95
  return publicSettings();
96
96
  },
97
- async getModels() {
98
- return [{
99
- id: '',
100
- name: 'Codex default',
101
- protocol: 'codex-sdk',
102
- available: true,
103
- capabilities: { supportsReasoningLevel: true, supportsStreaming: true, supportsTools: true }
104
- }];
105
- },
106
97
  async testConnection() {
107
98
  if (!runtime) throw new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
108
99
  statusCache = { expiresAt: 0, value: null };
@@ -4,14 +4,11 @@ import path from 'node:path';
4
4
 
5
5
  export const AGENT_PROVIDER_CODEX = 'codex';
6
6
  export const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_CODEX;
7
- export const DEFAULT_CODEX_MODEL_ID = '';
8
7
  export const DEFAULT_AGENT_WORKSPACE = path.join(os.homedir(), '.livedesk', 'agent-workspace');
9
8
  export const DEFAULT_AGENT_SETTINGS = Object.freeze({
10
- settingsVersion: 3,
9
+ settingsVersion: 4,
11
10
  enabled: true,
12
11
  provider: DEFAULT_AGENT_PROVIDER,
13
- codexModelId: DEFAULT_CODEX_MODEL_ID,
14
- codexReasoningEffort: 'medium',
15
12
  codexSandboxMode: 'read-only',
16
13
  codexApprovalPolicy: 'never',
17
14
  codexWorkingDirectory: DEFAULT_AGENT_WORKSPACE,
@@ -25,20 +22,6 @@ export const DEFAULT_AGENT_SETTINGS = Object.freeze({
25
22
  maxConcurrentRequests: 2
26
23
  });
27
24
 
28
- const REASONING_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']);
29
- const SANDBOX_MODES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
30
- const APPROVAL_POLICIES = new Set(['never', 'on-request', 'on-failure', 'untrusted']);
31
- const WEB_SEARCH_MODES = new Set(['disabled', 'cached', 'live']);
32
-
33
- export class AgentSettingsError extends Error {
34
- constructor(code, message) {
35
- super(message);
36
- this.name = 'AgentSettingsError';
37
- this.code = code;
38
- this.status = 400;
39
- }
40
- }
41
-
42
25
  function booleanValue(value, fallback) {
43
26
  return typeof value === 'boolean' ? value : fallback;
44
27
  }
@@ -50,18 +33,7 @@ function numberValue(value, min, max, fallback, integer = false) {
50
33
  return integer ? Math.round(clamped) : clamped;
51
34
  }
52
35
 
53
- function stringValue(value, fallback, maxLength = 240) {
54
- if (typeof value !== 'string') return fallback;
55
- return value.trim().slice(0, maxLength);
56
- }
57
-
58
- function enumValue(value, allowed, fallback, code) {
59
- const normalized = stringValue(value, fallback, 80).toLowerCase();
60
- if (!allowed.has(normalized)) throw new AgentSettingsError(code, `Invalid agent setting: ${normalized}.`);
61
- return normalized;
62
- }
63
-
64
- function normalizeWorkingDirectory(value) {
36
+ function normalizeWorkingDirectory() {
65
37
  // The agent runtime owns this directory. Do not allow a browser/API caller
66
38
  // to move Codex into a user project or an arbitrary filesystem root.
67
39
  return DEFAULT_AGENT_WORKSPACE;
@@ -70,18 +42,16 @@ function normalizeWorkingDirectory(value) {
70
42
  export function normalizeAgentSettings(value = {}) {
71
43
  const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
72
44
  return {
73
- settingsVersion: 3,
45
+ settingsVersion: 4,
74
46
  enabled: booleanValue(source.enabled, DEFAULT_AGENT_SETTINGS.enabled),
75
47
  // LiveDesk has one Agent runtime. Old provider values are deliberately
76
48
  // normalized to Codex so an upgrade cannot reactivate a retired provider.
77
49
  provider: AGENT_PROVIDER_CODEX,
78
- codexModelId: stringValue(source.codexModelId, DEFAULT_AGENT_SETTINGS.codexModelId, 160),
79
- codexReasoningEffort: enumValue(source.codexReasoningEffort, REASONING_EFFORTS, DEFAULT_AGENT_SETTINGS.codexReasoningEffort, 'agent-invalid-reasoning-effort'),
80
50
  // These are safety settings, not user-controlled execution toggles. They
81
51
  // are accepted for compatibility but always normalized to the safe floor.
82
52
  codexSandboxMode: 'read-only',
83
53
  codexApprovalPolicy: 'never',
84
- codexWorkingDirectory: normalizeWorkingDirectory(source.codexWorkingDirectory),
54
+ codexWorkingDirectory: normalizeWorkingDirectory(),
85
55
  codexTaskTimeoutMs: numberValue(source.codexTaskTimeoutMs, 30000, 1800000, DEFAULT_AGENT_SETTINGS.codexTaskTimeoutMs, true),
86
56
  codexMaxTurns: numberValue(source.codexMaxTurns, 1, 40, DEFAULT_AGENT_SETTINGS.codexMaxTurns, true),
87
57
  codexMaxToolCalls: numberValue(source.codexMaxToolCalls, 1, 100, DEFAULT_AGENT_SETTINGS.codexMaxToolCalls, true),