livedesk 0.1.450 → 0.1.452

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/bin/livedesk.js CHANGED
@@ -2794,6 +2794,10 @@ async function runClient(args, resolvedRole = null, runtimeLock = null) {
2794
2794
  : String(process.env.LIVEDESK_SKIP_BROWSER_OPEN || ''),
2795
2795
  LIVEDESK_DEVICE_ID: String(resolvedRole?.deviceId || process.env.LIVEDESK_DEVICE_ID || ''),
2796
2796
  LIVEDESK_ROLE_SOURCE: String(resolvedRole?.source || process.env.LIVEDESK_ROLE_SOURCE || ''),
2797
+ LIVEDESK_ASSIGNED_HUB_ID: String(resolvedRole?.assignedHubId || ''),
2798
+ LIVEDESK_ROLE_VERSION: Number.isInteger(resolvedRole?.roleVersion) && resolvedRole.roleVersion >= 0
2799
+ ? String(resolvedRole.roleVersion)
2800
+ : '0',
2797
2801
  LIVEDESK_RUNTIME_OWNER_PID: String(runtimeOwner?.pid || ''),
2798
2802
  LIVEDESK_RUNTIME_OWNER_TOKEN: String(runtimeOwner?.ownerToken || ''),
2799
2803
  LIVEDESK_RUNTIME_OWNER_INSTANCE_MARKER: String(runtimeOwner?.ownerInstanceMarker || ''),
@@ -3301,6 +3301,8 @@ async function chooseClientConnection(supabase, options = {}) {
3301
3301
  deviceId: options.deviceId,
3302
3302
  engine: options.engine,
3303
3303
  slot: options.slot,
3304
+ assignedHubId: process.env.LIVEDESK_ASSIGNED_HUB_ID,
3305
+ roleVersion: process.env.LIVEDESK_ROLE_VERSION,
3304
3306
  savedSession: options.savedSession,
3305
3307
  initialChoice: options.initialChoice,
3306
3308
  initialChoiceMessage: options.initialChoiceMessage,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.205",
3
+ "version": "0.1.207",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,10 +40,10 @@
40
40
  "ws": "^8.18.3"
41
41
  },
42
42
  "optionalDependencies": {
43
- "@livedesk/fast-linux-x64": "0.1.412",
44
- "@livedesk/fast-osx-arm64": "0.1.412",
45
- "@livedesk/fast-osx-x64": "0.1.412",
46
- "@livedesk/fast-win-x64": "0.1.412"
43
+ "@livedesk/fast-linux-x64": "0.1.413",
44
+ "@livedesk/fast-osx-arm64": "0.1.413",
45
+ "@livedesk/fast-osx-x64": "0.1.413",
46
+ "@livedesk/fast-win-x64": "0.1.413"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public"
@@ -150,6 +150,11 @@ function normalizeSlotNumber(value) {
150
150
  : 0;
151
151
  }
152
152
 
153
+ function normalizeRoleVersion(value) {
154
+ const roleVersion = Number(String(value ?? '').trim());
155
+ return Number.isInteger(roleVersion) && roleVersion >= 0 ? roleVersion : 0;
156
+ }
157
+
153
158
  function readCpuTotals() {
154
159
  return os.cpus().reduce((totals, cpu) => {
155
160
  const times = Object.values(cpu.times || {}).map(Number);
@@ -655,9 +660,9 @@ export function createClientRuntimeServer(options = {}) {
655
660
  manager: '',
656
661
  pairToken: '',
657
662
  slotNumber: normalizeSlotNumber(options.slot),
658
- assignedHubId: '',
663
+ assignedHubId: normalizeString(options.assignedHubId ?? process.env.LIVEDESK_ASSIGNED_HUB_ID, 160),
659
664
  endpointCandidates: [],
660
- roleVersion: 0,
665
+ roleVersion: normalizeRoleVersion(options.roleVersion ?? process.env.LIVEDESK_ROLE_VERSION),
661
666
  connectedAt: '',
662
667
  message: 'Sign in with Google or enter a Hub PIN to start this Client.',
663
668
  startup: false,
@@ -788,6 +793,12 @@ export function createClientRuntimeServer(options = {}) {
788
793
  name: accountProfile.name || state.auth.name,
789
794
  avatarUrl: accountProfile.avatarUrl || state.auth.avatarUrl
790
795
  };
796
+ state.lastError = '';
797
+ if (state.message.startsWith('Client authentication needs attention:')) {
798
+ state.message = state.agent.state === 'running'
799
+ ? 'Connected to the LiveDesk Hub.'
800
+ : normalizeString(message, 1000) || 'Client credentials accepted. Finding the Hub.';
801
+ }
791
802
  }
792
803
  return;
793
804
  }
@@ -795,7 +806,9 @@ export function createClientRuntimeServer(options = {}) {
795
806
  loggedOut = false;
796
807
  state.connectedAt = new Date().toISOString();
797
808
  state.manager = normalizeString(choice?.manager, 256);
798
- state.assignedHubId = normalizeString(choice?.hubDeviceId, 160);
809
+ if (Object.prototype.hasOwnProperty.call(choice || {}, 'hubDeviceId')) {
810
+ state.assignedHubId = normalizeString(choice?.hubDeviceId, 160);
811
+ }
799
812
  state.endpointCandidates = Array.isArray(choice?.endpointCandidates)
800
813
  ? choice.endpointCandidates.map(value => normalizeString(value, 256)).filter(Boolean)
801
814
  : [];
@@ -1337,7 +1350,9 @@ export function createClientRuntimeServer(options = {}) {
1337
1350
  if (patch.manager) state.manager = normalizeString(patch.manager, 256);
1338
1351
  if (patch.pairToken) state.pairToken = normalizeString(patch.pairToken, 256);
1339
1352
  if (normalizeSlotNumber(patch.slotNumber)) state.slotNumber = normalizeSlotNumber(patch.slotNumber);
1340
- if (patch.assignedHubId) state.assignedHubId = normalizeString(patch.assignedHubId, 160);
1353
+ if (Object.prototype.hasOwnProperty.call(patch, 'assignedHubId')) {
1354
+ state.assignedHubId = normalizeString(patch.assignedHubId, 160);
1355
+ }
1341
1356
  if (patch.message) state.message = normalizeString(patch.message, 1000);
1342
1357
  if (Number.isInteger(patch.roleVersion)) state.roleVersion = patch.roleVersion;
1343
1358
  if (Array.isArray(patch.endpointCandidates)) state.endpointCandidates = patch.endpointCandidates;
package/hub/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "LiveDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -1,7 +1,7 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
- import { AgentSettingsStore, AGENT_PROVIDER_CODEX } from './agent-settings.js';
4
- import { AgentProviderError } from './provider-errors.js';
3
+ import { AgentSettingsStore } from './agent-settings.js';
4
+ import { AgentRuntimeError } from './agent-runtime-error.js';
5
5
 
6
6
  function publicCodexStatus(status = {}) {
7
7
  return {
@@ -42,66 +42,22 @@ export function createAgentManager({
42
42
 
43
43
  async function publicSettings() {
44
44
  const current = await settings.get();
45
- const { codexMaxTurns: _codexMaxTurns, ...exposedSettings } = current;
46
45
  const codex = await getCodexStatus();
47
- const securityStatus = runtime?.getSecurityStatus?.() || {
48
- isolatedCodexHome: false,
49
- restrictedEnvironment: false,
50
- livedeskMcpOnly: false,
51
- selectedDeviceScopeEnforced: false,
52
- failClosed: true,
53
- verified: false,
54
- state: 'unavailable'
55
- };
56
46
  return {
57
- ...exposedSettings,
58
- provider: AGENT_PROVIDER_CODEX,
47
+ enabled: current.enabled === true,
59
48
  codexInstallation: codex.installed ? 'installed' : 'not-installed',
60
49
  codexAuth: codex.authenticated,
61
- codexStatus: codex.status,
62
- codexPath: codex.codexPath,
63
- agentWorkspacePath: current.codexWorkingDirectory,
64
- securityStatus
50
+ codexStatus: codex.status
65
51
  };
66
52
  }
67
53
 
68
54
  return {
69
55
  getSettings: publicSettings,
70
- async updateSettings(patch = {}) {
71
- const nextPatch = { ...patch };
72
- const requestedProvider = String(nextPatch.provider || AGENT_PROVIDER_CODEX).trim().toLowerCase();
73
- const suppliedLegacyKey = typeof nextPatch.apiKey === 'string' && nextPatch.apiKey.trim().length > 0;
74
- delete nextPatch.provider;
75
- delete nextPatch.apiKey;
76
- if (requestedProvider !== AGENT_PROVIDER_CODEX || suppliedLegacyKey) {
77
- throw new AgentProviderError(
78
- 'agent-provider-retired',
79
- 'LiveDesk uses the Hub-hosted Codex Agent and does not configure alternate model providers.',
80
- { status: 400 }
81
- );
82
- }
83
- await settings.update({ ...nextPatch, provider: AGENT_PROVIDER_CODEX });
84
- statusCache = { expiresAt: 0, value: null };
85
- return publicSettings();
86
- },
87
- async resetSettings() {
88
- await settings.reset();
89
- statusCache = { expiresAt: 0, value: null };
90
- return publicSettings();
91
- },
92
- // Kept as a harmless compatibility endpoint for an older cached web UI.
93
- // No credential is read, written, or returned by the current Hub.
94
- async deleteApiKey() {
95
- return publicSettings();
96
- },
97
56
  async testConnection() {
98
- if (!runtime) throw new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
57
+ if (!runtime) throw new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
99
58
  statusCache = { expiresAt: 0, value: null };
100
59
  return runtime.testConnection();
101
60
  },
102
- async createPlan() {
103
- throw new AgentProviderError('agent-codex-run-required', 'Codex Agent requests use the LiveDesk tool loop.', { status: 409 });
104
- },
105
61
  async createSummary(input) {
106
62
  const source = input && typeof input === 'object' ? input : {};
107
63
  const results = Array.isArray(source.results) ? source.results : [];
@@ -110,9 +66,8 @@ export function createAgentManager({
110
66
  };
111
67
  },
112
68
  async startRun(input) {
113
- const current = await settings.get();
114
- if (current.provider !== AGENT_PROVIDER_CODEX || !runtime) {
115
- throw new AgentProviderError('agent-codex-not-active', 'Codex SDK is not the active Agent runtime.', { status: 409 });
69
+ if (!runtime) {
70
+ throw new AgentRuntimeError('agent-codex-not-active', 'Codex SDK is not available for LiveDesk Agent commands.', { status: 409 });
116
71
  }
117
72
  return runtime.start(input);
118
73
  },
@@ -0,0 +1,9 @@
1
+ export class AgentRuntimeError extends Error {
2
+ constructor(code, message, { status = 502, retryable = false } = {}) {
3
+ super(message);
4
+ this.name = 'AgentRuntimeError';
5
+ this.code = code;
6
+ this.status = status;
7
+ this.retryable = retryable;
8
+ }
9
+ }
@@ -2,23 +2,13 @@ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
 
5
- export const AGENT_PROVIDER_CODEX = 'codex';
6
- export const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_CODEX;
7
- export const DEFAULT_AGENT_WORKSPACE = path.join(os.homedir(), '.livedesk', 'agent-workspace');
8
5
  export const DEFAULT_AGENT_SETTINGS = Object.freeze({
9
- settingsVersion: 4,
6
+ settingsVersion: 5,
10
7
  enabled: true,
11
- provider: DEFAULT_AGENT_PROVIDER,
12
- codexSandboxMode: 'read-only',
13
- codexApprovalPolicy: 'never',
14
- codexWorkingDirectory: DEFAULT_AGENT_WORKSPACE,
15
8
  codexTaskTimeoutMs: 600000,
16
9
  codexMaxTurns: 12,
17
10
  codexMaxToolCalls: 20,
18
11
  codexResumeSessions: true,
19
- codexShowDetailedEvents: true,
20
- codexNetworkAccessEnabled: false,
21
- codexWebSearchMode: 'disabled',
22
12
  maxConcurrentRequests: 2
23
13
  });
24
14
 
@@ -33,32 +23,15 @@ function numberValue(value, min, max, fallback, integer = false) {
33
23
  return integer ? Math.round(clamped) : clamped;
34
24
  }
35
25
 
36
- function normalizeWorkingDirectory() {
37
- // The agent runtime owns this directory. Do not allow a browser/API caller
38
- // to move Codex into a user project or an arbitrary filesystem root.
39
- return DEFAULT_AGENT_WORKSPACE;
40
- }
41
-
42
26
  export function normalizeAgentSettings(value = {}) {
43
27
  const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
44
28
  return {
45
- settingsVersion: 4,
29
+ settingsVersion: 5,
46
30
  enabled: booleanValue(source.enabled, DEFAULT_AGENT_SETTINGS.enabled),
47
- // LiveDesk has one Agent runtime. Old provider values are deliberately
48
- // normalized to Codex so an upgrade cannot reactivate a retired provider.
49
- provider: AGENT_PROVIDER_CODEX,
50
- // These are safety settings, not user-controlled execution toggles. They
51
- // are accepted for compatibility but always normalized to the safe floor.
52
- codexSandboxMode: 'read-only',
53
- codexApprovalPolicy: 'never',
54
- codexWorkingDirectory: normalizeWorkingDirectory(),
55
31
  codexTaskTimeoutMs: numberValue(source.codexTaskTimeoutMs, 30000, 1800000, DEFAULT_AGENT_SETTINGS.codexTaskTimeoutMs, true),
56
32
  codexMaxTurns: numberValue(source.codexMaxTurns, 1, 40, DEFAULT_AGENT_SETTINGS.codexMaxTurns, true),
57
33
  codexMaxToolCalls: numberValue(source.codexMaxToolCalls, 1, 100, DEFAULT_AGENT_SETTINGS.codexMaxToolCalls, true),
58
34
  codexResumeSessions: booleanValue(source.codexResumeSessions, DEFAULT_AGENT_SETTINGS.codexResumeSessions),
59
- codexShowDetailedEvents: booleanValue(source.codexShowDetailedEvents, DEFAULT_AGENT_SETTINGS.codexShowDetailedEvents),
60
- codexNetworkAccessEnabled: false,
61
- codexWebSearchMode: 'disabled',
62
35
  maxConcurrentRequests: numberValue(source.maxConcurrentRequests, 1, 8, DEFAULT_AGENT_SETTINGS.maxConcurrentRequests, true)
63
36
  };
64
37
  }
@@ -95,10 +68,4 @@ export class AgentSettingsStore {
95
68
  return { ...next };
96
69
  }
97
70
 
98
- async reset() {
99
- const next = normalizeAgentSettings(DEFAULT_AGENT_SETTINGS);
100
- await writeJsonAtomic(this.filePath, next);
101
- this.settings = next;
102
- return { ...next };
103
- }
104
71
  }
@@ -3,10 +3,9 @@ import { access, link, mkdir, stat, unlink } from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { spawn } from 'node:child_process';
6
- import { createRequire } from 'node:module';
7
- import { AgentProviderError } from './provider-errors.js';
8
- import { AGENT_PROVIDER_CODEX } from './agent-settings.js';
9
- import { AGENT_TOOL_NAMES } from './agent-tool-registry.js';
6
+ import { createRequire } from 'node:module';
7
+ import { AgentRuntimeError } from './agent-runtime-error.js';
8
+ import { AGENT_TOOL_NAMES } from './agent-tool-registry.js';
10
9
  import { createAgentPermissionPolicy, hashAgentPermissionPolicy } from './agent-permissions.js';
11
10
 
12
11
  const require = createRequire(import.meta.url);
@@ -58,12 +57,12 @@ function isAbortError(error) {
58
57
 
59
58
  function normalizeCodexError(error) {
60
59
  const message = safeText(error?.message, 800) || 'Codex run failed.';
61
- if (error instanceof AgentProviderError && error.code !== 'codex-run-failed') return error;
62
- if (/out of credits|usage limit|rate limit|too many requests/i.test(message)) return new AgentProviderError('codex-usage-limit-reached', 'The Codex workspace has no remaining usage.', { status: 402, retryable: true });
63
- if (/refresh[_ -]?token[_ -]?(?:already[_ -]?)?used|token refresh|refresh credential/i.test(message)) return new AgentProviderError('codex-auth-stale', 'The saved Codex sign-in is stale. LiveDesk refreshed its Codex authentication bridge and will retry.', { status: 401, retryable: true });
64
- if (/not authenticated|not logged in|authentication|unauthorized|login required/i.test(message)) return new AgentProviderError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
65
- if (/user cancelled MCP tool call/i.test(message)) return new AgentProviderError('codex-mcp-tool-rejected', 'Codex rejected the LiveDesk MCP tool before it reached the Hub.', { status: 502 });
66
- return new AgentProviderError('codex-run-failed', message, { status: 502, retryable: true });
60
+ if (error instanceof AgentRuntimeError && error.code !== 'codex-run-failed') return error;
61
+ 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 });
62
+ 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 });
63
+ 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 });
64
+ 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 });
65
+ return new AgentRuntimeError('codex-run-failed', message, { status: 502, retryable: true });
67
66
  }
68
67
 
69
68
  function codexThreadOptions(workspace) {
@@ -165,16 +164,16 @@ async function ensureCodexHome(codexHome, globalCodexHome, { preferGlobalAuth =
165
164
  const resolvedHome = path.resolve(codexHome);
166
165
  const globalHome = path.resolve(globalCodexHome);
167
166
  if (!resolvedHome || resolvedHome === globalHome) {
168
- throw new AgentProviderError('codex-isolation-unavailable', 'Codex requires a dedicated LiveDesk home.', { status: 503 });
167
+ throw new AgentRuntimeError('codex-isolation-unavailable', 'Codex requires a dedicated LiveDesk home.', { status: 503 });
169
168
  }
170
169
  try {
171
170
  await mkdir(resolvedHome, { recursive: true, mode: 0o700 });
172
171
  if (await access(path.join(resolvedHome, 'config.toml')).then(() => true, () => false)) {
173
- throw new AgentProviderError('codex-environment-isolation-failed', 'The dedicated Codex home contains an unmanaged config.toml.', { status: 503 });
172
+ throw new AgentRuntimeError('codex-environment-isolation-failed', 'The dedicated Codex home contains an unmanaged config.toml.', { status: 503 });
174
173
  }
175
174
  } catch (error) {
176
- if (error instanceof AgentProviderError) throw error;
177
- const wrapped = new AgentProviderError('codex-environment-isolation-failed', 'The dedicated Codex home could not be prepared.', { status: 503 });
175
+ if (error instanceof AgentRuntimeError) throw error;
176
+ const wrapped = new AgentRuntimeError('codex-environment-isolation-failed', 'The dedicated Codex home could not be prepared.', { status: 503 });
178
177
  wrapped.cause = error;
179
178
  throw wrapped;
180
179
  }
@@ -201,7 +200,7 @@ async function ensureCodexHome(codexHome, globalCodexHome, { preferGlobalAuth =
201
200
  await replaceAuthHardLink(sourceAuthPath, targetAuthPath);
202
201
  }
203
202
  } catch (error) {
204
- const wrapped = new AgentProviderError('codex-environment-isolation-failed', 'The Codex authentication bridge could not be prepared.', { status: 503 });
203
+ const wrapped = new AgentRuntimeError('codex-environment-isolation-failed', 'The Codex authentication bridge could not be prepared.', { status: 503 });
205
204
  wrapped.cause = error;
206
205
  throw wrapped;
207
206
  }
@@ -244,7 +243,7 @@ function runAgeMs(run) {
244
243
  }
245
244
 
246
245
  function codexAbortError(code, message) {
247
- return new AgentProviderError(code, message, { status: code === 'codex-run-timeout' ? 504 : 409, retryable: code === 'codex-run-timeout' });
246
+ return new AgentRuntimeError(code, message, { status: code === 'codex-run-timeout' ? 504 : 409, retryable: code === 'codex-run-timeout' });
248
247
  }
249
248
 
250
249
  function safeAgentDeviceIds(value) {
@@ -332,7 +331,7 @@ export function createCodexAgentRuntime({
332
331
  function assertSecurityConfiguration() {
333
332
  const status = securityStatus();
334
333
  if (!status.isolatedCodexHome || !status.restrictedEnvironment || !status.livedeskMcpOnly || !status.selectedDeviceScopeEnforced) {
335
- throw new AgentProviderError('codex-unsupported-security-config', 'Codex security isolation is not available.', { status: 503 });
334
+ throw new AgentRuntimeError('codex-unsupported-security-config', 'Codex security isolation is not available.', { status: 503 });
336
335
  }
337
336
  }
338
337
 
@@ -365,7 +364,7 @@ export function createCodexAgentRuntime({
365
364
 
366
365
  async function loadCodex() {
367
366
  if (!codexModulePromise) codexModulePromise = import('@openai/codex-sdk').catch(error => {
368
- const wrapped = new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed in this LiveDesk package.', { status: 503 });
367
+ const wrapped = new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed in this LiveDesk package.', { status: 503 });
369
368
  wrapped.cause = error;
370
369
  throw wrapped;
371
370
  });
@@ -396,7 +395,7 @@ export function createCodexAgentRuntime({
396
395
  ? 'signed-in'
397
396
  : 'not-signed-in';
398
397
  } catch (error) {
399
- if (error instanceof AgentProviderError) throw error;
398
+ if (error instanceof AgentRuntimeError) throw error;
400
399
  detail = safeText(error?.message, 300);
401
400
  }
402
401
  return { installed: true, authenticated, status: authenticated === 'signed-in' ? 'ready' : 'auth-required', codexPath: pathToCli, detail };
@@ -406,8 +405,8 @@ export function createCodexAgentRuntime({
406
405
  assertSecurityConfiguration();
407
406
  const startedAt = Date.now();
408
407
  const status = await getStatus();
409
- if (!status.installed) throw new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
410
- if (status.authenticated !== 'signed-in') throw new AgentProviderError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
408
+ if (!status.installed) throw new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
409
+ if (status.authenticated !== 'signed-in') throw new AgentRuntimeError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
411
410
  const settings = await settingsStore.get();
412
411
  await mkdir(workspace, { recursive: true });
413
412
  await prepareCodexHome();
@@ -417,7 +416,7 @@ export function createCodexAgentRuntime({
417
416
  try {
418
417
  const thread = codex.startThread(codexThreadOptions(workspace));
419
418
  const turn = await thread.run('Reply with the single word READY. Do not use tools.', { signal: controller.signal });
420
- if (!String(turn.finalResponse || '').trim()) throw new AgentProviderError('codex-empty-response', 'Codex returned an empty response.', { status: 502 });
419
+ if (!String(turn.finalResponse || '').trim()) throw new AgentRuntimeError('codex-empty-response', 'Codex returned an empty response.', { status: 502 });
421
420
  return { ok: true, latencyMs: Date.now() - startedAt, threadId: thread.id || '' };
422
421
  } catch (error) {
423
422
  throw normalizeCodexError(error);
@@ -427,11 +426,10 @@ export function createCodexAgentRuntime({
427
426
  }
428
427
 
429
428
  function publicRun(run) {
430
- return {
431
- runId: run.runId,
432
- status: run.status,
433
- provider: AGENT_PROVIDER_CODEX,
434
- instruction: run.instruction,
429
+ return {
430
+ runId: run.runId,
431
+ status: run.status,
432
+ instruction: run.instruction,
435
433
  threadId: run.threadId,
436
434
  finalResponse: run.finalResponse,
437
435
  error: run.error,
@@ -494,15 +492,15 @@ export function createCodexAgentRuntime({
494
492
  timeoutTimer.unref?.();
495
493
  await mkdir(workspace, { recursive: true });
496
494
  if (activeCount > settings.maxConcurrentRequests) {
497
- throw new AgentProviderError('agent-concurrency-limit', 'The LiveDesk Agent concurrency limit is reached.', { status: 429, retryable: true });
495
+ throw new AgentRuntimeError('agent-concurrency-limit', 'The LiveDesk Agent concurrency limit is reached.', { status: 429, retryable: true });
498
496
  }
499
497
  if (run.abortController.signal.aborted) {
500
498
  if (run.abortReason === 'timeout') throw codexAbortError('codex-run-timeout', 'The Codex task exceeded its timeout.');
501
499
  throw Object.assign(new Error('The Codex task was cancelled.'), { name: 'AbortError' });
502
500
  }
503
501
  const status = await getStatus();
504
- if (!status.installed) throw new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
505
- if (status.authenticated !== 'signed-in') throw new AgentProviderError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
502
+ if (!status.installed) throw new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
503
+ if (status.authenticated !== 'signed-in') throw new AgentRuntimeError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
506
504
  reportProgress(run, 'preparing-tools', 'Codex is ready. Preparing the LiveDesk tools for this Client.');
507
505
  session = createMcpSession({
508
506
  runId: run.runId,
@@ -554,14 +552,14 @@ export function createCodexAgentRuntime({
554
552
  run.abortReason = 'turn-limit';
555
553
  run.abortController.abort();
556
554
  session.cancel();
557
- throw new AgentProviderError('codex-turn-limit-reached', 'Codex exceeded the LiveDesk turn limit.', { status: 409 });
555
+ throw new AgentRuntimeError('codex-turn-limit-reached', 'Codex exceeded the LiveDesk turn limit.', { status: 409 });
558
556
  }
559
557
  }
560
558
  if (run.toolCallCount > run.permissionPolicy.maxToolCalls) {
561
559
  run.abortReason = 'tool-limit';
562
560
  run.abortController.abort();
563
561
  session.cancel();
564
- throw new AgentProviderError('codex-tool-limit-reached', 'Codex exceeded the LiveDesk tool-call limit.', { status: 409 });
562
+ throw new AgentRuntimeError('codex-tool-limit-reached', 'Codex exceeded the LiveDesk tool-call limit.', { status: 409 });
565
563
  }
566
564
  if (event.type === 'item.completed' && event.item?.type === 'mcp_tool_call' && event.item?.status === 'failed') {
567
565
  throw new Error(safeText(event.item?.error?.message, 800) || `LiveDesk tool ${safeText(event.item?.tool, 120) || 'call'} failed.`);
@@ -643,21 +641,20 @@ export function createCodexAgentRuntime({
643
641
 
644
642
  async function start(input = {}) {
645
643
  const instruction = safeText(input.instruction, 4000);
646
- if (!instruction) throw new AgentProviderError('agent-invalid-instruction', 'Instruction is required.', { status: 400 });
644
+ if (!instruction) throw new AgentRuntimeError('agent-invalid-instruction', 'Instruction is required.', { status: 400 });
647
645
  const deviceIds = safeAgentDeviceIds(input.deviceIds);
648
- if (deviceIds.length === 0) throw new AgentProviderError('agent-no-target-devices', 'Select at least one connected device.', { status: 400 });
646
+ if (deviceIds.length === 0) throw new AgentRuntimeError('agent-no-target-devices', 'Select at least one connected device.', { status: 400 });
649
647
  const settings = await settingsStore.get();
650
- if (!settings.enabled) throw new AgentProviderError('agent-ai-disabled', 'Codex Agent is disabled in Settings.', { status: 409 });
648
+ if (!settings.enabled) throw new AgentRuntimeError('agent-disabled', 'Codex Agent is disabled in Settings.', { status: 409 });
651
649
  assertSecurityConfiguration();
652
650
  pruneRuns();
653
- if (runs.size >= MAX_RUNS) throw new AgentProviderError('agent-run-limit', 'The LiveDesk Agent run history is full.', { status: 429, retryable: true });
651
+ if (runs.size >= MAX_RUNS) throw new AgentRuntimeError('agent-run-limit', 'The LiveDesk Agent run history is full.', { status: 429, retryable: true });
654
652
  const permissionPolicy = freezeAgentPermissionPolicy(input.permissionPolicy && typeof input.permissionPolicy === 'object'
655
653
  ? input.permissionPolicy
656
654
  : createAgentPermissionPolicy({ mode: input.permissionMode || 'safe-auto', deviceIds, maxToolCalls: settings.codexMaxToolCalls }));
657
- const run = {
658
- runId: crypto.randomUUID(),
659
- status: 'queued',
660
- provider: AGENT_PROVIDER_CODEX,
655
+ const run = {
656
+ runId: crypto.randomUUID(),
657
+ status: 'queued',
661
658
  instruction,
662
659
  deviceIds,
663
660
  threadId: '',
@@ -3898,14 +3898,21 @@ export function createRemoteHub(options = {}) {
3898
3898
  : null,
3899
3899
  nativeEncoderHardwareQueryStatus: safeString(message.nativeEncoderHardwareQueryStatus, 80),
3900
3900
  nativeCaptureFps: Number.isFinite(Number(message.nativeCaptureFps)) ? Number(message.nativeCaptureFps) : 0,
3901
+ nativeCompressionFps: Number.isFinite(Number(message.nativeCompressionFps)) ? Number(message.nativeCompressionFps) : 0,
3901
3902
  nativeEncodedFps: Number.isFinite(Number(message.nativeEncodedFps)) ? Number(message.nativeEncodedFps) : 0,
3902
3903
  nativeCaptureSampleCount: Number.isFinite(Number(message.nativeCaptureSampleCount)) ? Number(message.nativeCaptureSampleCount) : 0,
3903
3904
  nativeEncodableFrameCount: Number.isFinite(Number(message.nativeEncodableFrameCount)) ? Number(message.nativeEncodableFrameCount) : 0,
3904
3905
  nativeSkippedFrameCount: Number.isFinite(Number(message.nativeSkippedFrameCount)) ? Number(message.nativeSkippedFrameCount) : 0,
3906
+ nativeCompressionOutputCount: Number.isFinite(Number(message.nativeCompressionOutputCount)) ? Number(message.nativeCompressionOutputCount) : 0,
3905
3907
  nativeEncodedFrameCount: Number.isFinite(Number(message.nativeEncodedFrameCount)) ? Number(message.nativeEncodedFrameCount) : 0,
3906
3908
  nativeEncodeFramesInFlight: Number.isFinite(Number(message.nativeEncodeFramesInFlight)) ? Number(message.nativeEncodeFramesInFlight) : 0,
3907
3909
  nativeBackpressureSkippedFrameCount: Number.isFinite(Number(message.nativeBackpressureSkippedFrameCount)) ? Number(message.nativeBackpressureSkippedFrameCount) : 0,
3908
3910
  nativeIdleSkippedFrameCount: Number.isFinite(Number(message.nativeIdleSkippedFrameCount)) ? Number(message.nativeIdleSkippedFrameCount) : 0,
3911
+ nativeOutputQueueDepth: Number.isFinite(Number(message.nativeOutputQueueDepth)) ? Number(message.nativeOutputQueueDepth) : 0,
3912
+ nativeOutputQueueBytes: Number.isFinite(Number(message.nativeOutputQueueBytes)) ? Number(message.nativeOutputQueueBytes) : 0,
3913
+ nativeOutputQueueHighWatermark: Number.isFinite(Number(message.nativeOutputQueueHighWatermark)) ? Number(message.nativeOutputQueueHighWatermark) : 0,
3914
+ nativeOutputQueueDroppedCount: Number.isFinite(Number(message.nativeOutputQueueDroppedCount)) ? Number(message.nativeOutputQueueDroppedCount) : 0,
3915
+ nativeOutputQueueAwaitingKeyFrame: message.nativeOutputQueueAwaitingKeyFrame === true,
3909
3916
  nativeCaptureTelemetryAt: safeString(message.nativeCaptureTelemetryAt, 80),
3910
3917
  droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
3911
3918
  hardwareEncoder: safeString(message.hardwareEncoder, 80),
@@ -4280,14 +4287,21 @@ export function createRemoteHub(options = {}) {
4280
4287
  : null,
4281
4288
  nativeEncoderHardwareQueryStatus: safeString(message.nativeEncoderHardwareQueryStatus, 80),
4282
4289
  nativeCaptureFps: Number.isFinite(Number(message.nativeCaptureFps)) ? Number(message.nativeCaptureFps) : 0,
4290
+ nativeCompressionFps: Number.isFinite(Number(message.nativeCompressionFps)) ? Number(message.nativeCompressionFps) : 0,
4283
4291
  nativeEncodedFps: Number.isFinite(Number(message.nativeEncodedFps)) ? Number(message.nativeEncodedFps) : 0,
4284
4292
  nativeCaptureSampleCount: Number.isFinite(Number(message.nativeCaptureSampleCount)) ? Number(message.nativeCaptureSampleCount) : 0,
4285
4293
  nativeEncodableFrameCount: Number.isFinite(Number(message.nativeEncodableFrameCount)) ? Number(message.nativeEncodableFrameCount) : 0,
4286
4294
  nativeSkippedFrameCount: Number.isFinite(Number(message.nativeSkippedFrameCount)) ? Number(message.nativeSkippedFrameCount) : 0,
4295
+ nativeCompressionOutputCount: Number.isFinite(Number(message.nativeCompressionOutputCount)) ? Number(message.nativeCompressionOutputCount) : 0,
4287
4296
  nativeEncodedFrameCount: Number.isFinite(Number(message.nativeEncodedFrameCount)) ? Number(message.nativeEncodedFrameCount) : 0,
4288
4297
  nativeEncodeFramesInFlight: Number.isFinite(Number(message.nativeEncodeFramesInFlight)) ? Number(message.nativeEncodeFramesInFlight) : 0,
4289
4298
  nativeBackpressureSkippedFrameCount: Number.isFinite(Number(message.nativeBackpressureSkippedFrameCount)) ? Number(message.nativeBackpressureSkippedFrameCount) : 0,
4290
4299
  nativeIdleSkippedFrameCount: Number.isFinite(Number(message.nativeIdleSkippedFrameCount)) ? Number(message.nativeIdleSkippedFrameCount) : 0,
4300
+ nativeOutputQueueDepth: Number.isFinite(Number(message.nativeOutputQueueDepth)) ? Number(message.nativeOutputQueueDepth) : 0,
4301
+ nativeOutputQueueBytes: Number.isFinite(Number(message.nativeOutputQueueBytes)) ? Number(message.nativeOutputQueueBytes) : 0,
4302
+ nativeOutputQueueHighWatermark: Number.isFinite(Number(message.nativeOutputQueueHighWatermark)) ? Number(message.nativeOutputQueueHighWatermark) : 0,
4303
+ nativeOutputQueueDroppedCount: Number.isFinite(Number(message.nativeOutputQueueDroppedCount)) ? Number(message.nativeOutputQueueDroppedCount) : 0,
4304
+ nativeOutputQueueAwaitingKeyFrame: message.nativeOutputQueueAwaitingKeyFrame === true,
4291
4305
  nativeCaptureTelemetryAt: safeString(message.nativeCaptureTelemetryAt, 80),
4292
4306
  droppedByAgent: Number.isFinite(Number(message.droppedByAgent)) ? Number(message.droppedByAgent) : 0,
4293
4307
  hubIngressDropped: Number.isFinite(Number(message.hubIngressDropped)) ? Number(message.hubIngressDropped) : 0,