livedesk 0.1.207 → 0.1.208
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/hub/src/agents/agent-manager.js +3 -1
- package/hub/src/agents/codex-agent-runtime.js +21 -5
- package/hub/src/server.js +19 -1
- package/package.json +1 -1
- package/web/dist/assets/index-BIAVgQv7.js +15 -0
- package/web/dist/assets/{index-DIA3wv8F.css → index-CtAsVGFt.css} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-CHVI5IbK.js +0 -15
|
@@ -68,7 +68,9 @@ export function createAgentManager({
|
|
|
68
68
|
restrictedEnvironment: false,
|
|
69
69
|
livedeskMcpOnly: false,
|
|
70
70
|
selectedDeviceScopeEnforced: false,
|
|
71
|
-
failClosed: true
|
|
71
|
+
failClosed: true,
|
|
72
|
+
verified: false,
|
|
73
|
+
state: 'unavailable'
|
|
72
74
|
};
|
|
73
75
|
return {
|
|
74
76
|
...exposedSettings,
|
|
@@ -247,19 +247,35 @@ export function createCodexAgentRuntime({
|
|
|
247
247
|
const runs = new Map();
|
|
248
248
|
let activeCount = 0;
|
|
249
249
|
let codexModulePromise;
|
|
250
|
+
let securityVerificationState = 'configured';
|
|
250
251
|
const workspace = path.resolve(dataDir, 'agent-workspace');
|
|
251
252
|
const codexHome = path.resolve(dataDir, 'codex-home');
|
|
252
253
|
|
|
253
254
|
function securityStatus() {
|
|
255
|
+
const configured = codexHome !== path.resolve(os.homedir(), '.codex')
|
|
256
|
+
&& typeof createMcpSession === 'function'
|
|
257
|
+
&& Boolean(mcpServerPath);
|
|
254
258
|
return {
|
|
255
259
|
isolatedCodexHome: codexHome !== path.resolve(os.homedir(), '.codex'),
|
|
256
260
|
restrictedEnvironment: true,
|
|
257
261
|
livedeskMcpOnly: typeof createMcpSession === 'function' && Boolean(mcpServerPath),
|
|
258
262
|
selectedDeviceScopeEnforced: typeof createMcpSession === 'function',
|
|
259
|
-
failClosed: true
|
|
263
|
+
failClosed: true,
|
|
264
|
+
verified: configured && securityVerificationState === 'verified',
|
|
265
|
+
state: configured ? securityVerificationState : 'unavailable'
|
|
260
266
|
};
|
|
261
267
|
}
|
|
262
268
|
|
|
269
|
+
async function prepareCodexHome() {
|
|
270
|
+
try {
|
|
271
|
+
await ensureCodexHome(codexHome);
|
|
272
|
+
securityVerificationState = 'verified';
|
|
273
|
+
} catch (error) {
|
|
274
|
+
securityVerificationState = 'unavailable';
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
263
279
|
function assertSecurityConfiguration() {
|
|
264
280
|
const status = securityStatus();
|
|
265
281
|
if (!status.isolatedCodexHome || !status.restrictedEnvironment || !status.livedeskMcpOnly || !status.selectedDeviceScopeEnforced) {
|
|
@@ -320,7 +336,7 @@ export function createCodexAgentRuntime({
|
|
|
320
336
|
let authenticated = 'unknown';
|
|
321
337
|
let detail = '';
|
|
322
338
|
try {
|
|
323
|
-
await
|
|
339
|
+
await prepareCodexHome();
|
|
324
340
|
const result = await runCli(pathToCli, ['login', 'status'], 5000, safeCodexEnv(codexHome));
|
|
325
341
|
detail = safeText(result.stdout || result.stderr, 300);
|
|
326
342
|
authenticated = /logged in|authenticated|chatgpt/i.test(`${result.stdout}\n${result.stderr}`)
|
|
@@ -341,7 +357,7 @@ export function createCodexAgentRuntime({
|
|
|
341
357
|
if (status.authenticated !== 'signed-in') throw new AgentProviderError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
|
|
342
358
|
const settings = await settingsStore.get();
|
|
343
359
|
await mkdir(workspace, { recursive: true });
|
|
344
|
-
await
|
|
360
|
+
await prepareCodexHome();
|
|
345
361
|
const codex = await createCodexClient({ env: safeCodexEnv(codexHome), config: baseCodexConfig() });
|
|
346
362
|
const controller = new AbortController();
|
|
347
363
|
const timer = setTimeout(() => controller.abort(), Math.min(30000, settings.codexTaskTimeoutMs));
|
|
@@ -425,14 +441,14 @@ export function createCodexAgentRuntime({
|
|
|
425
441
|
const status = await getStatus();
|
|
426
442
|
if (!status.installed) throw new AgentProviderError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
|
|
427
443
|
if (status.authenticated !== 'signed-in') throw new AgentProviderError('codex-auth-required', 'Sign in with the Codex CLI first.', { status: 401 });
|
|
428
|
-
session = createMcpSession({ runId: run.runId, signal: run.abortController.signal, allowedDeviceIds: input.deviceIds });
|
|
444
|
+
session = createMcpSession({ runId: run.runId, signal: run.abortController.signal, allowedDeviceIds: input.deviceIds, maxToolCalls: settings.codexMaxToolCalls });
|
|
429
445
|
run.mcpToken = session.token;
|
|
430
446
|
if (run.abortController.signal.aborted) {
|
|
431
447
|
session.cancel();
|
|
432
448
|
if (run.abortReason === 'timeout') throw codexAbortError('codex-run-timeout', 'The Codex task exceeded its timeout.');
|
|
433
449
|
throw Object.assign(new Error('The Codex task was cancelled.'), { name: 'AbortError' });
|
|
434
450
|
}
|
|
435
|
-
await
|
|
451
|
+
await prepareCodexHome();
|
|
436
452
|
const codex = await createCodexClient({ env: safeCodexEnv(codexHome), config: codexConfig({ mcpServerPath, mcpUrl: session.url, token: session.token, workspace }) });
|
|
437
453
|
const threadOptions = codexThreadOptions(settings, workspace);
|
|
438
454
|
const thread = input.resumeThreadId && settings.codexResumeSessions
|
package/hub/src/server.js
CHANGED
|
@@ -215,6 +215,8 @@ const agentMcpRunBatches = new Map();
|
|
|
215
215
|
const agentMcpRunCleanupTimers = new Map();
|
|
216
216
|
const AGENT_MCP_RUN_TTL_MS = 60 * 60 * 1000;
|
|
217
217
|
const MAX_AGENT_MCP_RUN_RECORDS = 500;
|
|
218
|
+
const DEFAULT_AGENT_MCP_TOOL_CALL_LIMIT = 20;
|
|
219
|
+
const MAX_AGENT_MCP_TOOL_CALL_LIMIT = 100;
|
|
218
220
|
|
|
219
221
|
function connectedAgentDeviceIds() {
|
|
220
222
|
const devices = remoteHub.listDevices({ includeDataUrl: false });
|
|
@@ -247,7 +249,13 @@ function scheduleAgentMcpRunCleanup(runId) {
|
|
|
247
249
|
agentMcpRunCleanupTimers.set(runId, timer);
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
function
|
|
252
|
+
function normalizeAgentMcpToolCallLimit(value) {
|
|
253
|
+
const limit = Number(value);
|
|
254
|
+
if (!Number.isFinite(limit)) return DEFAULT_AGENT_MCP_TOOL_CALL_LIMIT;
|
|
255
|
+
return Math.min(MAX_AGENT_MCP_TOOL_CALL_LIMIT, Math.max(1, Math.floor(limit)));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function createAgentMcpSession({ runId, signal, allowedDeviceIds: allowedDeviceIdsInput = [], maxToolCalls }) {
|
|
251
259
|
const token = crypto.randomBytes(32).toString('hex');
|
|
252
260
|
const allowedDeviceIds = createAgentDeviceScope(allowedDeviceIdsInput);
|
|
253
261
|
const initialResolution = resolveAgentTargetIds({ allowedDeviceIds, connectedDeviceIds: connectedAgentDeviceIds() });
|
|
@@ -260,6 +268,9 @@ function createAgentMcpSession({ runId, signal, allowedDeviceIds: allowedDeviceI
|
|
|
260
268
|
runId: String(runId || ''),
|
|
261
269
|
signal,
|
|
262
270
|
allowedDeviceIds,
|
|
271
|
+
toolCallCount: 0,
|
|
272
|
+
maxToolCalls: normalizeAgentMcpToolCallLimit(maxToolCalls),
|
|
273
|
+
toolLimitReached: false,
|
|
263
274
|
cancelled: false,
|
|
264
275
|
cancel() {
|
|
265
276
|
if (this.cancelled) return;
|
|
@@ -300,6 +311,13 @@ function validateAgentMcpArguments(name, args) {
|
|
|
300
311
|
|
|
301
312
|
async function dispatchAgentMcpTool(session, name, args = {}) {
|
|
302
313
|
if (session.cancelled || session.signal?.aborted) return { ok: false, error: 'cancelled-by-user' };
|
|
314
|
+
// This check runs synchronously before the first await, so concurrent Node
|
|
315
|
+
// requests cannot pass the same remaining budget and queue extra Client work.
|
|
316
|
+
if (session.toolCallCount >= session.maxToolCalls) {
|
|
317
|
+
session.toolLimitReached = true;
|
|
318
|
+
return { ok: false, error: 'codex-tool-limit-reached' };
|
|
319
|
+
}
|
|
320
|
+
session.toolCallCount += 1;
|
|
303
321
|
const validationError = validateAgentMcpArguments(name, args);
|
|
304
322
|
if (validationError) return { ok: false, error: validationError };
|
|
305
323
|
if (name === 'livedesk.list_devices') {
|