livedesk 0.1.223 → 0.1.225

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.
@@ -672,6 +672,7 @@ function createDeviceCounters(overrides = {}) {
672
672
  audioStreamsStopped: 0,
673
673
  audioFramesReceived: 0,
674
674
  audioFramesDropped: 0,
675
+ audioStatusReceived: 0,
675
676
  tasksQueued: 0,
676
677
  taskResultsReceived: 0,
677
678
  taskResultsFailed: 0,
@@ -958,6 +959,7 @@ function serializeDevice(device, options = {}) {
958
959
  byteLength: device.latestAudioFrame.byteLength
959
960
  }
960
961
  : null,
962
+ latestAudioStatus: device.latestAudioStatus ? { ...device.latestAudioStatus } : null,
961
963
  latestTask: device.latestTask ? { ...device.latestTask } : null,
962
964
  synthetic: device.synthetic === true,
963
965
  recentTasks: Array.isArray(device.recentTasks)
@@ -2456,6 +2458,7 @@ export function createRemoteHub(options = {}) {
2456
2458
  activeLiveStream: null,
2457
2459
  activeAudioStream: null,
2458
2460
  latestAudioFrame: null,
2461
+ latestAudioStatus: null,
2459
2462
  latestTask,
2460
2463
  recentTasks: latestTask ? [latestTask] : [],
2461
2464
  pendingTaskCommands: new Map(),
@@ -2621,6 +2624,7 @@ export function createRemoteHub(options = {}) {
2621
2624
  activeLiveStream: null,
2622
2625
  activeAudioStream: null,
2623
2626
  latestAudioFrame: null,
2627
+ latestAudioStatus: null,
2624
2628
  latestTask: null,
2625
2629
  recentTasks: [],
2626
2630
  pendingTaskCommands: new Map(),
@@ -3590,6 +3594,50 @@ export function createRemoteHub(options = {}) {
3590
3594
  return true;
3591
3595
  }
3592
3596
 
3597
+ function applyAudioStatus(device, message, framePayload, transport = 'binary') {
3598
+ if (!Buffer.isBuffer(framePayload) || framePayload.length < 1) {
3599
+ device.counters.audioFramesDropped += 1;
3600
+ return false;
3601
+ }
3602
+
3603
+ const streamId = safeString(message.streamId, 128) || device.activeAudioStream?.streamId || 'audio';
3604
+ const status = {
3605
+ streamId,
3606
+ commandId: safeString(message.commandId, 128),
3607
+ captureAlive: message.captureAlive === true,
3608
+ helperAlive: message.helperAlive === true,
3609
+ helperPid: Number(message.helperPid || 0) || 0,
3610
+ captureGeneration: Number(message.captureGeneration || 0) || 0,
3611
+ lastPcmAtEpochMs: Number(message.lastPcmAtEpochMs || 0) || 0,
3612
+ silentForMs: Math.max(0, Number(message.silentForMs || 0) || 0),
3613
+ restartCount: Math.max(0, Number(message.restartCount || 0) || 0),
3614
+ error: safeString(message.error, 500),
3615
+ statusAtEpochMs: Number(message.statusAtEpochMs || 0) || Date.now(),
3616
+ receivedAt: new Date().toISOString(),
3617
+ transport
3618
+ };
3619
+ device.latestAudioStatus = status;
3620
+ if (device.activeAudioStream && device.activeAudioStream.streamId === streamId) {
3621
+ device.activeAudioStream.lastStatusAt = status.receivedAt;
3622
+ device.activeAudioStream.captureAlive = status.captureAlive;
3623
+ device.activeAudioStream.helperAlive = status.helperAlive;
3624
+ device.activeAudioStream.captureGeneration = status.captureGeneration;
3625
+ device.activeAudioStream.lastPcmAtEpochMs = status.lastPcmAtEpochMs;
3626
+ device.activeAudioStream.silentForMs = status.silentForMs;
3627
+ device.activeAudioStream.restartCount = status.restartCount;
3628
+ device.activeAudioStream.audioError = status.error;
3629
+ }
3630
+ device.counters.audioStatusReceived += 1;
3631
+ emitAudio({
3632
+ deviceId: device.deviceId,
3633
+ audioStatus: true,
3634
+ frame: status,
3635
+ payload: framePayload,
3636
+ byteLength: framePayload.length
3637
+ });
3638
+ return true;
3639
+ }
3640
+
3593
3641
  function handleAgentBinaryFrame(socket, state, header, framePayload) {
3594
3642
  if (!state.authenticated || !state.device) {
3595
3643
  writeJsonLine(socket, { type: 'error', error: 'hello-required' });
@@ -3632,6 +3680,11 @@ export function createRemoteHub(options = {}) {
3632
3680
  return;
3633
3681
  }
3634
3682
 
3683
+ if (frameKind === 'audio-status' || header.type === 'audio.status.binary') {
3684
+ applyAudioStatus(device, header, framePayload, 'binary');
3685
+ return;
3686
+ }
3687
+
3635
3688
  emitRemoteEvent('RemoteAgentMessageIgnored', device, {
3636
3689
  messageType: safeString(header.type, 80),
3637
3690
  frameKind
@@ -3650,7 +3703,12 @@ export function createRemoteHub(options = {}) {
3650
3703
  && header.isKeyFrame !== true
3651
3704
  && chunkType !== 'key';
3652
3705
  });
3653
- const index = deltaIndex >= 0 ? deltaIndex : 0;
3706
+ const nonStatusIndex = state.binaryQueue.findIndex(item => {
3707
+ const header = item?.header || {};
3708
+ const frameKind = safeString(header.frameKind || header.kind || header.frameType || '', 40).toLowerCase();
3709
+ return frameKind !== 'audio-status' && header.type !== 'audio.status.binary';
3710
+ });
3711
+ const index = deltaIndex >= 0 ? deltaIndex : nonStatusIndex >= 0 ? nonStatusIndex : 0;
3654
3712
  state.binaryQueue.splice(index, 1);
3655
3713
  state.binaryQueueDrops = Number(state.binaryQueueDrops || 0) + 1;
3656
3714
  }
@@ -3829,6 +3887,16 @@ export function createRemoteHub(options = {}) {
3829
3887
  hubAcknowledgedAtEpochMs: Date.now()
3830
3888
  });
3831
3889
  }
3890
+ if (message.type === 'input.error') {
3891
+ emitEvent('RemoteInputError', {
3892
+ deviceId: device.deviceId,
3893
+ inputSeq: Number(message.inputSeq || 0) || 0,
3894
+ inputType: safeString(message.inputType, 48),
3895
+ error: safeString(message.error, 600) || 'remote-input-failed',
3896
+ failedAtEpochMs: Number(message.failedAtEpochMs || 0) || 0,
3897
+ hubAcknowledgedAtEpochMs: Date.now()
3898
+ });
3899
+ }
3832
3900
  return;
3833
3901
  }
3834
3902
  if (state.frameOnly) {
package/hub/src/server.js CHANGED
@@ -73,15 +73,24 @@ function readPositiveIntegerEnv(name, fallback) {
73
73
  }
74
74
 
75
75
  function handleRemoteHubEvent(type, event) {
76
- if (type === 'RemoteInputApplied') {
76
+ if (type === 'RemoteInputApplied') {
77
77
  for (const ws of inputClients) {
78
78
  sendJson(ws, {
79
79
  type: 'RemoteInputApplied',
80
80
  ...event
81
81
  });
82
82
  }
83
- return;
84
- }
83
+ return;
84
+ }
85
+ if (type === 'RemoteInputError') {
86
+ for (const ws of inputClients) {
87
+ sendJson(ws, {
88
+ type: 'RemoteInputError',
89
+ ...event
90
+ });
91
+ }
92
+ return;
93
+ }
85
94
  if (type === 'RemoteDeviceConnected' || type === 'RemoteDeviceDisconnected') {
86
95
  connectedDeviceCount = Number(remoteHub.getStatus({ includeSecrets: false }).connectedDeviceCount || 0);
87
96
  }
@@ -1345,24 +1354,38 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
1345
1354
  return Buffer.concat([header, metaBuffer, payload], 4 + metaBuffer.length + payload.length);
1346
1355
  }
1347
1356
 
1348
- function buildRemoteAudioBinaryPacket(audioEvent) {
1349
- const payload = audioEvent?.payload;
1350
- if (!Buffer.isBuffer(payload) || payload.length === 0) {
1351
- return null;
1352
- }
1353
- const frame = audioEvent.frame || {};
1354
- const metadata = {
1355
- type: 'remote.audio.binary',
1356
- deviceId: audioEvent.deviceId || frame.deviceId || '',
1357
- frameSeq: Number(frame.frameSeq || 0) || 0,
1358
- streamId: frame.streamId || '',
1357
+ function buildRemoteAudioBinaryPacket(audioEvent) {
1358
+ const payload = audioEvent?.payload;
1359
+ if (!Buffer.isBuffer(payload) || payload.length === 0) {
1360
+ return null;
1361
+ }
1362
+ const frame = audioEvent.frame || {};
1363
+ const audioStatus = audioEvent.audioStatus === true;
1364
+ const metadata = {
1365
+ type: audioStatus ? 'remote.audio.status' : 'remote.audio.binary',
1366
+ frameKind: audioStatus ? 'audio-status' : 'audio',
1367
+ deviceId: audioEvent.deviceId || frame.deviceId || '',
1368
+ frameSeq: Number(frame.frameSeq || 0) || 0,
1369
+ streamId: frame.streamId || '',
1359
1370
  mimeType: audioEvent.mimeType || frame.mimeType || frame.format || 'audio/webm;codecs=opus',
1360
1371
  sampleRate: Number(frame.sampleRate || 0) || 48000,
1361
1372
  channels: Number(frame.channels || 0) || 2,
1362
1373
  capturedAt: frame.capturedAt || '',
1363
1374
  receivedAt: frame.receivedAt || '',
1364
- byteLength: Number(audioEvent.byteLength || payload.length) || payload.length
1365
- };
1375
+ byteLength: Number(audioEvent.byteLength || payload.length) || payload.length,
1376
+ ...(audioStatus ? {
1377
+ commandId: frame.commandId || '',
1378
+ captureAlive: frame.captureAlive === true,
1379
+ helperAlive: frame.helperAlive === true,
1380
+ helperPid: Number(frame.helperPid || 0) || 0,
1381
+ captureGeneration: Number(frame.captureGeneration || 0) || 0,
1382
+ lastPcmAtEpochMs: Number(frame.lastPcmAtEpochMs || 0) || 0,
1383
+ silentForMs: Math.max(0, Number(frame.silentForMs || 0) || 0),
1384
+ restartCount: Math.max(0, Number(frame.restartCount || 0) || 0),
1385
+ error: String(frame.error || ''),
1386
+ statusAtEpochMs: Number(frame.statusAtEpochMs || 0) || Date.now()
1387
+ } : {})
1388
+ };
1366
1389
  const metaBuffer = Buffer.from(JSON.stringify(metadata), 'utf8');
1367
1390
  const header = Buffer.allocUnsafe(4);
1368
1391
  header.writeUInt32BE(metaBuffer.length, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.223",
3
+ "version": "0.1.225",
4
4
  "description": "LiveDesk Hub and client launcher",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
34
- "@livedesk/client": "0.1.126",
34
+ "@livedesk/client": "0.1.128",
35
35
  "@openai/codex-sdk": "0.144.5",
36
36
  "cors": "^2.8.5",
37
37
  "express": "^4.21.2",