@openclaw/facetime 2026.9.6 → 2026.9.7

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.
Files changed (57) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{setup-DUgC9yE7.mjs → setup-CoxvlB5L.mjs} +91 -128
  3. package/dist/doctor-contract-api.js +189 -7
  4. package/dist/index.js +1 -1
  5. package/dist/runtime-api.js +197 -238
  6. package/node_modules/typebox/build/compile/code.mjs +1 -2
  7. package/node_modules/typebox/build/compile/validator.mjs +5 -1
  8. package/node_modules/typebox/build/format/iri.mjs +16 -9
  9. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  10. package/node_modules/typebox/build/guard/guard.d.mts +7 -5
  11. package/node_modules/typebox/build/guard/guard.mjs +14 -9
  12. package/node_modules/typebox/build/guard/index.mjs +6 -0
  13. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
  14. package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
  15. package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
  16. package/node_modules/typebox/build/schema/build.mjs +2 -3
  17. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
  18. package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
  19. package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
  20. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
  21. package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
  22. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
  23. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
  24. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
  25. package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
  26. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
  27. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
  28. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
  29. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
  30. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
  31. package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
  32. package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
  33. package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
  34. package/node_modules/typebox/build/schema/parse.mjs +3 -0
  35. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
  36. package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
  37. package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
  38. package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
  39. package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
  40. package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
  41. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  42. package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
  43. package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
  44. package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
  45. package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
  46. package/node_modules/typebox/package.json +1 -1
  47. package/node_modules/typebox/readme.md +3 -1
  48. package/package.json +5 -5
  49. package/src/audio-pump.ts +9 -16
  50. package/src/call-events.ts +12 -26
  51. package/src/call-lifecycle.ts +3 -19
  52. package/src/runtime-call-control.ts +3 -12
  53. package/src/runtime-call-events.ts +4 -9
  54. package/src/runtime-state.ts +0 -10
  55. package/src/setup.ts +96 -173
  56. package/src/talk-driver.ts +217 -242
  57. /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
@@ -25,12 +25,7 @@ import {
25
25
  type PendingFaceTimeDial,
26
26
  } from "./outbound-call.js";
27
27
  import { retainHelperResultPeers } from "./runtime-helper-results.js";
28
- import {
29
- createManagedCall,
30
- readCallUUID,
31
- updateCallStatus,
32
- type ActiveFaceTimeCall,
33
- } from "./runtime-state.js";
28
+ import { ActiveFaceTimeCall, readCallUUID, updateCallStatus } from "./runtime-state.js";
34
29
 
