Files
bot/apps/macos/Sources/Bot/ExecApprovalsSocket.swift
zooqueen 4a86b4d9b9 Merge remote-tracking branch 'upstream/main'
# Conflicts:
#	.dockerignore
#	.github/actionlint.yaml
#	.github/actions/setup-node-env/action.yml
#	.github/workflows/auto-response.yml
#	.github/workflows/ci.yml
#	.github/workflows/docker-release.yml
#	.github/workflows/install-smoke.yml
#	.gitignore
#	AGENTS.md
#	CHANGELOG.md
#	CONTRIBUTING.md
#	Dockerfile
#	README.md
#	SECURITY.md
#	appcast.xml
#	apps/android/README.md
#	apps/android/app/build.gradle.kts
#	apps/android/app/src/main/AndroidManifest.xml
#	apps/android/app/src/main/java/ai/hanzo/bot/android/MainViewModel.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/NodeRuntime.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/SecurePrefs.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/gateway/GatewaySession.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/CalendarHandler.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/CameraCaptureManager.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/ConnectionManager.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/ContactsHandler.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/DeviceHandler.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/InvokeDispatcher.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/MotionHandler.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/NodeUtils.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/PhotosHandler.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/ScreenRecordManager.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/node/SystemHandler.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/ui/OnboardingFlow.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/ui/VoiceTabScreen.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/ui/chat/Base64ImageState.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/voice/ElevenLabsStreamingTts.kt
#	apps/android/app/src/main/java/ai/hanzo/bot/android/voice/TalkModeManager.kt
#	apps/android/app/src/main/java/ai/openclaw/android/node/InvokeCommandRegistry.kt
#	apps/android/app/src/main/java/ai/openclaw/android/protocol/OpenClawProtocolConstants.kt
#	apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt
#	apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatMarkdown.kt
#	apps/android/app/src/main/java/ai/openclaw/android/ui/chat/ChatMessageViews.kt
#	apps/android/app/src/test/java/ai/hanzo/bot/android/gateway/GatewaySessionInvokeTimeoutTest.kt
#	apps/android/app/src/test/java/ai/hanzo/bot/android/node/CalendarHandlerTest.kt
#	apps/android/app/src/test/java/ai/hanzo/bot/android/node/ContactsHandlerTest.kt
#	apps/android/app/src/test/java/ai/hanzo/bot/android/node/MotionHandlerTest.kt
#	apps/android/app/src/test/java/ai/hanzo/bot/android/node/NodeHandlerRobolectricTest.kt
#	apps/android/app/src/test/java/ai/hanzo/bot/android/node/PhotosHandlerTest.kt
#	apps/android/app/src/test/java/ai/hanzo/bot/android/node/SystemHandlerTest.kt
#	apps/android/app/src/test/java/ai/openclaw/android/gateway/GatewaySessionInvokeTest.kt
#	apps/android/app/src/test/java/ai/openclaw/android/node/InvokeCommandRegistryTest.kt
#	apps/android/app/src/test/java/ai/openclaw/android/protocol/OpenClawProtocolConstantsTest.kt
#	apps/android/benchmark/build.gradle.kts
#	apps/ios/Config/Signing.xcconfig
#	apps/ios/Sources/Camera/CameraController.swift
#	apps/ios/Sources/Contacts/ContactsService.swift
#	apps/ios/Sources/Gateway/GatewayConnectionController.swift
#	apps/ios/Sources/Gateway/GatewayDiscoveryModel.swift
#	apps/ios/Sources/Gateway/GatewayServiceResolver.swift
#	apps/ios/Sources/Gateway/GatewaySettingsStore.swift
#	apps/ios/Sources/Gateway/KeychainStore.swift
#	apps/ios/Sources/Info.plist
#	apps/ios/Sources/Location/LocationService.swift
#	apps/ios/Sources/Model/NodeAppModel+Canvas.swift
#	apps/ios/Sources/Model/NodeAppModel.swift
#	apps/ios/Sources/OpenClawApp.swift
#	apps/ios/Sources/Screen/ScreenController.swift
#	apps/ios/Sources/Screen/ScreenRecordService.swift
#	apps/ios/Sources/Services/WatchMessagingService.swift
#	apps/ios/Sources/Voice/TalkModeManager.swift
#	apps/ios/Tests/DeepLinkParserTests.swift
#	apps/ios/Tests/GatewayConnectionControllerTests.swift
#	apps/ios/Tests/GatewayConnectionSecurityTests.swift
#	apps/ios/Tests/GatewaySettingsStoreTests.swift
#	apps/ios/Tests/Info.plist
#	apps/ios/Tests/NodeAppModelInvokeTests.swift
#	apps/ios/Tests/VoiceWakeManagerExtractCommandTests.swift
#	apps/ios/project.yml
#	apps/macos/Sources/Bot/AgentWorkspaceConfig.swift
#	apps/macos/Sources/Bot/AudioInputDeviceObserver.swift
#	apps/macos/Sources/Bot/CanvasA2UIActionMessageHandler.swift
#	apps/macos/Sources/Bot/CanvasFileWatcher.swift
#	apps/macos/Sources/Bot/CanvasWindowController.swift
#	apps/macos/Sources/Bot/ColorHexSupport.swift
#	apps/macos/Sources/Bot/DurationFormattingSupport.swift
#	apps/macos/Sources/Bot/ExecApprovals.swift
#	apps/macos/Sources/Bot/ExecEnvOptions.swift
#	apps/macos/Sources/Bot/GatewayDiscoveryMenu.swift
#	apps/macos/Sources/Bot/GatewayDiscoverySelectionSupport.swift
#	apps/macos/Sources/Bot/GatewayEndpointStore.swift
#	apps/macos/Sources/Bot/GatewayPushSubscription.swift
#	apps/macos/Sources/Bot/GatewayRemoteConfig.swift
#	apps/macos/Sources/Bot/GeneralSettings.swift
#	apps/macos/Sources/Bot/JSONObjectExtractionSupport.swift
#	apps/macos/Sources/Bot/Logging/BotLogging.swift
#	apps/macos/Sources/Bot/MenuHeaderCard.swift
#	apps/macos/Sources/Bot/MenuItemHighlightColors.swift
#	apps/macos/Sources/Bot/MenuSessionsHeaderView.swift
#	apps/macos/Sources/Bot/MicRefreshSupport.swift
#	apps/macos/Sources/Bot/NodeMode/MacNodeLocationService.swift
#	apps/macos/Sources/Bot/OnboardingView+Pages.swift
#	apps/macos/Sources/Bot/OverlayPanelFactory.swift
#	apps/macos/Sources/Bot/PeekabooBridgeHostCoordinator.swift
#	apps/macos/Sources/Bot/PermissionMonitoringSupport.swift
#	apps/macos/Sources/Bot/PlatformLabelFormatter.swift
#	apps/macos/Sources/Bot/RemotePortTunnel.swift
#	apps/macos/Sources/Bot/Resources/Info.plist
#	apps/macos/Sources/Bot/ScreenRecordService.swift
#	apps/macos/Sources/Bot/SelectableRow.swift
#	apps/macos/Sources/Bot/SettingsRefreshButton.swift
#	apps/macos/Sources/Bot/SettingsSidebarCard.swift
#	apps/macos/Sources/Bot/SettingsSidebarScroll.swift
#	apps/macos/Sources/Bot/SimpleFileWatcher.swift
#	apps/macos/Sources/Bot/SimpleFileWatcherOwner.swift
#	apps/macos/Sources/Bot/SimpleTaskSupport.swift
#	apps/macos/Sources/Bot/SystemSettingsURLSupport.swift
#	apps/macos/Sources/Bot/TextSummarySupport.swift
#	apps/macos/Sources/Bot/TrackingAreaSupport.swift
#	apps/macos/Sources/Bot/VoiceOverlayTextFormatting.swift
#	apps/macos/Sources/Bot/VoiceWakeRecognitionDebugSupport.swift
#	apps/macos/Sources/BotDiscovery/GatewayDiscoveryModel.swift
#	apps/macos/Sources/BotDiscovery/TailscaleServeGatewayDiscovery.swift
#	apps/macos/Sources/BotMacCLI/CLIArgParsingSupport.swift
#	apps/macos/Sources/BotMacCLI/WizardCommand.swift
#	apps/macos/Sources/BotProtocol/GatewayModels.swift
#	apps/macos/Sources/OpenClaw/OnboardingView+Actions.swift
#	apps/macos/Sources/OpenClaw/OnboardingView+Monitoring.swift
#	apps/macos/Tests/BotIPCTests/AgentEventStoreTests.swift
#	apps/macos/Tests/BotIPCTests/AnyCodableEncodingTests.swift
#	apps/macos/Tests/BotIPCTests/CameraCaptureServiceTests.swift
#	apps/macos/Tests/BotIPCTests/CameraIPCTests.swift
#	apps/macos/Tests/BotIPCTests/CanvasIPCTests.swift
#	apps/macos/Tests/BotIPCTests/CanvasWindowSmokeTests.swift
#	apps/macos/Tests/BotIPCTests/CommandResolverTests.swift
#	apps/macos/Tests/BotIPCTests/ExecApprovalHelpersTests.swift
#	apps/macos/Tests/BotIPCTests/ExecApprovalsSocketPathGuardTests.swift
#	apps/macos/Tests/BotIPCTests/ExecApprovalsStoreRefactorTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayChannelConfigureTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayChannelConnectTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayChannelRequestTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayChannelShutdownTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayConnectionControlTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayDiscoveryModelTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayEndpointStoreTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayFrameDecodeTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayProcessManagerTests.swift
#	apps/macos/Tests/BotIPCTests/GatewayWebSocketTestSupport.swift
#	apps/macos/Tests/BotIPCTests/LowCoverageHelperTests.swift
#	apps/macos/Tests/BotIPCTests/LowCoverageViewSmokeTests.swift
#	apps/macos/Tests/BotIPCTests/MacNodeRuntimeTests.swift
#	apps/macos/Tests/BotIPCTests/NixModeStableSuiteTests.swift
#	apps/macos/Tests/BotIPCTests/OpenClawConfigFileTests.swift
#	apps/macos/Tests/BotIPCTests/PermissionManagerLocationTests.swift
#	apps/macos/Tests/BotIPCTests/PermissionManagerTests.swift
#	apps/macos/Tests/BotIPCTests/TailscaleServeGatewayDiscoveryTests.swift
#	apps/macos/Tests/BotIPCTests/TalkModeConfigParsingTests.swift
#	apps/macos/Tests/BotIPCTests/TestFSHelpers.swift
#	apps/macos/Tests/BotIPCTests/VoiceWakeGlobalSettingsSyncTests.swift
#	apps/macos/Tests/BotIPCTests/VoiceWakeRuntimeTests.swift
#	apps/macos/Tests/BotIPCTests/VoiceWakeTestSupport.swift
#	apps/macos/Tests/BotIPCTests/WebChatSwiftUISmokeTests.swift
#	apps/macos/Tests/BotIPCTests/WorkActivityStoreTests.swift
#	apps/macos/Tests/OpenClawIPCTests/ExecAllowlistTests.swift
#	apps/shared/BotKit/Sources/BotChatUI/ChatMessageViews.swift
#	apps/shared/BotKit/Sources/BotKit/BonjourServiceResolverSupport.swift
#	apps/shared/BotKit/Sources/BotKit/CalendarCommands.swift
#	apps/shared/BotKit/Sources/BotKit/CameraAuthorization.swift
#	apps/shared/BotKit/Sources/BotKit/CameraCapturePipelineSupport.swift
#	apps/shared/BotKit/Sources/BotKit/CameraSessionConfiguration.swift
#	apps/shared/BotKit/Sources/BotKit/CaptureRateLimits.swift
#	apps/shared/BotKit/Sources/BotKit/DeviceAuthPayload.swift
#	apps/shared/BotKit/Sources/BotKit/GatewayChannel.swift
#	apps/shared/BotKit/Sources/BotKit/GatewayConnectChallengeSupport.swift
#	apps/shared/BotKit/Sources/BotKit/GatewayDiscoveryBrowserSupport.swift
#	apps/shared/BotKit/Sources/BotKit/GatewayTLSPinning.swift
#	apps/shared/BotKit/Sources/BotKit/GenericPasswordKeychainStore.swift
#	apps/shared/BotKit/Sources/BotKit/LocalNetworkURLSupport.swift
#	apps/shared/BotKit/Sources/BotKit/LocationCurrentRequest.swift
#	apps/shared/BotKit/Sources/BotKit/LocationServiceSupport.swift
#	apps/shared/BotKit/Sources/BotKit/LoopbackHost.swift
#	apps/shared/BotKit/Sources/BotKit/MotionCommands.swift
#	apps/shared/BotKit/Sources/BotKit/NetworkInterfaceIPv4.swift
#	apps/shared/BotKit/Sources/BotKit/NetworkInterfaces.swift
#	apps/shared/BotKit/Sources/BotKit/OpenClawDateRangeLimitParams.swift
#	apps/shared/BotKit/Sources/BotKit/ThrowingContinuationSupport.swift
#	apps/shared/BotKit/Sources/BotKit/WebViewJavaScriptSupport.swift
#	apps/shared/BotKit/Sources/BotProtocol/GatewayModels.swift
#	apps/shared/BotKit/Tests/BotKitTests/ChatViewModelTests.swift
#	apps/shared/BotKit/Tests/BotKitTests/TestAsyncHelpers.swift
#	apps/shared/OpenClawKit/Tests/OpenClawKitTests/GatewayNodeSessionTests.swift
#	assets/chrome-extension/background.js
#	docker-compose.yml
#	docker-setup.sh
#	docs/automation/cron-jobs.md
#	docs/automation/hooks.md
#	docs/brave-search.md
#	docs/channels/bluebubbles.md
#	docs/channels/channel-routing.md
#	docs/channels/feishu.md
#	docs/channels/googlechat.md
#	docs/channels/index.md
#	docs/channels/line.md
#	docs/channels/mattermost.md
#	docs/channels/telegram.md
#	docs/channels/tlon.md
#	docs/channels/zalouser.md
#	docs/ci.md
#	docs/cli/acp.md
#	docs/cli/config.md
#	docs/cli/index.md
#	docs/cli/node.md
#	docs/cli/onboard.md
#	docs/cli/plugins.md
#	docs/cli/qr.md
#	docs/cli/secrets.md
#	docs/concepts/agent-loop.md
#	docs/concepts/compaction.md
#	docs/concepts/memory.md
#	docs/concepts/model-providers.md
#	docs/concepts/multi-agent.md
#	docs/concepts/oauth.md
#	docs/concepts/session-tool.md
#	docs/concepts/streaming.md
#	docs/docs.json
#	docs/gateway/authentication.md
#	docs/gateway/background-process.md
#	docs/gateway/configuration-examples.md
#	docs/gateway/configuration-reference.md
#	docs/gateway/doctor.md
#	docs/gateway/remote.md
#	docs/gateway/sandboxing.md
#	docs/gateway/secrets-plan-contract.md
#	docs/gateway/secrets.md
#	docs/gateway/security/index.md
#	docs/help/faq.md
#	docs/help/testing.md
#	docs/install/docker.md
#	docs/install/installer.md
#	docs/nodes/audio.md
#	docs/nodes/index.md
#	docs/nodes/media-understanding.md
#	docs/perplexity.md
#	docs/platforms/android.md
#	docs/platforms/ios.md
#	docs/platforms/mac/release.md
#	docs/platforms/macos.md
#	docs/platforms/raspberry-pi.md
#	docs/plugins/zalouser.md
#	docs/providers/anthropic.md
#	docs/providers/index.md
#	docs/providers/minimax.md
#	docs/providers/moonshot.md
#	docs/providers/ollama.md
#	docs/providers/openai.md
#	docs/reference/api-usage-costs.md
#	docs/reference/secretref-credential-surface.md
#	docs/reference/secretref-user-supplied-credentials-matrix.json
#	docs/reference/test.md
#	docs/reference/token-use.md
#	docs/security/CONTRIBUTING-THREAT-MODEL.md
#	docs/security/README.md
#	docs/security/THREAT-MODEL-ATLAS.md
#	docs/start/onboarding.md
#	docs/start/wizard.md
#	docs/tools/acp-agents.md
#	docs/tools/browser.md
#	docs/tools/diffs.md
#	docs/tools/exec.md
#	docs/tools/index.md
#	docs/tools/pdf.md
#	docs/tools/plugin.md
#	docs/tools/slash-commands.md
#	docs/tools/thinking.md
#	docs/tools/web.md
#	docs/vps.md
#	docs/web/dashboard.md
#	docs/web/tui.md
#	extensions/acpx/index.ts
#	extensions/acpx/package.json
#	extensions/acpx/skills/acp-router/SKILL.md
#	extensions/acpx/src/config.test.ts
#	extensions/acpx/src/config.ts
#	extensions/acpx/src/ensure.ts
#	extensions/acpx/src/runtime-internals/events.ts
#	extensions/acpx/src/runtime-internals/process.test.ts
#	extensions/acpx/src/runtime-internals/process.ts
#	extensions/acpx/src/runtime-internals/test-fixtures.ts
#	extensions/acpx/src/runtime.test.ts
#	extensions/acpx/src/runtime.ts
#	extensions/acpx/src/service.test.ts
#	extensions/acpx/src/service.ts
#	extensions/bluebubbles/index.ts
#	extensions/bluebubbles/package.json
#	extensions/bluebubbles/src/account-resolve.ts
#	extensions/bluebubbles/src/accounts.ts
#	extensions/bluebubbles/src/actions.test.ts
#	extensions/bluebubbles/src/actions.ts
#	extensions/bluebubbles/src/attachments.test.ts
#	extensions/bluebubbles/src/attachments.ts
#	extensions/bluebubbles/src/channel.ts
#	extensions/bluebubbles/src/chat.ts
#	extensions/bluebubbles/src/config-schema.ts
#	extensions/bluebubbles/src/history.ts
#	extensions/bluebubbles/src/media-send.test.ts
#	extensions/bluebubbles/src/media-send.ts
#	extensions/bluebubbles/src/monitor-debounce.ts
#	extensions/bluebubbles/src/monitor-processing.ts
#	extensions/bluebubbles/src/monitor-shared.ts
#	extensions/bluebubbles/src/monitor.test.ts
#	extensions/bluebubbles/src/monitor.ts
#	extensions/bluebubbles/src/monitor.webhook-auth.test.ts
#	extensions/bluebubbles/src/monitor.webhook-route.test.ts
#	extensions/bluebubbles/src/onboarding.secret-input.test.ts
#	extensions/bluebubbles/src/onboarding.ts
#	extensions/bluebubbles/src/probe.ts
#	extensions/bluebubbles/src/reactions.ts
#	extensions/bluebubbles/src/runtime.ts
#	extensions/bluebubbles/src/secret-input.ts
#	extensions/bluebubbles/src/send.test.ts
#	extensions/bluebubbles/src/send.ts
#	extensions/bluebubbles/src/targets.ts
#	extensions/bluebubbles/src/types.ts
#	extensions/copilot-proxy/index.ts
#	extensions/copilot-proxy/package.json
#	extensions/device-pair/index.ts
#	extensions/diagnostics-otel/index.ts
#	extensions/diagnostics-otel/package.json
#	extensions/diagnostics-otel/src/service.test.ts
#	extensions/diagnostics-otel/src/service.ts
#	extensions/diffs/README.md
#	extensions/diffs/assets/viewer-runtime.js
#	extensions/diffs/index.ts
#	extensions/diffs/package.json
#	extensions/diffs/src/browser.test.ts
#	extensions/diffs/src/browser.ts
#	extensions/diffs/src/config.ts
#	extensions/diffs/src/http.test.ts
#	extensions/diffs/src/http.ts
#	extensions/diffs/src/render.test.ts
#	extensions/diffs/src/render.ts
#	extensions/diffs/src/store.test.ts
#	extensions/diffs/src/store.ts
#	extensions/diffs/src/tool.test.ts
#	extensions/diffs/src/tool.ts
#	extensions/diffs/src/url.test.ts
#	extensions/diffs/src/url.ts
#	extensions/diffs/src/viewer-assets.test.ts
#	extensions/diffs/src/viewer-client.ts
#	extensions/diffs/src/viewer-payload.ts
#	extensions/discord/index.ts
#	extensions/discord/package.json
#	extensions/discord/src/channel.test.ts
#	extensions/discord/src/channel.ts
#	extensions/discord/src/runtime.ts
#	extensions/discord/src/subagent-hooks.test.ts
#	extensions/discord/src/subagent-hooks.ts
#	extensions/feishu/index.ts
#	extensions/feishu/package.json
#	extensions/feishu/src/accounts.test.ts
#	extensions/feishu/src/accounts.ts
#	extensions/feishu/src/bitable.ts
#	extensions/feishu/src/bot.stripBotMention.test.ts
#	extensions/feishu/src/bot.test.ts
#	extensions/feishu/src/bot.ts
#	extensions/feishu/src/card-action.ts
#	extensions/feishu/src/channel.test.ts
#	extensions/feishu/src/channel.ts
#	extensions/feishu/src/chat.ts
#	extensions/feishu/src/client.test.ts
#	extensions/feishu/src/client.ts
#	extensions/feishu/src/config-schema.ts
#	extensions/feishu/src/dedup.ts
#	extensions/feishu/src/directory.ts
#	extensions/feishu/src/docx.account-selection.test.ts
#	extensions/feishu/src/docx.ts
#	extensions/feishu/src/drive.ts
#	extensions/feishu/src/dynamic-agent.ts
#	extensions/feishu/src/media.test.ts
#	extensions/feishu/src/media.ts
#	extensions/feishu/src/monitor.account.ts
#	extensions/feishu/src/monitor.reaction.test.ts
#	extensions/feishu/src/monitor.startup.test.ts
#	extensions/feishu/src/monitor.startup.ts
#	extensions/feishu/src/monitor.state.ts
#	extensions/feishu/src/monitor.transport.ts
#	extensions/feishu/src/monitor.ts
#	extensions/feishu/src/monitor.webhook-security.test.ts
#	extensions/feishu/src/onboarding.status.test.ts
#	extensions/feishu/src/onboarding.ts
#	extensions/feishu/src/outbound.test.ts
#	extensions/feishu/src/outbound.ts
#	extensions/feishu/src/perm.ts
#	extensions/feishu/src/policy.test.ts
#	extensions/feishu/src/policy.ts
#	extensions/feishu/src/probe.ts
#	extensions/feishu/src/reactions.ts
#	extensions/feishu/src/reply-dispatcher.test.ts
#	extensions/feishu/src/reply-dispatcher.ts
#	extensions/feishu/src/runtime.ts
#	extensions/feishu/src/secret-input.ts
#	extensions/feishu/src/send-target.test.ts
#	extensions/feishu/src/send-target.ts
#	extensions/feishu/src/send.reply-fallback.test.ts
#	extensions/feishu/src/send.test.ts
#	extensions/feishu/src/send.ts
#	extensions/feishu/src/streaming-card.test.ts
#	extensions/feishu/src/streaming-card.ts
#	extensions/feishu/src/targets.ts
#	extensions/feishu/src/tool-account-routing.test.ts
#	extensions/feishu/src/tool-account.ts
#	extensions/feishu/src/tool-factory-test-harness.ts
#	extensions/feishu/src/types.ts
#	extensions/feishu/src/typing.ts
#	extensions/feishu/src/wiki.ts
#	extensions/google-gemini-cli-auth/index.ts
#	extensions/google-gemini-cli-auth/oauth.test.ts
#	extensions/google-gemini-cli-auth/oauth.ts
#	extensions/google-gemini-cli-auth/package.json
#	extensions/googlechat/index.ts
#	extensions/googlechat/package.json
#	extensions/googlechat/src/accounts.ts
#	extensions/googlechat/src/actions.ts
#	extensions/googlechat/src/channel.startup.test.ts
#	extensions/googlechat/src/channel.ts
#	extensions/googlechat/src/monitor-access.ts
#	extensions/googlechat/src/monitor-types.ts
#	extensions/googlechat/src/monitor-webhook.ts
#	extensions/googlechat/src/monitor.ts
#	extensions/googlechat/src/monitor.webhook-routing.test.ts
#	extensions/googlechat/src/onboarding.ts
#	extensions/googlechat/src/resolve-target.test.ts
#	extensions/googlechat/src/runtime.ts
#	extensions/googlechat/src/types.config.ts
#	extensions/imessage/index.ts
#	extensions/imessage/package.json
#	extensions/imessage/src/channel.ts
#	extensions/imessage/src/runtime.ts
#	extensions/irc/index.ts
#	extensions/irc/package.json
#	extensions/irc/src/accounts.ts
#	extensions/irc/src/channel.ts
#	extensions/irc/src/config-schema.ts
#	extensions/irc/src/inbound.ts
#	extensions/irc/src/monitor.ts
#	extensions/irc/src/onboarding.test.ts
#	extensions/irc/src/onboarding.ts
#	extensions/irc/src/runtime.ts
#	extensions/irc/src/types.ts
#	extensions/line/index.ts
#	extensions/line/package.json
#	extensions/line/src/card-command.ts
#	extensions/line/src/channel.logout.test.ts
#	extensions/line/src/channel.sendPayload.test.ts
#	extensions/line/src/channel.startup.test.ts
#	extensions/line/src/channel.ts
#	extensions/line/src/runtime.ts
#	extensions/llm-task/index.ts
#	extensions/llm-task/package.json
#	extensions/llm-task/src/llm-task-tool.ts
#	extensions/lobster/index.ts
#	extensions/lobster/package.json
#	extensions/lobster/src/lobster-tool.test.ts
#	extensions/lobster/src/lobster-tool.ts
#	extensions/lobster/src/windows-spawn.ts
#	extensions/matrix/CHANGELOG.md
#	extensions/matrix/index.ts
#	extensions/matrix/package.json
#	extensions/matrix/src/actions.ts
#	extensions/matrix/src/channel.directory.test.ts
#	extensions/matrix/src/channel.ts
#	extensions/matrix/src/config-schema.ts
#	extensions/matrix/src/directory-live.ts
#	extensions/matrix/src/group-mentions.ts
#	extensions/matrix/src/matrix/accounts.ts
#	extensions/matrix/src/matrix/client/config.ts
#	extensions/matrix/src/matrix/client/create-client.ts
#	extensions/matrix/src/matrix/client/shared.test.ts
#	extensions/matrix/src/matrix/client/shared.ts
#	extensions/matrix/src/matrix/deps.ts
#	extensions/matrix/src/matrix/monitor/access-policy.ts
#	extensions/matrix/src/matrix/monitor/allowlist.ts
#	extensions/matrix/src/matrix/monitor/auto-join.ts
#	extensions/matrix/src/matrix/monitor/direct.ts
#	extensions/matrix/src/matrix/monitor/events.test.ts
#	extensions/matrix/src/matrix/monitor/events.ts
#	extensions/matrix/src/matrix/monitor/handler.body-for-agent.test.ts
#	extensions/matrix/src/matrix/monitor/handler.ts
#	extensions/matrix/src/matrix/monitor/index.ts
#	extensions/matrix/src/matrix/monitor/location.ts
#	extensions/matrix/src/matrix/monitor/media.test.ts
#	extensions/matrix/src/matrix/monitor/replies.test.ts
#	extensions/matrix/src/matrix/monitor/replies.ts
#	extensions/matrix/src/matrix/monitor/rooms.ts
#	extensions/matrix/src/matrix/poll-types.ts
#	extensions/matrix/src/matrix/probe.ts
#	extensions/matrix/src/matrix/send-queue.ts
#	extensions/matrix/src/matrix/send.test.ts
#	extensions/matrix/src/matrix/send.ts
#	extensions/matrix/src/onboarding.ts
#	extensions/matrix/src/outbound.ts
#	extensions/matrix/src/resolve-targets.test.ts
#	extensions/matrix/src/resolve-targets.ts
#	extensions/matrix/src/runtime.ts
#	extensions/matrix/src/secret-input.ts
#	extensions/matrix/src/tool-actions.ts
#	extensions/matrix/src/types.ts
#	extensions/mattermost/index.ts
#	extensions/mattermost/package.json
#	extensions/mattermost/src/channel.test.ts
#	extensions/mattermost/src/channel.ts
#	extensions/mattermost/src/config-schema.ts
#	extensions/mattermost/src/group-mentions.ts
#	extensions/mattermost/src/mattermost/accounts.test.ts
#	extensions/mattermost/src/mattermost/accounts.ts
#	extensions/mattermost/src/mattermost/client.ts
#	extensions/mattermost/src/mattermost/monitor-auth.ts
#	extensions/mattermost/src/mattermost/monitor-helpers.ts
#	extensions/mattermost/src/mattermost/monitor-websocket.test.ts
#	extensions/mattermost/src/mattermost/monitor-websocket.ts
#	extensions/mattermost/src/mattermost/monitor.authz.test.ts
#	extensions/mattermost/src/mattermost/monitor.ts
#	extensions/mattermost/src/mattermost/probe.ts
#	extensions/mattermost/src/mattermost/reactions.test-helpers.ts
#	extensions/mattermost/src/mattermost/reactions.ts
#	extensions/mattermost/src/mattermost/send.test.ts
#	extensions/mattermost/src/mattermost/send.ts
#	extensions/mattermost/src/mattermost/slash-commands.ts
#	extensions/mattermost/src/mattermost/slash-http.test.ts
#	extensions/mattermost/src/mattermost/slash-http.ts
#	extensions/mattermost/src/mattermost/slash-state.ts
#	extensions/mattermost/src/onboarding-helpers.ts
#	extensions/mattermost/src/onboarding.status.test.ts
#	extensions/mattermost/src/onboarding.ts
#	extensions/mattermost/src/runtime.ts
#	extensions/mattermost/src/secret-input.ts
#	extensions/mattermost/src/types.ts
#	extensions/memory-core/index.ts
#	extensions/memory-core/package.json
#	extensions/memory-lancedb/index.ts
#	extensions/memory-lancedb/package.json
#	extensions/minimax-portal-auth/index.ts
#	extensions/minimax-portal-auth/oauth.ts
#	extensions/minimax-portal-auth/package.json
#	extensions/msteams/CHANGELOG.md
#	extensions/msteams/index.ts
#	extensions/msteams/package.json
#	extensions/msteams/src/attachments.test.ts
#	extensions/msteams/src/attachments/download.ts
#	extensions/msteams/src/attachments/graph.ts
#	extensions/msteams/src/attachments/payload.ts
#	extensions/msteams/src/attachments/remote-media.ts
#	extensions/msteams/src/attachments/shared.ts
#	extensions/msteams/src/channel.directory.test.ts
#	extensions/msteams/src/channel.ts
#	extensions/msteams/src/directory-live.ts
#	extensions/msteams/src/file-lock.ts
#	extensions/msteams/src/graph.ts
#	extensions/msteams/src/media-helpers.ts
#	extensions/msteams/src/messenger.test.ts
#	extensions/msteams/src/messenger.ts
#	extensions/msteams/src/monitor-handler.file-consent.test.ts
#	extensions/msteams/src/monitor-handler.ts
#	extensions/msteams/src/monitor-handler/message-handler.authz.test.ts
#	extensions/msteams/src/monitor-handler/message-handler.ts
#	extensions/msteams/src/monitor.lifecycle.test.ts
#	extensions/msteams/src/monitor.test.ts
#	extensions/msteams/src/monitor.ts
#	extensions/msteams/src/onboarding.ts
#	extensions/msteams/src/outbound.ts
#	extensions/msteams/src/policy.test.ts
#	extensions/msteams/src/policy.ts
#	extensions/msteams/src/probe.test.ts
#	extensions/msteams/src/probe.ts
#	extensions/msteams/src/reply-dispatcher.ts
#	extensions/msteams/src/runtime.ts
#	extensions/msteams/src/secret-input.ts
#	extensions/msteams/src/send-context.ts
#	extensions/msteams/src/send.test.ts
#	extensions/msteams/src/send.ts
#	extensions/msteams/src/store-fs.ts
#	extensions/msteams/src/test-runtime.ts
#	extensions/msteams/src/token.ts
#	extensions/nextcloud-talk/index.ts
#	extensions/nextcloud-talk/package.json
#	extensions/nextcloud-talk/src/accounts.ts
#	extensions/nextcloud-talk/src/channel.startup.test.ts
#	extensions/nextcloud-talk/src/channel.ts
#	extensions/nextcloud-talk/src/config-schema.ts
#	extensions/nextcloud-talk/src/inbound.authz.test.ts
#	extensions/nextcloud-talk/src/inbound.ts
#	extensions/nextcloud-talk/src/monitor.replay.test.ts
#	extensions/nextcloud-talk/src/monitor.ts
#	extensions/nextcloud-talk/src/onboarding.ts
#	extensions/nextcloud-talk/src/policy.ts
#	extensions/nextcloud-talk/src/replay-guard.ts
#	extensions/nextcloud-talk/src/room-info.ts
#	extensions/nextcloud-talk/src/runtime.ts
#	extensions/nextcloud-talk/src/secret-input.ts
#	extensions/nextcloud-talk/src/types.ts
#	extensions/nostr/CHANGELOG.md
#	extensions/nostr/index.ts
#	extensions/nostr/package.json
#	extensions/nostr/src/channel.ts
#	extensions/nostr/src/config-schema.ts
#	extensions/nostr/src/nostr-profile-http.ts
#	extensions/nostr/src/nostr-state-store.test.ts
#	extensions/nostr/src/runtime.ts
#	extensions/nostr/src/types.ts
#	extensions/open-prose/index.ts
#	extensions/open-prose/package.json
#	extensions/phone-control/index.test.ts
#	extensions/phone-control/index.ts
#	extensions/qwen-portal-auth/index.ts
#	extensions/qwen-portal-auth/oauth.ts
#	extensions/signal/index.ts
#	extensions/signal/package.json
#	extensions/signal/src/channel.ts
#	extensions/signal/src/runtime.ts
#	extensions/slack/index.ts
#	extensions/slack/package.json
#	extensions/slack/src/channel.test.ts
#	extensions/slack/src/channel.ts
#	extensions/slack/src/runtime.ts
#	extensions/synology-chat/index.ts
#	extensions/synology-chat/package.json
#	extensions/synology-chat/src/channel.integration.test.ts
#	extensions/synology-chat/src/channel.test.ts
#	extensions/synology-chat/src/channel.ts
#	extensions/synology-chat/src/runtime.ts
#	extensions/synology-chat/src/test-http-utils.ts
#	extensions/synology-chat/src/webhook-handler.ts
#	extensions/talk-voice/index.ts
#	extensions/telegram/index.ts
#	extensions/telegram/package.json
#	extensions/telegram/src/channel.test.ts
#	extensions/telegram/src/channel.ts
#	extensions/telegram/src/runtime.ts
#	extensions/test-utils/plugin-runtime-mock.ts
#	extensions/test-utils/runtime-env.ts
#	extensions/test-utils/start-account-context.ts
#	extensions/thread-ownership/index.ts
#	extensions/tlon/index.ts
#	extensions/tlon/package.json
#	extensions/tlon/src/channel.ts
#	extensions/tlon/src/config-schema.ts
#	extensions/tlon/src/monitor/discovery.ts
#	extensions/tlon/src/monitor/history.ts
#	extensions/tlon/src/monitor/index.ts
#	extensions/tlon/src/monitor/media.ts
#	extensions/tlon/src/monitor/processed-messages.ts
#	extensions/tlon/src/onboarding.ts
#	extensions/tlon/src/runtime.ts
#	extensions/tlon/src/types.ts
#	extensions/tlon/src/urbit/auth.ssrf.test.ts
#	extensions/tlon/src/urbit/auth.ts
#	extensions/tlon/src/urbit/base-url.ts
#	extensions/tlon/src/urbit/channel-client.ts
#	extensions/tlon/src/urbit/channel-ops.ts
#	extensions/tlon/src/urbit/context.ts
#	extensions/tlon/src/urbit/fetch.ts
#	extensions/tlon/src/urbit/sse-client.ts
#	extensions/tlon/src/urbit/upload.test.ts
#	extensions/tlon/src/urbit/upload.ts
#	extensions/twitch/CHANGELOG.md
#	extensions/twitch/index.ts
#	extensions/twitch/package.json
#	extensions/twitch/src/config-schema.ts
#	extensions/twitch/src/config.ts
#	extensions/twitch/src/monitor.ts
#	extensions/twitch/src/onboarding.test.ts
#	extensions/twitch/src/onboarding.ts
#	extensions/twitch/src/plugin.test.ts
#	extensions/twitch/src/plugin.ts
#	extensions/twitch/src/probe.ts
#	extensions/twitch/src/runtime.ts
#	extensions/twitch/src/send.ts
#	extensions/twitch/src/status.ts
#	extensions/twitch/src/test-fixtures.ts
#	extensions/twitch/src/token.test.ts
#	extensions/twitch/src/twitch-client.ts
#	extensions/voice-call/CHANGELOG.md
#	extensions/voice-call/index.ts
#	extensions/voice-call/package.json
#	extensions/voice-call/src/cli.ts
#	extensions/voice-call/src/config.ts
#	extensions/voice-call/src/manager.test-harness.ts
#	extensions/voice-call/src/manager.test.ts
#	extensions/voice-call/src/manager.ts
#	extensions/voice-call/src/providers/shared/guarded-json-api.ts
#	extensions/voice-call/src/providers/twilio.ts
#	extensions/voice-call/src/webhook.ts
#	extensions/whatsapp/index.ts
#	extensions/whatsapp/package.json
#	extensions/whatsapp/src/channel.ts
#	extensions/whatsapp/src/resolve-target.test.ts
#	extensions/whatsapp/src/runtime.ts
#	extensions/zalo/CHANGELOG.md
#	extensions/zalo/index.ts
#	extensions/zalo/package.json
#	extensions/zalo/src/accounts.ts
#	extensions/zalo/src/actions.ts
#	extensions/zalo/src/channel.directory.test.ts
#	extensions/zalo/src/channel.sendpayload.test.ts
#	extensions/zalo/src/channel.ts
#	extensions/zalo/src/config-schema.ts
#	extensions/zalo/src/group-access.ts
#	extensions/zalo/src/monitor.ts
#	extensions/zalo/src/monitor.webhook.test.ts
#	extensions/zalo/src/monitor.webhook.ts
#	extensions/zalo/src/onboarding.status.test.ts
#	extensions/zalo/src/onboarding.ts
#	extensions/zalo/src/probe.ts
#	extensions/zalo/src/runtime.ts
#	extensions/zalo/src/secret-input.ts
#	extensions/zalo/src/send.ts
#	extensions/zalo/src/status-issues.ts
#	extensions/zalo/src/token.test.ts
#	extensions/zalo/src/token.ts
#	extensions/zalo/src/types.ts
#	extensions/zalouser/CHANGELOG.md
#	extensions/zalouser/README.md
#	extensions/zalouser/index.ts
#	extensions/zalouser/package.json
#	extensions/zalouser/src/accounts.test.ts
#	extensions/zalouser/src/accounts.ts
#	extensions/zalouser/src/channel.sendpayload.test.ts
#	extensions/zalouser/src/channel.ts
#	extensions/zalouser/src/config-schema.ts
#	extensions/zalouser/src/monitor.account-scope.test.ts
#	extensions/zalouser/src/monitor.group-gating.test.ts
#	extensions/zalouser/src/monitor.ts
#	extensions/zalouser/src/onboarding.ts
#	extensions/zalouser/src/probe.ts
#	extensions/zalouser/src/runtime.ts
#	extensions/zalouser/src/send.test.ts
#	extensions/zalouser/src/status-issues.ts
#	extensions/zalouser/src/tool.test.ts
#	extensions/zalouser/src/zalo-js.ts
#	extensions/zalouser/src/zca.ts
#	package.json
#	pnpm-lock.yaml
#	scripts/bench-cli-startup.ts
#	scripts/check-no-random-messaging-tmp.mjs
#	scripts/check-plugin-sdk-exports.mjs
#	scripts/ci-changed-scope.mjs
#	scripts/dev/discord-acp-plain-language-smoke.ts
#	scripts/docker/install-sh-nonroot/Dockerfile
#	scripts/docker/install-sh-nonroot/run.sh
#	scripts/docker/install-sh-smoke/Dockerfile
#	scripts/docker/install-sh-smoke/run.sh
#	scripts/e2e/Dockerfile
#	scripts/e2e/onboard-docker.sh
#	scripts/install.ps1
#	scripts/install.sh
#	scripts/podman/openclaw.container.in
#	scripts/pr
#	scripts/release-check.ts
#	scripts/sandbox-browser-entrypoint.sh
#	scripts/test-hotspots.mjs
#	scripts/test-parallel.mjs
#	scripts/test-perf-budget.mjs
#	scripts/update-contributors.ts
#	scripts/write-cli-startup-metadata.ts
#	setup-podman.sh
#	skills/coding-agent/SKILL.md
#	skills/openai-image-gen/SKILL.md
#	src/acp/client.test.ts
#	src/acp/client.ts
#	src/acp/policy.test.ts
#	src/agents/acp-spawn.test.ts
#	src/agents/acp-spawn.ts
#	src/agents/apply-patch.ts
#	src/agents/auth-profiles/order.test.ts
#	src/agents/auth-profiles/order.ts
#	src/agents/bash-tools.exec-runtime.ts
#	src/agents/bash-tools.exec.path.test.ts
#	src/agents/bash-tools.exec.pty.test.ts
#	src/agents/bash-tools.shared.test.ts
#	src/agents/bash-tools.shared.ts
#	src/agents/bootstrap-files.test.ts
#	src/agents/bot-tools.sessions.test.ts
#	src/agents/bot-tools.subagents.sessions-spawn.test-harness.ts
#	src/agents/bot-tools.ts
#	src/agents/cli-backends.ts
#	src/agents/cli-runner.ts
#	src/agents/cli-runner/helpers.ts
#	src/agents/context.lookup.test.ts
#	src/agents/context.ts
#	src/agents/failover-error.test.ts
#	src/agents/failover-error.ts
#	src/agents/internal-events.ts
#	src/agents/memory-search.test.ts
#	src/agents/model-selection.test.ts
#	src/agents/models-config.applies-config-env-vars.test.ts
#	src/agents/models-config.fills-missing-provider-apikey-from-env-var.test.ts
#	src/agents/models-config.preserves-explicit-reasoning-override.test.ts
#	src/agents/models-config.providers.normalize-keys.test.ts
#	src/agents/models-config.providers.ollama-autodiscovery.test.ts
#	src/agents/models-config.providers.ollama.test.ts
#	src/agents/models-config.ts
#	src/agents/models-config.uses-first-github-copilot-profile-env-tokens.test.ts
#	src/agents/openai-ws-stream.ts
#	src/agents/openclaw-tools.camera.test.ts
#	src/agents/openclaw-tools.subagents.sessions-spawn-default-timeout-absent.test.ts
#	src/agents/openclaw-tools.subagents.sessions-spawn-default-timeout.test.ts
#	src/agents/path-policy.test.ts
#	src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts
#	src/agents/pi-embedded-helpers/errors.ts
#	src/agents/pi-embedded-helpers/failover-matches.ts
#	src/agents/pi-embedded-runner.run-embedded-pi-agent.auth-profile-rotation.e2e.test.ts
#	src/agents/pi-embedded-runner/extra-params.ts
#	src/agents/pi-embedded-runner/model.test.ts
#	src/agents/pi-embedded-runner/model.ts
#	src/agents/pi-embedded-runner/run.ts
#	src/agents/pi-embedded-runner/run/attempt.test.ts
#	src/agents/pi-embedded-runner/run/attempt.ts
#	src/agents/pi-embedded-runner/run/images.ts
#	src/agents/pi-embedded-runner/run/params.ts
#	src/agents/pi-embedded-runner/skills-runtime.integration.test.ts
#	src/agents/pi-embedded-runner/skills-runtime.test.ts
#	src/agents/pi-embedded-runner/skills-runtime.ts
#	src/agents/pi-extensions/compaction-safeguard.test.ts
#	src/agents/pi-extensions/compaction-safeguard.ts
#	src/agents/pi-model-discovery.auth.test.ts
#	src/agents/pi-tool-definition-adapter.ts
#	src/agents/pi-tools.create-bot-coding-tools.adds-claude-style-aliases-schemas-without-dropping.e2e.test.ts
#	src/agents/pi-tools.host-edit.ts
#	src/agents/pi-tools.read.host-edit-access.test.ts
#	src/agents/pi-tools.read.host-edit-recovery.test.ts
#	src/agents/pi-tools.read.ts
#	src/agents/pi-tools.sandbox-mounted-paths.workspace-only.test.ts
#	src/agents/pi-tools.workspace-only-false.test.ts
#	src/agents/pi-tools.workspace-paths.test.ts
#	src/agents/sandbox-media-paths.test.ts
#	src/agents/sandbox/browser.create.test.ts
#	src/agents/sandbox/browser.ts
#	src/agents/sandbox/constants.ts
#	src/agents/sandbox/docker.config-hash-recreate.test.ts
#	src/agents/sandbox/docker.windows.test.ts
#	src/agents/sandbox/fs-bridge.test.ts
#	src/agents/sandbox/fs-bridge.ts
#	src/agents/sandbox/novnc-auth.ts
#	src/agents/sandbox/validate-sandbox-security.test.ts
#	src/agents/sandbox/workspace-mounts.ts
#	src/agents/sandbox/workspace.test.ts
#	src/agents/schema/clean-for-xai.test.ts
#	src/agents/schema/clean-for-xai.ts
#	src/agents/session-transcript-repair.attachments.test.ts
#	src/agents/session-transcript-repair.test.ts
#	src/agents/session-transcript-repair.ts
#	src/agents/session-write-lock.test.ts
#	src/agents/skills-install-download.ts
#	src/agents/skills-install.test.ts
#	src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts
#	src/agents/skills.buildworkspaceskillsnapshot.test.ts
#	src/agents/skills.loadworkspaceskillentries.test.ts
#	src/agents/skills.test.ts
#	src/agents/skills/env-overrides.ts
#	src/agents/skills/frontmatter.test.ts
#	src/agents/skills/frontmatter.ts
#	src/agents/skills/plugin-skills.test.ts
#	src/agents/subagent-announce.format.e2e.test.ts
#	src/agents/subagent-announce.ts
#	src/agents/subagent-registry-cleanup.test.ts
#	src/agents/subagent-registry.ts
#	src/agents/subagent-spawn.ts
#	src/agents/system-prompt.ts
#	src/agents/test-helpers/session-config.ts
#	src/agents/tool-display.json
#	src/agents/tool-display.ts
#	src/agents/tools/browser-tool.actions.ts
#	src/agents/tools/discord-actions-messaging.ts
#	src/agents/tools/image-tool.test.ts
#	src/agents/tools/image-tool.ts
#	src/agents/tools/media-tool-shared.ts
#	src/agents/tools/message-tool.test.ts
#	src/agents/tools/model-config.helpers.ts
#	src/agents/tools/nodes-tool.ts
#	src/agents/tools/nodes-utils.ts
#	src/agents/tools/pdf-tool.helpers.ts
#	src/agents/tools/pdf-tool.test.ts
#	src/agents/tools/pdf-tool.ts
#	src/agents/tools/sessions-resolution.test.ts
#	src/agents/tools/sessions-spawn-tool.test.ts
#	src/agents/tools/sessions-spawn-tool.ts
#	src/agents/tools/sessions.test.ts
#	src/agents/tools/telegram-actions.test.ts
#	src/agents/tools/tool-runtime.helpers.ts
#	src/agents/tools/web-search.ts
#	src/auto-reply/reply.directive.directive-behavior.e2e-mocks.ts
#	src/auto-reply/reply.directive.directive-behavior.prefers-alias-matches-fuzzy-selection-is-ambiguous.test.ts
#	src/auto-reply/reply.heartbeat-typing.test.ts
#	src/auto-reply/reply.raw-body.test.ts
#	src/auto-reply/reply.triggers.trigger-handling.stages-inbound-media-into-sandbox-workspace.test.ts
#	src/auto-reply/reply/abort.test.ts
#	src/auto-reply/reply/abort.ts
#	src/auto-reply/reply/acp-projector.ts
#	src/auto-reply/reply/acp-stream-settings.ts
#	src/auto-reply/reply/agent-runner-execution.ts
#	src/auto-reply/reply/agent-runner-memory.ts
#	src/auto-reply/reply/agent-runner-reminder-guard.ts
#	src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts
#	src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts
#	src/auto-reply/reply/commands-acp.test.ts
#	src/auto-reply/reply/commands-acp/context.ts
#	src/auto-reply/reply/commands-acp/install-hints.test.ts
#	src/auto-reply/reply/commands-acp/install-hints.ts
#	src/auto-reply/reply/commands-acp/lifecycle.ts
#	src/auto-reply/reply/commands-acp/shared.ts
#	src/auto-reply/reply/commands-context-report.ts
#	src/auto-reply/reply/commands-core.ts
#	src/auto-reply/reply/commands-models.ts
#	src/auto-reply/reply/commands-session-lifecycle.test.ts
#	src/auto-reply/reply/commands-session.ts
#	src/auto-reply/reply/commands-subagents/shared.ts
#	src/auto-reply/reply/commands-types.ts
#	src/auto-reply/reply/commands.test.ts
#	src/auto-reply/reply/directive-handling.auth.ts
#	src/auto-reply/reply/dispatch-acp-delivery.test.ts
#	src/auto-reply/reply/dispatch-acp-delivery.ts
#	src/auto-reply/reply/dispatch-acp.test.ts
#	src/auto-reply/reply/dispatch-acp.ts
#	src/auto-reply/reply/get-reply-run.media-only.test.ts
#	src/auto-reply/reply/get-reply-run.ts
#	src/auto-reply/reply/get-reply.message-hooks.test.ts
#	src/auto-reply/reply/inbound-context.ts
#	src/auto-reply/reply/memory-flush.ts
#	src/auto-reply/reply/message-preprocess-hooks.test.ts
#	src/auto-reply/reply/message-preprocess-hooks.ts
#	src/auto-reply/reply/model-selection.test.ts
#	src/auto-reply/reply/normalize-reply.ts
#	src/auto-reply/reply/post-compaction-context.test.ts
#	src/auto-reply/reply/post-compaction-context.ts
#	src/auto-reply/reply/queue/enqueue.ts
#	src/auto-reply/reply/reply-flow.test.ts
#	src/auto-reply/reply/reply-payloads.ts
#	src/auto-reply/reply/session-delivery.ts
#	src/auto-reply/reply/session-fork.ts
#	src/auto-reply/reply/session-hooks-context.test.ts
#	src/auto-reply/reply/session-hooks.ts
#	src/auto-reply/reply/session-reset-prompt.test.ts
#	src/auto-reply/reply/session-reset-prompt.ts
#	src/auto-reply/reply/session-updates.ts
#	src/auto-reply/reply/session.test.ts
#	src/auto-reply/reply/session.ts
#	src/auto-reply/reply/stage-sandbox-media.ts
#	src/auto-reply/reply/test-fixtures/acp-runtime.ts
#	src/auto-reply/skill-commands.test.ts
#	src/auto-reply/skill-commands.ts
#	src/browser/bridge-server.ts
#	src/browser/cdp-proxy-bypass.ts
#	src/browser/chrome.test.ts
#	src/browser/chrome.ts
#	src/browser/config.test.ts
#	src/browser/config.ts
#	src/browser/constants.ts
#	src/browser/extension-relay-auth.test.ts
#	src/browser/extension-relay.test.ts
#	src/browser/extension-relay.ts
#	src/browser/output-atomic.ts
#	src/browser/pw-session.ts
#	src/browser/pw-tools-core.downloads.ts
#	src/browser/pw-tools-core.waits-next-download-saves-it.test.ts
#	src/browser/server-context.availability.ts
#	src/browser/server-context.ensure-browser-available.waits-for-cdp-ready.test.ts
#	src/browser/server-context.remote-profile-tab-ops.suite.ts
#	src/browser/server-context.remote-tab-ops.harness.ts
#	src/browser/server-context.reset.test.ts
#	src/browser/server-context.reset.ts
#	src/browser/server-context.selection.ts
#	src/browser/server-context.tab-ops.ts
#	src/browser/server-context.tab-selection-state.suite.ts
#	src/browser/server-context.ts
#	src/canvas-host/a2ui/index.html
#	src/channels/account-summary.ts
#	src/channels/dock.ts
#	src/channels/inbound-debounce-policy.ts
#	src/channels/plugins/account-helpers.test.ts
#	src/channels/plugins/account-helpers.ts
#	src/channels/plugins/actions/actions.test.ts
#	src/channels/plugins/actions/discord/handle-action.ts
#	src/channels/plugins/actions/signal.ts
#	src/channels/plugins/actions/telegram.ts
#	src/channels/plugins/index.ts
#	src/channels/plugins/onboarding/discord.ts
#	src/channels/plugins/onboarding/helpers.test.ts
#	src/channels/plugins/onboarding/helpers.ts
#	src/channels/plugins/onboarding/slack.ts
#	src/channels/plugins/onboarding/telegram.ts
#	src/channels/plugins/outbound/discord.ts
#	src/channels/plugins/outbound/slack.ts
#	src/channels/plugins/outbound/whatsapp.ts
#	src/channels/plugins/types.adapters.ts
#	src/channels/session-envelope.ts
#	src/channels/session-meta.ts
#	src/channels/session.test.ts
#	src/channels/thread-bindings-policy.ts
#	src/cli/argv.test.ts
#	src/cli/banner.test.ts
#	src/cli/browser-cli-actions-input/register.element.ts
#	src/cli/browser-cli-actions-input/register.form-wait-eval.ts
#	src/cli/browser-cli-manage.timeout-option.test.ts
#	src/cli/channel-options.test.ts
#	src/cli/channel-options.ts
#	src/cli/command-secret-gateway.test.ts
#	src/cli/command-secret-gateway.ts
#	src/cli/config-cli.test.ts
#	src/cli/config-cli.ts
#	src/cli/daemon-cli.coverage.test.ts
#	src/cli/daemon-cli/install.ts
#	src/cli/daemon-cli/restart-health.test.ts
#	src/cli/daemon-cli/status.gather.test.ts
#	src/cli/daemon-cli/status.gather.ts
#	src/cli/daemon-cli/status.print.ts
#	src/cli/gateway-cli/run.option-collisions.test.ts
#	src/cli/nodes-camera.test.ts
#	src/cli/nodes-cli/register.camera.ts
#	src/cli/plugin-install-plan.test.ts
#	src/cli/plugins-cli.ts
#	src/cli/program/config-guard.ts
#	src/cli/program/preaction.test.ts
#	src/cli/program/routes.test.ts
#	src/cli/qr-cli.test.ts
#	src/cli/qr-cli.ts
#	src/cli/route.test.ts
#	src/cli/run-main.profile-env.test.ts
#	src/cli/run-main.test.ts
#	src/cli/secrets-cli.test.ts
#	src/cli/tagline.test.ts
#	src/cli/tagline.ts
#	src/cli/update-cli.test.ts
#	src/cli/update-cli/restart-helper.test.ts
#	src/cli/update-cli/restart-helper.ts
#	src/cli/update-cli/shared.ts
#	src/commands/agent.acp.test.ts
#	src/commands/agent.test.ts
#	src/commands/agent.ts
#	src/commands/agents.bindings.ts
#	src/commands/agents.commands.bind.ts
#	src/commands/agents.commands.list.ts
#	src/commands/agents.config.ts
#	src/commands/auth-choice.apply-helpers.ts
#	src/commands/auth-choice.apply.anthropic.test.ts
#	src/commands/channels.adds-non-default-telegram-account.test.ts
#	src/commands/channels/resolve.ts
#	src/commands/channels/shared.ts
#	src/commands/config-validation.ts
#	src/commands/configure.gateway-auth.ts
#	src/commands/configure.gateway.test.ts
#	src/commands/configure.gateway.ts
#	src/commands/configure.wizard.ts
#	src/commands/dashboard.ts
#	src/commands/doctor-config-flow.missing-explicit-default-account.test.ts
#	src/commands/doctor-config-flow.ts
#	src/commands/doctor-gateway-daemon-flow.ts
#	src/commands/doctor-gateway-services.ts
#	src/commands/doctor-memory-search.test.ts
#	src/commands/doctor-memory-search.ts
#	src/commands/doctor-platform-notes.launchctl-env-overrides.test.ts
#	src/commands/doctor-platform-notes.startup-optimization.test.ts
#	src/commands/doctor-platform-notes.ts
#	src/commands/doctor-sandbox.warns-sandbox-enabled-without-docker.test.ts
#	src/commands/doctor-security.ts
#	src/commands/doctor-state-integrity.cloud-storage.test.ts
#	src/commands/doctor-state-integrity.linux-storage.test.ts
#	src/commands/doctor-state-integrity.test.ts
#	src/commands/doctor-state-integrity.ts
#	src/commands/doctor-state-migrations.test.ts
#	src/commands/gateway-status/helpers.ts
#	src/commands/models/aliases.ts
#	src/commands/models/auth-order.ts
#	src/commands/models/fallbacks-shared.ts
#	src/commands/models/list.list-command.ts
#	src/commands/models/list.probe.ts
#	src/commands/models/load-config.ts
#	src/commands/models/shared.ts
#	src/commands/oauth-tls-preflight.doctor.test.ts
#	src/commands/oauth-tls-preflight.ts
#	src/commands/onboard-auth.credentials.test.ts
#	src/commands/onboard-channels.e2e.test.ts
#	src/commands/onboard-config.test.ts
#	src/commands/onboard-custom.test.ts
#	src/commands/onboard-custom.ts
#	src/commands/onboard-non-interactive.gateway.test.ts
#	src/commands/onboard-non-interactive/local/daemon-install.ts
#	src/commands/onboard-non-interactive/local/gateway-config.ts
#	src/commands/onboard-remote.test.ts
#	src/commands/onboard-remote.ts
#	src/commands/session-store-targets.ts
#	src/commands/status-all.ts
#	src/commands/status-all/channels.ts
#	src/commands/status.scan.ts
#	src/commands/status.summary.ts
#	src/commands/status.test.ts
#	src/config/backup-rotation.ts
#	src/config/config-misc.test.ts
#	src/config/config.agent-concurrency-defaults.test.ts
#	src/config/config.backup-rotation.test-helpers.ts
#	src/config/config.backup-rotation.test.ts
#	src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts
#	src/config/config.plugin-validation.test.ts
#	src/config/config.telegram-audio-preflight.test.ts
#	src/config/defaults.ts
#	src/config/gateway-control-ui-origins.ts
#	src/config/io.compat.test.ts
#	src/config/io.write-config.test.ts
#	src/config/issue-format.ts
#	src/config/legacy-migrate.test.ts
#	src/config/legacy.migrations.part-3.ts
#	src/config/legacy.rules.ts
#	src/config/paths.ts
#	src/config/redact-snapshot.raw.ts
#	src/config/redact-snapshot.test.ts
#	src/config/redact-snapshot.ts
#	src/config/schema.help.ts
#	src/config/sessions.test.ts
#	src/config/sessions/sessions.test.ts
#	src/config/sessions/store-maintenance.ts
#	src/config/sessions/store.ts
#	src/config/talk.ts
#	src/config/types.acp.ts
#	src/config/types.agents.ts
#	src/config/types.browser.ts
#	src/config/types.secrets.ts
#	src/config/types.telegram.ts
#	src/config/types.ts
#	src/config/types.tts.ts
#	src/config/validation.ts
#	src/config/zod-schema.agent-runtime.ts
#	src/config/zod-schema.core.ts
#	src/config/zod-schema.ts
#	src/cron/delivery.test.ts
#	src/cron/delivery.ts
#	src/cron/isolated-agent.delivers-response-has-heartbeat-ok-but-includes.test.ts
#	src/cron/isolated-agent.delivery.test-helpers.ts
#	src/cron/isolated-agent.skips-delivery-without-whatsapp-recipient-besteffortdeliver-true.test.ts
#	src/cron/isolated-agent.subagent-model.test.ts
#	src/cron/isolated-agent.uses-last-non-empty-agent-text-as.test.ts
#	src/cron/isolated-agent/delivery-dispatch.ts
#	src/cron/isolated-agent/run.test-harness.ts
#	src/cron/isolated-agent/run.ts
#	src/cron/run-log.test.ts
#	src/cron/schedule.test.ts
#	src/cron/schedule.ts
#	src/cron/service.armtimer-tight-loop.test.ts
#	src/cron/service.failure-alert.test.ts
#	src/cron/service.heartbeat-ok-summary-suppressed.test.ts
#	src/cron/service.issue-19676-at-reschedule.test.ts
#	src/cron/service.issue-regressions.test-helpers.ts
#	src/cron/service.issue-regressions.test.ts
#	src/cron/service.jobs.top-of-hour-stagger.test.ts
#	src/cron/service.list-page-sort-guards.test.ts
#	src/cron/service.main-job-passes-heartbeat-target-last.test.ts
#	src/cron/service.session-reaper-in-finally.test.ts
#	src/cron/service/jobs.ts
#	src/cron/service/timer.ts
#	src/cron/store.test.ts
#	src/cron/store.ts
#	src/daemon/service-env.test.ts
#	src/daemon/service-env.ts
#	src/daemon/systemd.ts
#	src/discord/audit.test.ts
#	src/discord/components.ts
#	src/discord/mentions.ts
#	src/discord/monitor.test.ts
#	src/discord/monitor.tool-result.accepts-guild-messages-mentionpatterns-match.e2e.test.ts
#	src/discord/monitor/agent-components.ts
#	src/discord/monitor/dm-command-decision.ts
#	src/discord/monitor/exec-approvals.ts
#	src/discord/monitor/listeners.test.ts
#	src/discord/monitor/listeners.ts
#	src/discord/monitor/message-handler.preflight.test.ts
#	src/discord/monitor/message-handler.preflight.ts
#	src/discord/monitor/message-handler.process.ts
#	src/discord/monitor/message-handler.ts
#	src/discord/monitor/native-command.options.test.ts
#	src/discord/monitor/native-command.ts
#	src/discord/monitor/preflight-audio.ts
#	src/discord/monitor/provider.lifecycle.ts
#	src/discord/monitor/provider.test.ts
#	src/discord/monitor/provider.ts
#	src/discord/monitor/reply-delivery.ts
#	src/discord/monitor/status.ts
#	src/discord/monitor/thread-bindings.config.ts
#	src/discord/monitor/thread-bindings.lifecycle.test.ts
#	src/discord/monitor/thread-bindings.lifecycle.ts
#	src/discord/send.components.ts
#	src/discord/send.outbound.ts
#	src/discord/send.shared.ts
#	src/discord/token.test.ts
#	src/discord/token.ts
#	src/discord/voice-message.test.ts
#	src/discord/voice-message.ts
#	src/discord/voice/command.ts
#	src/discord/voice/manager.e2e.test.ts
#	src/discord/voice/manager.ts
#	src/docker-setup.e2e.test.ts
#	src/dockerfile.test.ts
#	src/entry.ts
#	src/gateway/android-node.capabilities.live.test.ts
#	src/gateway/call.test.ts
#	src/gateway/call.ts
#	src/gateway/channel-health-monitor.test.ts
#	src/gateway/channel-health-monitor.ts
#	src/gateway/channel-health-policy.test.ts
#	src/gateway/channel-health-policy.ts
#	src/gateway/client.test.ts
#	src/gateway/client.ts
#	src/gateway/config-reload-plan.ts
#	src/gateway/config-reload.test.ts
#	src/gateway/config-reload.ts
#	src/gateway/control-ui-contract.ts
#	src/gateway/control-ui-routing.test.ts
#	src/gateway/control-ui.http.test.ts
#	src/gateway/credentials.test.ts
#	src/gateway/credentials.ts
#	src/gateway/gateway-config-prompts.shared.ts
#	src/gateway/gateway-models.profiles.live.test.ts
#	src/gateway/gateway.test.ts
#	src/gateway/http-utils.request-context.test.ts
#	src/gateway/http-utils.ts
#	src/gateway/node-command-policy.ts
#	src/gateway/openai-http.message-channel.test.ts
#	src/gateway/openai-http.test.ts
#	src/gateway/openresponses-http.test.ts
#	src/gateway/openresponses-http.ts
#	src/gateway/origin-check.ts
#	src/gateway/probe-auth.ts
#	src/gateway/server-channels.ts
#	src/gateway/server-cron.ts
#	src/gateway/server-http.ts
#	src/gateway/server-methods/agent.ts
#	src/gateway/server-methods/agents-mutate.test.ts
#	src/gateway/server-methods/chat.directive-tags.test.ts
#	src/gateway/server-methods/chat.ts
#	src/gateway/server-methods/nodes.canvas-capability-refresh.test.ts
#	src/gateway/server-methods/secrets.ts
#	src/gateway/server-methods/send.test.ts
#	src/gateway/server-methods/usage.ts
#	src/gateway/server-reload-handlers.ts
#	src/gateway/server-runtime-state.ts
#	src/gateway/server.agent.gateway-server-agent-b.test.ts
#	src/gateway/server.auth.control-ui.suite.ts
#	src/gateway/server.auth.default-token.suite.ts
#	src/gateway/server.auth.modes.suite.ts
#	src/gateway/server.auth.shared.ts
#	src/gateway/server.auth.test.ts
#	src/gateway/server.chat.gateway-server-chat.test.ts
#	src/gateway/server.cron.test.ts
#	src/gateway/server.hooks.test.ts
#	src/gateway/server.impl.ts
#	src/gateway/server.plugin-http-auth.test.ts
#	src/gateway/server.reload.test.ts
#	src/gateway/server.roles-allowlist-update.test.ts
#	src/gateway/server.sessions.gateway-server-sessions-a.test.ts
#	src/gateway/server/http-auth.ts
#	src/gateway/server/ws-connection.ts
#	src/gateway/server/ws-connection/message-handler.ts
#	src/gateway/session-utils.test.ts
#	src/gateway/session-utils.ts
#	src/gateway/sessions-patch.test.ts
#	src/gateway/startup-auth.test.ts
#	src/gateway/startup-auth.ts
#	src/gateway/startup-control-ui-origins.ts
#	src/gateway/test-helpers.server.ts
#	src/hooks/bundled/session-memory/handler.test.ts
#	src/hooks/install.test.ts
#	src/hooks/install.ts
#	src/hooks/internal-hooks.test.ts
#	src/hooks/internal-hooks.ts
#	src/hooks/loader.test.ts
#	src/hooks/message-hook-mappers.test.ts
#	src/hooks/message-hook-mappers.ts
#	src/hooks/workspace.test.ts
#	src/i18n/registry.test.ts
#	src/imessage/monitor/inbound-processing.test.ts
#	src/infra/archive.ts
#	src/infra/boundary-file-read.ts
#	src/infra/device-auth-store.ts
#	src/infra/exec-approval-forwarder.test.ts
#	src/infra/exec-approval-forwarder.ts
#	src/infra/fs-safe.test.ts
#	src/infra/fs-safe.ts
#	src/infra/heartbeat-runner.scheduler.test.ts
#	src/infra/install-safe-path.test.ts
#	src/infra/install-source-utils.test.ts
#	src/infra/outbound/deliver.test.ts
#	src/infra/outbound/deliver.ts
#	src/infra/outbound/message-action-params.test.ts
#	src/infra/outbound/message-action-params.ts
#	src/infra/outbound/message-action-runner.ts
#	src/infra/outbound/outbound.test.ts
#	src/infra/outbound/sanitize-text.ts
#	src/infra/outbound/target-normalization.ts
#	src/infra/outbound/targets.channel-resolution.test.ts
#	src/infra/outbound/targets.test.ts
#	src/infra/process-respawn.test.ts
#	src/infra/process-respawn.ts
#	src/infra/restart-stale-pids.ts
#	src/infra/safe-open-sync.test.ts
#	src/infra/system-events.test.ts
#	src/infra/system-presence.version.test.ts
#	src/infra/tmp-openclaw-dir.test.ts
#	src/infra/update-runner.test.ts
#	src/infra/update-runner.ts
#	src/infra/update-startup.test.ts
#	src/infra/update-startup.ts
#	src/line/accounts.test.ts
#	src/line/accounts.ts
#	src/line/bot-handlers.ts
#	src/line/bot-message-context.test.ts
#	src/line/bot-message-context.ts
#	src/line/bot.ts
#	src/logging/console-settings.test.ts
#	src/logging/console.ts
#	src/logging/diagnostic.ts
#	src/logging/logger-settings.test.ts
#	src/logging/logger-timestamp.test.ts
#	src/logging/logger.settings.test.ts
#	src/logging/logger.ts
#	src/logging/subsystem.ts
#	src/media-understanding/apply.echo-transcript.test.ts
#	src/media-understanding/apply.test.ts
#	src/media-understanding/attachments.cache.ts
#	src/media-understanding/attachments.guards.test.ts
#	src/media-understanding/attachments.normalize.ts
#	src/media-understanding/attachments.select.ts
#	src/media-understanding/audio-transcription-runner.ts
#	src/media-understanding/echo-transcript.ts
#	src/media-understanding/providers/image.ts
#	src/media-understanding/resolve.test.ts
#	src/media-understanding/runner.entries.guards.test.ts
#	src/media-understanding/runner.entries.ts
#	src/media-understanding/runner.proxy.test.ts
#	src/media-understanding/runner.skip-tiny-audio.test.ts
#	src/media-understanding/transcribe-audio.test.ts
#	src/media-understanding/transcribe-audio.ts
#	src/media/fetch.ts
#	src/media/server.outside-workspace.test.ts
#	src/media/store.outside-workspace.test.ts
#	src/memory/batch-openai.ts
#	src/memory/batch-voyage.ts
#	src/memory/embeddings-ollama.test.ts
#	src/memory/embeddings-ollama.ts
#	src/memory/embeddings-remote-client.ts
#	src/memory/manager.get-concurrency.test.ts
#	src/memory/manager.mistral-provider.test.ts
#	src/memory/manager.readonly-recovery.test.ts
#	src/memory/qmd-manager.test.ts
#	src/memory/qmd-manager.ts
#	src/node-host/invoke-system-run-plan.test.ts
#	src/node-host/invoke-system-run-plan.ts
#	src/node-host/invoke-system-run.test.ts
#	src/node-host/invoke-system-run.ts
#	src/node-host/invoke.ts
#	src/node-host/runner.credentials.test.ts
#	src/node-host/runner.ts
#	src/pairing/setup-code.test.ts
#	src/pairing/setup-code.ts
#	src/plugin-sdk/channel-config-helpers.ts
#	src/plugin-sdk/command-auth.ts
#	src/plugin-sdk/index.ts
#	src/plugin-sdk/webhook-request-guards.test.ts
#	src/plugin-sdk/webhook-request-guards.ts
#	src/plugin-sdk/webhook-targets.test.ts
#	src/plugins/bundled-sources.test.ts
#	src/plugins/commands.test.ts
#	src/plugins/discovery.test.ts
#	src/plugins/discovery.ts
#	src/plugins/hooks.before-agent-start.test.ts
#	src/plugins/hooks.model-override-wiring.test.ts
#	src/plugins/hooks.test-helpers.ts
#	src/plugins/install.test.ts
#	src/plugins/install.ts
#	src/plugins/loader.test.ts
#	src/plugins/loader.ts
#	src/plugins/manifest-registry.test.ts
#	src/plugins/registry.ts
#	src/plugins/runtime/index.ts
#	src/plugins/runtime/runtime-channel.ts
#	src/plugins/runtime/runtime-config.ts
#	src/plugins/runtime/runtime-events.ts
#	src/plugins/runtime/runtime-logging.ts
#	src/plugins/runtime/runtime-media.ts
#	src/plugins/runtime/runtime-system.ts
#	src/plugins/runtime/runtime-tools.ts
#	src/plugins/runtime/runtime-whatsapp.ts
#	src/plugins/runtime/types.contract.test.ts
#	src/plugins/schema-validator.ts
#	src/plugins/types.ts
#	src/plugins/update.test.ts
#	src/process/exec.test.ts
#	src/routing/bindings.ts
#	src/routing/resolve-route.test.ts
#	src/routing/resolve-route.ts
#	src/scripts/ci-changed-scope.test.ts
#	src/secrets/apply.test.ts
#	src/secrets/apply.ts
#	src/secrets/audit.test.ts
#	src/secrets/audit.ts
#	src/secrets/auth-store-paths.ts
#	src/secrets/command-config.test.ts
#	src/secrets/command-config.ts
#	src/secrets/configure-plan.test.ts
#	src/secrets/configure-plan.ts
#	src/secrets/configure.ts
#	src/secrets/credential-matrix.ts
#	src/secrets/path-utils.test.ts
#	src/secrets/path-utils.ts
#	src/secrets/resolve.test.ts
#	src/secrets/runtime-config-collectors-channels.ts
#	src/secrets/runtime-config-collectors-core.ts
#	src/secrets/runtime-config-collectors.ts
#	src/secrets/runtime-gateway-auth-surfaces.test.ts
#	src/secrets/runtime-gateway-auth-surfaces.ts
#	src/secrets/runtime-shared.ts
#	src/secrets/runtime.coverage.test.ts
#	src/secrets/runtime.test.ts
#	src/secrets/runtime.ts
#	src/secrets/storage-scan.ts
#	src/secrets/target-registry-data.ts
#	src/secrets/target-registry-pattern.ts
#	src/secrets/target-registry-query.ts
#	src/secrets/target-registry-types.ts
#	src/secrets/target-registry.test.ts
#	src/security/audit-extra.async.ts
#	src/security/audit.test.ts
#	src/security/audit.ts
#	src/security/fix.test.ts
#	src/slack/actions.ts
#	src/slack/message-actions.test.ts
#	src/slack/monitor/auth.test.ts
#	src/slack/monitor/auth.ts
#	src/slack/monitor/context.test.ts
#	src/slack/monitor/context.ts
#	src/slack/monitor/events/interactions.modal.ts
#	src/slack/monitor/events/interactions.test.ts
#	src/slack/monitor/events/interactions.ts
#	src/slack/monitor/events/messages.ts
#	src/slack/monitor/events/reactions.test.ts
#	src/slack/monitor/media.test.ts
#	src/slack/monitor/message-handler.ts
#	src/slack/monitor/message-handler/dispatch.ts
#	src/slack/monitor/message-handler/prepare-content.ts
#	src/slack/monitor/message-handler/prepare-thread-context.ts
#	src/slack/monitor/message-handler/prepare.test-helpers.ts
#	src/slack/monitor/message-handler/prepare.test.ts
#	src/slack/monitor/message-handler/prepare.thread-session-key.test.ts
#	src/slack/monitor/message-handler/prepare.ts
#	src/slack/monitor/provider.ts
#	src/slack/monitor/slash.test.ts
#	src/slack/monitor/slash.ts
#	src/slack/send.ts
#	src/telegram/accounts.test.ts
#	src/telegram/accounts.ts
#	src/telegram/bot-handlers.ts
#	src/telegram/bot-message-context.audio-transcript.test.ts
#	src/telegram/bot-message-context.dm-threads.test.ts
#	src/telegram/bot-message-context.implicit-mention.test.ts
#	src/telegram/bot-message-context.ts
#	src/telegram/bot-message-dispatch.test.ts
#	src/telegram/bot-native-command-menu.ts
#	src/telegram/bot-native-commands.session-meta.test.ts
#	src/telegram/bot-native-commands.skills-allowlist.test.ts
#	src/telegram/bot-native-commands.test.ts
#	src/telegram/bot-native-commands.ts
#	src/telegram/bot.create-telegram-bot.test-harness.ts
#	src/telegram/bot.create-telegram-bot.test.ts
#	src/telegram/bot.test.ts
#	src/telegram/bot.ts
#	src/telegram/bot/delivery.replies.ts
#	src/telegram/bot/delivery.resolve-media.ts
#	src/telegram/bot/delivery.send.ts
#	src/telegram/bot/helpers.ts
#	src/telegram/draft-stream.test-helpers.ts
#	src/telegram/draft-stream.test.ts
#	src/telegram/draft-stream.ts
#	src/telegram/group-access.policy-access.test.ts
#	src/telegram/lane-delivery.test.ts
#	src/telegram/lane-delivery.ts
#	src/telegram/send.ts
#	src/telegram/token.test.ts
#	src/telegram/token.ts
#	src/terminal/note.ts
#	src/tts/tts.ts
#	src/tui/gateway-chat.test.ts
#	src/tui/gateway-chat.ts
#	src/tui/tui-local-shell.test.ts
#	src/tui/tui-local-shell.ts
#	src/version.test.ts
#	src/version.ts
#	src/web/auto-reply.broadcast-groups.combined.test.ts
#	src/web/auto-reply.broadcast-groups.skips-unknown-broadcast-agent-ids-agents-list.test.ts
#	src/web/auto-reply.typing-controller-idle.test.ts
#	src/web/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts
#	src/web/auto-reply.web-auto-reply.monitor-logging.test.ts
#	src/web/auto-reply/monitor/process-message.ts
#	src/web/media.ts
#	src/wizard/onboarding.finalize.test.ts
#	src/wizard/onboarding.finalize.ts
#	src/wizard/onboarding.gateway-config.test.ts
#	src/wizard/onboarding.gateway-config.ts
#	src/wizard/onboarding.secret-input.test.ts
#	src/wizard/onboarding.secret-input.ts
#	src/wizard/onboarding.test.ts
#	src/wizard/onboarding.ts
#	src/wizard/onboarding.types.ts
#	test/cli-json-stdout.e2e.test.ts
#	test/fixtures/plugins-install/voice-call-0.0.1.tgz
#	test/fixtures/plugins-install/voice-call-0.0.2.tgz
#	test/fixtures/plugins-install/zipper-0.0.1.zip
#	test/git-hooks-pre-commit.test.ts
#	test/helpers/temp-home.ts
#	test/release-check.test.ts
#	test/scripts/ios-team-id.test.ts
#	tsconfig.plugin-sdk.dts.json
#	tsdown.config.ts
#	ui/src/i18n/lib/registry.ts
#	ui/src/i18n/lib/translate.ts
#	ui/src/i18n/lib/types.ts
#	ui/src/i18n/locales/de.ts
#	ui/src/i18n/locales/en.ts
#	ui/src/i18n/locales/pt-BR.ts
#	ui/src/i18n/locales/zh-CN.ts
#	ui/src/i18n/locales/zh-TW.ts
#	ui/src/ui/app-gateway.ts
#	ui/src/ui/app-render.helpers.node.test.ts
#	ui/src/ui/controllers/cron.ts
#	ui/src/ui/device-auth.ts
#	ui/src/ui/markdown.test.ts
#	ui/src/ui/markdown.ts
#	ui/src/ui/storage.node.test.ts
#	ui/src/ui/storage.ts
#	ui/src/ui/tool-display.ts
#	ui/src/ui/views/cron.ts
#	ui/src/ui/views/nodes-exec-approvals.ts
#	vitest.config.ts
2026-03-05 13:32:25 -08:00

