@livedesk/hub 0.1.21 → 0.1.23
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/remote-hub.js +33 -7
- package/src/server.js +28 -17
- package/src/settings/settings-schema.js +1 -0
package/package.json
CHANGED
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
|
: '';
|
|
@@ -5458,11 +5459,13 @@ export function createRemoteHub(options = {}) {
|
|
|
5458
5459
|
}
|
|
5459
5460
|
|
|
5460
5461
|
function sendCommand(deviceId, command) {
|
|
5461
|
-
const device = devices.get(String(deviceId || ''));
|
|
5462
|
+
const device = devices.get(String(deviceId || ''));
|
|
5462
5463
|
const commandName = safeString(command?.command || 'ping', 80);
|
|
5463
|
-
const requiredPermission = commandName === 'input.control'
|
|
5464
|
-
? 'allowControl'
|
|
5465
|
-
: commandName
|
|
5464
|
+
const requiredPermission = commandName === 'input.control'
|
|
5465
|
+
? 'allowControl'
|
|
5466
|
+
: commandName === 'system.power'
|
|
5467
|
+
? 'allowPowerActions'
|
|
5468
|
+
: commandName.startsWith('file.transfer')
|
|
5466
5469
|
? 'allowFileTransfer'
|
|
5467
5470
|
: commandName === 'audio.start'
|
|
5468
5471
|
? 'allowRemoteAudio'
|
|
@@ -5521,8 +5524,30 @@ export function createRemoteHub(options = {}) {
|
|
|
5521
5524
|
command: payload.command,
|
|
5522
5525
|
channel: dedicatedFileSocket ? 'file' : 'control'
|
|
5523
5526
|
});
|
|
5524
|
-
return { ok: true, commandId };
|
|
5525
|
-
}
|
|
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
|
+
}
|
|
5526
5551
|
|
|
5527
5552
|
// Client updates are an explicit Hub maintenance action, not an Agent task.
|
|
5528
5553
|
// This legacy path is kept for clients that predate the dedicated update command.
|
|
@@ -7396,6 +7421,7 @@ export function createRemoteHub(options = {}) {
|
|
|
7396
7421
|
disconnectDevice,
|
|
7397
7422
|
assignDeviceSlot,
|
|
7398
7423
|
sendCommand,
|
|
7424
|
+
refreshDevicePolicies,
|
|
7399
7425
|
sendLegacyClientUpdate,
|
|
7400
7426
|
sendInputControl,
|
|
7401
7427
|
releaseInputOwner,
|
package/src/server.js
CHANGED
|
@@ -1229,11 +1229,12 @@ hubTransferJobs = createHubTransferJobs({
|
|
|
1229
1229
|
maxConcurrent: Number(process.env.LIVEDESK_MAX_FILE_JOBS || 2),
|
|
1230
1230
|
commandResultTimeoutMs: readPositiveIntegerEnv('LIVEDESK_FILE_CHUNK_ACK_TIMEOUT_MS', 30_000)
|
|
1231
1231
|
});
|
|
1232
|
-
const hubSharedFolders = createHubSharedFolders({
|
|
1233
|
-
filesystem: hubFilesystem,
|
|
1234
|
-
transferJobs: hubTransferJobs,
|
|
1235
|
-
remoteHub
|
|
1236
|
-
|
|
1232
|
+
const hubSharedFolders = createHubSharedFolders({
|
|
1233
|
+
filesystem: hubFilesystem,
|
|
1234
|
+
transferJobs: hubTransferJobs,
|
|
1235
|
+
remoteHub,
|
|
1236
|
+
dataDir: agentDataDir
|
|
1237
|
+
});
|
|
1237
1238
|
|
|
1238
1239
|
const app = express();
|
|
1239
1240
|
const httpServer = createServer(app);
|
|
@@ -1450,7 +1451,8 @@ function normalizeDeviceIds(value) {
|
|
|
1450
1451
|
function rejectRetiredAgentTaskRequest(req, res) {
|
|
1451
1452
|
const body = req.body && typeof req.body === 'object' ? req.body : {};
|
|
1452
1453
|
const approvalLevel = String(body.approvalLevel || '').trim().toLowerCase();
|
|
1453
|
-
const hasModelSelection =
|
|
1454
|
+
const hasModelSelection = ['model', 'aiModel', 'aiProvider', 'provider']
|
|
1455
|
+
.some(key => Object.prototype.hasOwnProperty.call(body, key));
|
|
1454
1456
|
if (approvalLevel !== 'ai-assist' && !hasModelSelection) {
|
|
1455
1457
|
return false;
|
|
1456
1458
|
}
|
|
@@ -2715,6 +2717,7 @@ app.patch('/api/settings', async (req, res) => {
|
|
|
2715
2717
|
if (patch.agent && Object.prototype.hasOwnProperty.call(patch.agent, 'enabled')) {
|
|
2716
2718
|
await agentSettingsStore.update({ enabled: record.settings.agent?.enabled === true });
|
|
2717
2719
|
}
|
|
2720
|
+
remoteHub.refreshDevicePolicies();
|
|
2718
2721
|
res.json({ ok: true, revision: record.revision, updatedAt: record.updatedAt, settings: record.settings });
|
|
2719
2722
|
} catch (error) {
|
|
2720
2723
|
if (error instanceof SettingsConflictError) {
|
|
@@ -2972,6 +2975,9 @@ app.post('/api/internal/agent-mcp/tool', async (req, res) => {
|
|
|
2972
2975
|
|
|
2973
2976
|
app.post('/api/settings/agent/run', async (req, res) => {
|
|
2974
2977
|
noStore(res);
|
|
2978
|
+
if (rejectRetiredAgentTaskRequest(req, res)) {
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2975
2981
|
try {
|
|
2976
2982
|
if (!(await synchronizeAgentEnablement())) {
|
|
2977
2983
|
throw new AgentRuntimeError('agent-disabled', 'Enable Codex Agent in Settings before running commands.', { status: 409 });
|
|
@@ -4006,11 +4012,12 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
4006
4012
|
}
|
|
4007
4013
|
|
|
4008
4014
|
const settings = await liveDeskSettingsStore.get();
|
|
4009
|
-
|
|
4015
|
+
const policy = effectiveDevicePolicy(settings);
|
|
4016
|
+
if (policy.accessMode !== 'trusted-only') {
|
|
4010
4017
|
res.status(403).json({ ok: false, error: 'remote-actions-disabled' });
|
|
4011
4018
|
return;
|
|
4012
4019
|
}
|
|
4013
|
-
if (
|
|
4020
|
+
if (policy.allowPowerActions !== true) {
|
|
4014
4021
|
res.status(403).json({ ok: false, error: 'power-actions-disabled' });
|
|
4015
4022
|
return;
|
|
4016
4023
|
}
|
|
@@ -4027,14 +4034,18 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
4027
4034
|
return;
|
|
4028
4035
|
}
|
|
4029
4036
|
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4037
|
+
// The Client enforces the same policy. Queue the current policy immediately
|
|
4038
|
+
// before the command so enabling this visible setting works without forcing
|
|
4039
|
+
// a reconnect, while TCP ordering keeps the command behind the policy update.
|
|
4040
|
+
remoteHub.refreshDevicePolicies([req.params.deviceId]);
|
|
4041
|
+
const result = remoteHub.sendCommand(req.params.deviceId, {
|
|
4042
|
+
command: 'system.power',
|
|
4043
|
+
payload: {
|
|
4044
|
+
action,
|
|
4045
|
+
delaySec: 0,
|
|
4046
|
+
permissionMode: 'manual-confirmed',
|
|
4047
|
+
requestSource: 'hub-action-dock'
|
|
4048
|
+
}
|
|
4038
4049
|
});
|
|
4039
4050
|
recordAgentAudit({
|
|
4040
4051
|
event: 'manual-action-dispatched',
|
|
@@ -4043,7 +4054,7 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
4043
4054
|
category: 'systemPower',
|
|
4044
4055
|
decision: result.ok ? 'allow' : 'deny',
|
|
4045
4056
|
status: result.ok ? 'queued' : 'failed',
|
|
4046
|
-
details: { action,
|
|
4057
|
+
details: { action, commandId: result.commandId || '', error: result.error || '' }
|
|
4047
4058
|
});
|
|
4048
4059
|
if (!result.ok) {
|
|
4049
4060
|
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
|