@livedesk/hub 0.1.20 → 0.1.22
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 +10 -1
- package/src/remote-hub.js +47 -7
- package/src/server.js +29 -12
- package/src/settings/settings-schema.js +1 -0
package/package.json
CHANGED
|
@@ -13,6 +13,15 @@ function publicCodexStatus(status = {}) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function publicCodexConnection(result = {}) {
|
|
17
|
+
const latencyMs = Number(result.latencyMs);
|
|
18
|
+
return {
|
|
19
|
+
ok: result.ok === true,
|
|
20
|
+
latencyMs: Number.isFinite(latencyMs) ? Math.max(0, Math.round(latencyMs)) : 0,
|
|
21
|
+
threadId: String(result.threadId || '').slice(0, 200)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
16
25
|
export function createAgentManager({
|
|
17
26
|
dataDir = path.join(os.homedir(), '.livedesk'),
|
|
18
27
|
settingsStore,
|
|
@@ -56,7 +65,7 @@ export function createAgentManager({
|
|
|
56
65
|
async testConnection() {
|
|
57
66
|
if (!runtime) throw new AgentRuntimeError('codex-sdk-not-installed', 'Codex SDK is not installed.', { status: 503 });
|
|
58
67
|
statusCache = { expiresAt: 0, value: null };
|
|
59
|
-
return runtime.testConnection();
|
|
68
|
+
return publicCodexConnection(await runtime.testConnection());
|
|
60
69
|
},
|
|
61
70
|
async createSummary(input) {
|
|
62
71
|
const source = input && typeof input === 'object' ? input : {};
|
package/src/remote-hub.js
CHANGED
|
@@ -658,7 +658,8 @@ function getRetiredAgentTaskError(options = {}) {
|
|
|
658
658
|
const approvalLevel = safeString(options?.approvalLevel, 80).toLowerCase();
|
|
659
659
|
const hasModelSelection = options
|
|
660
660
|
&& typeof options === 'object'
|
|
661
|
-
&&
|
|
661
|
+
&& ['model', 'aiModel', 'aiProvider', 'provider']
|
|
662
|
+
.some(key => Object.prototype.hasOwnProperty.call(options, key));
|
|
662
663
|
return approvalLevel === 'ai-assist' || hasModelSelection
|
|
663
664
|
? RETIRED_AGENT_TASK_ERROR
|
|
664
665
|
: '';
|
|
@@ -3892,6 +3893,13 @@ export function createRemoteHub(options = {}) {
|
|
|
3892
3893
|
agentQueueBeforePaceMs: Number.isFinite(Number(message.agentQueueBeforePaceMs)) ? Number(message.agentQueueBeforePaceMs) : 0,
|
|
3893
3894
|
agentFrameAgeMs: Number.isFinite(Number(message.agentFrameAgeMs)) ? Number(message.agentFrameAgeMs) : 0,
|
|
3894
3895
|
nativeCaptureTelemetryAvailable: message.nativeCaptureTelemetryAvailable === true,
|
|
3896
|
+
nativeCaptureTelemetryVersion: Number.isFinite(Number(message.nativeCaptureTelemetryVersion)) ? Number(message.nativeCaptureTelemetryVersion) : 0,
|
|
3897
|
+
nativeCaptureTelemetryAgeMs: message.nativeCaptureTelemetryAgeMs === null
|
|
3898
|
+
|| message.nativeCaptureTelemetryAgeMs === undefined
|
|
3899
|
+
? undefined
|
|
3900
|
+
: Number.isFinite(Number(message.nativeCaptureTelemetryAgeMs))
|
|
3901
|
+
? Math.max(0, Number(message.nativeCaptureTelemetryAgeMs))
|
|
3902
|
+
: undefined,
|
|
3895
3903
|
nativeEncoderHardwareAccelerationKnown: message.nativeEncoderHardwareAccelerationKnown === true,
|
|
3896
3904
|
nativeEncoderHardwareAccelerated: typeof message.nativeEncoderHardwareAccelerated === 'boolean'
|
|
3897
3905
|
? message.nativeEncoderHardwareAccelerated
|
|
@@ -4281,6 +4289,13 @@ export function createRemoteHub(options = {}) {
|
|
|
4281
4289
|
agentQueueBeforePaceMs: Number.isFinite(Number(message.agentQueueBeforePaceMs)) ? Number(message.agentQueueBeforePaceMs) : 0,
|
|
4282
4290
|
agentFrameAgeMs: Number.isFinite(Number(message.agentFrameAgeMs)) ? Number(message.agentFrameAgeMs) : 0,
|
|
4283
4291
|
nativeCaptureTelemetryAvailable: message.nativeCaptureTelemetryAvailable === true,
|
|
4292
|
+
nativeCaptureTelemetryVersion: Number.isFinite(Number(message.nativeCaptureTelemetryVersion)) ? Number(message.nativeCaptureTelemetryVersion) : 0,
|
|
4293
|
+
nativeCaptureTelemetryAgeMs: message.nativeCaptureTelemetryAgeMs === null
|
|
4294
|
+
|| message.nativeCaptureTelemetryAgeMs === undefined
|
|
4295
|
+
? undefined
|
|
4296
|
+
: Number.isFinite(Number(message.nativeCaptureTelemetryAgeMs))
|
|
4297
|
+
? Math.max(0, Number(message.nativeCaptureTelemetryAgeMs))
|
|
4298
|
+
: undefined,
|
|
4284
4299
|
nativeEncoderHardwareAccelerationKnown: message.nativeEncoderHardwareAccelerationKnown === true,
|
|
4285
4300
|
nativeEncoderHardwareAccelerated: typeof message.nativeEncoderHardwareAccelerated === 'boolean'
|
|
4286
4301
|
? message.nativeEncoderHardwareAccelerated
|
|
@@ -5444,11 +5459,13 @@ export function createRemoteHub(options = {}) {
|
|
|
5444
5459
|
}
|
|
5445
5460
|
|
|
5446
5461
|
function sendCommand(deviceId, command) {
|
|
5447
|
-
const device = devices.get(String(deviceId || ''));
|
|
5462
|
+
const device = devices.get(String(deviceId || ''));
|
|
5448
5463
|
const commandName = safeString(command?.command || 'ping', 80);
|
|
5449
|
-
const requiredPermission = commandName === 'input.control'
|
|
5450
|
-
? 'allowControl'
|
|
5451
|
-
: commandName
|
|
5464
|
+
const requiredPermission = commandName === 'input.control'
|
|
5465
|
+
? 'allowControl'
|
|
5466
|
+
: commandName === 'system.power'
|
|
5467
|
+
? 'allowPowerActions'
|
|
5468
|
+
: commandName.startsWith('file.transfer')
|
|
5452
5469
|
? 'allowFileTransfer'
|
|
5453
5470
|
: commandName === 'audio.start'
|
|
5454
5471
|
? 'allowRemoteAudio'
|
|
@@ -5507,8 +5524,30 @@ export function createRemoteHub(options = {}) {
|
|
|
5507
5524
|
command: payload.command,
|
|
5508
5525
|
channel: dedicatedFileSocket ? 'file' : 'control'
|
|
5509
5526
|
});
|
|
5510
|
-
return { ok: true, commandId };
|
|
5511
|
-
}
|
|
5527
|
+
return { ok: true, commandId };
|
|
5528
|
+
}
|
|
5529
|
+
|
|
5530
|
+
function refreshDevicePolicies(deviceIds = undefined) {
|
|
5531
|
+
const requestedIds = Array.isArray(deviceIds) && deviceIds.length > 0
|
|
5532
|
+
? new Set(deviceIds.map(deviceId => safeString(deviceId, 160)).filter(Boolean))
|
|
5533
|
+
: null;
|
|
5534
|
+
let updated = 0;
|
|
5535
|
+
let total = 0;
|
|
5536
|
+
for (const device of devices.values()) {
|
|
5537
|
+
if (requestedIds && !requestedIds.has(device.deviceId)) continue;
|
|
5538
|
+
if (!device?.socket || device.socket.destroyed || !device.connected) continue;
|
|
5539
|
+
total += 1;
|
|
5540
|
+
const effectivePolicy = getDevicePolicy(device);
|
|
5541
|
+
if (writeJsonLine(device.socket, {
|
|
5542
|
+
type: 'policy.update',
|
|
5543
|
+
effectivePolicy,
|
|
5544
|
+
updatedAt: new Date().toISOString()
|
|
5545
|
+
})) {
|
|
5546
|
+
updated += 1;
|
|
5547
|
+
}
|
|
5548
|
+
}
|
|
5549
|
+
return { ok: updated === total, updated, total };
|
|
5550
|
+
}
|
|
5512
5551
|
|
|
5513
5552
|
// Client updates are an explicit Hub maintenance action, not an Agent task.
|
|
5514
5553
|
// This legacy path is kept for clients that predate the dedicated update command.
|
|
@@ -7382,6 +7421,7 @@ export function createRemoteHub(options = {}) {
|
|
|
7382
7421
|
disconnectDevice,
|
|
7383
7422
|
assignDeviceSlot,
|
|
7384
7423
|
sendCommand,
|
|
7424
|
+
refreshDevicePolicies,
|
|
7385
7425
|
sendLegacyClientUpdate,
|
|
7386
7426
|
sendInputControl,
|
|
7387
7427
|
releaseInputOwner,
|
package/src/server.js
CHANGED
|
@@ -1450,7 +1450,8 @@ function normalizeDeviceIds(value) {
|
|
|
1450
1450
|
function rejectRetiredAgentTaskRequest(req, res) {
|
|
1451
1451
|
const body = req.body && typeof req.body === 'object' ? req.body : {};
|
|
1452
1452
|
const approvalLevel = String(body.approvalLevel || '').trim().toLowerCase();
|
|
1453
|
-
const hasModelSelection =
|
|
1453
|
+
const hasModelSelection = ['model', 'aiModel', 'aiProvider', 'provider']
|
|
1454
|
+
.some(key => Object.prototype.hasOwnProperty.call(body, key));
|
|
1454
1455
|
if (approvalLevel !== 'ai-assist' && !hasModelSelection) {
|
|
1455
1456
|
return false;
|
|
1456
1457
|
}
|
|
@@ -2102,6 +2103,13 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
|
|
|
2102
2103
|
agentQueueBeforePaceMs: Number(frame.agentQueueBeforePaceMs || 0) || 0,
|
|
2103
2104
|
agentFrameAgeMs: Number(frame.agentFrameAgeMs || 0) || 0,
|
|
2104
2105
|
nativeCaptureTelemetryAvailable: frame.nativeCaptureTelemetryAvailable === true,
|
|
2106
|
+
nativeCaptureTelemetryVersion: Number(frame.nativeCaptureTelemetryVersion || 0) || 0,
|
|
2107
|
+
nativeCaptureTelemetryAgeMs: frame.nativeCaptureTelemetryAgeMs === null
|
|
2108
|
+
|| frame.nativeCaptureTelemetryAgeMs === undefined
|
|
2109
|
+
? undefined
|
|
2110
|
+
: Number.isFinite(Number(frame.nativeCaptureTelemetryAgeMs))
|
|
2111
|
+
? Math.max(0, Number(frame.nativeCaptureTelemetryAgeMs))
|
|
2112
|
+
: undefined,
|
|
2105
2113
|
nativeEncoderHardwareAccelerationKnown: frame.nativeEncoderHardwareAccelerationKnown === true,
|
|
2106
2114
|
nativeEncoderHardwareAccelerated: typeof frame.nativeEncoderHardwareAccelerated === 'boolean'
|
|
2107
2115
|
? frame.nativeEncoderHardwareAccelerated
|
|
@@ -2708,6 +2716,7 @@ app.patch('/api/settings', async (req, res) => {
|
|
|
2708
2716
|
if (patch.agent && Object.prototype.hasOwnProperty.call(patch.agent, 'enabled')) {
|
|
2709
2717
|
await agentSettingsStore.update({ enabled: record.settings.agent?.enabled === true });
|
|
2710
2718
|
}
|
|
2719
|
+
remoteHub.refreshDevicePolicies();
|
|
2711
2720
|
res.json({ ok: true, revision: record.revision, updatedAt: record.updatedAt, settings: record.settings });
|
|
2712
2721
|
} catch (error) {
|
|
2713
2722
|
if (error instanceof SettingsConflictError) {
|
|
@@ -2965,6 +2974,9 @@ app.post('/api/internal/agent-mcp/tool', async (req, res) => {
|
|
|
2965
2974
|
|
|
2966
2975
|
app.post('/api/settings/agent/run', async (req, res) => {
|
|
2967
2976
|
noStore(res);
|
|
2977
|
+
if (rejectRetiredAgentTaskRequest(req, res)) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2968
2980
|
try {
|
|
2969
2981
|
if (!(await synchronizeAgentEnablement())) {
|
|
2970
2982
|
throw new AgentRuntimeError('agent-disabled', 'Enable Codex Agent in Settings before running commands.', { status: 409 });
|
|
@@ -3999,11 +4011,12 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
3999
4011
|
}
|
|
4000
4012
|
|
|
4001
4013
|
const settings = await liveDeskSettingsStore.get();
|
|
4002
|
-
|
|
4014
|
+
const policy = effectiveDevicePolicy(settings);
|
|
4015
|
+
if (policy.accessMode !== 'trusted-only') {
|
|
4003
4016
|
res.status(403).json({ ok: false, error: 'remote-actions-disabled' });
|
|
4004
4017
|
return;
|
|
4005
4018
|
}
|
|
4006
|
-
if (
|
|
4019
|
+
if (policy.allowPowerActions !== true) {
|
|
4007
4020
|
res.status(403).json({ ok: false, error: 'power-actions-disabled' });
|
|
4008
4021
|
return;
|
|
4009
4022
|
}
|
|
@@ -4020,14 +4033,18 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
4020
4033
|
return;
|
|
4021
4034
|
}
|
|
4022
4035
|
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4036
|
+
// The Client enforces the same policy. Queue the current policy immediately
|
|
4037
|
+
// before the command so enabling this visible setting works without forcing
|
|
4038
|
+
// a reconnect, while TCP ordering keeps the command behind the policy update.
|
|
4039
|
+
remoteHub.refreshDevicePolicies([req.params.deviceId]);
|
|
4040
|
+
const result = remoteHub.sendCommand(req.params.deviceId, {
|
|
4041
|
+
command: 'system.power',
|
|
4042
|
+
payload: {
|
|
4043
|
+
action,
|
|
4044
|
+
delaySec: 0,
|
|
4045
|
+
permissionMode: 'manual-confirmed',
|
|
4046
|
+
requestSource: 'hub-action-dock'
|
|
4047
|
+
}
|
|
4031
4048
|
});
|
|
4032
4049
|
recordAgentAudit({
|
|
4033
4050
|
event: 'manual-action-dispatched',
|
|
@@ -4036,7 +4053,7 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
4036
4053
|
category: 'systemPower',
|
|
4037
4054
|
decision: result.ok ? 'allow' : 'deny',
|
|
4038
4055
|
status: result.ok ? 'queued' : 'failed',
|
|
4039
|
-
details: { action,
|
|
4056
|
+
details: { action, commandId: result.commandId || '', error: result.error || '' }
|
|
4040
4057
|
});
|
|
4041
4058
|
if (!result.ok) {
|
|
4042
4059
|
res.status(result.error === 'device-not-found' ? 404 : 409).json(result);
|
|
@@ -251,6 +251,7 @@ export function effectiveDevicePolicy(settings = DEFAULT_LIVEDESK_SETTINGS) {
|
|
|
251
251
|
allowUnencryptedLanFallback: normalized.security.allowUnencryptedLanFallback,
|
|
252
252
|
allowControl: allowMutation && normalized.control.allowKeyboardMouse,
|
|
253
253
|
allowClipboardText: allowMutation && normalized.control.allowClipboardText,
|
|
254
|
+
allowPowerActions: allowMutation && normalized.control.allowRemoteRestart,
|
|
254
255
|
allowFileTransfer: allowMutation && normalized.filesAudio.allowFileTransfer,
|
|
255
256
|
allowRemoteAudio: accessMode !== 'block-remote-access' && normalized.filesAudio.allowRemoteAudio,
|
|
256
257
|
allowAgent: allowMutation && normalized.agent.enabled
|