@livedesk/hub 0.1.14 → 0.1.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "LiveDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -1,175 +1,132 @@
1
- import os from 'node:os';
2
- import path from 'node:path';
3
- import { AgentSettingsStore, AGENT_PROVIDER_CODEX, AGENT_PROVIDER_OPENCODE_GO } from './agent-settings.js';
4
- import { createOsSecretStore } from './secret-store.js';
5
- import { createOpenCodeGoProvider } from './opencode-go-provider.js';
6
- import { AgentProviderError } from './provider-errors.js';
7
- import { createCodexAgentRuntime } from './codex-agent-runtime.js';
8
-
9
- function maskApiKey(value) {
10
- const key = String(value || '');
11
- return key.length >= 4 ? `••••${key.slice(-4)}` : '';
12
- }
13
-
14
- function publicCodexStatus(status = {}) {
15
- return {
16
- installed: status.installed === true,
17
- authenticated: ['signed-in', 'not-signed-in'].includes(status.authenticated) ? status.authenticated : 'unknown',
18
- status: String(status.status || 'unknown').slice(0, 40),
19
- codexPath: String(status.codexPath || '').slice(0, 500),
20
- detail: String(status.detail || '').slice(0, 300)
21
- };
22
- }
23
-
24
- export function createAgentManager({
25
- dataDir = path.join(os.homedir(), '.livedesk'),
26
- settingsStore,
27
- secretStore,
28
- provider,
29
- codexRuntime
30
- } = {}) {
31
- const settings = settingsStore || new AgentSettingsStore({ dataDir });
32
- const secrets = secretStore || createOsSecretStore({ dataDir });
33
- const legacyProvider = provider || createOpenCodeGoProvider();
34
- const runtime = codexRuntime;
35
- let statusCache = { expiresAt: 0, value: null };
36
-
37
- async function getCodexStatus() {
38
- if (!runtime) return { installed: false, authenticated: 'unknown', status: 'unavailable', codexPath: '' };
39
- if (statusCache.value && statusCache.expiresAt > Date.now()) return statusCache.value;
40
- let value;
41
- try {
42
- value = publicCodexStatus(await runtime.getStatus());
43
- } catch (error) {
44
- value = publicCodexStatus({ installed: true, authenticated: 'unknown', status: error?.code || 'security-unavailable', detail: error?.message || 'Codex security isolation is unavailable.' });
45
- }
46
- statusCache = { value, expiresAt: Date.now() + 10000 };
47
- return value;
48
- }
49
-
50
- async function publicSettings() {
51
- const current = await settings.get();
52
- const { codexMaxTurns: _codexMaxTurns, ...exposedSettings } = current;
53
- let hasApiKey = false;
54
- let apiKeyPreview = '';
55
- let secretStoreAvailable = true;
56
- if (current.provider === AGENT_PROVIDER_OPENCODE_GO) {
57
- try {
58
- const key = await secrets.getApiKey(AGENT_PROVIDER_OPENCODE_GO);
59
- hasApiKey = Boolean(key);
60
- apiKeyPreview = maskApiKey(key);
61
- } catch {
62
- secretStoreAvailable = false;
63
- }
64
- }
65
- const codex = await getCodexStatus();
66
- const securityStatus = runtime?.getSecurityStatus?.() || {
67
- isolatedCodexHome: false,
68
- restrictedEnvironment: false,
69
- livedeskMcpOnly: false,
70
- selectedDeviceScopeEnforced: false,
71
- failClosed: true,
72
- verified: false,
73
- state: 'unavailable'
74
- };
75
- return {
76
- ...exposedSettings,
77
- hasApiKey,
78
- apiKeyPreview,
79
- secretStoreAvailable,
80
- codexInstallation: codex.installed ? 'installed' : 'not-installed',
81
- codexAuth: codex.authenticated,
82
- codexStatus: codex.status,
83
- codexPath: codex.codexPath,
84
- agentWorkspacePath: current.codexWorkingDirectory,
85
- securityStatus
86
- };
87
- }
88
-
89
- async function legacyApiKey() {
90
- const key = await secrets.getApiKey(AGENT_PROVIDER_OPENCODE_GO);
91
- if (!key) throw new AgentProviderError('agent-api-key-missing', 'OpenCode Go API key is not configured.', { status: 401 });
92
- return key;
93
- }
94
-
95
- async function currentProvider() {
96
- const current = await settings.get();
97
- return { current, active: current.provider === AGENT_PROVIDER_CODEX ? runtime : legacyProvider };
98
- }
99
-
100
- return {
101
- getSettings: publicSettings,
102
- async updateSettings(patch = {}) {
103
- const nextPatch = { ...patch };
104
- const apiKey = typeof nextPatch.apiKey === 'string' ? nextPatch.apiKey.trim() : '';
105
- delete nextPatch.apiKey;
106
- const current = await settings.get();
107
- const requestedProvider = String(nextPatch.provider || current.provider);
108
- if (apiKey) {
109
- if (requestedProvider !== AGENT_PROVIDER_OPENCODE_GO) {
110
- throw new AgentProviderError('agent-codex-does-not-use-api-key', 'Codex authentication is owned by the installed Codex CLI.', { status: 400 });
111
- }
112
- await secrets.setApiKey(AGENT_PROVIDER_OPENCODE_GO, apiKey);
113
- }
114
- await settings.update(nextPatch);
115
- statusCache = { expiresAt: 0, value: null };
116
- return publicSettings();
117
- },
118
- async resetSettings() {
119
- await settings.reset();
120
- statusCache = { expiresAt: 0, value: null };
121
- return publicSettings();
122
- },
123
- async deleteApiKey() {
124
- await secrets.deleteApiKey(AGENT_PROVIDER_OPENCODE_GO);
125
- return publicSettings();
126
- },
127
- async getModels() {
128
- const { current, active } = await currentProvider();
129
- if (current.provider === AGENT_PROVIDER_CODEX) {
130
- return [{ id: '', name: 'Codex default', protocol: 'codex-sdk', available: true, capabilities: { supportsReasoningLevel: true, supportsStreaming: true, supportsTools: true } }];
131
- }
132
- return active.getModels(current, await legacyApiKey());
133
- },
134
- async testConnection() {
135
- const { current, active } = await currentProvider();
136
- if (current.provider === AGENT_PROVIDER_CODEX) {
137
- if (!active) throw new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
138
- statusCache = { expiresAt: 0, value: null };
139
- return active.testConnection();
140
- }
141
- return active.testConnection(current, await legacyApiKey());
142
- },
143
- async createPlan(input) {
144
- const { current, active } = await currentProvider();
145
- if (!current.enabled) throw new AgentProviderError('agent-ai-disabled', 'Agent AI is disabled in Settings.', { status: 409 });
146
- if (current.provider === AGENT_PROVIDER_CODEX) {
147
- throw new AgentProviderError('agent-codex-run-required', 'Codex runs use the LiveDesk tool loop.', { status: 409 });
148
- }
149
- return active.createPlan(input, current, await legacyApiKey());
150
- },
151
- async createSummary(input) {
152
- const { current, active } = await currentProvider();
153
- if (!current.enabled || !current.generateSummary) throw new AgentProviderError('agent-summary-disabled', 'Agent summaries are disabled in Settings.', { status: 409 });
154
- if (current.provider === AGENT_PROVIDER_CODEX) {
155
- const source = input && typeof input === 'object' ? input : {};
156
- const results = Array.isArray(source.results) ? source.results : [];
157
- return { summary: String(results.length ? `${source.completed || 0} completed, ${source.failed || 0} failed.` : 'LiveDesk Agent run completed.').slice(0, 1200), modelId: current.codexModelId || 'Codex default' };
158
- }
159
- const source = input && typeof input === 'object' ? input : {};
160
- const results = Array.isArray(source.results) ? source.results : [];
161
- const safeResults = current.includeRawDeviceDetails
162
- ? results.map(result => ({ deviceName: String(result?.deviceName || '').slice(0, 120), status: String(result?.status || '').slice(0, 40), result: String(result?.result || '').slice(0, 1200), error: String(result?.error || '').slice(0, 500) }))
163
- : results.map(result => ({ deviceName: String(result?.deviceName || '').slice(0, 120), status: String(result?.status || '').slice(0, 40), hasResult: Boolean(result?.result), hasError: Boolean(result?.error) }));
164
- return active.createSummary({ instruction: String(source.instruction || '').slice(0, 500), operation: String(source.operation || '').slice(0, 80), summary: { total: Number(source.total || results.length), completed: Number(source.completed || 0), failed: Number(source.failed || 0), results: safeResults } }, current, await legacyApiKey());
165
- },
166
- async startRun(input) {
167
- const current = await settings.get();
168
- if (current.provider !== AGENT_PROVIDER_CODEX || !runtime) throw new AgentProviderError('agent-codex-not-active', 'Codex SDK is not the active Agent provider.', { status: 409 });
169
- return runtime.start(input);
170
- },
171
- getRun(runId) { return runtime?.get(runId) || null; },
172
- cancelRun(runId) { return runtime?.cancel(runId) || null; },
173
- cancelAllRuns() { return runtime?.cancelAll?.() || 0; }
174
- };
175
- }
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { AgentSettingsStore, AGENT_PROVIDER_CODEX } from './agent-settings.js';
4
+ import { AgentProviderError } from './provider-errors.js';
5
+
6
+ function publicCodexStatus(status = {}) {
7
+ return {
8
+ installed: status.installed === true,
9
+ authenticated: ['signed-in', 'not-signed-in'].includes(status.authenticated) ? status.authenticated : 'unknown',
10
+ status: String(status.status || 'unknown').slice(0, 40),
11
+ codexPath: String(status.codexPath || '').slice(0, 500),
12
+ detail: String(status.detail || '').slice(0, 300)
13
+ };
14
+ }
15
+
16
+ export function createAgentManager({
17
+ dataDir = path.join(os.homedir(), '.livedesk'),
18
+ settingsStore,
19
+ codexRuntime
20
+ } = {}) {
21
+ const settings = settingsStore || new AgentSettingsStore({ dataDir });
22
+ const runtime = codexRuntime;
23
+ let statusCache = { expiresAt: 0, value: null };
24
+
25
+ async function getCodexStatus() {
26
+ if (!runtime) return { installed: false, authenticated: 'unknown', status: 'unavailable', codexPath: '' };
27
+ if (statusCache.value && statusCache.expiresAt > Date.now()) return statusCache.value;
28
+ let value;
29
+ try {
30
+ value = publicCodexStatus(await runtime.getStatus());
31
+ } catch (error) {
32
+ value = publicCodexStatus({
33
+ installed: true,
34
+ authenticated: 'unknown',
35
+ status: error?.code || 'security-unavailable',
36
+ detail: error?.message || 'Codex security isolation is unavailable.'
37
+ });
38
+ }
39
+ statusCache = { value, expiresAt: Date.now() + 10000 };
40
+ return value;
41
+ }
42
+
43
+ async function publicSettings() {
44
+ const current = await settings.get();
45
+ const { codexMaxTurns: _codexMaxTurns, ...exposedSettings } = current;
46
+ 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
+ return {
57
+ ...exposedSettings,
58
+ provider: AGENT_PROVIDER_CODEX,
59
+ codexInstallation: codex.installed ? 'installed' : 'not-installed',
60
+ codexAuth: codex.authenticated,
61
+ codexStatus: codex.status,
62
+ codexPath: codex.codexPath,
63
+ agentWorkspacePath: current.codexWorkingDirectory,
64
+ securityStatus
65
+ };
66
+ }
67
+
68
+ return {
69
+ 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
+ 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
+ async testConnection() {
107
+ if (!runtime) throw new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
108
+ statusCache = { expiresAt: 0, value: null };
109
+ return runtime.testConnection();
110
+ },
111
+ async createPlan() {
112
+ throw new AgentProviderError('agent-codex-run-required', 'Codex Agent requests use the LiveDesk tool loop.', { status: 409 });
113
+ },
114
+ async createSummary(input) {
115
+ const source = input && typeof input === 'object' ? input : {};
116
+ const results = Array.isArray(source.results) ? source.results : [];
117
+ return {
118
+ summary: String(results.length ? `${source.completed || 0} completed, ${source.failed || 0} failed.` : 'LiveDesk Agent run completed.').slice(0, 1200)
119
+ };
120
+ },
121
+ async startRun(input) {
122
+ const current = await settings.get();
123
+ if (current.provider !== AGENT_PROVIDER_CODEX || !runtime) {
124
+ throw new AgentProviderError('agent-codex-not-active', 'Codex SDK is not the active Agent runtime.', { status: 409 });
125
+ }
126
+ return runtime.start(input);
127
+ },
128
+ getRun(runId) { return runtime?.get(runId) || null; },
129
+ cancelRun(runId) { return runtime?.cancel(runId) || null; },
130
+ cancelAllRuns() { return runtime?.cancelAll?.() || 0; }
131
+ };
132
+ }
@@ -3,13 +3,11 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
 
