mirror of
https://github.com/hanzoai/bot.git
synced 2026-10-30 17:24:26 +00:00
# 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
1042 lines
38 KiB
Swift
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()
|
|
}
|
|
}
|