1042 lines
38 KiB
Swift

import AppKit
import CryptoKit
import Darwin
import Foundation
import BotKit
import OSLog
struct ExecApprovalPromptRequest: Codable, Sendable {
var command: String
var cwd: String?
var host: String?
var security: String?
var ask: String?
var agentId: String?
var resolvedPath: String?
var sessionKey: String?
}
private struct ExecApprovalSocketRequest: Codable {
var type: String
var token: String
var id: String
var request: ExecApprovalPromptRequest
}
private struct ExecApprovalSocketDecision: Codable {
var type: String
var id: String
var decision: ExecApprovalDecision
}
private struct ExecHostSocketRequest: Codable {
var type: String
var id: String
var nonce: String
var ts: Int
var hmac: String
var requestJson: String
}
private struct ExecHostRequest: Codable {
var command: [String]
var rawCommand: String?
var cwd: String?
var env: [String: String]?
var timeoutMs: Int?
var needsScreenRecording: Bool?
var agentId: String?
var sessionKey: String?
var approvalDecision: ExecApprovalDecision?
}
private struct ExecHostRunResult: Codable {
var exitCode: Int?
var timedOut: Bool
var success: Bool
var stdout: String
var stderr: String
var error: String?
}
private struct ExecHostError: Codable {
var code: String
var message: String
var reason: String?
}
private struct ExecHostResponse: Codable {
var type: String
var id: String
var ok: Bool
var payload: ExecHostRunResult?
var error: ExecHostError?
}
private func readLineFromHandle(_ handle: FileHandle, maxBytes: Int) throws -> String? {
var buffer = Data()
while buffer.count < maxBytes {
let chunk = try handle.read(upToCount: 4096) ?? Data()
if chunk.isEmpty { break }
buffer.append(chunk)
if buffer.contains(0x0A) { break }
}
guard let newlineIndex = buffer.firstIndex(of: 0x0A) else {
guard !buffer.isEmpty else { return nil }
return String(data: buffer, encoding: .utf8)
}
let lineData = buffer.subdata(in: 0..<newlineIndex)
return String(data: lineData, encoding: .utf8)
}
enum ExecApprovalsSocketClient {
private struct TimeoutError: LocalizedError {
var message: String
var errorDescription: String? {
self.message
}
}
static func requestDecision(
socketPath: String,
token: String,
request: ExecApprovalPromptRequest,
timeoutMs: Int = 15000) async -> ExecApprovalDecision?
{
let trimmedPath = socketPath.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedToken = token.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmedPath.isEmpty, !trimmedToken.isEmpty else { return nil }
do {
return try await AsyncTimeout.withTimeoutMs(
timeoutMs: timeoutMs,
onTimeout: {
TimeoutError(message: "exec approvals socket timeout")
},
operation: {
try await Task.detached {
try self.requestDecisionSync(
socketPath: trimmedPath,
token: trimmedToken,
request: request)
}.value
})
} catch {
return nil
}
}
private static func requestDecisionSync(
socketPath: String,
token: String,
request: ExecApprovalPromptRequest) throws -> ExecApprovalDecision?
{
let fd = socket(AF_UNIX, SOCK_STREAM, 0)
guard fd >= 0 else {
throw NSError(domain: "ExecApprovals", code: 1, userInfo: [
NSLocalizedDescriptionKey: "socket create failed",
])
}
var addr = sockaddr_un()
addr.sun_family = sa_family_t(AF_UNIX)
let maxLen = MemoryLayout.size(ofValue: addr.sun_path)
if socketPath.utf8.count >= maxLen {
throw NSError(domain: "ExecApprovals", code: 2, userInfo: [
NSLocalizedDescriptionKey: "socket path too long",
])
}
socketPath.withCString { cstr in
withUnsafeMutablePointer(to: &addr.sun_path) { ptr in
let raw = UnsafeMutableRawPointer(ptr).assumingMemoryBound(to: Int8.self)
strncpy(raw, cstr, maxLen - 1)
}
}
let size = socklen_t(MemoryLayout.size(ofValue: addr))
let result = withUnsafePointer(to: &addr) { ptr in
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { rebound in
connect(fd, rebound, size)
}
}
if result != 0 {
throw NSError(domain: "ExecApprovals", code: 3, userInfo: [
NSLocalizedDescriptionKey: "socket connect failed",
])
}
let handle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
let message = ExecApprovalSocketRequest(
type: "request",
token: token,
id: UUID().uuidString,
request: request)
let data = try JSONEncoder().encode(message)
var payload = data
payload.append(0x0A)
try handle.write(contentsOf: payload)
guard let line = try readLineFromHandle(handle, maxBytes: 256_000),
let lineData = line.data(using: .utf8)
else { return nil }
let response = try JSONDecoder().decode(ExecApprovalSocketDecision.self, from: lineData)
return response.decision
}
}
@MainActor
final class ExecApprovalsPromptServer {
static let shared = ExecApprovalsPromptServer()
private var server: ExecApprovalsSocketServer?
func start() {
guard self.server == nil else { return }
let approvals = ExecApprovalsStore.resolve(agentId: nil)
let server = ExecApprovalsSocketServer(
socketPath: approvals.socketPath,
token: approvals.token,
onPrompt: { request in
await ExecApprovalsPromptPresenter.prompt(request)
},
onExec: { request in
await ExecHostExecutor.handle(request)
})
server.start()
self.server = server
}
func stop() {
self.server?.stop()
self.server = nil
}
}
enum ExecApprovalsPromptPresenter {
@MainActor
static func prompt(_ request: ExecApprovalPromptRequest) -> ExecApprovalDecision {
NSApp.activate(ignoringOtherApps: true)
let alert = NSAlert()
alert.alertStyle = .warning
alert.messageText = "Allow this command?"
alert.informativeText = "Review the command details before allowing."
alert.accessoryView = self.buildAccessoryView(request)
alert.addButton(withTitle: "Allow Once")
alert.addButton(withTitle: "Always Allow")
alert.addButton(withTitle: "Don't Allow")
if #available(macOS 11.0, *), alert.buttons.indices.contains(2) {
alert.buttons[2].hasDestructiveAction = true
}
switch alert.runModal() {
case .alertFirstButtonReturn:
return .allowOnce
case .alertSecondButtonReturn:
return .allowAlways
default:
return .deny
}
}
@MainActor
private static func buildAccessoryView(_ request: ExecApprovalPromptRequest) -> NSView {
let stack = NSStackView()
stack.orientation = .vertical
stack.spacing = 8
stack.alignment = .leading
stack.translatesAutoresizingMaskIntoConstraints = false
stack.widthAnchor.constraint(greaterThanOrEqualToConstant: 380).isActive = true
let commandTitle = NSTextField(labelWithString: "Command")
commandTitle.font = NSFont.boldSystemFont(ofSize: NSFont.systemFontSize)
stack.addArrangedSubview(commandTitle)
let commandText = NSTextView()
commandText.isEditable = false
commandText.isSelectable = true
commandText.drawsBackground = true
commandText.backgroundColor = NSColor.textBackgroundColor
commandText.font = NSFont.monospacedSystemFont(ofSize: NSFont.systemFontSize, weight: .regular)
commandText.string = request.command
commandText.textContainerInset = NSSize(width: 6, height: 6)
commandText.textContainer?.lineFragmentPadding = 0
commandText.textContainer?.widthTracksTextView = true
commandText.isHorizontallyResizable = false
commandText.isVerticallyResizable = true
let commandScroll = NSScrollView()
commandScroll.borderType = .lineBorder
commandScroll.hasVerticalScroller = true
commandScroll.hasHorizontalScroller = false
commandScroll.autohidesScrollers = true
commandScroll.documentView = commandText
commandScroll.translatesAutoresizingMaskIntoConstraints = false
commandScroll.widthAnchor.constraint(greaterThanOrEqualToConstant: 380).isActive = true
commandScroll.widthAnchor.constraint(lessThanOrEqualToConstant: 440).isActive = true
commandScroll.heightAnchor.constraint(greaterThanOrEqualToConstant: 56).isActive = true
commandScroll.heightAnchor.constraint(lessThanOrEqualToConstant: 120).isActive = true
stack.addArrangedSubview(commandScroll)
let contextTitle = NSTextField(labelWithString: "Context")
contextTitle.font = NSFont.boldSystemFont(ofSize: NSFont.systemFontSize)
stack.addArrangedSubview(contextTitle)
let contextStack = NSStackView()
contextStack.orientation = .vertical
contextStack.spacing = 4
contextStack.alignment = .leading
let trimmedCwd = request.cwd?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedCwd.isEmpty {
self.addDetailRow(title: "Working directory", value: trimmedCwd, to: contextStack)
}
let trimmedAgent = request.agentId?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedAgent.isEmpty {
self.addDetailRow(title: "Agent", value: trimmedAgent, to: contextStack)
}
let trimmedPath = request.resolvedPath?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedPath.isEmpty {
self.addDetailRow(title: "Executable", value: trimmedPath, to: contextStack)
}
let trimmedHost = request.host?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if !trimmedHost.isEmpty {
self.addDetailRow(title: "Host", value: trimmedHost, to: contextStack)
}
if let security = request.security?.trimmingCharacters(in: .whitespacesAndNewlines), !security.isEmpty {
self.addDetailRow(title: "Security", value: security, to: contextStack)
}
if let ask = request.ask?.trimmingCharacters(in: .whitespacesAndNewlines), !ask.isEmpty {
self.addDetailRow(title: "Ask mode", value: ask, to: contextStack)
}
if contextStack.arrangedSubviews.isEmpty {
let empty = NSTextField(labelWithString: "No additional context provided.")
empty.textColor = NSColor.secondaryLabelColor
empty.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
contextStack.addArrangedSubview(empty)
}
stack.addArrangedSubview(contextStack)
let footer = NSTextField(labelWithString: "This runs on this machine.")
footer.textColor = NSColor.secondaryLabelColor
footer.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
stack.addArrangedSubview(footer)
return stack
}
@MainActor
private static func addDetailRow(title: String, value: String, to stack: NSStackView) {
let row = NSStackView()
row.orientation = .horizontal
row.spacing = 6
row.alignment = .firstBaseline
let titleLabel = NSTextField(labelWithString: "\(title):")
titleLabel.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize, weight: .semibold)
titleLabel.textColor = NSColor.secondaryLabelColor
let valueLabel = NSTextField(labelWithString: value)
valueLabel.font = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
valueLabel.lineBreakMode = .byTruncatingMiddle
valueLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
row.addArrangedSubview(titleLabel)
row.addArrangedSubview(valueLabel)
stack.addArrangedSubview(row)
}
}
@MainActor
private enum ExecHostExecutor {
private struct ExecApprovalContext {
let command: [String]
let displayCommand: String
let trimmedAgent: String?
let approvals: ExecApprovalsResolved
let security: ExecSecurity
let ask: ExecAsk
let autoAllowSkills: Bool
let env: [String: String]?
let resolution: ExecCommandResolution?
let allowlistMatch: ExecAllowlistEntry?
let skillAllow: Bool
}
private static let blockedEnvKeys: Set<String> = [
"PATH",
"NODE_OPTIONS",
"PYTHONHOME",
"PYTHONPATH",
"PERL5LIB",
"PERL5OPT",
"RUBYOPT",
]
private static let blockedEnvPrefixes: [String] = [
"DYLD_",
"LD_",
]
static func handle(_ request: ExecHostRequest) async -> ExecHostResponse {
let command = request.command.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
guard !command.isEmpty else {
return self.errorResponse(
code: "INVALID_REQUEST",
message: "command required",
reason: "invalid")
}
let context = await self.buildContext(request: request, command: command)
if context.security == .deny {
return self.errorResponse(
code: "UNAVAILABLE",
message: "SYSTEM_RUN_DISABLED: security=deny",
reason: "security=deny")
}
let approvalDecision = request.approvalDecision
if approvalDecision == .deny {
return self.errorResponse(
code: "UNAVAILABLE",
message: "SYSTEM_RUN_DENIED: user denied",
reason: "user-denied")
}
var approvedByAsk = approvalDecision != nil
if ExecApprovalHelpers.requiresAsk(
ask: context.ask,
security: context.security,
allowlistMatch: context.allowlistMatch,
skillAllow: context.skillAllow),
approvalDecision == nil
{
let decision = ExecApprovalsPromptPresenter.prompt(
ExecApprovalPromptRequest(
command: context.displayCommand,
cwd: request.cwd,
host: "node",
security: context.security.rawValue,
ask: context.ask.rawValue,
agentId: context.trimmedAgent,
resolvedPath: context.resolution?.resolvedPath,
sessionKey: request.sessionKey))
switch decision {
case .deny:
return self.errorResponse(
code: "UNAVAILABLE",
message: "SYSTEM_RUN_DENIED: user denied",
reason: "user-denied")
case .allowAlways:
approvedByAsk = true
self.persistAllowlistEntry(decision: decision, context: context)
case .allowOnce:
approvedByAsk = true
}
}
self.persistAllowlistEntry(decision: approvalDecision, context: context)
if context.security == .allowlist,
context.allowlistMatch == nil,
!context.skillAllow,
!approvedByAsk
{
return self.errorResponse(
code: "UNAVAILABLE",
message: "SYSTEM_RUN_DENIED: allowlist miss",
reason: "allowlist-miss")
}
if let match = context.allowlistMatch {
ExecApprovalsStore.recordAllowlistUse(
agentId: context.trimmedAgent,
pattern: match.pattern,
command: context.displayCommand,
resolvedPath: context.resolution?.resolvedPath)
}
if let errorResponse = await self.ensureScreenRecordingAccess(request.needsScreenRecording) {
return errorResponse
}
return await self.runCommand(
command: command,
cwd: request.cwd,
env: context.env,
timeoutMs: request.timeoutMs)
}
private static func buildContext(request: ExecHostRequest, command: [String]) async -> ExecApprovalContext {
let displayCommand = ExecCommandFormatter.displayString(
for: command,
rawCommand: request.rawCommand)
let agentId = request.agentId?.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedAgent = (agentId?.isEmpty == false) ? agentId : nil
let approvals = ExecApprovalsStore.resolve(agentId: trimmedAgent)
let security = approvals.agent.security
let ask = approvals.agent.ask
let autoAllowSkills = approvals.agent.autoAllowSkills
let env = self.sanitizedEnv(request.env)
let resolution = ExecCommandResolution.resolve(
command: command,
rawCommand: request.rawCommand,
cwd: request.cwd,
env: env)
let allowlistMatch = security == .allowlist
? ExecAllowlistMatcher.match(entries: approvals.allowlist, resolution: resolution)
: nil
let skillAllow: Bool
if autoAllowSkills, let name = resolution?.executableName {
let bins = await SkillBinsCache.shared.currentBins()
skillAllow = bins.contains(name)
} else {
skillAllow = false
}
return ExecApprovalContext(
command: command,
displayCommand: displayCommand,
trimmedAgent: trimmedAgent,
approvals: approvals,
security: security,
ask: ask,
autoAllowSkills: autoAllowSkills,
env: env,
resolution: resolution,
allowlistMatch: allowlistMatch,
skillAllow: skillAllow)
}
private static func persistAllowlistEntry(
decision: ExecApprovalDecision?,
context: ExecApprovalContext)
{
guard decision == .allowAlways, context.security == .allowlist else { return }
guard let pattern = ExecApprovalHelpers.allowlistPattern(
command: context.command,
resolution: context.resolution)
else {
return
}
ExecApprovalsStore.addAllowlistEntry(agentId: context.trimmedAgent, pattern: pattern)
}
private static func ensureScreenRecordingAccess(_ needsScreenRecording: Bool?) async -> ExecHostResponse? {
guard needsScreenRecording == true else { return nil }
let authorized = await PermissionManager
.status([.screenRecording])[.screenRecording] ?? false
if authorized { return nil }
return self.errorResponse(
code: "UNAVAILABLE",
message: "PERMISSION_MISSING: screenRecording",
reason: "permission:screenRecording")
}
private static func runCommand(
command: [String],
cwd: String?,
env: [String: String]?,
timeoutMs: Int?) async -> ExecHostResponse
{
let timeoutSec = timeoutMs.flatMap { Double($0) / 1000.0 }
let result = await Task.detached { () -> ShellExecutor.ShellResult in
await ShellExecutor.runDetailed(
command: command,
cwd: cwd,
env: env,
timeout: timeoutSec)
}.value
let payload = ExecHostRunResult(
exitCode: result.exitCode,
timedOut: result.timedOut,
success: result.success,
stdout: result.stdout,
stderr: result.stderr,
error: result.errorMessage)
return self.successResponse(payload)
}
private static func errorResponse(
code: String,
message: String,
reason: String?) -> ExecHostResponse
{
ExecHostResponse(
type: "exec-res",
id: UUID().uuidString,
ok: false,
payload: nil,
error: ExecHostError(code: code, message: message, reason: reason))
}
private static func successResponse(_ payload: ExecHostRunResult) -> ExecHostResponse {
ExecHostResponse(
type: "exec-res",
id: UUID().uuidString,
ok: true,
payload: payload,
error: nil)
}
private static func sanitizedEnv(_ overrides: [String: String]?) -> [String: String]? {
guard let overrides else { return nil }
var merged = ProcessInfo.processInfo.environment
for (rawKey, value) in overrides {
let key = rawKey.trimmingCharacters(in: .whitespacesAndNewlines)
guard !key.isEmpty else { continue }
let upper = key.uppercased()
if self.blockedEnvKeys.contains(upper) { continue }
if self.blockedEnvPrefixes.contains(where: { upper.hasPrefix($0) }) { continue }
merged[key] = value
}
return merged
}
}
enum ExecApprovalsSocketPathKind: Equatable {
case missing
case directory
case socket
case symlink
case other
}
enum ExecApprovalsSocketPathGuardError: LocalizedError {
case lstatFailed(path: String, code: Int32)
case parentPathInvalid(path: String, kind: ExecApprovalsSocketPathKind)
case socketPathInvalid(path: String, kind: ExecApprovalsSocketPathKind)
case unlinkFailed(path: String, code: Int32)
case createParentDirectoryFailed(path: String, message: String)
case setParentDirectoryPermissionsFailed(path: String, message: String)
var errorDescription: String? {
switch self {
case let .lstatFailed(path, code):
"lstat failed for \(path) (errno \(code))"
case let .parentPathInvalid(path, kind):
"socket parent path invalid (\(kind)) at \(path)"
case let .socketPathInvalid(path, kind):
"socket path invalid (\(kind)) at \(path)"
case let .unlinkFailed(path, code):
"unlink failed for \(path) (errno \(code))"
case let .createParentDirectoryFailed(path, message):
"socket parent directory create failed at \(path): \(message)"
case let .setParentDirectoryPermissionsFailed(path, message):
"socket parent directory chmod failed at \(path): \(message)"
}
}
}
enum ExecApprovalsSocketPathGuard {
static let parentDirectoryPermissions = 0o700
static func pathKind(at path: String) throws -> ExecApprovalsSocketPathKind {
var status = stat()
let result = lstat(path, &status)
if result != 0 {
if errno == ENOENT {
return .missing
}
throw ExecApprovalsSocketPathGuardError.lstatFailed(path: path, code: errno)
}
let fileType = status.st_mode & mode_t(S_IFMT)
if fileType == mode_t(S_IFDIR) { return .directory }
if fileType == mode_t(S_IFSOCK) { return .socket }
if fileType == mode_t(S_IFLNK) { return .symlink }
return .other
}
static func hardenParentDirectory(for socketPath: String) throws {
let parentURL = URL(fileURLWithPath: socketPath).deletingLastPathComponent()
let parentPath = parentURL.path
switch try self.pathKind(at: parentPath) {
case .missing, .directory:
break
case let kind:
throw ExecApprovalsSocketPathGuardError.parentPathInvalid(path: parentPath, kind: kind)
}
do {
try FileManager().createDirectory(at: parentURL, withIntermediateDirectories: true)
} catch {
throw ExecApprovalsSocketPathGuardError.createParentDirectoryFailed(
path: parentPath,
message: error.localizedDescription)
}
do {
try FileManager().setAttributes(
[.posixPermissions: self.parentDirectoryPermissions],
ofItemAtPath: parentPath)
} catch {
throw ExecApprovalsSocketPathGuardError.setParentDirectoryPermissionsFailed(
path: parentPath,
message: error.localizedDescription)
}
}
static func removeExistingSocket(at socketPath: String) throws {
let kind = try self.pathKind(at: socketPath)
switch kind {
case .missing:
return
case .socket:
break
case .directory, .symlink, .other:
throw ExecApprovalsSocketPathGuardError.socketPathInvalid(path: socketPath, kind: kind)
}
if unlink(socketPath) != 0, errno != ENOENT {
throw ExecApprovalsSocketPathGuardError.unlinkFailed(path: socketPath, code: errno)
}
}
}
enum ExecApprovalsSocketPathKind: Equatable {
case missing
case directory
case socket
case symlink
case other
}
enum ExecApprovalsSocketPathGuardError: LocalizedError {
case lstatFailed(path: String, code: Int32)
case parentPathInvalid(path: String, kind: ExecApprovalsSocketPathKind)
case socketPathInvalid(path: String, kind: ExecApprovalsSocketPathKind)
case unlinkFailed(path: String, code: Int32)
case createParentDirectoryFailed(path: String, message: String)
case setParentDirectoryPermissionsFailed(path: String, message: String)
var errorDescription: String? {
switch self {
case let .lstatFailed(path, code):
"lstat failed for \(path) (errno \(code))"
case let .parentPathInvalid(path, kind):
"socket parent path invalid (\(kind)) at \(path)"
case let .socketPathInvalid(path, kind):
"socket path invalid (\(kind)) at \(path)"
case let .unlinkFailed(path, code):
"unlink failed for \(path) (errno \(code))"
case let .createParentDirectoryFailed(path, message):
"socket parent directory create failed at \(path): \(message)"
case let .setParentDirectoryPermissionsFailed(path, message):
"socket parent directory chmod failed at \(path): \(message)"
}
}
}
enum ExecApprovalsSocketPathGuard {
static let parentDirectoryPermissions = 0o700
static func pathKind(at path: String) throws -> ExecApprovalsSocketPathKind {
var status = stat()
let result = lstat(path, &status)
if result != 0 {
if errno == ENOENT {
return .missing
}
throw ExecApprovalsSocketPathGuardError.lstatFailed(path: path, code: errno)
}
let fileType = status.st_mode & mode_t(S_IFMT)
if fileType == mode_t(S_IFDIR) { return .directory }
if fileType == mode_t(S_IFSOCK) { return .socket }
if fileType == mode_t(S_IFLNK) { return .symlink }
return .other
}
static func hardenParentDirectory(for socketPath: String) throws {
let parentURL = URL(fileURLWithPath: socketPath).deletingLastPathComponent()
let parentPath = parentURL.path
switch try self.pathKind(at: parentPath) {
case .missing, .directory:
break
case let kind:
throw ExecApprovalsSocketPathGuardError.parentPathInvalid(path: parentPath, kind: kind)
}
do {
try FileManager().createDirectory(at: parentURL, withIntermediateDirectories: true)
} catch {
throw ExecApprovalsSocketPathGuardError.createParentDirectoryFailed(
path: parentPath,
message: error.localizedDescription)
}
do {
try FileManager().setAttributes(
[.posixPermissions: self.parentDirectoryPermissions],
ofItemAtPath: parentPath)
} catch {
throw ExecApprovalsSocketPathGuardError.setParentDirectoryPermissionsFailed(
path: parentPath,
message: error.localizedDescription)
}
}
static func removeExistingSocket(at socketPath: String) throws {
let kind = try self.pathKind(at: socketPath)
switch kind {
case .missing:
return
case .socket:
break
case .directory, .symlink, .other:
throw ExecApprovalsSocketPathGuardError.socketPathInvalid(path: socketPath, kind: kind)
}
if unlink(socketPath) != 0, errno != ENOENT {
throw ExecApprovalsSocketPathGuardError.unlinkFailed(path: socketPath, code: errno)
}
}
}
private final class ExecApprovalsSocketServer: @unchecked Sendable {
private let logger = Logger(subsystem: "ai.bot", category: "exec-approvals.socket")
private let socketPath: String
private let token: String
private let onPrompt: @Sendable (ExecApprovalPromptRequest) async -> ExecApprovalDecision
private let onExec: @Sendable (ExecHostRequest) async -> ExecHostResponse
private var socketFD: Int32 = -1
private var acceptTask: Task<Void, Never>?
private var isRunning = false
init(
socketPath: String,
token: String,
onPrompt: @escaping @Sendable (ExecApprovalPromptRequest) async -> ExecApprovalDecision,
onExec: @escaping @Sendable (ExecHostRequest) async -> ExecHostResponse)
{
self.socketPath = socketPath
self.token = token
self.onPrompt = onPrompt
self.onExec = onExec
}
func start() {
guard !self.isRunning else { return }
self.isRunning = true
self.acceptTask = Task.detached { [weak self] in
await self?.runAcceptLoop()
}
}
func stop() {
self.isRunning = false
self.acceptTask?.cancel()
self.acceptTask = nil
if self.socketFD >= 0 {
close(self.socketFD)
self.socketFD = -1
}
if !self.socketPath.isEmpty {
do {
try ExecApprovalsSocketPathGuard.removeExistingSocket(at: self.socketPath)
} catch {
self.logger
.warning("exec approvals socket cleanup failed: \(error.localizedDescription, privacy: .public)")
}
}
}
private func runAcceptLoop() async {
let fd = self.openSocket()
guard fd >= 0 else {
self.isRunning = false
return
}
self.socketFD = fd
while self.isRunning {
var addr = sockaddr_un()
var len = socklen_t(MemoryLayout.size(ofValue: addr))
let client = withUnsafeMutablePointer(to: &addr) { ptr in
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { rebound in
accept(fd, rebound, &len)
}
}
if client < 0 {
if errno == EINTR { continue }
break
}
Task.detached { [weak self] in
await self?.handleClient(fd: client)
}
}
}
private func openSocket() -> Int32 {
let fd = socket(AF_UNIX, SOCK_STREAM, 0)
guard fd >= 0 else {
self.logger.error("exec approvals socket create failed")
return -1
}
do {
try ExecApprovalsSocketPathGuard.hardenParentDirectory(for: self.socketPath)
try ExecApprovalsSocketPathGuard.removeExistingSocket(at: self.socketPath)
} catch {
self.logger
.error("exec approvals socket path hardening failed: \(error.localizedDescription, privacy: .public)")
close(fd)
return -1
}
var addr = sockaddr_un()
addr.sun_family = sa_family_t(AF_UNIX)
let maxLen = MemoryLayout.size(ofValue: addr.sun_path)
if self.socketPath.utf8.count >= maxLen {
self.logger.error("exec approvals socket path too long")
close(fd)
return -1
}
self.socketPath.withCString { cstr in
withUnsafeMutablePointer(to: &addr.sun_path) { ptr in
let raw = UnsafeMutableRawPointer(ptr).assumingMemoryBound(to: Int8.self)
memset(raw, 0, maxLen)
strncpy(raw, cstr, maxLen - 1)
}
}
let size = socklen_t(MemoryLayout.size(ofValue: addr))
let result = withUnsafePointer(to: &addr) { ptr in
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { rebound in
bind(fd, rebound, size)
}
}
if result != 0 {
self.logger.error("exec approvals socket bind failed")
close(fd)
return -1
}
if chmod(self.socketPath, 0o600) != 0 {
self.logger.error("exec approvals socket chmod failed")
close(fd)
try? ExecApprovalsSocketPathGuard.removeExistingSocket(at: self.socketPath)
return -1
}
if listen(fd, 16) != 0 {
self.logger.error("exec approvals socket listen failed")
close(fd)
try? ExecApprovalsSocketPathGuard.removeExistingSocket(at: self.socketPath)
return -1
}
self.logger.info("exec approvals socket listening at \(self.socketPath, privacy: .public)")
return fd
}
private func handleClient(fd: Int32) async {
let handle = FileHandle(fileDescriptor: fd, closeOnDealloc: true)
do {
guard self.isAllowedPeer(fd: fd) else {
try self.sendApprovalResponse(handle: handle, id: UUID().uuidString, decision: .deny)
return
}
guard let line = try readLineFromHandle(handle, maxBytes: 256_000),
let data = line.data(using: .utf8)
else {
return
}
guard
let envelope = try JSONSerialization.jsonObject(with: data) as? [String: Any],
let type = envelope["type"] as? String
else {
return
}
if type == "request" {
let request = try JSONDecoder().decode(ExecApprovalSocketRequest.self, from: data)
guard request.token == self.token else {
try self.sendApprovalResponse(handle: handle, id: request.id, decision: .deny)
return
}
let decision = await self.onPrompt(request.request)
try self.sendApprovalResponse(handle: handle, id: request.id, decision: decision)
return
}
if type == "exec" {
let request = try JSONDecoder().decode(ExecHostSocketRequest.self, from: data)
let response = await self.handleExecRequest(request)
try self.sendExecResponse(handle: handle, response: response)
return
}
} catch {
self.logger.error("exec approvals socket handling failed: \(error.localizedDescription, privacy: .public)")
}
}
private func sendApprovalResponse(
handle: FileHandle,
id: String,
decision: ExecApprovalDecision) throws
{
let response = ExecApprovalSocketDecision(type: "decision", id: id, decision: decision)
let data = try JSONEncoder().encode(response)
var payload = data
payload.append(0x0A)
try handle.write(contentsOf: payload)
}
private func sendExecResponse(handle: FileHandle, response: ExecHostResponse) throws {
let data = try JSONEncoder().encode(response)
var payload = data
payload.append(0x0A)
try handle.write(contentsOf: payload)
}
private func isAllowedPeer(fd: Int32) -> Bool {
var uid = uid_t(0)
var gid = gid_t(0)
if getpeereid(fd, &uid, &gid) != 0 {
return false
}
return uid == geteuid()
}
private func handleExecRequest(_ request: ExecHostSocketRequest) async -> ExecHostResponse {
let nowMs = Int(Date().timeIntervalSince1970 * 1000)
if abs(nowMs - request.ts) > 10000 {
return ExecHostResponse(
type: "exec-res",
id: request.id,
ok: false,
payload: nil,
error: ExecHostError(code: "INVALID_REQUEST", message: "expired request", reason: "ttl"))
}
let expected = self.hmacHex(nonce: request.nonce, ts: request.ts, requestJson: request.requestJson)
if expected != request.hmac {
return ExecHostResponse(
type: "exec-res",
id: request.id,
ok: false,
payload: nil,
error: ExecHostError(code: "INVALID_REQUEST", message: "invalid auth", reason: "hmac"))
}
guard let requestData = request.requestJson.data(using: .utf8),
let payload = try? JSONDecoder().decode(ExecHostRequest.self, from: requestData)
else {
return ExecHostResponse(
type: "exec-res",
id: request.id,
ok: false,
payload: nil,
error: ExecHostError(code: "INVALID_REQUEST", message: "invalid payload", reason: "json"))
}
let response = await self.onExec(payload)
return ExecHostResponse(
type: "exec-res",
id: request.id,
ok: response.ok,
payload: response.payload,
error: response.error)
}
private func hmacHex(nonce: String, ts: Int, requestJson: String) -> String {
let key = SymmetricKey(data: Data(self.token.utf8))
let message = "\(nonce):\(ts):\(requestJson)"
let mac = HMAC<SHA256>.authenticationCode(for: Data(message.utf8), using: key)
return mac.map { String(format: "%02x", $0) }.joined()
}
}