@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "LiveDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
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
- && Object.prototype.hasOwnProperty.call(options, 'model');
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.startsWith('file.transfer')
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 = Object.prototype.hasOwnProperty.call(body, 'model');
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
- if (settings.agent?.enabled !== true) {
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 (settings.agent?.allowPowerActions !== true) {
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
- const label = action === 'shutdown' ? 'Shut down' : `${action.slice(0, 1).toUpperCase()}${action.slice(1)}`;
4031
- const result = remoteHub.requestAgentTask(req.params.deviceId, {
4032
- instruction: `${label} this Client. This action was explicitly requested and confirmed from the LiveDesk Hub action panel.`,
4033
- title: `${label} Client`,
4034
- operation: 'system.power',
4035
- approvalLevel: 'task-only',
4036
- toolArguments: { action, delaySec: 0 },
4037
- permissionMode: 'ask'
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, taskId: result.taskId || '', error: result.error || '' }
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