@livedesk/hub 0.1.15 → 0.1.17
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 +1 -1
- package/src/agents/agent-manager.js +123 -175
- package/src/agents/agent-settings.js +9 -100
- package/src/agents/agent-tool-registry.js +1 -1
- package/src/agents/codex-agent-runtime.js +14 -16
- package/src/remote-hub.js +92 -124
- package/src/server.js +58 -46
- package/src/agents/opencode-go-provider.js +0 -260
- package/src/agents/secret-store.js +0 -165
package/package.json
CHANGED
|
@@ -1,175 +1,123 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { AgentSettingsStore, AGENT_PROVIDER_CODEX
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
async
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 testConnection() {
|
|
98
|
+
if (!runtime) throw new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
|
|
99
|
+
statusCache = { expiresAt: 0, value: null };
|
|
100
|
+
return runtime.testConnection();
|
|
101
|
+
},
|
|
102
|
+
async createPlan() {
|
|
103
|
+
throw new AgentProviderError('agent-codex-run-required', 'Codex Agent requests use the LiveDesk tool loop.', { status: 409 });
|
|
104
|
+
},
|
|
105
|
+
async createSummary(input) {
|
|
106
|
+
const source = input && typeof input === 'object' ? input : {};
|
|
107
|
+
const results = Array.isArray(source.results) ? source.results : [];
|
|
108
|
+
return {
|
|
109
|
+
summary: String(results.length ? `${source.completed || 0} completed, ${source.failed || 0} failed.` : 'LiveDesk Agent run completed.').slice(0, 1200)
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
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 });
|
|
116
|
+
}
|
|
117
|
+
return runtime.start(input);
|
|
118
|
+
},
|
|
119
|
+
getRun(runId) { return runtime?.get(runId) || null; },
|
|
120
|
+
cancelRun(runId) { return runtime?.cancel(runId) || null; },
|
|
121
|
+
cancelAllRuns() { return runtime?.cancelAll?.() || 0; }
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -3,17 +3,12 @@ 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
|
-
export const DEFAULT_CODEX_MODEL_ID = '';
|
|
10
7
|
export const DEFAULT_AGENT_WORKSPACE = path.join(os.homedir(), '.livedesk', 'agent-workspace');
|
|
11
8
|
export const DEFAULT_AGENT_SETTINGS = Object.freeze({
|
|
12
|
-
settingsVersion:
|
|
9
|
+
settingsVersion: 4,
|
|
13
10
|
enabled: true,
|
|
14
11
|
provider: DEFAULT_AGENT_PROVIDER,
|
|
15
|
-
codexModelId: DEFAULT_CODEX_MODEL_ID,
|
|
16
|
-
codexReasoningEffort: 'medium',
|
|
17
12
|
codexSandboxMode: 'read-only',
|
|
18
13
|
codexApprovalPolicy: 'never',
|
|
19
14
|
codexWorkingDirectory: DEFAULT_AGENT_WORKSPACE,
|
|
@@ -24,44 +19,9 @@ export const DEFAULT_AGENT_SETTINGS = Object.freeze({
|
|
|
24
19
|
codexShowDetailedEvents: true,
|
|
25
20
|
codexNetworkAccessEnabled: false,
|
|
26
21
|
codexWebSearchMode: 'disabled',
|
|
27
|
-
|
|
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
|
|
22
|
+
maxConcurrentRequests: 2
|
|
47
23
|
});
|
|
48
24
|
|
|
49
|
-
const PROVIDERS = new Set([AGENT_PROVIDER_CODEX, AGENT_PROVIDER_OPENCODE_GO]);
|
|
50
|
-
const REASONING_EFFORTS = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']);
|
|
51
|
-
const SANDBOX_MODES = new Set(['read-only', 'workspace-write', 'danger-full-access']);
|
|
52
|
-
const APPROVAL_POLICIES = new Set(['never', 'on-request', 'on-failure', 'untrusted']);
|
|
53
|
-
const WEB_SEARCH_MODES = new Set(['disabled', 'cached', 'live']);
|
|
54
|
-
const LEGACY_REASONING_LEVELS = new Set(['auto', 'low', 'medium', 'high']);
|
|
55
|
-
|
|
56
|
-
export class AgentSettingsError extends Error {
|
|
57
|
-
constructor(code, message) {
|
|
58
|
-
super(message);
|
|
59
|
-
this.name = 'AgentSettingsError';
|
|
60
|
-
this.code = code;
|
|
61
|
-
this.status = 400;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
25
|
function booleanValue(value, fallback) {
|
|
66
26
|
return typeof value === 'boolean' ? value : fallback;
|
|
67
27
|
}
|
|
@@ -73,31 +33,7 @@ function numberValue(value, min, max, fallback, integer = false) {
|
|
|
73
33
|
return integer ? Math.round(clamped) : clamped;
|
|
74
34
|
}
|
|
75
35
|
|
|
76
|
-
function
|
|
77
|
-
if (typeof value !== 'string') return fallback;
|
|
78
|
-
return value.trim().slice(0, maxLength);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function enumValue(value, allowed, fallback, code) {
|
|
82
|
-
const normalized = stringValue(value, fallback, 80).toLowerCase();
|
|
83
|
-
if (!allowed.has(normalized)) throw new AgentSettingsError(code, `Invalid agent setting: ${normalized}.`);
|
|
84
|
-
return normalized;
|
|
85
|
-
}
|
|
86
|
-
|
|
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
|
-
function normalizeWorkingDirectory(value) {
|
|
36
|
+
function normalizeWorkingDirectory() {
|
|
101
37
|
// The agent runtime owns this directory. Do not allow a browser/API caller
|
|
102
38
|
// to move Codex into a user project or an arbitrary filesystem root.
|
|
103
39
|
return DEFAULT_AGENT_WORKSPACE;
|
|
@@ -105,25 +41,17 @@ function normalizeWorkingDirectory(value) {
|
|
|
105
41
|
|
|
106
42
|
export function normalizeAgentSettings(value = {}) {
|
|
107
43
|
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
44
|
return {
|
|
117
|
-
settingsVersion:
|
|
45
|
+
settingsVersion: 4,
|
|
118
46
|
enabled: booleanValue(source.enabled, DEFAULT_AGENT_SETTINGS.enabled),
|
|
119
|
-
provider
|
|
120
|
-
|
|
121
|
-
|
|
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,
|
|
122
50
|
// These are safety settings, not user-controlled execution toggles. They
|
|
123
51
|
// are accepted for compatibility but always normalized to the safe floor.
|
|
124
52
|
codexSandboxMode: 'read-only',
|
|
125
53
|
codexApprovalPolicy: 'never',
|
|
126
|
-
codexWorkingDirectory: normalizeWorkingDirectory(
|
|
54
|
+
codexWorkingDirectory: normalizeWorkingDirectory(),
|
|
127
55
|
codexTaskTimeoutMs: numberValue(source.codexTaskTimeoutMs, 30000, 1800000, DEFAULT_AGENT_SETTINGS.codexTaskTimeoutMs, true),
|
|
128
56
|
codexMaxTurns: numberValue(source.codexMaxTurns, 1, 40, DEFAULT_AGENT_SETTINGS.codexMaxTurns, true),
|
|
129
57
|
codexMaxToolCalls: numberValue(source.codexMaxToolCalls, 1, 100, DEFAULT_AGENT_SETTINGS.codexMaxToolCalls, true),
|
|
@@ -131,26 +59,7 @@ export function normalizeAgentSettings(value = {}) {
|
|
|
131
59
|
codexShowDetailedEvents: booleanValue(source.codexShowDetailedEvents, DEFAULT_AGENT_SETTINGS.codexShowDetailedEvents),
|
|
132
60
|
codexNetworkAccessEnabled: false,
|
|
133
61
|
codexWebSearchMode: 'disabled',
|
|
134
|
-
|
|
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)
|
|
62
|
+
maxConcurrentRequests: numberValue(source.maxConcurrentRequests, 1, 8, DEFAULT_AGENT_SETTINGS.maxConcurrentRequests, true)
|
|
154
63
|
};
|
|
155
64
|
}
|
|
156
65
|
|
|
@@ -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
|
|
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,
|
|
@@ -66,15 +66,13 @@ function normalizeCodexError(error) {
|
|
|
66
66
|
return new AgentProviderError('codex-run-failed', message, { status: 502, retryable: true });
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function codexThreadOptions(
|
|
70
|
-
return {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
modelReasoningEffort: settings.codexReasoningEffort,
|
|
77
|
-
networkAccessEnabled: false,
|
|
69
|
+
function codexThreadOptions(workspace) {
|
|
70
|
+
return {
|
|
71
|
+
sandboxMode: 'read-only',
|
|
72
|
+
approvalPolicy: 'never',
|
|
73
|
+
workingDirectory: workspace,
|
|
74
|
+
skipGitRepoCheck: true,
|
|
75
|
+
networkAccessEnabled: false,
|
|
78
76
|
webSearchMode: 'disabled',
|
|
79
77
|
webSearchEnabled: false,
|
|
80
78
|
additionalDirectories: []
|
|
@@ -417,10 +415,10 @@ export function createCodexAgentRuntime({
|
|
|
417
415
|
const controller = new AbortController();
|
|
418
416
|
const timer = setTimeout(() => controller.abort(), Math.min(30000, settings.codexTaskTimeoutMs));
|
|
419
417
|
try {
|
|
420
|
-
const thread = codex.startThread(codexThreadOptions(
|
|
421
|
-
const turn = await thread.run('Reply with the single word READY. Do not use tools.', { signal: controller.signal });
|
|
422
|
-
if (!String(turn.finalResponse || '').trim()) throw new AgentProviderError('codex-empty-response', 'Codex returned an empty response.', { status: 502 });
|
|
423
|
-
return { ok: true,
|
|
418
|
+
const thread = codex.startThread(codexThreadOptions(workspace));
|
|
419
|
+
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 });
|
|
421
|
+
return { ok: true, latencyMs: Date.now() - startedAt, threadId: thread.id || '' };
|
|
424
422
|
} catch (error) {
|
|
425
423
|
throw normalizeCodexError(error);
|
|
426
424
|
} finally {
|
|
@@ -472,7 +470,7 @@ export function createCodexAgentRuntime({
|
|
|
472
470
|
'Never use arbitrary MCP servers, change permissions, forge approvals, request credentials, or invent a tool result.',
|
|
473
471
|
`The Hub has fixed this run to permission mode ${permissionPolicy?.mode || 'ask'} and enforces the policy independently of your instructions.`,
|
|
474
472
|
'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
|
|
473
|
+
'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
474
|
`Selected device IDs: ${JSON.stringify(deviceIds)}`,
|
|
477
475
|
'Return a concise Korean or English summary grounded only in tool results. Do not invent results.',
|
|
478
476
|
`User request: ${safeText(instruction, 4000)}`
|
|
@@ -531,7 +529,7 @@ export function createCodexAgentRuntime({
|
|
|
531
529
|
if (run.abortReason === 'timeout') throw codexAbortError('codex-run-timeout', 'The Codex task exceeded its timeout.');
|
|
532
530
|
throw Object.assign(new Error('The Codex task was cancelled.'), { name: 'AbortError' });
|
|
533
531
|
}
|
|
534
|
-
const threadOptions = codexThreadOptions(
|
|
532
|
+
const threadOptions = codexThreadOptions(workspace);
|
|
535
533
|
run.status = 'running';
|
|
536
534
|
run.updatedAt = new Date().toISOString();
|
|
537
535
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
@@ -649,7 +647,7 @@ export function createCodexAgentRuntime({
|
|
|
649
647
|
const deviceIds = safeAgentDeviceIds(input.deviceIds);
|
|
650
648
|
if (deviceIds.length === 0) throw new AgentProviderError('agent-no-target-devices', 'Select at least one connected device.', { status: 400 });
|
|
651
649
|
const settings = await settingsStore.get();
|
|
652
|
-
if (!settings.enabled) throw new AgentProviderError('agent-ai-disabled', 'Agent
|
|
650
|
+
if (!settings.enabled) throw new AgentProviderError('agent-ai-disabled', 'Codex Agent is disabled in Settings.', { status: 409 });
|
|
653
651
|
assertSecurityConfiguration();
|
|
654
652
|
pruneRuns();
|
|
655
653
|
if (runs.size >= MAX_RUNS) throw new AgentProviderError('agent-run-limit', 'The LiveDesk Agent run history is full.', { status: 429, retryable: true });
|