ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Friend macOS 桌面端权限弹窗循环修复:跳过即持久关闭的“自动路径永不弹窗“授权策略

Friend macOS 桌面端权限弹窗循环修复:跳过即持久关闭的“自动路径永不弹窗“授权策略 Friend macOS 桌面端权限弹窗循环修复跳过即持久关闭的自动路径永不弹窗授权策略【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend本指南基于 Friend 开源仓库桌面端desktop/macos的一次权限体验专项修复完整讲解 macOS 应用用户跳过/拒绝权限后系统 TCC 权限弹窗反复出现并抢占焦点这一经典问题的成因、策略设计与落地实现。读者将掌握一套可复用的用户意图优先授权治理思路如何用持久化意图intent与自动路径永不弹窗automatic paths never prompt两条原则从根上消除启动、唤醒、设置同步等后台场景的权限弹窗循环并配套可离线运行的策略测试policy tests。问题本质为何Skip for now会变成无限弹窗循环本次修复针对的是一个非常典型的 macOS 权限反模式。pr-body.md将其描述为onboarding 阶段用户选择 Skip for now跳过只是推进了流程并未真正记录用户不想要这个能力这一决定导致后续所有自动恢复路径都再次尝试申请权限从而陷入死循环。具体成因链如下麦克风步骤点 Skip for now 只推进了流程。transcriptionEnabled的继任配置项audioRecordingMode位于AssistantSettings仍停留在默认值onlyMeetingsrestorePersistedCaptureServices持久化采集服务恢复位于 DesktopHomeView.swift在**启动launch、应用重新激活didBecomeActive、密钥加载key load、设置同步settings sync**时都会执行恢复流程调用AppState.startTranscription()而该方法在缺少麦克风授权时调用requestMicrophonePermission()——即NSApp.activate()AVCaptureDevice.requestAccess主动把应用切到前台并弹出系统授权弹窗跳过skip让 TCC 状态保持.notDetermined未决定因此每次恢复都会重新武装re-arm系统弹窗用户 dismiss 弹窗会让应用重新激活reactivate进而再次触发恢复逻辑——循环重复。同样的未加守卫的请求还存在于两个额外位置PushToTalkManager.startAudioTranscription和ChatToolExecutor.requestMicrophonePermissionDirectly()分别见 PushToTalkManager.swift 与 ChatToolExecutor.swift。一句话概括根因跳过是一个用户决定但这个决定没有被持久化成关闭off状态也没有被任何自动路径读取。核心策略一个共享的授权策略枚举修复的基石是引入共享策略MicrophoneCaptureAuthorizationPolicy它把所有授权状态 → 动作的映射收敛为可测试的纯函数实现在 SharedCaptureSilentMicRecoveryPolicy.swift。策略定义了四种动作动作含义.proceed授权已以应用有利的方式落定直接武装采集.requestPermission首次运行询问 macOS之后用action(afterRequestGranted:)重新决策.surfacePermissionAlert用户或 MDM明确拒绝——告诉用户真实原因而不是武装一条最终会变成硬件故障告警的零采样流.abandonAutomaticStart本次启动是自动的启动、重新激活、密钥加载、设置同步、唤醒、post-onboarding且授权未落定。自动路径永不弹出 TCC 弹窗或告警直接放弃本次启动让持久化意图等待用户显式的 Listen/Grant 操作核心决策规则只有一条即action(for:userInitiated:)static func action(for status: AVAuthorizationStatus, userInitiated: Bool) - Action { let resolved action(for: status) if resolved .proceed { return .proceed } return userInitiated ? resolved : .abandonAutomaticStart }该文件的源码注释点明了设计意图只有显式用户操作才能弹出系统弹窗或呈现拒绝告警一切自动路径都放弃启动skipped or denied permissions must not auto-reprompt。同时策略对未知授权状态采取失败关闭fail closedunknown default一律解析为.surfacePermissionAlert因为未来的授权状态不代表用户同意。策略还负责区分静音麦克风的最终告警归属terminalAlert(for:)在授权状态下报.hardware硬件问题否则报.permission权限问题。这源自一个底层事实见同一文件源码注释CoreAudio HAL 捕获在 TCC 为notDetermined或已吊销时不会主动触发麦克风弹窗而是成功返回一条永远零采样zero-sample的流被静音麦克风看门狗误判为硬件故障。因此必须先查权限再归咎硬件。麦克风路径跳过即写入持久化 off自动调用点全部传 userInitiated: false在 AppStateTranscription.swift 中startTranscription增加userInitiated参数默认true并用策略门控麦克风启动func startTranscription( source: AudioSource? nil, conversationRole: MeetingConversationBoundaryPolicy.Role .ambient, userInitiated: Bool true ) { guard !isTranscribing else { return } guard AssistantSettings.shared.audioRecordingMode ! .off else { log(Transcription: start ignored because Audio Recording is Off) return } // ... let action MicrophoneCaptureAuthorizationPolicy.action( for: AudioCaptureService.authorizationStatus(), userInitiated: userInitiated) guard action .proceed else { if action .abandonAutomaticStart { log(Transcription: automatic start abandoned — microphone permission not granted; automatic paths never prompt) } else { requestMicrophonePermission() } return } // ... 继续启动 }配套的修复动作answerMic()跳过时写入audioRecordingMode .off把跳过变成持久化的关闭状态而 Allow允许则会撤销之前的跳过PersistedCaptureLaunchPolicy.shouldStartTranscription现在要求存在实时已答复live-answered的麦克风授权使得DesktopHomeView.restorePersistedCaptureServices在无授权时根本不会尝试启动见 DesktopHomeView.swift所有自动调用点统一传userInitiated: false包括唤醒wake、模式变更观察者mode-change observer、4 小时/STT 轮换4h/STT rotations、首选麦克风重连preferred-mic reconnect、两个 onboarding 完成点both onboarding completions——源码中这些位置均以startTranscription(... userInitiated: false)调用见 AppStateTranscription.swift 中 356、1080、1134、1276 行附近Settings 里的音频录制开关在开启时显式请求权限与CaptureListeningLogic.cycleListening的行为对齐。此外toggleTranscription()在显式开启前会先做AudioCaptureService.checkPermission()检查未授权则调用requestMicrophonePermission()——这属于用户显式操作允许弹窗。屏幕录制路径记录用户答案作为持久化意图屏幕Screen权限的修复思路与麦克风略有不同关键在意图与授权分离answerScreen()把用户的回答记录为长期的screenAnalysisEnabled意图SBOnboardingModel.complete()不再在用户跳过之后强制启用屏幕分析新增screenAnalysisIntentAtCompletion接缝seam来注入此行为只有授权真实存在时才调用startMonitoring。即用户跳过屏幕录制screenAnalysisEnabled意图保持关闭后续自动路径自然不会再尝试启动屏幕监控。Sidebar 状态投影Skip ≠ Denied修复还纠正了侧边栏sidebar权限行的状态展示问题核心原则是已 onboarding 但未授予不等同于被拒绝。用户按了 Skip for now 是主动选择侧边栏不应把其渲染成红色被拒绝状态诱导用户去修复一个他们刚做出的决定。实现集中在 SBOnboardingPermissionIntentPolicy.swiftenum SBOnboardingPermissionIntentPolicy { static func screenRecordingDenied( hasCompletedOnboarding: Bool, screenAnalysisIntentEnabled: Bool, permissionGranted: Bool ) - Bool { hasCompletedOnboarding screenAnalysisIntentEnabled !permissionGranted } static func accessibilityDenied( hasCompletedOnboarding: Bool, accessibilityUsable: Bool, skippedInOnboarding: Bool ) - Bool { hasCompletedOnboarding !accessibilityUsable !skippedInOnboarding } }两类权限被区别对待的原因在于底层系统差异源码注释明确说明麦克风与通知macOS 暴露真实的 TCC 答案.denied因此直接读取 TCC 状态即可通知的启动自动弹窗此前已在startMonitoring处修复本次验证未回归屏幕录制与辅助功能AccessibilitymacOS没有公开的 denied/notDetermined 区分无法直接问系统。因此屏幕录制仅当捕获意图开启且授权缺失时才判定为 denied——用户想要 Rewind 而 macOS 未放行跳过会把意图关掉自然不再显示为 denied辅助功能除非 onboarding 记录了跳过标记否则视为 denied。新增持久化标记onboardingAccessibilitySkipped写入/清除逻辑由SBOnboardingSkipPermissionIntentTests覆盖有标记主动跳过即停止红色脉冲无标记旧版本完成 onboarding、标记存在前安装的用户保持原有的缺失即 denied读取。Full Disk Access 与自动文件索引后台扫描永不弹每个文件夹的授权框自动文件索引路径scheduleInitialFileIndexing的首次回填 每 3 小时的重扫同样可能触发 macOS 的逐文件夹 TCC 授权弹窗。修复引入 FileIndexScanPolicy.swift标准扫描根standardScanRoots包含Downloads、Documents、Desktop、Developer、Projects、Code、src、repos、Sites、/Applications与~/Applications其中Documents、Desktop、Downloads被tccProtectedFolderNames标记为 macOS受逐文件夹 TCC 保护的目录无 Full Disk AccessFDA时枚举它们会弹出系统授权对话框新增automaticScanRoots(homeURL:applicationsURL:fullDiskAccessGranted:)无 FDA 时自动索引把受保护根目录从扫描范围剔除后台扫描永远不可能是弹出 grant access to Documents? 弹窗的元凶显式的用户操作Settings → Rescan files保持完整根集合与自动路径不弹窗、显式操作才申请的总原则一致为配合部分扫描的删除deletion环节automaticScanPlan同时返回retainedPrefixes因 TCC 不可用而被剔除的根目录下的既有索引行必须在此次部分扫描的删除阶段存活避免误删见 FileIndexScanPolicy.swift 中AutomaticScanPlan结构。Chat 与 PTTPush-to-Talk诚实上报杜绝死循环聊天与 PTT 两条路径修复了明知已拒绝还要空跑requestAccess的问题request_permission麦克风/通知先检查当前状态已拒绝时如实上报被拒denied不再调用无意义的requestAccess也不再强制跳转系统设置设置面板settings panes只在用户刚刚拒绝了一张新的弹窗时才打开——即拒绝了新弹窗 → 引导去设置是有意义的而上次已拒绝 → 每次操作都跳设置是骚扰PTT 在权限不可用时以.permissionDenied结束当前回合而不是每次按键都重跑一次无效果的请求。测试设计与验证命令本次修复的可验证性核心在于所有策略决策都被设计为不依赖 TCC 数据库的纯函数因此可以离线、确定性地覆盖每一种授权组合。新增与扩展的测试套件均位于desktop/macos/Desktop/Tests/SBOnboardingSkipPermissionIntentTests跳过麦克风 ⇒ 模式为 off 且恢复策略拒绝启动跳过之后允许allow-after-skip恢复非 off 模式跳过屏幕 ⇒ 意图关闭允许 ⇒ 意图开启完成接缝对已跳过授权永不强制开启AX 跳过标记的设置与清除侧边栏投影sidebar projections。核心用例包括testSkipMicWritesDurableOffForTheAutomaticListeningPath、testAllowMicAfterAnEarlierSkipRestoresANonOffMode、testCompletionNeverForcesScreenAnalysisOnForASkippedGrant、testSkipAccessibilityRecordsTheDurableSkipMarker等MicrophoneCaptureAuthorizationPolicyTeststestAuthorizedProceedsToCapture、testNotDeterminedRequestsPermissionInsteadOfArmingCapture、testDeniedSurfacesPermissionAlert、testAutomaticStartsNeverRequestOrSurfaceRegardlessOfStatus断言自动路径对authorized/notDetermined/denied均解析为.abandonAutomaticStart、testExhaustedWatchdogBlamesPermissionWhenDenied等PersistedCaptureLaunchPolicyTests恢复restore在无授权时拒绝启动FileIndexScanPolicyTests无 FDA 时自动根排除 TCC 保护目录有 FDA 时与标准根一致。官方验证命令全部通过xcrun swift build -c debug --package-path Desktop xcrun swift test --package-path Desktop --filter PersistedCaptureLaunchPolicyTests|MicrophoneCaptureAuthorizationPolicyTests|SBOnboardingSkipPermissionIntentTests|FileIndexScanPolicyTests|SBOnboardingStepTelemetryTests|SBOnboardingPermissionFlowTests|OnboardingPermissionToolTests|FileIndexerServiceTests|SettingsSyncCaptureRestorationTests|ShellListeningCycleTests|PTTWarmMicKeepAliveTests需要说明的限制本次未进行命名 bundlenamed-bundle的 onboarding 流程实机运行——变更路径均为上述测试套件覆盖的策略接缝一次真实的 TCC 演练需要交互式授权对话框属于已知的验证边界。设计总结三条可复用的原则回顾整个修复可以提炼出三条对任何 macOS 桌面应用都有普适价值的授权治理原则跳过/拒绝必须持久化Skip for now 必须写入持久化意图麦克风的audioRecordingMode .off、屏幕的screenAnalysisEnabled false、AX 的onboardingAccessibilitySkipped标记否则每次启动都会重新武装弹窗自动路径永不弹窗启动、重新激活、密钥加载、设置同步、唤醒、轮换、post-onboarding 等一切非用户显式触发的路径遇未授权一律abandonAutomaticStart把决定权交还给用户的显式操作Listen / Grant / 设置开关策略决策做成纯函数授权状态到动作的映射独立于 TCC 数据库使每个状态组合都可以用单元测试确定性覆盖避免依赖交互式弹窗做回归验证。本次修复对应的失败分类Failure-Class为FC-privileged-consent-resampled-on-timer定时器驱动的特权同意被重复采样scripts/pr-preflight --suggest报告该 diff 未影响任何产品不变量product invariants。相关回归测试的完整入口与策略实现可继续在 MicrophoneCaptureAuthorizationPolicyTests.swift、SBOnboardingSkipPermissionIntentTests.swift 与 SharedCaptureSilentMicRecoveryPolicy.swift 中深入研读。【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表