livedesk 0.1.457 → 0.1.458

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.211",
3
+ "version": "0.1.212",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,10 +40,10 @@
40
40
  "ws": "^8.18.3"
41
41
  },
42
42
  "optionalDependencies": {
43
- "@livedesk/fast-linux-x64": "0.1.415",
44
- "@livedesk/fast-osx-arm64": "0.1.415",
45
- "@livedesk/fast-osx-x64": "0.1.415",
46
- "@livedesk/fast-win-x64": "0.1.415"
43
+ "@livedesk/fast-linux-x64": "0.1.416",
44
+ "@livedesk/fast-osx-arm64": "0.1.416",
45
+ "@livedesk/fast-osx-x64": "0.1.416",
46
+ "@livedesk/fast-win-x64": "0.1.416"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public"
@@ -25,6 +25,36 @@ function parseMacProcessRecords(psOutput) {
25
25
  return records;
26
26
  }
27
27
 
28
+ function parseMacProcessResourceRecords(psOutput) {
29
+ const records = [];
30
+ for (const rawLine of String(psOutput || '').split(/\r?\n/)) {
31
+ const match = rawLine.match(/^\s*(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)$/);
32
+ if (!match) continue;
33
+ const pid = Number(match[1]);
34
+ const parentPid = Number(match[2]);
35
+ const cpuPercent = Number(match[4]);
36
+ const rssKiB = Number(match[5]);
37
+ if (
38
+ !Number.isInteger(pid)
39
+ || pid <= 1
40
+ || !Number.isInteger(parentPid)
41
+ || !Number.isFinite(cpuPercent)
42
+ || !Number.isFinite(rssKiB)
43
+ ) {
44
+ continue;
45
+ }
46
+ records.push({
47
+ pid,
48
+ parentPid,
49
+ commandName: basenameWithoutQuotes(match[3]),
50
+ cpuPercent: Math.max(0, cpuPercent),
51
+ rssBytes: Math.max(0, Math.round(rssKiB * 1024)),
52
+ commandLine: String(match[6] || '').trim()
53
+ });
54
+ }
55
+ return records;
56
+ }
57
+
28
58
  function isMacCaptureHelperRecord(record) {
29
59
  const commandName = String(record?.commandName || '');
30
60
  const commandLine = normalizeProcessText(record?.commandLine);
@@ -46,6 +76,64 @@ function finite(value, fallback = 0) {
46
76
  return Number.isFinite(parsed) ? parsed : fallback;
47
77
  }
48
78
 
79
+ export function createAsyncSingleFlightSampler(sampleImpl) {
80
+ if (typeof sampleImpl !== 'function') {
81
+ throw new TypeError('An async sample function is required.');
82
+ }
83
+
84
+ let inFlight = null;
85
+ let requestedCount = 0;
86
+ let startedCount = 0;
87
+ let skippedCount = 0;
88
+
89
+ const start = args => {
90
+ startedCount += 1;
91
+ const current = Promise.resolve().then(() => sampleImpl(...args));
92
+ const tracked = current.finally(() => {
93
+ if (inFlight === tracked) {
94
+ inFlight = null;
95
+ }
96
+ });
97
+ inFlight = tracked;
98
+ return tracked;
99
+ };
100
+
101
+ return {
102
+ request(...args) {
103
+ requestedCount += 1;
104
+ if (inFlight) {
105
+ skippedCount += 1;
106
+ return null;
107
+ }
108
+ return start(args);
109
+ },
110
+ async run(...args) {
111
+ requestedCount += 1;
112
+ while (inFlight) {
113
+ const pending = inFlight;
114
+ try {
115
+ await pending;
116
+ } catch {
117
+ // A required sample gets its own attempt after a failed periodic one.
118
+ }
119
+ }
120
+ return start(args);
121
+ },
122
+ async waitForIdle() {
123
+ if (!inFlight) return;
124
+ await inFlight;
125
+ },
126
+ getState() {
127
+ return {
128
+ inFlight: inFlight !== null,
129
+ requestedCount,
130
+ startedCount,
131
+ skippedCount
132
+ };
133
+ }
134
+ };
135
+ }
136
+
49
137
  function basenameWithoutQuotes(value) {
50
138
  return path.basename(String(value || '').trim().replace(/^["']|["']$/g, ''));
51
139
  }
@@ -54,47 +142,56 @@ export function parseMacHelperProcesses(psOutput) {
54
142
  return parseMacProcessRecords(psOutput).filter(isMacCaptureHelperRecord);
55
143
  }
56
144
 
57
- export function parseMacLiveDeskRuntimeProcesses(psOutput, { ignorePids = [] } = {}) {
145
+ function classifyMacLiveDeskRuntimeRecord(record) {
146
+ const commandName = String(record?.commandName || '');
147
+ const commandLine = normalizeProcessText(record?.commandLine);
148
+ if (isMacCaptureHelperRecord(record)) {
149
+ return 'capture-helper';
150
+ }
151
+ if (
152
+ /^livedesk-client-fast$/i.test(commandName)
153
+ || /(?:^|\/|[\s"'])livedesk-client-fast(?:\.dll)?(?:\s|$|["'])/i.test(commandLine)
154
+ ) {
155
+ return 'client-agent';
156
+ }
157
+ if (
158
+ /(?:node_modules\/(?:livedesk\/client|@livedesk\/client)|packages\/(?:livedesk\/client|client))\/bin\/livedesk-client(?:-node)?\.js(?:\s|$|["'])/i
159
+ .test(commandLine)
160
+ ) {
161
+ return 'client-runtime';
162
+ }
163
+ if (
164
+ /(?:node_modules\/livedesk|packages\/livedesk)\/bin\/livedesk\.js(?:\s|$|["'])/i
165
+ .test(commandLine)
166
+ || /(?:^|[\s"'])[^"'\s]*node_modules\/\.bin\/livedesk(?:\s|$|["'])/i
167
+ .test(commandLine)
168
+ ) {
169
+ return 'unified-runtime';
170
+ }
171
+ if (
172
+ /(?:node_modules\/(?:livedesk\/hub|@livedesk\/hub)|packages\/(?:livedesk\/hub|hub))\/src\/server\.js(?:\s|$|["'])/i
173
+ .test(commandLine)
174
+ ) {
175
+ return 'hub-runtime';
176
+ }
177
+ if (
178
+ /(?:^|\/)LiveDesk\.app\/Contents\/MacOS\/LiveDesk(?:\s|$|["'])/i.test(commandLine)
179
+ ) {
180
+ return 'desktop-runtime';
181
+ }
182
+ return '';
183
+ }
184
+
185
+ function identifyMacLiveDeskRuntimeRecords(records, { ignorePids = [] } = {}) {
58
186
  const ignored = new Set(
59
187
  (Array.isArray(ignorePids) ? ignorePids : [])
60
188
  .map(Number)
61
189
  .filter(pid => Number.isInteger(pid) && pid > 1)
62
190
  );
63
191
  const results = [];
64
- for (const record of parseMacProcessRecords(psOutput)) {
192
+ for (const record of records) {
65
193
  if (ignored.has(record.pid)) continue;
66
- const commandName = String(record.commandName || '');
67
- const commandLine = normalizeProcessText(record.commandLine);
68
- let kind = '';
69
- if (isMacCaptureHelperRecord(record)) {
70
- kind = 'capture-helper';
71
- } else if (
72
- /^livedesk-client-fast$/i.test(commandName)
73
- || /(?:^|\/|[\s"'])livedesk-client-fast(?:\.dll)?(?:\s|$|["'])/i.test(commandLine)
74
- ) {
75
- kind = 'client-agent';
76
- } else if (
77
- /(?:node_modules\/(?:livedesk\/client|@livedesk\/client)|packages\/(?:livedesk\/client|client))\/bin\/livedesk-client(?:-node)?\.js(?:\s|$|["'])/i
78
- .test(commandLine)
79
- ) {
80
- kind = 'client-runtime';
81
- } else if (
82
- /(?:node_modules\/livedesk|packages\/livedesk)\/bin\/livedesk\.js(?:\s|$|["'])/i
83
- .test(commandLine)
84
- || /(?:^|[\s"'])[^"'\s]*node_modules\/\.bin\/livedesk(?:\s|$|["'])/i
85
- .test(commandLine)
86
- ) {
87
- kind = 'unified-runtime';
88
- } else if (
89
- /(?:node_modules\/(?:livedesk\/hub|@livedesk\/hub)|packages\/(?:livedesk\/hub|hub))\/src\/server\.js(?:\s|$|["'])/i
90
- .test(commandLine)
91
- ) {
92
- kind = 'hub-runtime';
93
- } else if (
94
- /(?:^|\/)LiveDesk\.app\/Contents\/MacOS\/LiveDesk(?:\s|$|["'])/i.test(commandLine)
95
- ) {
96
- kind = 'desktop-runtime';
97
- }
194
+ const kind = classifyMacLiveDeskRuntimeRecord(record);
98
195
  if (!kind) continue;
99
196
  results.push({
100
197
  ...record,
@@ -104,6 +201,191 @@ export function parseMacLiveDeskRuntimeProcesses(psOutput, { ignorePids = [] } =
104
201
  return results;
105
202
  }
106
203
 
204
+ export function parseMacLiveDeskRuntimeProcesses(psOutput, options = {}) {
205
+ return identifyMacLiveDeskRuntimeRecords(parseMacProcessRecords(psOutput), options);
206
+ }
207
+
208
+ function hasAncestorPid(record, ancestorPid, recordsByPid) {
209
+ const expectedAncestor = Number(ancestorPid);
210
+ if (!Number.isInteger(expectedAncestor) || expectedAncestor <= 1) return false;
211
+ let parentPid = Number(record?.parentPid || 0);
212
+ const visited = new Set();
213
+ for (let depth = 0; depth < 16 && parentPid > 1 && !visited.has(parentPid); depth += 1) {
214
+ if (parentPid === expectedAncestor) return true;
215
+ visited.add(parentPid);
216
+ parentPid = Number(recordsByPid.get(parentPid)?.parentPid || 0);
217
+ }
218
+ return false;
219
+ }
220
+
221
+ function normalizedOwnedPids(values) {
222
+ return new Set(
223
+ (Array.isArray(values) ? values : [])
224
+ .map(Number)
225
+ .filter(pid => Number.isInteger(pid) && pid > 1)
226
+ );
227
+ }
228
+
229
+ /**
230
+ * Returns a report-safe process sample. Command lines and executable paths are
231
+ * used only in-memory for strict LiveDesk/helper identity and never returned.
232
+ */
233
+ export function buildMacDiagnosticResourceSample(
234
+ psOutput,
235
+ {
236
+ diagnosticPid = 0,
237
+ hubPid = 0,
238
+ agentPid = 0,
239
+ decoderPids = []
240
+ } = {}
241
+ ) {
242
+ const records = parseMacProcessResourceRecords(psOutput);
243
+ const recordsByPid = new Map(records.map(record => [record.pid, record]));
244
+ const normalizedDiagnosticPid = Number(diagnosticPid);
245
+ const normalizedHubPid = Number(hubPid);
246
+ const normalizedAgentPid = Number(agentPid);
247
+ const ownedDecoderPids = normalizedOwnedPids(decoderPids);
248
+ const helpers = records.filter(isMacCaptureHelperRecord);
249
+ // The public `npx livedesk diagnose mac-physical` coordinator is itself
250
+ // classified as a unified LiveDesk runtime. Exclude only that exact,
251
+ // caller-owned PID from ownership/drain counts. Descendants and neighboring
252
+ // LiveDesk runtimes remain visible and therefore continue to fail closed.
253
+ const liveDeskRecords = identifyMacLiveDeskRuntimeRecords(records, {
254
+ ignorePids: [normalizedDiagnosticPid]
255
+ });
256
+ const hubRecord = recordsByPid.get(normalizedHubPid);
257
+ const agentRecord = recordsByPid.get(normalizedAgentPid);
258
+ const hubOwned = classifyMacLiveDeskRuntimeRecord(hubRecord) === 'hub-runtime';
259
+ const agentOwned = classifyMacLiveDeskRuntimeRecord(agentRecord) === 'client-agent';
260
+ const ownedLiveDeskPids = new Set();
261
+ if (hubOwned) ownedLiveDeskPids.add(normalizedHubPid);
262
+ if (agentOwned) ownedLiveDeskPids.add(normalizedAgentPid);
263
+ const helperOwnership = new Map();
264
+ for (const helper of helpers) {
265
+ const owned = agentOwned && hasAncestorPid(helper, normalizedAgentPid, recordsByPid);
266
+ helperOwnership.set(helper.pid, owned);
267
+ if (owned) ownedLiveDeskPids.add(helper.pid);
268
+ }
269
+
270
+ const resourceRecords = [];
271
+ const addResource = (record, role, ownedByDiagnostic) => {
272
+ if (!record) return;
273
+ resourceRecords.push({
274
+ role,
275
+ pid: record.pid,
276
+ ownedByDiagnostic: ownedByDiagnostic === true,
277
+ cpuPercent: Math.round(record.cpuPercent * 100) / 100,
278
+ rssBytes: record.rssBytes
279
+ });
280
+ };
281
+ addResource(hubRecord, 'hub', hubOwned);
282
+ addResource(agentRecord, 'remote-fast', agentOwned);
283
+ for (const decoderPid of [...ownedDecoderPids].sort((left, right) => left - right)) {
284
+ addResource(recordsByPid.get(decoderPid), 'decoder', true);
285
+ }
286
+ for (const helper of helpers.sort((left, right) => left.pid - right.pid)) {
287
+ addResource(helper, 'capture-helper', helperOwnership.get(helper.pid) === true);
288
+ }
289
+
290
+ return {
291
+ sampledAt: new Date().toISOString(),
292
+ totalLiveDeskProcessCount: liveDeskRecords.length,
293
+ captureHelperCount: helpers.length,
294
+ unownedLiveDeskPids: liveDeskRecords
295
+ .filter(record => !ownedLiveDeskPids.has(record.pid))
296
+ .map(record => record.pid)
297
+ .sort((left, right) => left - right),
298
+ processes: resourceRecords.sort((left, right) =>
299
+ left.role.localeCompare(right.role) || left.pid - right.pid
300
+ )
301
+ };
302
+ }
303
+
304
+ function percentile95(values) {
305
+ if (values.length === 0) return 0;
306
+ const sorted = [...values].sort((left, right) => left - right);
307
+ return sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)];
308
+ }
309
+
310
+ export function summarizeMacDiagnosticResourceSamples(
311
+ samples = [],
312
+ { expectedProcesses = [] } = {}
313
+ ) {
314
+ const normalizedSamples = (Array.isArray(samples) ? samples : [])
315
+ .filter(sample => sample && typeof sample === 'object');
316
+ const grouped = new Map();
317
+ for (const record of Array.isArray(expectedProcesses) ? expectedProcesses : []) {
318
+ const pid = Number(record?.pid);
319
+ const role = String(record?.role || '').trim();
320
+ if (!Number.isInteger(pid) || pid <= 1 || !role) continue;
321
+ grouped.set(`${role}:${pid}`, {
322
+ role,
323
+ pid,
324
+ ownedByDiagnostic: record?.ownedByDiagnostic === true,
325
+ cpuPercentSamples: [],
326
+ rssBytesPeak: 0
327
+ });
328
+ }
329
+ for (const sample of normalizedSamples) {
330
+ for (const record of Array.isArray(sample.processes) ? sample.processes : []) {
331
+ const pid = Number(record?.pid);
332
+ const role = String(record?.role || '').trim();
333
+ if (!Number.isInteger(pid) || pid <= 1 || !role) continue;
334
+ const key = `${role}:${pid}`;
335
+ let summary = grouped.get(key);
336
+ if (!summary) {
337
+ summary = {
338
+ role,
339
+ pid,
340
+ ownedByDiagnostic: true,
341
+ cpuPercentSamples: [],
342
+ rssBytesPeak: 0
343
+ };
344
+ grouped.set(key, summary);
345
+ }
346
+ summary.ownedByDiagnostic = summary.ownedByDiagnostic && record.ownedByDiagnostic === true;
347
+ summary.cpuPercentSamples.push(Math.max(0, finite(record.cpuPercent)));
348
+ summary.rssBytesPeak = Math.max(summary.rssBytesPeak, Math.max(0, finite(record.rssBytes)));
349
+ }
350
+ }
351
+ const processes = [...grouped.values()]
352
+ .map(summary => {
353
+ const cpuPercentSamples = summary.cpuPercentSamples
354
+ .slice(-120)
355
+ .map(value => Math.round(value * 100) / 100);
356
+ return {
357
+ role: summary.role,
358
+ pid: summary.pid,
359
+ ownedByDiagnostic: summary.ownedByDiagnostic,
360
+ sampleCount: summary.cpuPercentSamples.length,
361
+ cpuPercentSamples,
362
+ cpuPercentP95: Math.round(percentile95(summary.cpuPercentSamples) * 100) / 100,
363
+ cpuPercentPeak: Math.round(Math.max(0, ...summary.cpuPercentSamples) * 100) / 100,
364
+ rssBytesPeak: Math.round(summary.rssBytesPeak)
365
+ };
366
+ })
367
+ .sort((left, right) => left.role.localeCompare(right.role) || left.pid - right.pid);
368
+ const totalLiveDeskProcessCountSamples = normalizedSamples
369
+ .map(sample => Math.max(0, Math.round(finite(sample.totalLiveDeskProcessCount))));
370
+ const captureHelperCountSamples = normalizedSamples
371
+ .map(sample => Math.max(0, Math.round(finite(sample.captureHelperCount))));
372
+ const unownedLiveDeskPids = [...new Set(
373
+ normalizedSamples.flatMap(sample =>
374
+ (Array.isArray(sample.unownedLiveDeskPids) ? sample.unownedLiveDeskPids : [])
375
+ .map(Number)
376
+ .filter(pid => Number.isInteger(pid) && pid > 1)
377
+ )
378
+ )].sort((left, right) => left - right);
379
+ return {
380
+ sampleCount: normalizedSamples.length,
381
+ totalLiveDeskProcessCount: totalLiveDeskProcessCountSamples.at(-1) || 0,
382
+ totalLiveDeskProcessCountPeak: Math.max(0, ...totalLiveDeskProcessCountSamples),
383
+ captureHelperCountPeak: Math.max(0, ...captureHelperCountSamples),
384
+ unownedLiveDeskPids,
385
+ processes
386
+ };
387
+ }
388
+
107
389
  const MODIFIERS = Object.freeze([
108
390
  { code: 'ShiftLeft', key: 'Shift', keyCode: 16, location: 1, flag: 'shiftKey' },
109
391
  { code: 'ControlLeft', key: 'Control', keyCode: 17, location: 1, flag: 'ctrlKey' },
@@ -243,6 +525,49 @@ export function classifyMacPhysicalGate(snapshot = {}) {
243
525
  const helperPids = Array.isArray(helper.observedPids)
244
526
  ? helper.observedPids.map(Number).filter(pid => Number.isInteger(pid) && pid > 1)
245
527
  : [];
528
+ const resources = snapshot.resources || {};
529
+ const baselineResources = resources.baseline || {};
530
+ const steadyResources = resources.steadyCapture || {};
531
+ const cleanupResources = resources.afterCleanup || {};
532
+ const steadyProcesses = Array.isArray(steadyResources.processes)
533
+ ? steadyResources.processes
534
+ : [];
535
+ const sampledSteadyProcesses = steadyProcesses
536
+ .filter(processInfo => finite(processInfo?.sampleCount) > 0);
537
+ const sampledSteadyRoles = new Set(
538
+ sampledSteadyProcesses.map(processInfo => String(processInfo?.role || ''))
539
+ );
540
+ const resourceTelemetryAvailable = finite(baselineResources.sampleCount) > 0
541
+ && finite(steadyResources.sampleCount) > 0
542
+ && finite(cleanupResources.sampleCount) > 0;
543
+ const resourceOwnershipCurrent = finite(baselineResources.totalLiveDeskProcessCountPeak) === 0
544
+ && sampledSteadyRoles.has('hub')
545
+ && sampledSteadyRoles.has('remote-fast')
546
+ && sampledSteadyRoles.has('capture-helper')
547
+ && sampledSteadyProcesses.every(processInfo => processInfo?.ownedByDiagnostic === true)
548
+ && (!Array.isArray(steadyResources.unownedLiveDeskPids)
549
+ || steadyResources.unownedLiveDeskPids.length === 0);
550
+ const resourceDrained = finite(cleanupResources.totalLiveDeskProcessCount) === 0
551
+ && finite(cleanupResources.totalLiveDeskProcessCountPeak) === 0
552
+ && (!Array.isArray(cleanupResources.processes) || cleanupResources.processes.length === 0)
553
+ && (!Array.isArray(cleanupResources.unownedLiveDeskPids)
554
+ || cleanupResources.unownedLiveDeskPids.length === 0);
555
+ const resourceHelperPeak = Math.max(
556
+ finite(helper.maxConcurrent),
557
+ finite(steadyResources.captureHelperCountPeak)
558
+ );
559
+ const highCpuProcesses = steadyProcesses
560
+ .filter(processInfo =>
561
+ finite(processInfo?.cpuPercentP95) >= 80
562
+ || finite(processInfo?.cpuPercentPeak) >= 100
563
+ )
564
+ .map(processInfo => ({
565
+ role: String(processInfo?.role || ''),
566
+ pid: Math.max(0, Math.round(finite(processInfo?.pid))),
567
+ cpuPercentP95: Math.max(0, finite(processInfo?.cpuPercentP95)),
568
+ cpuPercentPeak: Math.max(0, finite(processInfo?.cpuPercentPeak)),
569
+ rssBytesPeak: Math.max(0, Math.round(finite(processInfo?.rssBytesPeak)))
570
+ }));
246
571
  const expectedModifierAcks = Math.max(1, finite(modifier.expectedAcks, 8));
247
572
  const checks = [
248
573
  check(
@@ -301,10 +626,30 @@ export function classifyMacPhysicalGate(snapshot = {}) {
301
626
  ),
302
627
  check(
303
628
  'single-helper',
304
- finite(helper.maxConcurrent) <= 1,
305
- `max=${finite(helper.maxConcurrent)} pids=${helperPids.join(',') || '-'}`
629
+ resourceHelperPeak <= 1,
630
+ `max=${resourceHelperPeak} pids=${helperPids.join(',') || '-'}`
306
631
  ),
307
632
  check('helper-drained', finite(helper.finalCount) === 0, `final=${finite(helper.finalCount)}`),
633
+ check(
634
+ 'process-telemetry',
635
+ resourceTelemetryAvailable,
636
+ resourceTelemetryAvailable
637
+ ? `baseline=${finite(baselineResources.sampleCount)} steady=${finite(steadyResources.sampleCount)} cleanup=${finite(cleanupResources.sampleCount)}`
638
+ : 'baseline, steady-capture, and after-cleanup samples are required'
639
+ ),
640
+ check(
641
+ 'resource-ownership',
642
+ resourceTelemetryAvailable && resourceOwnershipCurrent,
643
+ `baseline=${finite(baselineResources.totalLiveDeskProcessCountPeak)} `
644
+ + `steady=${finite(steadyResources.totalLiveDeskProcessCountPeak)} `
645
+ + `unowned=${Array.isArray(steadyResources.unownedLiveDeskPids) ? steadyResources.unownedLiveDeskPids.join(',') || '-' : '-'}`
646
+ ),
647
+ check(
648
+ 'resource-drained',
649
+ resourceTelemetryAvailable && resourceDrained,
650
+ `final=${finite(cleanupResources.totalLiveDeskProcessCount)} `
651
+ + `peak=${finite(cleanupResources.totalLiveDeskProcessCountPeak)}`
652
+ ),
308
653
  check('two-monitors', finite(monitor.count) >= 2, `count=${finite(monitor.count)}`),
309
654
  check(
310
655
  'monitor-generation',
@@ -350,6 +695,9 @@ export function classifyMacPhysicalGate(snapshot = {}) {
350
695
  'delivered-fps': 'agent-or-transport',
351
696
  'single-helper': 'duplicate-helper',
352
697
  'helper-drained': 'stale-helper',
698
+ 'process-telemetry': 'process-telemetry',
699
+ 'resource-ownership': 'resource-ownership',
700
+ 'resource-drained': 'resource-not-drained',
353
701
  'two-monitors': 'monitor-topology',
354
702
  'monitor-generation': 'monitor-generation',
355
703
  'monitor-pixels': 'monitor-binding',
@@ -360,6 +708,11 @@ export function classifyMacPhysicalGate(snapshot = {}) {
360
708
  ok: failed.length === 0,
361
709
  idleOptimized,
362
710
  bottleneck: failed.length > 0 ? bottleneckByCheck[failed[0].id] || failed[0].id : 'none',
711
+ processTelemetry: {
712
+ available: resourceTelemetryAvailable,
713
+ highLoad: highCpuProcesses.length > 0,
714
+ highLoadProcesses: highCpuProcesses
715
+ },
363
716
  checks
364
717
  };
365
718
  }