35
30
  type CallControl = {
36
31
  activateCallTalk(call: ActiveFaceTimeCall, options: { unmute: boolean }): Promise<void>;
@@ -152,7 +147,7 @@ export function createFaceTimeCallEventHandler(params: {
152
147
  params.logger.warn("[facetime] ignored incoming call; another FaceTime bridge is active");
153
148
  return;
154
149
  }
155
- const call = createManagedCall({
150
+ const call = new ActiveFaceTimeCall({
156
151
  callUUID,
157
152
  phase: "ringing",
158
153
  owner,
@@ -216,7 +211,7 @@ export function createFaceTimeCallEventHandler(params: {
216
211
  params.logger.warn("[facetime] ignored active call; another FaceTime bridge is active");
217
212
  return;
218
213
  }
219
- call = createManagedCall({
214
+ call = new ActiveFaceTimeCall({
220
215
  callUUID,
221
216
  phase: "active",
222
217
  owner,
@@ -299,7 +294,7 @@ export function createFaceTimeCallEventHandler(params: {
299
294
  }
300
295
  let ringingCall = resolveEventCall(event);
301
296
  if (!ringingCall && params.calls.size === 0) {
302
- ringingCall = createManagedCall({
297
+ ringingCall = new ActiveFaceTimeCall({
303
298
  callUUID,
304
299
  phase: "ringing",
305
300
  owner,
@@ -201,13 +201,3 @@ export function updateCallStatus(call: ActiveFaceTimeCall, event: FaceTimeCallSt
201
201
  );
202
202
  }
203
203
  }
204
-
205
- export function createManagedCall(params: {
206
- callUUID: string;
207
- phase: "ringing" | "active";
208
- owner: AuthenticatedFaceTimeOwner;
209
- handle?: string;
210
- peer?: FaceTimeHelperPeer;
211
- }): ActiveFaceTimeCall {
212
- return new ActiveFaceTimeCall(params);
213
- }
package/src/setup.ts CHANGED
@@ -33,7 +33,7 @@ type FaceTimeSetupCheck = {
33
33
  status: FaceTimeSetupCheckStatus;
34
34
  required: boolean;
35
35
  message: string;
36
- actionId?: string;
36
+ actionId?: keyof typeof SETUP_ACTIONS;
37
37
  };
38
38
 
39
39
  export type FaceTimeSetupReport = {
@@ -58,86 +58,123 @@ type SetupParams = {
58
58
  const XCODE_APP = "/Applications/Xcode.app";
59
59
  const XCODE_CLANG = `${XCODE_APP}/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`;
60
60
  const XCODE_MACOS_SDK = `${XCODE_APP}/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk`;
61
- const NATIVE_PACKAGE_COMMAND =
62
- "if brew list --versions openclaw-facetime >/dev/null 2>&1; then brew reinstall openclaw/tap/openclaw-facetime; else brew install openclaw/tap/openclaw-facetime; fi";
63
- const NATIVE_PACKAGE_ACTION = {
64
- id: "install-native-package",
65
- kind: "command",
66
- label: "Install or reinstall the FaceTime native package with Homebrew",
67
- command: NATIVE_PACKAGE_COMMAND,
68
- } as const satisfies FaceTimeSetupAction;
69
-
70
- const PRECHECK_ACTIONS: Record<
71
- string,
72
- Pick<FaceTimeSetupAction, "id" | "kind" | "label" | "command" | "gatewayMethod" | "settingsPath">
73
- > = {
74
- "capture-binary": {
75
- id: NATIVE_PACKAGE_ACTION.id,
61
+ const SETUP_ACTIONS = {
62
+ "install-native-package": {
76
63
  kind: "command",
77
- label: NATIVE_PACKAGE_ACTION.label,
78
- command: NATIVE_PACKAGE_ACTION.command,
64
+ label: "Install or reinstall the FaceTime native package with Homebrew",
65
+ command:
66
+ "if brew list --versions openclaw-facetime >/dev/null 2>&1; then brew reinstall openclaw/tap/openclaw-facetime; else brew install openclaw/tap/openclaw-facetime; fi",
79
67
  },
80
- "paired-driver-mic": {
81
- id: "install-driver",
82
- kind: "gateway",
83
- label: "Install or update the paired FaceTime audio driver",
84
- gatewayMethod: "facetime.installDriver",
68
+ "install-xcode-tools": {
69
+ kind: "command",
70
+ label: "Install full Xcode in /Applications",
71
+ command: "open 'https://apps.apple.com/us/app/xcode/id497799835'",
72
+ },
73
+ "enable-developer-tools": {
74
+ kind: "command",
75
+ label: "Enable developer tools access",
76
+ command: "sudo /usr/sbin/DevToolsSecurity -enable",
77
+ },
78
+ "disable-sip-debugging": {
79
+ kind: "recovery",
80
+ label: "Disable SIP debugging restrictions from macOS Recovery, then reboot and rerun setup",
81
+ command: "csrutil enable --without debug",
82
+ settingsPath: "macOS Recovery > Utilities > Terminal",
83
+ },
84
+ "verify-sip-status": {
85
+ kind: "command",
86
+ label: "Verify SIP status, then rerun setup",
87
+ command: "/usr/bin/csrutil status",
88
+ },
89
+ "configure-owner-handles": {
90
+ kind: "command",
91
+ label: "Configure ownerHandles",
92
+ command: "openclaw configure",
93
+ },
94
+ "restart-gateway": {
95
+ kind: "command",
96
+ label: "Stop the stale gateway process, then restart OpenClaw",
97
+ command: "openclaw gateway restart",
98
+ },
99
+ "wait-for-helper": {
100
+ kind: "automatic",
101
+ label: "Wait for automatic helper injection to finish",
102
+ },
103
+ "restart-call-apps": {
104
+ kind: "manual-test",
105
+ label: "Quit and reopen FaceTime and Phone, then let OpenClaw reinject the helper",
85
106
  },
86
- "paired-driver-feed": {
87
- id: "install-driver",
107
+ "install-driver": {
88
108
  kind: "gateway",
89
109
  label: "Install or update the paired FaceTime audio driver",
90
110
  gatewayMethod: "facetime.installDriver",
91
111
  },
92
- "physical-output": {
93
- id: "select-physical-output",
112
+ "select-physical-output": {
94
113
  kind: "system-settings",
95
114
  label: "Select MacBook Speakers or another physical output",
96
115
  settingsPath: "System Settings > Sound > Output",
97
116
  },
98
- "process-tap": {
99
- id: "grant-system-audio",
117
+ "grant-system-audio": {
100
118
  kind: "system-settings",
101
119
  label: "Allow OpenClaw to capture FaceTime app audio",
102
120
  settingsPath: "System Settings > Privacy & Security > Screen & System Audio Recording",
103
121
  },
104
- "realtime-provider": {
105
- id: "configure-realtime-provider",
122
+ "configure-realtime-provider": {
106
123
  kind: "command",
107
124
  label: "Configure authentication for a registered realtime voice provider",
108
125
  command: "openclaw configure",
109
126
  },
127
+ "verify-focus": {
128
+ kind: "system-settings",
129
+ label: "Verify Focus is off or allows the expected caller",
130
+ settingsPath: "Control Center > Focus",
131
+ },
132
+ "allow-sharing-notifications": {
133
+ kind: "system-settings",
134
+ label: "Allow notifications while mirroring or sharing the display",
135
+ settingsPath: "System Settings > Notifications",
136
+ },
137
+ "live-outbound-test": {
138
+ kind: "manual-test",
139
+ label: "Place one outbound FaceTime audio call with facetime.dial",
140
+ gatewayMethod: "facetime.dial",
141
+ },
142
+ "live-audio-test": {
143
+ kind: "manual-test",
144
+ label: "Run one inbound and one outbound FaceTime audio call",
145
+ },
146
+ "review-live-voicemail": {
147
+ kind: "system-settings",
148
+ label: "Review Live Voicemail if inbound audio calls are screened",
149
+ settingsPath: "Phone > Settings > Live Voicemail",
150
+ },
151
+ } satisfies Record<string, Omit<FaceTimeSetupAction, "id">>;
152
+
153
+ const PRECHECK_ACTIONS: Record<string, keyof typeof SETUP_ACTIONS> = {
154
+ "capture-binary": "install-native-package",
155
+ "paired-driver-mic": "install-driver",
156
+ "paired-driver-feed": "install-driver",
157
+ "physical-output": "select-physical-output",
158
+ "process-tap": "grant-system-audio",
159
+ "realtime-provider": "configure-realtime-provider",
110
160
  };
111
161
 
112
162
  function firstLine(value: unknown): string | undefined {
113
163
  return typeof value === "string" ? value.trim().split(/\r?\n/u)[0] || undefined : undefined;
114
164
  }
115
165
 
116
- function addAction(actions: FaceTimeSetupAction[], action: FaceTimeSetupAction): void {
117
- if (!actions.some((candidate) => candidate.id === action.id)) {
118
- actions.push(action);
119
- }
120
- }
121
-
122
- function addPreflightCheck(
123
- checks: FaceTimeSetupCheck[],
124
- actions: FaceTimeSetupAction[],
125
- check: FaceTimePreflightCheck,
126
- ): void {
166
+ function addPreflightCheck(checks: FaceTimeSetupCheck[], check: FaceTimePreflightCheck): void {
127
167
  if (check.id === "call-app-running") {
128
168
  return;
129
169
  }
130
- const action = PRECHECK_ACTIONS[check.id];
131
- if (!check.ok && action) {
132
- addAction(actions, action);
133
- }
170
+ const actionId = PRECHECK_ACTIONS[check.id];
134
171
  checks.push({
135
172
  id: check.id,
136
173
  label: check.label,
137
174
  status: check.ok ? "ready" : check.required ? "action-required" : "recommended",
138
175
  required: check.required,
139
176
  message: check.message ?? (check.ok ? "ready" : "not ready"),
140
- ...(!check.ok && action ? { actionId: action.id } : {}),
177
+ ...(!check.ok && actionId ? { actionId } : {}),
141
178
  });
142
179
  }
143
180
 
@@ -275,7 +312,6 @@ async function inspectDriver(params: SetupParams): Promise<{
275
312
 
276
313
  export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSetupReport> {
277
314
  const checks: FaceTimeSetupCheck[] = [];
278
- const actions: FaceTimeSetupAction[] = [];
279
315
 
280
316
  const [xcodeCompiler, xcodeSdk] = await Promise.all([
281
317
  checkCommand(params.runCommandWithTimeout, ["/bin/test", "-x", XCODE_CLANG], () => XCODE_CLANG),
@@ -294,16 +330,6 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
294
330
  : `Full Xcode is required at ${XCODE_APP}; Command Line Tools alone cannot perform protected-app injection or build the local audio driver`,
295
331
  ...(!xcodeReady ? { actionId: "install-xcode-tools" } : {}),
296
332
  });
297
- if (!xcodeReady) {
298
- // Keep the established action id stable for setup-report consumers while
299
- // directing operators to the full Xcode app required by the native setup boundary.
300
- addAction(actions, {
301
- id: "install-xcode-tools",
302
- kind: "command",
303
- label: "Install full Xcode in /Applications",
304
- command: "open 'https://apps.apple.com/us/app/xcode/id497799835'",
305
- });
306
- }
307
333
 
308
334
  const developerSecurity = await checkCommand(
309
335
  params.runCommandWithTimeout,
@@ -322,14 +348,6 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
322
348
  : "Developer tools access is disabled; helper injection cannot attach to FaceTime or Phone",
323
349
  ...(!developerSecurityEnabled ? { actionId: "enable-developer-tools" } : {}),
324
350
  });
325
- if (!developerSecurityEnabled) {
326
- addAction(actions, {
327
- id: "enable-developer-tools",
328
- kind: "command",
329
- label: "Enable developer tools access",
330
- command: "sudo /usr/sbin/DevToolsSecurity -enable",
331
- });
332
- }
333
351
 
334
352
  const sip = await checkCommand(
335
353
  params.runCommandWithTimeout,
@@ -359,22 +377,6 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
359
377
  : "Could not verify SIP debugging restrictions; run csrutil status before changing protection settings",
360
378
  ...(!debuggingRestrictionsDisabled ? { actionId: sipActionId } : {}),
361
379
  });
362
- if (!debuggingRestrictionsDisabled && debuggingRestrictionsEnabled) {
363
- addAction(actions, {
364
- id: "disable-sip-debugging",
365
- kind: "recovery",
366
- label: "Disable SIP debugging restrictions from macOS Recovery, then reboot and rerun setup",
367
- command: "csrutil enable --without debug",
368
- settingsPath: "macOS Recovery > Utilities > Terminal",
369
- });
370
- } else if (!debuggingRestrictionsDisabled) {
371
- addAction(actions, {
372
- id: "verify-sip-status",
373
- kind: "command",
374
- label: "Verify SIP status, then rerun setup",
375
- command: "/usr/bin/csrutil status",
376
- });
377
- }
378
380
 
379
381
  checks.push({
380
382
  id: "owner-handles",
@@ -387,14 +389,6 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
387
389
  : "Configure at least one owner email address or phone number",
388
390
  ...(params.config.ownerHandles.length === 0 ? { actionId: "configure-owner-handles" } : {}),
389
391
  });
390
- if (params.config.ownerHandles.length === 0) {
391
- addAction(actions, {
392
- id: "configure-owner-handles",
393
- kind: "command",
394
- label: "Configure ownerHandles",
395
- command: "openclaw configure",
396
- });
397
- }
398
392
 
399
393
  checks.push({
400
394
  id: "native-package",
@@ -404,11 +398,8 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
404
398
  message: params.nativePackageReady
405
399
  ? "Compatible FaceTime capture and helper artifacts are installed"
406
400
  : "Compatible FaceTime native helpers are not installed",
407
- ...(!params.nativePackageReady ? { actionId: NATIVE_PACKAGE_ACTION.id } : {}),
401
+ ...(!params.nativePackageReady ? { actionId: "install-native-package" } : {}),
408
402
  });
409
- if (!params.nativePackageReady) {
410
- addAction(actions, NATIVE_PACKAGE_ACTION);
411
- }
412
403
 
413
404
  // The runtime can finish asynchronous helper injection while the static
414
405
  // checks above run. Resolve its status only when composing the live checks
@@ -425,18 +416,10 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
425
416
  : (params.runtimeError ?? "FaceTime runtime is not running"),
426
417
  ...(!runtimeStatus
427
418
  ? {
428
- actionId: params.nativePackageReady ? "restart-gateway" : NATIVE_PACKAGE_ACTION.id,
419
+ actionId: params.nativePackageReady ? "restart-gateway" : "install-native-package",
429
420
  }
430
421
  : {}),
431
422
  });
432
- if (!runtimeStatus && params.nativePackageReady) {
433
- addAction(actions, {
434
- id: "restart-gateway",
435
- kind: "command",
436
- label: "Stop the stale gateway process, then restart OpenClaw",
437
- command: "openclaw gateway restart",
438
- });
439
- }
440
423
 
441
424
  if (runtimeStatus) {
442
425
  for (const target of runtimeStatus.helperTargets) {
@@ -467,19 +450,6 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
467
450
  ? { actionId: "restart-call-apps" }
468
451
  : {}),
469
452
  });
470
- if (status === "repairing") {
471
- addAction(actions, {
472
- id: "wait-for-helper",
473
- kind: "automatic",
474
- label: "Wait for automatic helper injection to finish",
475
- });
476
- } else if (status === "action-required") {
477
- addAction(actions, {
478
- id: "restart-call-apps",
479
- kind: "manual-test",
480
- label: "Quit and reopen FaceTime and Phone, then let OpenClaw reinject the helper",
481
- });
482
- }
483
453
  }
484
454
  }
485
455
 
@@ -499,18 +469,10 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
499
469
  : `Driver status: ${driver.status ?? "unknown"}`,
500
470
  ...(!driverReady
501
471
  ? {
502
- actionId: params.nativePackageReady ? "install-driver" : NATIVE_PACKAGE_ACTION.id,
472
+ actionId: params.nativePackageReady ? "install-driver" : "install-native-package",
503
473
  }
504
474
  : {}),
505
475
  });
506
- if (!driverReady && params.nativePackageReady) {
507
- addAction(actions, {
508
- id: "install-driver",
509
- kind: "gateway",
510
- label: "Install or update the paired FaceTime audio driver",
511
- gatewayMethod: "facetime.installDriver",
512
- });
513
- }
514
476
 
515
477
  if (params.preflight) {
516
478
  const preflight = await params.preflight;
@@ -518,31 +480,12 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
518
480
  if (check.id === "helper-connected" && runtimeStatus?.helperTargets.length) {
519
481
  continue;
520
482
  }
521
- addPreflightCheck(checks, actions, check);
483
+ addPreflightCheck(checks, check);
522
484
  }
523
485
  }
524
486
 
525
- const focus = await checkFocusMode(params);
526
- checks.push(focus);
527
- if (focus.status === "verify-on-call") {
528
- addAction(actions, {
529
- id: "verify-focus",
530
- kind: "system-settings",
531
- label: "Verify Focus is off or allows the expected caller",
532
- settingsPath: "Control Center > Focus",
533
- });
534
- }
535
-
536
- const sharingNotifications = await checkNotificationsDuringSharing(params.runCommandWithTimeout);
537
- checks.push(sharingNotifications);
538
- if (sharingNotifications.status === "action-required") {
539
- addAction(actions, {
540
- id: "allow-sharing-notifications",
541
- kind: "system-settings",
542
- label: "Allow notifications while mirroring or sharing the display",
543
- settingsPath: "System Settings > Notifications",
544
- });
545
- }
487
+ checks.push(await checkFocusMode(params));
488
+ checks.push(await checkNotificationsDuringSharing(params.runCommandWithTimeout));
546
489
 
547
490
  checks.push({
548
491
  id: "facetime-sign-in",
@@ -553,12 +496,6 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
553
496
  "macOS does not expose a supported sign-in readiness API; verify with one outbound call",
554
497
  actionId: "live-outbound-test",
555
498
  });
556
- addAction(actions, {
557
- id: "live-outbound-test",
558
- kind: "manual-test",
559
- label: "Place one outbound FaceTime audio call with facetime.dial",
560
- gatewayMethod: "facetime.dial",
561
- });
562
499
 
563
500
  const internalMediaStage = runtimeStatus?.calls.find(
564
501
  (call) =>
@@ -577,13 +514,6 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
577
514
  : "Internal media stages require an active call; remote audibility still needs a separate live check",
578
515
  ...(!internalMediaStage ? { actionId: "live-audio-test" } : {}),
579
516
  });
580
- if (!internalMediaStage) {
581
- addAction(actions, {
582
- id: "live-audio-test",
583
- kind: "manual-test",
584
- label: "Run one inbound and one outbound FaceTime audio call",
585
- });
586
- }
587
517
 
588
518
  checks.push({
589
519
  id: "live-voicemail",
@@ -594,22 +524,15 @@ export async function runFaceTimeSetup(params: SetupParams): Promise<FaceTimeSet
594
524
  "If incoming FaceTime Audio rings are intercepted, turn off Live Voicemail on this unattended Mac",
595
525
  actionId: "review-live-voicemail",
596
526
  });
597
- addAction(actions, {
598
- id: "review-live-voicemail",
599
- kind: "system-settings",
600
- label: "Review Live Voicemail if inbound audio calls are screened",
601
- settingsPath: "Phone > Settings > Live Voicemail",
602
- });
603
527
 
604
- const requiredReady = checks.every(
605
- (check) => !check.required || check.status === "ready" || check.status === "repairing",
606
- );
607
- const repairsPending = checks.some((check) => check.required && check.status === "repairing");
528
+ const ready = checks.every((check) => !check.required || check.status === "ready");
608
529
  return {
609
- ok: requiredReady && !repairsPending,
610
- readyForTest: requiredReady && !repairsPending,
530
+ ok: ready,
531
+ readyForTest: ready,
611
532
  liveCallProofRequired: true,
612
533
  checks,
613
- actions,
534
+ actions: [...new Set(checks.flatMap((check) => (check.actionId ? [check.actionId] : [])))].map(
535
+ (id) => Object.assign({ id }, SETUP_ACTIONS[id]),
536
+ ),
614
537
  };
615
538
  }