5
5
  export const AGENT_PROVIDER_CODEX = 'codex';
6
- export const AGENT_PROVIDER_OPENCODE_GO = 'opencode-go';
7
6
  export const DEFAULT_AGENT_PROVIDER = AGENT_PROVIDER_CODEX;
8
- export const DEFAULT_AGENT_BASE_URL = 'https://opencode.ai/zen/go/v1';
9
7
  export const DEFAULT_CODEX_MODEL_ID = '';
10
8
  export const DEFAULT_AGENT_WORKSPACE = path.join(os.homedir(), '.livedesk', 'agent-workspace');
11
9
  export const DEFAULT_AGENT_SETTINGS = Object.freeze({
12
- settingsVersion: 2,
10
+ settingsVersion: 3,
13
11
  enabled: true,
14
12
  provider: DEFAULT_AGENT_PROVIDER,
15
13
  codexModelId: DEFAULT_CODEX_MODEL_ID,
@@ -24,34 +22,13 @@ export const DEFAULT_AGENT_SETTINGS = Object.freeze({
24
22
  codexShowDetailedEvents: true,
25
23
  codexNetworkAccessEnabled: false,
26
24
  codexWebSearchMode: 'disabled',
27
- deterministicParserFirst: true,
28
- generateSummary: true,
29
- fallbackSummary: true,
30
- includeRawDeviceDetails: false,
31
- maxConcurrentRequests: 2,
32
- baseUrl: DEFAULT_AGENT_BASE_URL,
33
- defaultModelId: 'deepseek-v4-flash',
34
- planModelId: '',
35
- summaryModelId: '',
36
- reasoningLevel: 'auto',
37
- planTemperature: 0.1,
38
- summaryTemperature: 0.2,
39
- topP: 1,
40
- planMaxOutputTokens: 1024,
41
- summaryMaxOutputTokens: 1024,
42
- planTimeoutMs: 60000,
43
- summaryTimeoutMs: 60000,
44
- retryCount: 1,
45
- retryDelayMs: 1000,
46
- streamSummary: false
25
+ maxConcurrentRequests: 2
47
26
  });
48
27
 
49
- const PROVIDERS = new Set([AGENT_PROVIDER_CODEX, AGENT_PROVIDER_OPENCODE_GO]);
50
28
  const REASONING_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']);
51
29
  const SANDBOX_MODES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
52
30
  const APPROVAL_POLICIES = new Set(['never', 'on-request', 'on-failure', 'untrusted']);
53
31
  const WEB_SEARCH_MODES = new Set(['disabled', 'cached', 'live']);
54
- const LEGACY_REASONING_LEVELS = new Set(['auto', 'low', 'medium', 'high']);
55
32
 
56
33
  export class AgentSettingsError extends Error {
57
34
  constructor(code, message) {
@@ -84,19 +61,6 @@ function enumValue(value, allowed, fallback, code) {
84
61
  return normalized;
85
62
  }
86
63
 
87
- function normalizeBaseUrl(value) {
88
- const candidate = stringValue(value, DEFAULT_AGENT_SETTINGS.baseUrl, 500).replace(/\/+$/, '');
89
- try {
90
- const parsed = new URL(candidate);
91
- if (!['http:', 'https:'].includes(parsed.protocol) || !parsed.hostname) throw new Error('invalid protocol');
92
- parsed.hash = '';
93
- parsed.search = '';
94
- return parsed.toString().replace(/\/+$/, '');
95
- } catch {
96
- throw new AgentSettingsError('agent-invalid-base-url', 'Base URL must be a valid HTTP or HTTPS URL.');
97
- }
98
- }
99
-
100
64
  function normalizeWorkingDirectory(value) {
101
65
  // The agent runtime owns this directory. Do not allow a browser/API caller
102
66
  // to move Codex into a user project or an arbitrary filesystem root.
@@ -105,18 +69,12 @@ function normalizeWorkingDirectory(value) {
105
69
 
106
70
  export function normalizeAgentSettings(value = {}) {
107
71
  const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
108
- // The pre-Codex settings file was written by LiveDesk itself and had no
109
- // migration marker. Keep the legacy values but move that first-run config
110
- // to the new Codex default.
111
- const migratingLegacy = Number(source.settingsVersion || 0) < 2 && source.provider === AGENT_PROVIDER_OPENCODE_GO;
112
- const provider = migratingLegacy
113
- ? DEFAULT_AGENT_PROVIDER
114
- : enumValue(source.provider, PROVIDERS, DEFAULT_AGENT_PROVIDER, 'agent-unsupported-provider');
115
- const reasoningLevel = enumValue(source.reasoningLevel, LEGACY_REASONING_LEVELS, DEFAULT_AGENT_SETTINGS.reasoningLevel, 'agent-invalid-reasoning-level');
116
72
  return {
117
- settingsVersion: 2,
73
+ settingsVersion: 3,
118
74
  enabled: booleanValue(source.enabled, DEFAULT_AGENT_SETTINGS.enabled),
119
- provider,
75
+ // LiveDesk has one Agent runtime. Old provider values are deliberately
76
+ // normalized to Codex so an upgrade cannot reactivate a retired provider.
77
+ provider: AGENT_PROVIDER_CODEX,
120
78
  codexModelId: stringValue(source.codexModelId, DEFAULT_AGENT_SETTINGS.codexModelId, 160),
121
79
  codexReasoningEffort: enumValue(source.codexReasoningEffort, REASONING_EFFORTS, DEFAULT_AGENT_SETTINGS.codexReasoningEffort, 'agent-invalid-reasoning-effort'),
122
80
  // These are safety settings, not user-controlled execution toggles. They
@@ -131,26 +89,7 @@ export function normalizeAgentSettings(value = {}) {
131
89
  codexShowDetailedEvents: booleanValue(source.codexShowDetailedEvents, DEFAULT_AGENT_SETTINGS.codexShowDetailedEvents),
132
90
  codexNetworkAccessEnabled: false,
133
91
  codexWebSearchMode: 'disabled',
134
- deterministicParserFirst: booleanValue(source.deterministicParserFirst, DEFAULT_AGENT_SETTINGS.deterministicParserFirst),
135
- generateSummary: booleanValue(source.generateSummary, DEFAULT_AGENT_SETTINGS.generateSummary),
136
- fallbackSummary: booleanValue(source.fallbackSummary, DEFAULT_AGENT_SETTINGS.fallbackSummary),
137
- includeRawDeviceDetails: booleanValue(source.includeRawDeviceDetails, DEFAULT_AGENT_SETTINGS.includeRawDeviceDetails),
138
- maxConcurrentRequests: numberValue(source.maxConcurrentRequests, 1, 8, DEFAULT_AGENT_SETTINGS.maxConcurrentRequests, true),
139
- baseUrl: normalizeBaseUrl(source.baseUrl ?? DEFAULT_AGENT_SETTINGS.baseUrl),
140
- defaultModelId: stringValue(source.defaultModelId, DEFAULT_AGENT_SETTINGS.defaultModelId, 160),
141
- planModelId: stringValue(source.planModelId, DEFAULT_AGENT_SETTINGS.planModelId, 160),
142
- summaryModelId: stringValue(source.summaryModelId, DEFAULT_AGENT_SETTINGS.summaryModelId, 160),
143
- reasoningLevel,
144
- planTemperature: numberValue(source.planTemperature, 0, 2, DEFAULT_AGENT_SETTINGS.planTemperature),
145
- summaryTemperature: numberValue(source.summaryTemperature, 0, 2, DEFAULT_AGENT_SETTINGS.summaryTemperature),
146
- topP: numberValue(source.topP, 0, 1, DEFAULT_AGENT_SETTINGS.topP),
147
- planMaxOutputTokens: numberValue(source.planMaxOutputTokens, 128, 16384, DEFAULT_AGENT_SETTINGS.planMaxOutputTokens, true),
148
- summaryMaxOutputTokens: numberValue(source.summaryMaxOutputTokens, 128, 16384, DEFAULT_AGENT_SETTINGS.summaryMaxOutputTokens, true),
149
- planTimeoutMs: numberValue(source.planTimeoutMs, 5000, 180000, DEFAULT_AGENT_SETTINGS.planTimeoutMs, true),
150
- summaryTimeoutMs: numberValue(source.summaryTimeoutMs, 5000, 180000, DEFAULT_AGENT_SETTINGS.summaryTimeoutMs, true),
151
- retryCount: numberValue(source.retryCount, 0, 4, DEFAULT_AGENT_SETTINGS.retryCount, true),
152
- retryDelayMs: numberValue(source.retryDelayMs, 100, 10000, DEFAULT_AGENT_SETTINGS.retryDelayMs, true),
153
- streamSummary: booleanValue(source.streamSummary, DEFAULT_AGENT_SETTINGS.streamSummary)
92
+ maxConcurrentRequests: numberValue(source.maxConcurrentRequests, 1, 8, DEFAULT_AGENT_SETTINGS.maxConcurrentRequests, true)
154
93
  };
155
94
  }
156
95
 
@@ -79,7 +79,7 @@ export const AGENT_TOOL_DEFINITIONS = Object.freeze([
79
79
  },
80
80
  {
81
81
  name: 'livedesk.list_processes',
82
- description: 'Read process status from the selected Clients. Use processName for a named process such as ComfyUI.',
82
+ description: 'Read process status from the selected Clients. Use processName when the request names a specific process.',
83
83
  category: 'read',
84
84
  readOnly: true,
85
85
  mutating: false,
@@ -472,7 +472,7 @@ export function createCodexAgentRuntime({
472
472
  'Never use arbitrary MCP servers, change permissions, forge approvals, request credentials, or invent a tool result.',
473
473
  `The Hub has fixed this run to permission mode ${permissionPolicy?.mode || 'ask'} and enforces the policy independently of your instructions.`,
474
474
  '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.',
475
- 'You may perform multiple safe read-only checks when the request requires a sequence. For example, find Clients without ComfyUI, then check the service status only on those Clients.',
475
+ '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.',
476
476
  `Selected device IDs: ${JSON.stringify(deviceIds)}`,
477
477
  'Return a concise Korean or English summary grounded only in tool results. Do not invent results.',
478
478
  `User request: ${safeText(instruction, 4000)}`
@@ -649,7 +649,7 @@ export function createCodexAgentRuntime({
649
649
  const deviceIds = safeAgentDeviceIds(input.deviceIds);
650
650
  if (deviceIds.length === 0) throw new AgentProviderError('agent-no-target-devices', 'Select at least one connected device.', { status: 400 });
651
651
  const settings = await settingsStore.get();
652
- if (!settings.enabled) throw new AgentProviderError('agent-ai-disabled', 'Agent AI is disabled in Settings.', { status: 409 });
652
+ if (!settings.enabled) throw new AgentProviderError('agent-ai-disabled', 'Codex Agent is disabled in Settings.', { status: 409 });
653
653
  assertSecurityConfiguration();
654
654
  pruneRuns();
655
655
  if (runs.size >= MAX_RUNS) throw new AgentProviderError('agent-run-limit', 'The LiveDesk Agent run history is full.', { status: 429, retryable: true });