@livedesk/hub 0.1.21 → 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/remote-hub.js +33 -7
- package/src/server.js +22 -12
- 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
|
@@ -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
|
}
|
|
@@ -2715,6 +2716,7 @@ app.patch('/api/settings', async (req, res) => {
|
|
|
2715
2716
|
if (patch.agent && Object.prototype.hasOwnProperty.call(patch.agent, 'enabled')) {
|
|
2716
2717
|
await agentSettingsStore.update({ enabled: record.settings.agent?.enabled === true });
|
|
2717
2718
|
}
|
|
2719
|
+
remoteHub.refreshDevicePolicies();
|
|
2718
2720
|
res.json({ ok: true, revision: record.revision, updatedAt: record.updatedAt, settings: record.settings });
|
|
2719
2721
|
} catch (error) {
|
|
2720
2722
|
if (error instanceof SettingsConflictError) {
|
|
@@ -2972,6 +2974,9 @@ app.post('/api/internal/agent-mcp/tool', async (req, res) => {
|
|
|
2972
2974
|
|
|
2973
2975
|
app.post('/api/settings/agent/run', async (req, res) => {
|
|
2974
2976
|
noStore(res);
|
|
2977
|
+
if (rejectRetiredAgentTaskRequest(req, res)) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2975
2980
|
try {
|
|
2976
2981
|
if (!(await synchronizeAgentEnablement())) {
|
|
2977
2982
|
throw new AgentRuntimeError('agent-disabled', 'Enable Codex Agent in Settings before running commands.', { status: 409 });
|
|
@@ -4006,11 +4011,12 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
4006
4011
|
}
|
|
4007
4012
|
|
|
4008
4013
|
const settings = await liveDeskSettingsStore.get();
|
|
4009
|
-
|
|
4014
|
+
const policy = effectiveDevicePolicy(settings);
|
|
4015
|
+
if (policy.accessMode !== 'trusted-only') {
|
|
4010
4016
|
res.status(403).json({ ok: false, error: 'remote-actions-disabled' });
|
|
4011
4017
|
return;
|
|
4012
4018
|
}
|
|
4013
|
-
if (
|
|
4019
|
+
if (policy.allowPowerActions !== true) {
|
|
4014
4020
|
res.status(403).json({ ok: false, error: 'power-actions-disabled' });
|
|
4015
4021
|
return;
|
|
4016
4022
|
}
|
|
@@ -4027,14 +4033,18 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
4027
4033
|
return;
|
|
4028
4034
|
}
|
|
4029
4035
|
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
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
|
+
}
|
|
4038
4048
|
});
|
|
4039
4049
|
recordAgentAudit({
|
|
4040
4050
|
event: 'manual-action-dispatched',
|
|
@@ -4043,7 +4053,7 @@ app.post('/api/remote/devices/:deviceId/quick-actions', requireHubFeatureAccess,
|
|
|
4043
4053
|
category: 'systemPower',
|
|
4044
4054
|
decision: result.ok ? 'allow' : 'deny',
|
|
4045
4055
|
status: result.ok ? 'queued' : 'failed',
|
|
4046
|
-
details: { action,
|
|
4056
|
+
details: { action, commandId: result.commandId || '', error: result.error || '' }
|
|
4047
4057
|
});
|
|
4048
4058
|
if (!result.ok) {
|
|
4049
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
|