@intentcomputing/koko-agent-sdk 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/CODE_OF_CONDUCT.md +3 -0
- package/CONTRIBUTING.md +9 -0
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/README.zh-CN.md +50 -0
- package/SECURITY.md +5 -0
- package/android/build.gradle +40 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/cn/intentcomputing/kokoagentsdk/KokoAgentSdkModule.java +1356 -0
- package/android/src/main/java/cn/intentcomputing/kokoagentsdk/KokoAgentSdkPackage.java +23 -0
- package/docs/INTEGRATION.md +168 -0
- package/docs/INTEGRATION.zh-CN.md +140 -0
- package/docs/PRIVACY.md +11 -0
- package/docs/PRIVACY.zh-CN.md +11 -0
- package/docs/PUBLISH.md +11 -0
- package/docs/PUBLISH.zh-CN.md +11 -0
- package/ios/KokoAgentSdk/KokoAgentSdk.h +5 -0
- package/ios/KokoAgentSdk/KokoAgentSdk.m +2464 -0
- package/ios/KokoAgentSdk/KokoOcrBounds.h +20 -0
- package/ios/KokoAgentSdk/KokoScrollBounds.h +21 -0
- package/koko-agent-sdk.podspec +23 -0
- package/lib/commonjs/KokoAgent.js +1427 -0
- package/lib/commonjs/KokoAgent.js.map +1 -0
- package/lib/commonjs/assist/OnDeviceAssist.js +94 -0
- package/lib/commonjs/assist/OnDeviceAssist.js.map +1 -0
- package/lib/commonjs/assist/intentClassify.js +100 -0
- package/lib/commonjs/assist/intentClassify.js.map +1 -0
- package/lib/commonjs/assist/nodeRank.js +120 -0
- package/lib/commonjs/assist/nodeRank.js.map +1 -0
- package/lib/commonjs/assist/ocrTarget.js +135 -0
- package/lib/commonjs/assist/ocrTarget.js.map +1 -0
- package/lib/commonjs/assist/webVisualChange.js +29 -0
- package/lib/commonjs/assist/webVisualChange.js.map +1 -0
- package/lib/commonjs/client/ActionOutcomeUnknownError.js +28 -0
- package/lib/commonjs/client/ActionOutcomeUnknownError.js.map +1 -0
- package/lib/commonjs/client/AgentSession.js +858 -0
- package/lib/commonjs/client/AgentSession.js.map +1 -0
- package/lib/commonjs/client/GatewayClient.js +302 -0
- package/lib/commonjs/client/GatewayClient.js.map +1 -0
- package/lib/commonjs/client/OperationReplayGuard.js +62 -0
- package/lib/commonjs/client/OperationReplayGuard.js.map +1 -0
- package/lib/commonjs/client/RunCheckpoint.js +139 -0
- package/lib/commonjs/client/RunCheckpoint.js.map +1 -0
- package/lib/commonjs/client/loopDetect.js +184 -0
- package/lib/commonjs/client/loopDetect.js.map +1 -0
- package/lib/commonjs/client/recentSearch.js +50 -0
- package/lib/commonjs/client/recentSearch.js.map +1 -0
- package/lib/commonjs/client/walletChoiceGuard.js +18 -0
- package/lib/commonjs/client/walletChoiceGuard.js.map +1 -0
- package/lib/commonjs/client/webResultCategory.js +38 -0
- package/lib/commonjs/client/webResultCategory.js.map +1 -0
- package/lib/commonjs/commercial/AccountStore.js +78 -0
- package/lib/commonjs/commercial/AccountStore.js.map +1 -0
- package/lib/commonjs/commercial/AgentDebug.js +104 -0
- package/lib/commonjs/commercial/AgentDebug.js.map +1 -0
- package/lib/commonjs/commercial/ExecutionGuard.js +53 -0
- package/lib/commonjs/commercial/ExecutionGuard.js.map +1 -0
- package/lib/commonjs/commercial/UsageBus.js +42 -0
- package/lib/commonjs/commercial/UsageBus.js.map +1 -0
- package/lib/commonjs/commercial/branding.js +69 -0
- package/lib/commonjs/commercial/branding.js.map +1 -0
- package/lib/commonjs/commercial/devMode.js +52 -0
- package/lib/commonjs/commercial/devMode.js.map +1 -0
- package/lib/commonjs/commercial/trust.js +69 -0
- package/lib/commonjs/commercial/trust.js.map +1 -0
- package/lib/commonjs/confirmation/gate.js +62 -0
- package/lib/commonjs/confirmation/gate.js.map +1 -0
- package/lib/commonjs/cursor/CursorOverlay.js +141 -0
- package/lib/commonjs/cursor/CursorOverlay.js.map +1 -0
- package/lib/commonjs/cursor/PointerHand.js +51 -0
- package/lib/commonjs/cursor/PointerHand.js.map +1 -0
- package/lib/commonjs/cursor/controller.js +91 -0
- package/lib/commonjs/cursor/controller.js.map +1 -0
- package/lib/commonjs/cursor/pointer.js +49 -0
- package/lib/commonjs/cursor/pointer.js.map +1 -0
- package/lib/commonjs/debug/KokoRemoteSmoke.js +611 -0
- package/lib/commonjs/debug/KokoRemoteSmoke.js.map +1 -0
- package/lib/commonjs/explore/ExplorationGraph.js +418 -0
- package/lib/commonjs/explore/ExplorationGraph.js.map +1 -0
- package/lib/commonjs/explore/bootstrapExplore.js +59 -0
- package/lib/commonjs/explore/bootstrapExplore.js.map +1 -0
- package/lib/commonjs/host/HostActions.js +266 -0
- package/lib/commonjs/host/HostActions.js.map +1 -0
- package/lib/commonjs/i18n/index.js +169 -0
- package/lib/commonjs/i18n/index.js.map +1 -0
- package/lib/commonjs/i18n/runtimeMessages.js +184 -0
- package/lib/commonjs/i18n/runtimeMessages.js.map +1 -0
- package/lib/commonjs/index.js +640 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native/NativeBridge.js +132 -0
- package/lib/commonjs/native/NativeBridge.js.map +1 -0
- package/lib/commonjs/native/navigationLabel.js +16 -0
- package/lib/commonjs/native/navigationLabel.js.map +1 -0
- package/lib/commonjs/native/normalizeViewTree.js +28 -0
- package/lib/commonjs/native/normalizeViewTree.js.map +1 -0
- package/lib/commonjs/native/pruneOffscreen.js +76 -0
- package/lib/commonjs/native/pruneOffscreen.js.map +1 -0
- package/lib/commonjs/native/scrollViewport.js +69 -0
- package/lib/commonjs/native/scrollViewport.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/privacy/redact.js +108 -0
- package/lib/commonjs/privacy/redact.js.map +1 -0
- package/lib/commonjs/privacy/screenEvidence.js +59 -0
- package/lib/commonjs/privacy/screenEvidence.js.map +1 -0
- package/lib/commonjs/privacy/screenModel.js +404 -0
- package/lib/commonjs/privacy/screenModel.js.map +1 -0
- package/lib/commonjs/privacy/sensitiveScreen.js +58 -0
- package/lib/commonjs/privacy/sensitiveScreen.js.map +1 -0
- package/lib/commonjs/privacy/userGate.js +213 -0
- package/lib/commonjs/privacy/userGate.js.map +1 -0
- package/lib/commonjs/protocol/actions.js +112 -0
- package/lib/commonjs/protocol/actions.js.map +1 -0
- package/lib/commonjs/provider/HostAppearanceStore.js +111 -0
- package/lib/commonjs/provider/HostAppearanceStore.js.map +1 -0
- package/lib/commonjs/provider/KokoProvider.js +81 -0
- package/lib/commonjs/provider/KokoProvider.js.map +1 -0
- package/lib/commonjs/provider/KokoUiContext.js +102 -0
- package/lib/commonjs/provider/KokoUiContext.js.map +1 -0
- package/lib/commonjs/selector/match.js +378 -0
- package/lib/commonjs/selector/match.js.map +1 -0
- package/lib/commonjs/task/TaskStore.js +361 -0
- package/lib/commonjs/task/TaskStore.js.map +1 -0
- package/lib/commonjs/task/types.js +2 -0
- package/lib/commonjs/task/types.js.map +1 -0
- package/lib/commonjs/task/useTaskStore.js +14 -0
- package/lib/commonjs/task/useTaskStore.js.map +1 -0
- package/lib/commonjs/types.js +2 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/ui/KokoChatSheet.js +885 -0
- package/lib/commonjs/ui/KokoChatSheet.js.map +1 -0
- package/lib/commonjs/ui/KokoComposer.js +161 -0
- package/lib/commonjs/ui/KokoComposer.js.map +1 -0
- package/lib/commonjs/ui/KokoDialogHost.js +254 -0
- package/lib/commonjs/ui/KokoDialogHost.js.map +1 -0
- package/lib/commonjs/ui/KokoEntryButton.js +81 -0
- package/lib/commonjs/ui/KokoEntryButton.js.map +1 -0
- package/lib/commonjs/ui/KokoObserver.js +522 -0
- package/lib/commonjs/ui/KokoObserver.js.map +1 -0
- package/lib/commonjs/ui/KokoOverlayLayer.js +45 -0
- package/lib/commonjs/ui/KokoOverlayLayer.js.map +1 -0
- package/lib/commonjs/ui/KokoSubtitle.js +95 -0
- package/lib/commonjs/ui/KokoSubtitle.js.map +1 -0
- package/lib/commonjs/ui/KokoTaskPanel.js +322 -0
- package/lib/commonjs/ui/KokoTaskPanel.js.map +1 -0
- package/lib/commonjs/ui/hostOverlay.js +126 -0
- package/lib/commonjs/ui/hostOverlay.js.map +1 -0
- package/lib/commonjs/ui/icons.js +132 -0
- package/lib/commonjs/ui/icons.js.map +1 -0
- package/lib/commonjs/ui/progressChrome.js +66 -0
- package/lib/commonjs/ui/progressChrome.js.map +1 -0
- package/lib/commonjs/ui/safeArea.js +96 -0
- package/lib/commonjs/ui/safeArea.js.map +1 -0
- package/lib/commonjs/ui/useOverlayOrigin.js +40 -0
- package/lib/commonjs/ui/useOverlayOrigin.js.map +1 -0
- package/lib/module/KokoAgent.js +1418 -0
- package/lib/module/KokoAgent.js.map +1 -0
- package/lib/module/assist/OnDeviceAssist.js +89 -0
- package/lib/module/assist/OnDeviceAssist.js.map +1 -0
- package/lib/module/assist/intentClassify.js +95 -0
- package/lib/module/assist/intentClassify.js.map +1 -0
- package/lib/module/assist/nodeRank.js +115 -0
- package/lib/module/assist/nodeRank.js.map +1 -0
- package/lib/module/assist/ocrTarget.js +123 -0
- package/lib/module/assist/ocrTarget.js.map +1 -0
- package/lib/module/assist/webVisualChange.js +24 -0
- package/lib/module/assist/webVisualChange.js.map +1 -0
- package/lib/module/client/ActionOutcomeUnknownError.js +23 -0
- package/lib/module/client/ActionOutcomeUnknownError.js.map +1 -0
- package/lib/module/client/AgentSession.js +854 -0
- package/lib/module/client/AgentSession.js.map +1 -0
- package/lib/module/client/GatewayClient.js +293 -0
- package/lib/module/client/GatewayClient.js.map +1 -0
- package/lib/module/client/OperationReplayGuard.js +58 -0
- package/lib/module/client/OperationReplayGuard.js.map +1 -0
- package/lib/module/client/RunCheckpoint.js +135 -0
- package/lib/module/client/RunCheckpoint.js.map +1 -0
- package/lib/module/client/loopDetect.js +177 -0
- package/lib/module/client/loopDetect.js.map +1 -0
- package/lib/module/client/recentSearch.js +46 -0
- package/lib/module/client/recentSearch.js.map +1 -0
- package/lib/module/client/walletChoiceGuard.js +14 -0
- package/lib/module/client/walletChoiceGuard.js.map +1 -0
- package/lib/module/client/webResultCategory.js +34 -0
- package/lib/module/client/webResultCategory.js.map +1 -0
- package/lib/module/commercial/AccountStore.js +75 -0
- package/lib/module/commercial/AccountStore.js.map +1 -0
- package/lib/module/commercial/AgentDebug.js +99 -0
- package/lib/module/commercial/AgentDebug.js.map +1 -0
- package/lib/module/commercial/ExecutionGuard.js +49 -0
- package/lib/module/commercial/ExecutionGuard.js.map +1 -0
- package/lib/module/commercial/UsageBus.js +38 -0
- package/lib/module/commercial/UsageBus.js.map +1 -0
- package/lib/module/commercial/branding.js +63 -0
- package/lib/module/commercial/branding.js.map +1 -0
- package/lib/module/commercial/devMode.js +45 -0
- package/lib/module/commercial/devMode.js.map +1 -0
- package/lib/module/commercial/trust.js +62 -0
- package/lib/module/commercial/trust.js.map +1 -0
- package/lib/module/confirmation/gate.js +54 -0
- package/lib/module/confirmation/gate.js.map +1 -0
- package/lib/module/cursor/CursorOverlay.js +136 -0
- package/lib/module/cursor/CursorOverlay.js.map +1 -0
- package/lib/module/cursor/PointerHand.js +47 -0
- package/lib/module/cursor/PointerHand.js.map +1 -0
- package/lib/module/cursor/controller.js +87 -0
- package/lib/module/cursor/controller.js.map +1 -0
- package/lib/module/cursor/pointer.js +45 -0
- package/lib/module/cursor/pointer.js.map +1 -0
- package/lib/module/debug/KokoRemoteSmoke.js +608 -0
- package/lib/module/debug/KokoRemoteSmoke.js.map +1 -0
- package/lib/module/explore/ExplorationGraph.js +406 -0
- package/lib/module/explore/ExplorationGraph.js.map +1 -0
- package/lib/module/explore/bootstrapExplore.js +55 -0
- package/lib/module/explore/bootstrapExplore.js.map +1 -0
- package/lib/module/host/HostActions.js +255 -0
- package/lib/module/host/HostActions.js.map +1 -0
- package/lib/module/i18n/index.js +159 -0
- package/lib/module/i18n/index.js.map +1 -0
- package/lib/module/i18n/runtimeMessages.js +180 -0
- package/lib/module/i18n/runtimeMessages.js.map +1 -0
- package/lib/module/index.js +44 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native/NativeBridge.js +128 -0
- package/lib/module/native/NativeBridge.js.map +1 -0
- package/lib/module/native/navigationLabel.js +12 -0
- package/lib/module/native/navigationLabel.js.map +1 -0
- package/lib/module/native/normalizeViewTree.js +25 -0
- package/lib/module/native/normalizeViewTree.js.map +1 -0
- package/lib/module/native/pruneOffscreen.js +72 -0
- package/lib/module/native/pruneOffscreen.js.map +1 -0
- package/lib/module/native/scrollViewport.js +63 -0
- package/lib/module/native/scrollViewport.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/privacy/redact.js +103 -0
- package/lib/module/privacy/redact.js.map +1 -0
- package/lib/module/privacy/screenEvidence.js +50 -0
- package/lib/module/privacy/screenEvidence.js.map +1 -0
- package/lib/module/privacy/screenModel.js +397 -0
- package/lib/module/privacy/screenModel.js.map +1 -0
- package/lib/module/privacy/sensitiveScreen.js +52 -0
- package/lib/module/privacy/sensitiveScreen.js.map +1 -0
- package/lib/module/privacy/userGate.js +202 -0
- package/lib/module/privacy/userGate.js.map +1 -0
- package/lib/module/protocol/actions.js +108 -0
- package/lib/module/protocol/actions.js.map +1 -0
- package/lib/module/provider/HostAppearanceStore.js +101 -0
- package/lib/module/provider/HostAppearanceStore.js.map +1 -0
- package/lib/module/provider/KokoProvider.js +74 -0
- package/lib/module/provider/KokoProvider.js.map +1 -0
- package/lib/module/provider/KokoUiContext.js +96 -0
- package/lib/module/provider/KokoUiContext.js.map +1 -0
- package/lib/module/selector/match.js +366 -0
- package/lib/module/selector/match.js.map +1 -0
- package/lib/module/task/TaskStore.js +356 -0
- package/lib/module/task/TaskStore.js.map +1 -0
- package/lib/module/task/types.js +2 -0
- package/lib/module/task/types.js.map +1 -0
- package/lib/module/task/useTaskStore.js +10 -0
- package/lib/module/task/useTaskStore.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/ui/KokoChatSheet.js +879 -0
- package/lib/module/ui/KokoChatSheet.js.map +1 -0
- package/lib/module/ui/KokoComposer.js +155 -0
- package/lib/module/ui/KokoComposer.js.map +1 -0
- package/lib/module/ui/KokoDialogHost.js +247 -0
- package/lib/module/ui/KokoDialogHost.js.map +1 -0
- package/lib/module/ui/KokoEntryButton.js +74 -0
- package/lib/module/ui/KokoEntryButton.js.map +1 -0
- package/lib/module/ui/KokoObserver.js +516 -0
- package/lib/module/ui/KokoObserver.js.map +1 -0
- package/lib/module/ui/KokoOverlayLayer.js +39 -0
- package/lib/module/ui/KokoOverlayLayer.js.map +1 -0
- package/lib/module/ui/KokoSubtitle.js +90 -0
- package/lib/module/ui/KokoSubtitle.js.map +1 -0
- package/lib/module/ui/KokoTaskPanel.js +316 -0
- package/lib/module/ui/KokoTaskPanel.js.map +1 -0
- package/lib/module/ui/hostOverlay.js +120 -0
- package/lib/module/ui/hostOverlay.js.map +1 -0
- package/lib/module/ui/icons.js +124 -0
- package/lib/module/ui/icons.js.map +1 -0
- package/lib/module/ui/progressChrome.js +58 -0
- package/lib/module/ui/progressChrome.js.map +1 -0
- package/lib/module/ui/safeArea.js +92 -0
- package/lib/module/ui/safeArea.js.map +1 -0
- package/lib/module/ui/useOverlayOrigin.js +36 -0
- package/lib/module/ui/useOverlayOrigin.js.map +1 -0
- package/lib/typescript/commonjs/KokoAgent.d.ts +167 -0
- package/lib/typescript/commonjs/KokoAgent.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/OnDeviceAssist.d.ts +31 -0
- package/lib/typescript/commonjs/assist/OnDeviceAssist.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/intentClassify.d.ts +18 -0
- package/lib/typescript/commonjs/assist/intentClassify.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/nodeRank.d.ts +32 -0
- package/lib/typescript/commonjs/assist/nodeRank.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/ocrTarget.d.ts +25 -0
- package/lib/typescript/commonjs/assist/ocrTarget.d.ts.map +1 -0
- package/lib/typescript/commonjs/assist/webVisualChange.d.ts +6 -0
- package/lib/typescript/commonjs/assist/webVisualChange.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/ActionOutcomeUnknownError.d.ts +13 -0
- package/lib/typescript/commonjs/client/ActionOutcomeUnknownError.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/AgentSession.d.ts +56 -0
- package/lib/typescript/commonjs/client/AgentSession.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/GatewayClient.d.ts +149 -0
- package/lib/typescript/commonjs/client/GatewayClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/OperationReplayGuard.d.ts +13 -0
- package/lib/typescript/commonjs/client/OperationReplayGuard.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/RunCheckpoint.d.ts +57 -0
- package/lib/typescript/commonjs/client/RunCheckpoint.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/loopDetect.d.ts +15 -0
- package/lib/typescript/commonjs/client/loopDetect.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/recentSearch.d.ts +5 -0
- package/lib/typescript/commonjs/client/recentSearch.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/walletChoiceGuard.d.ts +4 -0
- package/lib/typescript/commonjs/client/walletChoiceGuard.d.ts.map +1 -0
- package/lib/typescript/commonjs/client/webResultCategory.d.ts +5 -0
- package/lib/typescript/commonjs/client/webResultCategory.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/AccountStore.d.ts +17 -0
- package/lib/typescript/commonjs/commercial/AccountStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/AgentDebug.d.ts +25 -0
- package/lib/typescript/commonjs/commercial/AgentDebug.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/ExecutionGuard.d.ts +18 -0
- package/lib/typescript/commonjs/commercial/ExecutionGuard.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/UsageBus.d.ts +18 -0
- package/lib/typescript/commonjs/commercial/UsageBus.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/branding.d.ts +25 -0
- package/lib/typescript/commonjs/commercial/branding.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/devMode.d.ts +26 -0
- package/lib/typescript/commonjs/commercial/devMode.d.ts.map +1 -0
- package/lib/typescript/commonjs/commercial/trust.d.ts +21 -0
- package/lib/typescript/commonjs/commercial/trust.d.ts.map +1 -0
- package/lib/typescript/commonjs/confirmation/gate.d.ts +18 -0
- package/lib/typescript/commonjs/confirmation/gate.d.ts.map +1 -0
- package/lib/typescript/commonjs/cursor/CursorOverlay.d.ts +13 -0
- package/lib/typescript/commonjs/cursor/CursorOverlay.d.ts.map +1 -0
- package/lib/typescript/commonjs/cursor/PointerHand.d.ts +16 -0
- package/lib/typescript/commonjs/cursor/PointerHand.d.ts.map +1 -0
- package/lib/typescript/commonjs/cursor/controller.d.ts +30 -0
- package/lib/typescript/commonjs/cursor/controller.d.ts.map +1 -0
- package/lib/typescript/commonjs/cursor/pointer.d.ts +23 -0
- package/lib/typescript/commonjs/cursor/pointer.d.ts.map +1 -0
- package/lib/typescript/commonjs/debug/KokoRemoteSmoke.d.ts +96 -0
- package/lib/typescript/commonjs/debug/KokoRemoteSmoke.d.ts.map +1 -0
- package/lib/typescript/commonjs/explore/ExplorationGraph.d.ts +96 -0
- package/lib/typescript/commonjs/explore/ExplorationGraph.d.ts.map +1 -0
- package/lib/typescript/commonjs/explore/bootstrapExplore.d.ts +40 -0
- package/lib/typescript/commonjs/explore/bootstrapExplore.d.ts.map +1 -0
- package/lib/typescript/commonjs/host/HostActions.d.ts +101 -0
- package/lib/typescript/commonjs/host/HostActions.d.ts.map +1 -0
- package/lib/typescript/commonjs/i18n/index.d.ts +20 -0
- package/lib/typescript/commonjs/i18n/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/i18n/runtimeMessages.d.ts +178 -0
- package/lib/typescript/commonjs/i18n/runtimeMessages.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +66 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/NativeBridge.d.ts +70 -0
- package/lib/typescript/commonjs/native/NativeBridge.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/navigationLabel.d.ts +4 -0
- package/lib/typescript/commonjs/native/navigationLabel.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/normalizeViewTree.d.ts +4 -0
- package/lib/typescript/commonjs/native/normalizeViewTree.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/pruneOffscreen.d.ts +3 -0
- package/lib/typescript/commonjs/native/pruneOffscreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/native/scrollViewport.d.ts +13 -0
- package/lib/typescript/commonjs/native/scrollViewport.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/privacy/redact.d.ts +9 -0
- package/lib/typescript/commonjs/privacy/redact.d.ts.map +1 -0
- package/lib/typescript/commonjs/privacy/screenEvidence.d.ts +14 -0
- package/lib/typescript/commonjs/privacy/screenEvidence.d.ts.map +1 -0
- package/lib/typescript/commonjs/privacy/screenModel.d.ts +26 -0
- package/lib/typescript/commonjs/privacy/screenModel.d.ts.map +1 -0
- package/lib/typescript/commonjs/privacy/sensitiveScreen.d.ts +8 -0
- package/lib/typescript/commonjs/privacy/sensitiveScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/privacy/userGate.d.ts +58 -0
- package/lib/typescript/commonjs/privacy/userGate.d.ts.map +1 -0
- package/lib/typescript/commonjs/protocol/actions.d.ts +7 -0
- package/lib/typescript/commonjs/protocol/actions.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/HostAppearanceStore.d.ts +64 -0
- package/lib/typescript/commonjs/provider/HostAppearanceStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/KokoProvider.d.ts +39 -0
- package/lib/typescript/commonjs/provider/KokoProvider.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider/KokoUiContext.d.ts +22 -0
- package/lib/typescript/commonjs/provider/KokoUiContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/selector/match.d.ts +27 -0
- package/lib/typescript/commonjs/selector/match.d.ts.map +1 -0
- package/lib/typescript/commonjs/task/TaskStore.d.ts +65 -0
- package/lib/typescript/commonjs/task/TaskStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/task/types.d.ts +66 -0
- package/lib/typescript/commonjs/task/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/task/useTaskStore.d.ts +3 -0
- package/lib/typescript/commonjs/task/useTaskStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/types.d.ts +372 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoChatSheet.d.ts +8 -0
- package/lib/typescript/commonjs/ui/KokoChatSheet.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoComposer.d.ts +13 -0
- package/lib/typescript/commonjs/ui/KokoComposer.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoDialogHost.d.ts +32 -0
- package/lib/typescript/commonjs/ui/KokoDialogHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoEntryButton.d.ts +18 -0
- package/lib/typescript/commonjs/ui/KokoEntryButton.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoObserver.d.ts +13 -0
- package/lib/typescript/commonjs/ui/KokoObserver.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoOverlayLayer.d.ts +35 -0
- package/lib/typescript/commonjs/ui/KokoOverlayLayer.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoSubtitle.d.ts +7 -0
- package/lib/typescript/commonjs/ui/KokoSubtitle.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/KokoTaskPanel.d.ts +7 -0
- package/lib/typescript/commonjs/ui/KokoTaskPanel.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hostOverlay.d.ts +28 -0
- package/lib/typescript/commonjs/ui/hostOverlay.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/icons.d.ts +26 -0
- package/lib/typescript/commonjs/ui/icons.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/progressChrome.d.ts +21 -0
- package/lib/typescript/commonjs/ui/progressChrome.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/safeArea.d.ts +12 -0
- package/lib/typescript/commonjs/ui/safeArea.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/useOverlayOrigin.d.ts +22 -0
- package/lib/typescript/commonjs/ui/useOverlayOrigin.d.ts.map +1 -0
- package/lib/typescript/module/KokoAgent.d.ts +167 -0
- package/lib/typescript/module/KokoAgent.d.ts.map +1 -0
- package/lib/typescript/module/assist/OnDeviceAssist.d.ts +31 -0
- package/lib/typescript/module/assist/OnDeviceAssist.d.ts.map +1 -0
- package/lib/typescript/module/assist/intentClassify.d.ts +18 -0
- package/lib/typescript/module/assist/intentClassify.d.ts.map +1 -0
- package/lib/typescript/module/assist/nodeRank.d.ts +32 -0
- package/lib/typescript/module/assist/nodeRank.d.ts.map +1 -0
- package/lib/typescript/module/assist/ocrTarget.d.ts +25 -0
- package/lib/typescript/module/assist/ocrTarget.d.ts.map +1 -0
- package/lib/typescript/module/assist/webVisualChange.d.ts +6 -0
- package/lib/typescript/module/assist/webVisualChange.d.ts.map +1 -0
- package/lib/typescript/module/client/ActionOutcomeUnknownError.d.ts +13 -0
- package/lib/typescript/module/client/ActionOutcomeUnknownError.d.ts.map +1 -0
- package/lib/typescript/module/client/AgentSession.d.ts +56 -0
- package/lib/typescript/module/client/AgentSession.d.ts.map +1 -0
- package/lib/typescript/module/client/GatewayClient.d.ts +149 -0
- package/lib/typescript/module/client/GatewayClient.d.ts.map +1 -0
- package/lib/typescript/module/client/OperationReplayGuard.d.ts +13 -0
- package/lib/typescript/module/client/OperationReplayGuard.d.ts.map +1 -0
- package/lib/typescript/module/client/RunCheckpoint.d.ts +57 -0
- package/lib/typescript/module/client/RunCheckpoint.d.ts.map +1 -0
- package/lib/typescript/module/client/loopDetect.d.ts +15 -0
- package/lib/typescript/module/client/loopDetect.d.ts.map +1 -0
- package/lib/typescript/module/client/recentSearch.d.ts +5 -0
- package/lib/typescript/module/client/recentSearch.d.ts.map +1 -0
- package/lib/typescript/module/client/walletChoiceGuard.d.ts +4 -0
- package/lib/typescript/module/client/walletChoiceGuard.d.ts.map +1 -0
- package/lib/typescript/module/client/webResultCategory.d.ts +5 -0
- package/lib/typescript/module/client/webResultCategory.d.ts.map +1 -0
- package/lib/typescript/module/commercial/AccountStore.d.ts +17 -0
- package/lib/typescript/module/commercial/AccountStore.d.ts.map +1 -0
- package/lib/typescript/module/commercial/AgentDebug.d.ts +25 -0
- package/lib/typescript/module/commercial/AgentDebug.d.ts.map +1 -0
- package/lib/typescript/module/commercial/ExecutionGuard.d.ts +18 -0
- package/lib/typescript/module/commercial/ExecutionGuard.d.ts.map +1 -0
- package/lib/typescript/module/commercial/UsageBus.d.ts +18 -0
- package/lib/typescript/module/commercial/UsageBus.d.ts.map +1 -0
- package/lib/typescript/module/commercial/branding.d.ts +25 -0
- package/lib/typescript/module/commercial/branding.d.ts.map +1 -0
- package/lib/typescript/module/commercial/devMode.d.ts +26 -0
- package/lib/typescript/module/commercial/devMode.d.ts.map +1 -0
- package/lib/typescript/module/commercial/trust.d.ts +21 -0
- package/lib/typescript/module/commercial/trust.d.ts.map +1 -0
- package/lib/typescript/module/confirmation/gate.d.ts +18 -0
- package/lib/typescript/module/confirmation/gate.d.ts.map +1 -0
- package/lib/typescript/module/cursor/CursorOverlay.d.ts +13 -0
- package/lib/typescript/module/cursor/CursorOverlay.d.ts.map +1 -0
- package/lib/typescript/module/cursor/PointerHand.d.ts +16 -0
- package/lib/typescript/module/cursor/PointerHand.d.ts.map +1 -0
- package/lib/typescript/module/cursor/controller.d.ts +30 -0
- package/lib/typescript/module/cursor/controller.d.ts.map +1 -0
- package/lib/typescript/module/cursor/pointer.d.ts +23 -0
- package/lib/typescript/module/cursor/pointer.d.ts.map +1 -0
- package/lib/typescript/module/debug/KokoRemoteSmoke.d.ts +96 -0
- package/lib/typescript/module/debug/KokoRemoteSmoke.d.ts.map +1 -0
- package/lib/typescript/module/explore/ExplorationGraph.d.ts +96 -0
- package/lib/typescript/module/explore/ExplorationGraph.d.ts.map +1 -0
- package/lib/typescript/module/explore/bootstrapExplore.d.ts +40 -0
- package/lib/typescript/module/explore/bootstrapExplore.d.ts.map +1 -0
- package/lib/typescript/module/host/HostActions.d.ts +101 -0
- package/lib/typescript/module/host/HostActions.d.ts.map +1 -0
- package/lib/typescript/module/i18n/index.d.ts +20 -0
- package/lib/typescript/module/i18n/index.d.ts.map +1 -0
- package/lib/typescript/module/i18n/runtimeMessages.d.ts +178 -0
- package/lib/typescript/module/i18n/runtimeMessages.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +66 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/native/NativeBridge.d.ts +70 -0
- package/lib/typescript/module/native/NativeBridge.d.ts.map +1 -0
- package/lib/typescript/module/native/navigationLabel.d.ts +4 -0
- package/lib/typescript/module/native/navigationLabel.d.ts.map +1 -0
- package/lib/typescript/module/native/normalizeViewTree.d.ts +4 -0
- package/lib/typescript/module/native/normalizeViewTree.d.ts.map +1 -0
- package/lib/typescript/module/native/pruneOffscreen.d.ts +3 -0
- package/lib/typescript/module/native/pruneOffscreen.d.ts.map +1 -0
- package/lib/typescript/module/native/scrollViewport.d.ts +13 -0
- package/lib/typescript/module/native/scrollViewport.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/privacy/redact.d.ts +9 -0
- package/lib/typescript/module/privacy/redact.d.ts.map +1 -0
- package/lib/typescript/module/privacy/screenEvidence.d.ts +14 -0
- package/lib/typescript/module/privacy/screenEvidence.d.ts.map +1 -0
- package/lib/typescript/module/privacy/screenModel.d.ts +26 -0
- package/lib/typescript/module/privacy/screenModel.d.ts.map +1 -0
- package/lib/typescript/module/privacy/sensitiveScreen.d.ts +8 -0
- package/lib/typescript/module/privacy/sensitiveScreen.d.ts.map +1 -0
- package/lib/typescript/module/privacy/userGate.d.ts +58 -0
- package/lib/typescript/module/privacy/userGate.d.ts.map +1 -0
- package/lib/typescript/module/protocol/actions.d.ts +7 -0
- package/lib/typescript/module/protocol/actions.d.ts.map +1 -0
- package/lib/typescript/module/provider/HostAppearanceStore.d.ts +64 -0
- package/lib/typescript/module/provider/HostAppearanceStore.d.ts.map +1 -0
- package/lib/typescript/module/provider/KokoProvider.d.ts +39 -0
- package/lib/typescript/module/provider/KokoProvider.d.ts.map +1 -0
- package/lib/typescript/module/provider/KokoUiContext.d.ts +22 -0
- package/lib/typescript/module/provider/KokoUiContext.d.ts.map +1 -0
- package/lib/typescript/module/selector/match.d.ts +27 -0
- package/lib/typescript/module/selector/match.d.ts.map +1 -0
- package/lib/typescript/module/task/TaskStore.d.ts +65 -0
- package/lib/typescript/module/task/TaskStore.d.ts.map +1 -0
- package/lib/typescript/module/task/types.d.ts +66 -0
- package/lib/typescript/module/task/types.d.ts.map +1 -0
- package/lib/typescript/module/task/useTaskStore.d.ts +3 -0
- package/lib/typescript/module/task/useTaskStore.d.ts.map +1 -0
- package/lib/typescript/module/types.d.ts +372 -0
- package/lib/typescript/module/types.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoChatSheet.d.ts +8 -0
- package/lib/typescript/module/ui/KokoChatSheet.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoComposer.d.ts +13 -0
- package/lib/typescript/module/ui/KokoComposer.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoDialogHost.d.ts +32 -0
- package/lib/typescript/module/ui/KokoDialogHost.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoEntryButton.d.ts +18 -0
- package/lib/typescript/module/ui/KokoEntryButton.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoObserver.d.ts +13 -0
- package/lib/typescript/module/ui/KokoObserver.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoOverlayLayer.d.ts +35 -0
- package/lib/typescript/module/ui/KokoOverlayLayer.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoSubtitle.d.ts +7 -0
- package/lib/typescript/module/ui/KokoSubtitle.d.ts.map +1 -0
- package/lib/typescript/module/ui/KokoTaskPanel.d.ts +7 -0
- package/lib/typescript/module/ui/KokoTaskPanel.d.ts.map +1 -0
- package/lib/typescript/module/ui/hostOverlay.d.ts +28 -0
- package/lib/typescript/module/ui/hostOverlay.d.ts.map +1 -0
- package/lib/typescript/module/ui/icons.d.ts +26 -0
- package/lib/typescript/module/ui/icons.d.ts.map +1 -0
- package/lib/typescript/module/ui/progressChrome.d.ts +21 -0
- package/lib/typescript/module/ui/progressChrome.d.ts.map +1 -0
- package/lib/typescript/module/ui/safeArea.d.ts +12 -0
- package/lib/typescript/module/ui/safeArea.d.ts.map +1 -0
- package/lib/typescript/module/ui/useOverlayOrigin.d.ts +22 -0
- package/lib/typescript/module/ui/useOverlayOrigin.d.ts.map +1 -0
- package/package.json +153 -0
- package/react-native.config.js +12 -0
- package/src/KokoAgent.ts +1415 -0
- package/src/assist/OnDeviceAssist.ts +133 -0
- package/src/assist/intentClassify.ts +122 -0
- package/src/assist/nodeRank.ts +167 -0
- package/src/assist/ocrTarget.ts +146 -0
- package/src/assist/webVisualChange.ts +24 -0
- package/src/client/ActionOutcomeUnknownError.ts +18 -0
- package/src/client/AgentSession.ts +952 -0
- package/src/client/GatewayClient.ts +439 -0
- package/src/client/OperationReplayGuard.ts +61 -0
- package/src/client/RunCheckpoint.ts +154 -0
- package/src/client/loopDetect.ts +213 -0
- package/src/client/recentSearch.ts +47 -0
- package/src/client/walletChoiceGuard.ts +15 -0
- package/src/client/webResultCategory.ts +28 -0
- package/src/commercial/AccountStore.ts +85 -0
- package/src/commercial/AgentDebug.ts +142 -0
- package/src/commercial/ExecutionGuard.ts +67 -0
- package/src/commercial/UsageBus.ts +44 -0
- package/src/commercial/branding.ts +97 -0
- package/src/commercial/devMode.ts +48 -0
- package/src/commercial/trust.ts +67 -0
- package/src/confirmation/gate.ts +82 -0
- package/src/cursor/CursorOverlay.tsx +138 -0
- package/src/cursor/PointerHand.tsx +49 -0
- package/src/cursor/controller.ts +98 -0
- package/src/cursor/pointer.ts +40 -0
- package/src/debug/KokoRemoteSmoke.tsx +561 -0
- package/src/explore/ExplorationGraph.ts +518 -0
- package/src/explore/bootstrapExplore.ts +89 -0
- package/src/host/HostActions.ts +349 -0
- package/src/i18n/index.ts +229 -0
- package/src/i18n/runtimeMessages.ts +180 -0
- package/src/index.ts +203 -0
- package/src/native/NativeBridge.ts +207 -0
- package/src/native/navigationLabel.ts +11 -0
- package/src/native/normalizeViewTree.ts +16 -0
- package/src/native/pruneOffscreen.ts +72 -0
- package/src/native/scrollViewport.ts +51 -0
- package/src/privacy/redact.ts +115 -0
- package/src/privacy/screenEvidence.ts +55 -0
- package/src/privacy/screenModel.ts +504 -0
- package/src/privacy/sensitiveScreen.ts +55 -0
- package/src/privacy/userGate.ts +233 -0
- package/src/protocol/actions.ts +66 -0
- package/src/provider/HostAppearanceStore.ts +148 -0
- package/src/provider/KokoProvider.tsx +118 -0
- package/src/provider/KokoUiContext.tsx +162 -0
- package/src/selector/match.ts +417 -0
- package/src/task/TaskStore.ts +387 -0
- package/src/task/types.ts +82 -0
- package/src/task/useTaskStore.ts +9 -0
- package/src/types.ts +318 -0
- package/src/ui/KokoChatSheet.tsx +930 -0
- package/src/ui/KokoComposer.tsx +174 -0
- package/src/ui/KokoDialogHost.tsx +249 -0
- package/src/ui/KokoEntryButton.tsx +74 -0
- package/src/ui/KokoObserver.tsx +587 -0
- package/src/ui/KokoOverlayLayer.tsx +53 -0
- package/src/ui/KokoSubtitle.tsx +80 -0
- package/src/ui/KokoTaskPanel.tsx +299 -0
- package/src/ui/hostOverlay.tsx +148 -0
- package/src/ui/icons.tsx +145 -0
- package/src/ui/progressChrome.ts +73 -0
- package/src/ui/safeArea.ts +95 -0
- package/src/ui/useOverlayOrigin.ts +29 -0
package/src/KokoAgent.ts
ADDED
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
import { Dimensions, Platform } from 'react-native';
|
|
2
|
+
import { CursorController } from './cursor/controller';
|
|
3
|
+
import { isSensitiveLabel, runConfirmationGate } from './confirmation/gate';
|
|
4
|
+
import { NativeBridge } from './native/NativeBridge';
|
|
5
|
+
import { executeAgentAction } from './protocol/actions';
|
|
6
|
+
import { centerOf, centerOfLabel, find, findAll, findBySoftLabel, findNodeById, flattenTree } from './selector/match';
|
|
7
|
+
import { sensitiveScreenKind } from './privacy/sensitiveScreen';
|
|
8
|
+
import { boundedScrollGesture, scrollViewports } from './native/scrollViewport';
|
|
9
|
+
import { canRunHostActionForNode, HostActionsStore, runHostAction } from './host/HostActions';
|
|
10
|
+
import { screenSnapshotLine } from './privacy/screenModel';
|
|
11
|
+
import { TaskStore, type TaskStoreImpl } from './task/TaskStore';
|
|
12
|
+
import { AgentSession, type AgentSessionOptions } from './client/AgentSession';
|
|
13
|
+
import { RunCheckpoint } from './client/RunCheckpoint';
|
|
14
|
+
import { ActionOutcomeUnknownError, ActionValidationError } from './client/ActionOutcomeUnknownError';
|
|
15
|
+
import { ExplorationGraph } from './explore/ExplorationGraph';
|
|
16
|
+
import { DialogBus } from './ui/KokoDialogHost';
|
|
17
|
+
import { pointsOverlapProgressChrome } from './ui/progressChrome';
|
|
18
|
+
import { UsageBus } from './commercial/UsageBus';
|
|
19
|
+
import { AccountStore } from './commercial/AccountStore';
|
|
20
|
+
import { ExecutionGuard } from './commercial/ExecutionGuard';
|
|
21
|
+
import { AgentDebug } from './commercial/AgentDebug';
|
|
22
|
+
import { gatewayLocale, currentLocale, t, runtimeText, message, type MessageKey, type MessageVars, type LocalizedText } from './i18n';
|
|
23
|
+
import { findOcrTextHit, ocrHitInFrames, visibleWebViewFrames } from './assist/ocrTarget';
|
|
24
|
+
import type {
|
|
25
|
+
AgentAction,
|
|
26
|
+
AgentActionResult,
|
|
27
|
+
DumpOptions,
|
|
28
|
+
KokoAgentConfig,
|
|
29
|
+
PinchOptions,
|
|
30
|
+
Point,
|
|
31
|
+
Selector,
|
|
32
|
+
SensitiveConfirmContext,
|
|
33
|
+
SwipeOptions,
|
|
34
|
+
TapOptions,
|
|
35
|
+
TypeOptions,
|
|
36
|
+
ViewNode,
|
|
37
|
+
} from './types';
|
|
38
|
+
|
|
39
|
+
export type TapTarget = {
|
|
40
|
+
selector?: Selector;
|
|
41
|
+
nodeId?: string;
|
|
42
|
+
point?: Point;
|
|
43
|
+
options?: TapOptions;
|
|
44
|
+
/** Used by confirmation gate + subtitle. */
|
|
45
|
+
label?: string;
|
|
46
|
+
/** Internal: preserve input-field targeting while focusing a type action. */
|
|
47
|
+
inputTarget?: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type TypeTarget = {
|
|
51
|
+
selector?: Selector;
|
|
52
|
+
nodeId?: string;
|
|
53
|
+
options?: TypeOptions;
|
|
54
|
+
label?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Koko Agent SDK runtime — dump / find / tap / swipe / pinch / type + cursor + confirm gate + task log.
|
|
59
|
+
*/
|
|
60
|
+
export class KokoAgent {
|
|
61
|
+
private config: KokoAgentConfig;
|
|
62
|
+
private lastTree: ViewNode | null = null;
|
|
63
|
+
private planningTree: ViewNode | null = null;
|
|
64
|
+
// OCR ids are local references to observed text, never caller-supplied points.
|
|
65
|
+
private readonly ocrTargets = new Map<string, { text: string; webview: boolean }>();
|
|
66
|
+
private nextOcrTargetId = 0;
|
|
67
|
+
/** Labels already confirmed in the current runGoal — avoid repeat dialogs on retry. */
|
|
68
|
+
private confirmedSensitive = new Set<string>();
|
|
69
|
+
/** Last tap used a host `run` callback (tab/CTA) — skip false tap_noop. */
|
|
70
|
+
lastTapViaHost: { kind?: string; label?: string } | null = null;
|
|
71
|
+
/** Current NL goal; direct SDK callers may opt into visible scroll search. */
|
|
72
|
+
activeGoal?: string;
|
|
73
|
+
|
|
74
|
+
constructor(config: KokoAgentConfig = {}) {
|
|
75
|
+
this.config = {
|
|
76
|
+
dumpMaxDepth: 80,
|
|
77
|
+
dumpSkipInvisible: true,
|
|
78
|
+
cursorDurationMs: 480,
|
|
79
|
+
observer: true,
|
|
80
|
+
...config,
|
|
81
|
+
confirmation: {
|
|
82
|
+
requireUserConfirm: true,
|
|
83
|
+
mode: 'preflight',
|
|
84
|
+
...config.confirmation,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
if (!this.config.onConfirmSensitive) {
|
|
88
|
+
this.config.onConfirmSensitive = async (ctx) => {
|
|
89
|
+
const locale = currentLocale(this.config.locale);
|
|
90
|
+
const label = ctx.label ?? ctx.action;
|
|
91
|
+
return DialogBus.requestConfirm(
|
|
92
|
+
ctx.action === 'preflight'
|
|
93
|
+
? t(locale, 'confirm.preflightTitle')
|
|
94
|
+
: t(locale, 'confirm.inlineTitle'),
|
|
95
|
+
ctx.action === 'preflight'
|
|
96
|
+
? t(locale, 'confirm.preflightBody', { label })
|
|
97
|
+
: t(locale, 'confirm.inlineBody', { label }),
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
configure(patch: Partial<KokoAgentConfig>) {
|
|
104
|
+
this.config = { ...this.config, ...patch };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getConfig(): KokoAgentConfig {
|
|
108
|
+
return this.config;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private message(key: MessageKey, vars?: MessageVars) {
|
|
112
|
+
return message(key, vars, this.config.locale);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private text(key: MessageKey, vars?: MessageVars) {
|
|
116
|
+
return runtimeText(key, vars, this.config.locale);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
isNativeReady(): boolean {
|
|
120
|
+
return NativeBridge.isAvailable();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Start a user-facing task (shows subtitle + appears in panel). */
|
|
124
|
+
startTask(title: string) {
|
|
125
|
+
this.confirmedSensitive.clear();
|
|
126
|
+
this.ocrTargets.clear();
|
|
127
|
+
this.planningTree = null;
|
|
128
|
+
const task = TaskStore.startTask(title);
|
|
129
|
+
UsageBus.emit({
|
|
130
|
+
type: 'task_started',
|
|
131
|
+
taskId: task.id,
|
|
132
|
+
goal: title,
|
|
133
|
+
at: Date.now(),
|
|
134
|
+
});
|
|
135
|
+
return task;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
finishTask(
|
|
139
|
+
status: 'success' | 'failed' | 'cancelled' = 'success',
|
|
140
|
+
summary?: LocalizedText,
|
|
141
|
+
) {
|
|
142
|
+
this.confirmedSensitive.clear();
|
|
143
|
+
CursorController.hide();
|
|
144
|
+
const snap = TaskStore.snapshot();
|
|
145
|
+
const active = snap.tasks.find((t) => t.id === snap.activeTaskId);
|
|
146
|
+
TaskStore.finishTask(status, undefined, summary);
|
|
147
|
+
UsageBus.emit({
|
|
148
|
+
type: 'task_finished',
|
|
149
|
+
taskId: active?.id,
|
|
150
|
+
status,
|
|
151
|
+
steps: active?.nodes?.length,
|
|
152
|
+
at: Date.now(),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Manually append an observer log node (for cloud agent narration). */
|
|
157
|
+
log(
|
|
158
|
+
kind: Parameters<typeof TaskStore.appendNode>[0]['kind'],
|
|
159
|
+
title: LocalizedText,
|
|
160
|
+
detail?: string,
|
|
161
|
+
) {
|
|
162
|
+
if (this.config.observer === false) return null;
|
|
163
|
+
return TaskStore.appendNode({ kind, title, detail, status: 'done' });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Open a cloud-backed agent session (official gateway; LLM vendor is server-side). */
|
|
167
|
+
createSession(opts: AgentSessionOptions): AgentSession {
|
|
168
|
+
return new AgentSession(this, {
|
|
169
|
+
...opts,
|
|
170
|
+
onAskUser: opts.onAskUser ?? ((q) => DialogBus.requestAsk(q)),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* One-shot: talk to official gateway → local dump/find/tap until done.
|
|
176
|
+
* Users never supply model API keys. Token comes from opts or KokoProvider config.gateway.
|
|
177
|
+
*/
|
|
178
|
+
private activeSession: AgentSession | null = null;
|
|
179
|
+
private executionEpoch = 0;
|
|
180
|
+
private claimingCheckpoint = false;
|
|
181
|
+
|
|
182
|
+
/** @internal Sessions created through createSession share runGoal's owner. */
|
|
183
|
+
acquireSession(session: AgentSession): boolean {
|
|
184
|
+
if ((this.activeSession && this.activeSession !== session) || this.claimingCheckpoint) {
|
|
185
|
+
throw new Error(runtimeText('runtime.alreadyRunning', undefined, this.config?.locale));
|
|
186
|
+
}
|
|
187
|
+
const acquired = !this.activeSession;
|
|
188
|
+
this.activeSession = session;
|
|
189
|
+
return acquired;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** @internal Release only an owner acquired by a direct session.run(). */
|
|
193
|
+
releaseSession(session: AgentSession) {
|
|
194
|
+
if (this.activeSession === session) this.activeSession = null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** @internal Invalidates gestures already waiting on confirmation/animation. */
|
|
198
|
+
cancelPendingActions() {
|
|
199
|
+
this.executionEpoch++;
|
|
200
|
+
CursorController.hide();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private assertExecutionCurrent(epoch: number) {
|
|
204
|
+
if (epoch !== this.executionEpoch) throw new Error('KokoAgent: task cancelled');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async runGoal(
|
|
208
|
+
goal: string,
|
|
209
|
+
opts?: Partial<AgentSessionOptions> & { resumes?: number },
|
|
210
|
+
) {
|
|
211
|
+
if (this.activeSession || this.claimingCheckpoint) {
|
|
212
|
+
throw new Error(runtimeText('runtime.alreadyRunning', undefined, this.config?.locale));
|
|
213
|
+
}
|
|
214
|
+
const token = opts?.token ?? this.config.gateway?.token;
|
|
215
|
+
if (!token) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
this.text('runtime.missingToken'),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const baseUrl = opts?.baseUrl ?? this.config.gateway?.baseUrl;
|
|
221
|
+
const locale = gatewayLocale(
|
|
222
|
+
currentLocale(opts?.locale ?? this.config.locale),
|
|
223
|
+
);
|
|
224
|
+
void AccountStore.refresh({ token, baseUrl });
|
|
225
|
+
const session = this.createSession({
|
|
226
|
+
locale,
|
|
227
|
+
resumedFromReload: (opts?.resumes ?? 0) > 0,
|
|
228
|
+
localRetries: opts?.localRetries,
|
|
229
|
+
userGateTimeoutMs: opts?.userGateTimeoutMs,
|
|
230
|
+
onAskUser: opts?.onAskUser,
|
|
231
|
+
onNarration: opts?.onNarration,
|
|
232
|
+
baseUrl,
|
|
233
|
+
token,
|
|
234
|
+
});
|
|
235
|
+
this.activeSession = session;
|
|
236
|
+
// An action can take down the context running it (locale/theme/RTL change,
|
|
237
|
+
// feature-flag remount). Persist the goal so the next init can resume it;
|
|
238
|
+
// see RunCheckpoint. Never let storage trouble block a run.
|
|
239
|
+
try {
|
|
240
|
+
await RunCheckpoint.begin(goal, opts?.resumes ?? 0).catch(() => {});
|
|
241
|
+
return await session.run(goal);
|
|
242
|
+
} finally {
|
|
243
|
+
// Keep ownership until cleanup settles; stop() must not let a second run
|
|
244
|
+
// overlap an outstanding gateway response or pending native gesture.
|
|
245
|
+
await RunCheckpoint.clear().catch(() => {});
|
|
246
|
+
if (this.activeSession === session) this.activeSession = null;
|
|
247
|
+
void AccountStore.refresh({ token, baseUrl });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Pick up a run that a JS-context restart interrupted, if any.
|
|
253
|
+
*
|
|
254
|
+
* Called once per init by KokoProvider. Resolves to the resumed goal, or null
|
|
255
|
+
* when there was nothing fresh to resume. The planner decides what is left to
|
|
256
|
+
* do from the screen the App came back on — if the goal already happened, it
|
|
257
|
+
* answers `done` without touching anything.
|
|
258
|
+
*/
|
|
259
|
+
async resumeInterruptedRun(): Promise<string | null> {
|
|
260
|
+
if (this.activeSession || this.claimingCheckpoint) return null;
|
|
261
|
+
this.claimingCheckpoint = true;
|
|
262
|
+
const epoch = this.executionEpoch;
|
|
263
|
+
let pending;
|
|
264
|
+
try {
|
|
265
|
+
pending = await RunCheckpoint.claimResumable().catch(() => null);
|
|
266
|
+
} finally {
|
|
267
|
+
this.claimingCheckpoint = false;
|
|
268
|
+
}
|
|
269
|
+
if (!pending || epoch !== this.executionEpoch) return null;
|
|
270
|
+
try {
|
|
271
|
+
await this.runGoal(pending.goal, { resumes: pending.resumes + 1 });
|
|
272
|
+
} catch {
|
|
273
|
+
// A resume is best-effort; a failure here must not break host startup.
|
|
274
|
+
}
|
|
275
|
+
return pending.goal;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* On-device exploration crawl — builds a local screen/transition graph
|
|
280
|
+
* without a host feature tree. Safe-ish (skips sensitive labels). Call
|
|
281
|
+
* after SDK init / on a debug build; then runGoal can reuse the graph.
|
|
282
|
+
*/
|
|
283
|
+
async bootstrapExplore(
|
|
284
|
+
opts?: import('./explore/bootstrapExplore').BootstrapExploreOptions,
|
|
285
|
+
) {
|
|
286
|
+
const { bootstrapExplore } = await import('./explore/bootstrapExplore');
|
|
287
|
+
return bootstrapExplore(this, opts);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Snapshot of the on-device exploration graph (nodes/edges). */
|
|
291
|
+
getExplorationGraph() {
|
|
292
|
+
return ExplorationGraph.snapshot();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
clearExplorationGraph() {
|
|
296
|
+
ExplorationGraph.clear();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Refresh plan / quota / entitlements from gateway. */
|
|
300
|
+
async refreshAccount() {
|
|
301
|
+
const token = this.config.gateway?.token;
|
|
302
|
+
if (!token) return null;
|
|
303
|
+
return AccountStore.refresh({
|
|
304
|
+
token,
|
|
305
|
+
baseUrl: this.config.gateway?.baseUrl,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
getAccount() {
|
|
310
|
+
return AccountStore.get();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Stop the in-flight runGoal / session, if any. */
|
|
314
|
+
stop() {
|
|
315
|
+
if (this.activeSession) this.activeSession.stop();
|
|
316
|
+
else this.cancelPendingActions();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Open chat sheet (history + NL input) from host UI. */
|
|
320
|
+
openChat(taskId?: string | null) {
|
|
321
|
+
TaskStore.openSheet(taskId);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
closeChat() {
|
|
325
|
+
TaskStore.closeSheet();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
getTaskStore(): TaskStoreImpl {
|
|
329
|
+
return TaskStore;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async dump(options?: DumpOptions): Promise<ViewNode> {
|
|
333
|
+
ExecutionGuard.assert('dump');
|
|
334
|
+
// Sheet Modal lives in another window and blocks host; close before scan.
|
|
335
|
+
if (TaskStore.snapshot().ui.sheetOpen) {
|
|
336
|
+
TaskStore.closeSheet();
|
|
337
|
+
// Host apps (OneKey) freezeOnBlur — need a beat to remount Empty states.
|
|
338
|
+
await sleep(480);
|
|
339
|
+
}
|
|
340
|
+
const silent = options?.silent === true;
|
|
341
|
+
const node = silent ? { id: '' } : this.track('dump', this.message('runtime.scan'));
|
|
342
|
+
try {
|
|
343
|
+
const tree = await NativeBridge.dump({
|
|
344
|
+
maxDepth: options?.maxDepth ?? this.config.dumpMaxDepth,
|
|
345
|
+
skipInvisible: options?.skipInvisible ?? this.config.dumpSkipInvisible,
|
|
346
|
+
});
|
|
347
|
+
this.lastTree = tree;
|
|
348
|
+
this.warnIfDepthCapped(
|
|
349
|
+
tree,
|
|
350
|
+
options?.maxDepth ?? this.config.dumpMaxDepth ?? 0,
|
|
351
|
+
);
|
|
352
|
+
if (!silent) this.done(node, this.message('runtime.scanned'));
|
|
353
|
+
return tree;
|
|
354
|
+
} catch (e) {
|
|
355
|
+
if (!silent) this.fail(node, e);
|
|
356
|
+
throw e;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Screenshot the host app. By default hides Koko overlay layers
|
|
362
|
+
* (progress / cursor / sheet) so the image matches what dump sees.
|
|
363
|
+
*/
|
|
364
|
+
async screenshot(options?: { hideOverlays?: boolean }) {
|
|
365
|
+
ExecutionGuard.assert('screenshot');
|
|
366
|
+
return NativeBridge.screenshot({
|
|
367
|
+
hideOverlays: options?.hideOverlays ?? true,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
getLastTree(): ViewNode | null {
|
|
372
|
+
return this.lastTree;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** @internal Return a locally observed OCR label for validating a planned action. */
|
|
376
|
+
observedOcrLabel(nodeId: string | undefined): string | undefined {
|
|
377
|
+
return nodeId ? this.ocrTargets.get(nodeId)?.text : undefined;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** @internal Pin the screen whose node ids the planner is about to see. */
|
|
381
|
+
setPlanningTree(tree: ViewNode | null) {
|
|
382
|
+
this.planningTree = tree;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async find(selector: Selector, tree?: ViewNode): Promise<ViewNode | null> {
|
|
386
|
+
const wantedLabel = selectorLabel(selector);
|
|
387
|
+
const label = wantedLabel ?? this.text('runtime.element');
|
|
388
|
+
const node = this.track('find', this.message('runtime.find', { label }));
|
|
389
|
+
try {
|
|
390
|
+
const root = tree ?? (await this.dump({ silent: true }));
|
|
391
|
+
if (sensitiveScreenKind(root)) throw new ActionValidationError(this.message('runtime.privateStep'));
|
|
392
|
+
let hit = find(root, selector);
|
|
393
|
+
// OCR can confirm only a unique visible text label. It cannot prove an
|
|
394
|
+
// index, nearby context, element type, or actual clickability.
|
|
395
|
+
const ocrEligible = selector.match.length === 1 &&
|
|
396
|
+
(('text' in selector.match[0]!) || ('accessibilityLabel' in selector.match[0]!)) &&
|
|
397
|
+
(selector.index === undefined || selector.index === 0);
|
|
398
|
+
if (!hit && !!wantedLabel && ocrEligible && NativeBridge.isAvailable()) {
|
|
399
|
+
try {
|
|
400
|
+
const ocr = await NativeBridge.recognizeScreenText({
|
|
401
|
+
maxTokens: 32,
|
|
402
|
+
hideOverlays: true,
|
|
403
|
+
});
|
|
404
|
+
const directHit = findOcrTextHit(ocr.texts || [], label);
|
|
405
|
+
const textHit = directHit || findOcrTextHit(ocr.texts || [], label, {
|
|
406
|
+
translatedWebRegions: visibleWebViewFrames(root),
|
|
407
|
+
});
|
|
408
|
+
if (textHit?.frame) {
|
|
409
|
+
const f = textHit.frame;
|
|
410
|
+
// The id carries no coordinates. A later tap must recognize this
|
|
411
|
+
// text again and use its current frame rather than this snapshot.
|
|
412
|
+
const id = `ocr:local-${++this.nextOcrTargetId}`;
|
|
413
|
+
this.ocrTargets.set(id, { text: textHit.text, webview: !directHit });
|
|
414
|
+
if (this.ocrTargets.size > 32) this.ocrTargets.delete(this.ocrTargets.keys().next().value!);
|
|
415
|
+
hit = {
|
|
416
|
+
id,
|
|
417
|
+
type: 'KokoOcrText',
|
|
418
|
+
frame: f,
|
|
419
|
+
// Only return the requested label, not arbitrary OCR screen text.
|
|
420
|
+
// Raw OCR remains on-device even if a webpage contains secrets.
|
|
421
|
+
text: label,
|
|
422
|
+
clickable: true,
|
|
423
|
+
enabled: true,
|
|
424
|
+
visible: true,
|
|
425
|
+
children: [],
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
} catch {
|
|
429
|
+
// OCR is optional; a normal miss remains a normal miss.
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (!hit && !tree && this.activeGoal) {
|
|
433
|
+
hit = await this.revealVisibleTarget(selector, wantedLabel || undefined);
|
|
434
|
+
}
|
|
435
|
+
this.done(node, this.message(hit ? 'runtime.found' : 'runtime.notFound', { label }), hit ? hit.id : undefined);
|
|
436
|
+
return hit;
|
|
437
|
+
} catch (e) {
|
|
438
|
+
this.fail(node, e);
|
|
439
|
+
throw e;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async findAll(selector: Selector, tree?: ViewNode): Promise<ViewNode[]> {
|
|
444
|
+
const root = tree ?? this.lastTree ?? (await this.dump({ silent: true }));
|
|
445
|
+
return findAll(root, selector);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async tap(target: TapTarget): Promise<void> {
|
|
449
|
+
const epoch = this.executionEpoch;
|
|
450
|
+
const assertCurrent = () => this.assertExecutionCurrent(epoch);
|
|
451
|
+
ExecutionGuard.assert('tap');
|
|
452
|
+
if (sensitiveScreenKind(await NativeBridge.dump())) {
|
|
453
|
+
throw new ActionValidationError(this.message('runtime.privateStep'));
|
|
454
|
+
}
|
|
455
|
+
assertCurrent();
|
|
456
|
+
this.lastTapViaHost = null;
|
|
457
|
+
let label = target.label ?? selectorLabel(target.selector) ?? this.text('runtime.target');
|
|
458
|
+
let hostHit = target.nodeId || target.point ? undefined : HostActionsStore.findForTap({
|
|
459
|
+
label,
|
|
460
|
+
text: selectorLabel(target.selector),
|
|
461
|
+
testID: selectorTestID(target.selector),
|
|
462
|
+
});
|
|
463
|
+
// Planner often taps dump nodeId of a native tab label; resolve copy then
|
|
464
|
+
// match host run (geometric hit-test on UITabBar is unreliable).
|
|
465
|
+
let preresolved: ViewNode | null = null;
|
|
466
|
+
if (!hostHit?.run) {
|
|
467
|
+
try {
|
|
468
|
+
preresolved = await this.resolveNode(target);
|
|
469
|
+
const nodeLabel = (
|
|
470
|
+
preresolved?.text ||
|
|
471
|
+
preresolved?.accessibilityLabel ||
|
|
472
|
+
''
|
|
473
|
+
).trim();
|
|
474
|
+
if (nodeLabel) {
|
|
475
|
+
const byNode = HostActionsStore.findForTap({
|
|
476
|
+
label: nodeLabel,
|
|
477
|
+
text: nodeLabel,
|
|
478
|
+
testID: preresolved?.testID,
|
|
479
|
+
});
|
|
480
|
+
if (byNode?.run) {
|
|
481
|
+
hostHit = byNode;
|
|
482
|
+
label = nodeLabel;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
} catch {
|
|
486
|
+
// Geometric path will surface the same resolve error.
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
assertCurrent();
|
|
490
|
+
// Host callbacks use the same visibility and confirmation guards as taps.
|
|
491
|
+
if (hostHit?.run) {
|
|
492
|
+
assertCurrent();
|
|
493
|
+
// A host callback is not permission to activate a mounted but hidden row.
|
|
494
|
+
const visible = preresolved ?? (target.options?.revealIfNeeded === false
|
|
495
|
+
? find(await this.dump({ silent: true }),
|
|
496
|
+
target.selector || { match: [{ text: hostHit.text || hostHit.label }] }, tapFindOptions(target))
|
|
497
|
+
: await this.revealVisibleTarget(
|
|
498
|
+
target.selector || { match: [{ text: hostHit.text || hostHit.label }] },
|
|
499
|
+
hostHit.text || hostHit.label,
|
|
500
|
+
tapFindOptions(target).preferNonInput,
|
|
501
|
+
));
|
|
502
|
+
if (visible) preresolved = visible;
|
|
503
|
+
if (!visible || !canRunHostActionForNode(hostHit, visible, this.lastTree)) hostHit = undefined;
|
|
504
|
+
}
|
|
505
|
+
if (hostHit?.run) {
|
|
506
|
+
const node = this.track('tap', this.message('runtime.tap', { label }));
|
|
507
|
+
try {
|
|
508
|
+
const confirmKey = `tap:${label}`;
|
|
509
|
+
if (!this.confirmedSensitive.has(confirmKey)) {
|
|
510
|
+
const allowed = await runConfirmationGate(
|
|
511
|
+
{ action: 'tap', label, selector: target.selector },
|
|
512
|
+
this.config.confirmation,
|
|
513
|
+
this.config.onConfirmSensitive,
|
|
514
|
+
);
|
|
515
|
+
assertCurrent();
|
|
516
|
+
if (!allowed) {
|
|
517
|
+
TaskStore.updateNode(node.id, {
|
|
518
|
+
status: 'failed',
|
|
519
|
+
title: this.message('runtime.confirmBlocked', { label }),
|
|
520
|
+
});
|
|
521
|
+
throw new ActionValidationError(
|
|
522
|
+
this.message('runtime.confirmationRequired'),
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
if (isSensitiveLabel(label, this.config.confirmation)) {
|
|
526
|
+
this.confirmedSensitive.add(confirmKey);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const silent = target.options?.silent;
|
|
530
|
+
const beforeSnap = await this.snapshotLine(true);
|
|
531
|
+
const duration =
|
|
532
|
+
target.options?.cursorDurationMs ?? this.config.cursorDurationMs ?? 320;
|
|
533
|
+
const spot = await this.hostActionPoint(target, preresolved);
|
|
534
|
+
if (!silent) {
|
|
535
|
+
if (spot) {
|
|
536
|
+
assertCurrent();
|
|
537
|
+
await CursorController.moveTo(spot.x, spot.y, duration);
|
|
538
|
+
assertCurrent();
|
|
539
|
+
CursorController.setPressing(true);
|
|
540
|
+
await sleep(40);
|
|
541
|
+
} else {
|
|
542
|
+
CursorController.hide();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
if (!spot) throw new ActionValidationError(this.message('runtime.hostTargetMissing'));
|
|
547
|
+
await this.assertTapTargetCurrent(target, spot, preresolved);
|
|
548
|
+
assertCurrent();
|
|
549
|
+
if (!(await runHostAction(hostHit.run))) {
|
|
550
|
+
throw new ActionOutcomeUnknownError(`host action for ${label} did not settle`);
|
|
551
|
+
}
|
|
552
|
+
} finally {
|
|
553
|
+
if (!silent) {
|
|
554
|
+
await sleep(120);
|
|
555
|
+
CursorController.setPressing(false);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// One-shot CTAs: drop so the next screen is not poisoned.
|
|
559
|
+
// Tabs / persistent chrome keep `oneshot: false`.
|
|
560
|
+
if (hostHit.oneshot !== false) {
|
|
561
|
+
HostActionsStore.unregisterAction(hostHit.id);
|
|
562
|
+
}
|
|
563
|
+
// Navigation / root reset needs a beat before dump.
|
|
564
|
+
await sleep(hostHit.kind === 'tab' ? 700 : 900);
|
|
565
|
+
assertCurrent();
|
|
566
|
+
// Observe the result, but never turn an uncertain callback into a
|
|
567
|
+
// second coordinate tap: async work may already have committed.
|
|
568
|
+
if (beforeSnap && (await this.snapshotLine()) === beforeSnap) {
|
|
569
|
+
this.lastTapViaHost = null;
|
|
570
|
+
// A resolved callback may already have submitted async work. An
|
|
571
|
+
// unchanged screen does not prove it is safe to perform it again.
|
|
572
|
+
throw new ActionOutcomeUnknownError(`host action for ${label} is unverified; not replayed`);
|
|
573
|
+
} else {
|
|
574
|
+
this.lastTapViaHost = { kind: hostHit.kind, label };
|
|
575
|
+
// Settings / modal hosts stack above progress — remount chrome on top.
|
|
576
|
+
TaskStore.liftChrome();
|
|
577
|
+
this.done(node, this.message('runtime.tapped', { label }));
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
} catch (e) {
|
|
581
|
+
this.fail(node, e);
|
|
582
|
+
throw e;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
assertCurrent();
|
|
587
|
+
let controlNode = preresolved ?? (await this.resolveNode(target));
|
|
588
|
+
let point: Point;
|
|
589
|
+
try {
|
|
590
|
+
point = await this.resolvePoint(target);
|
|
591
|
+
} catch (e) {
|
|
592
|
+
// No target evidence means no decorative tap at a guessed position.
|
|
593
|
+
throw e;
|
|
594
|
+
}
|
|
595
|
+
// Refresh control metadata from the same visible tree used for the point.
|
|
596
|
+
({ point, node: controlNode } = await this.ensureSafeTapPoint(
|
|
597
|
+
target,
|
|
598
|
+
point,
|
|
599
|
+
controlNode,
|
|
600
|
+
));
|
|
601
|
+
assertCurrent();
|
|
602
|
+
const isToggle = Boolean(controlNode?.toggle);
|
|
603
|
+
const adjustableKind = controlNode ? classifyAdjustable(controlNode) : null;
|
|
604
|
+
const node = this.track(
|
|
605
|
+
'tap',
|
|
606
|
+
isToggle
|
|
607
|
+
? this.message('runtime.toggle', { label })
|
|
608
|
+
: adjustableKind
|
|
609
|
+
? this.message('runtime.adjust', { label })
|
|
610
|
+
: this.message('runtime.tap', { label }),
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
try {
|
|
614
|
+
const confirmKey = `tap:${label}`;
|
|
615
|
+
if (!this.confirmedSensitive.has(confirmKey)) {
|
|
616
|
+
const allowed = await runConfirmationGate(
|
|
617
|
+
{ action: 'tap', label, selector: target.selector },
|
|
618
|
+
this.config.confirmation,
|
|
619
|
+
this.config.onConfirmSensitive,
|
|
620
|
+
);
|
|
621
|
+
assertCurrent();
|
|
622
|
+
if (!allowed) {
|
|
623
|
+
TaskStore.updateNode(node.id, {
|
|
624
|
+
status: 'failed',
|
|
625
|
+
title: this.message('runtime.confirmBlocked', { label }),
|
|
626
|
+
});
|
|
627
|
+
throw new ActionValidationError(this.message('runtime.confirmationRequired'));
|
|
628
|
+
}
|
|
629
|
+
if (isSensitiveLabel(label, this.config.confirmation)) {
|
|
630
|
+
this.confirmedSensitive.add(confirmKey);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const silent = target.options?.silent;
|
|
635
|
+
const duration = target.options?.cursorDurationMs ?? this.config.cursorDurationMs ?? 320;
|
|
636
|
+
|
|
637
|
+
// Switches / toggles: prefer a real tap — native UISwitch handling is reliable.
|
|
638
|
+
// (A JS→swipe path used to miss UISwitch in dispatchSwipe.)
|
|
639
|
+
if (controlNode?.toggle && controlNode.frame) {
|
|
640
|
+
const targetPoint = centerOf(controlNode);
|
|
641
|
+
if (!silent) {
|
|
642
|
+
assertCurrent();
|
|
643
|
+
await CursorController.moveTo(targetPoint.x, targetPoint.y, duration);
|
|
644
|
+
assertCurrent();
|
|
645
|
+
CursorController.setPressing(true);
|
|
646
|
+
await sleep(40);
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
await withChromeClearance([targetPoint], async () => {
|
|
650
|
+
await this.assertTapTargetCurrent(target, point, controlNode);
|
|
651
|
+
assertCurrent(); return NativeBridge.tap(targetPoint);
|
|
652
|
+
});
|
|
653
|
+
} finally {
|
|
654
|
+
if (!silent && epoch === this.executionEpoch) {
|
|
655
|
+
CursorController.showAt(targetPoint.x, targetPoint.y, 0);
|
|
656
|
+
await sleep(120);
|
|
657
|
+
CursorController.setPressing(false);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
this.done(node, this.message('runtime.toggled', { label }));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Slider / picker: route to swipe. Stepper / segment: plain tap (native handles ± / index).
|
|
665
|
+
if (controlNode?.frame && adjustableKind && (adjustableKind === 'slider' || adjustableKind === 'picker' || adjustableKind === 'other')) {
|
|
666
|
+
const gesture = adjustableGesture(controlNode, adjustableKind, point);
|
|
667
|
+
if (gesture) {
|
|
668
|
+
if (!silent) {
|
|
669
|
+
assertCurrent();
|
|
670
|
+
await CursorController.moveTo(gesture.from.x, gesture.from.y, duration);
|
|
671
|
+
assertCurrent();
|
|
672
|
+
CursorController.setPressing(true);
|
|
673
|
+
await sleep(40);
|
|
674
|
+
}
|
|
675
|
+
try {
|
|
676
|
+
await withChromeClearance([gesture.from, gesture.to], async () =>
|
|
677
|
+
{ await this.assertTapTargetCurrent(target, point, controlNode);
|
|
678
|
+
assertCurrent(); return NativeBridge.swipe(gesture.from, gesture.to, gesture.durationMs); },
|
|
679
|
+
);
|
|
680
|
+
} finally {
|
|
681
|
+
if (!silent && epoch === this.executionEpoch) {
|
|
682
|
+
CursorController.showAt(gesture.to.x, gesture.to.y, 0);
|
|
683
|
+
await sleep(120);
|
|
684
|
+
CursorController.setPressing(false);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
this.done(node, this.message('runtime.adjusted', { label }));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// System Alert / ActionSheet buttons: invoke handler by title (hit-testing is unreliable).
|
|
693
|
+
if (controlNode && isAlertActionNode(controlNode)) {
|
|
694
|
+
const title =
|
|
695
|
+
(controlNode.text || controlNode.accessibilityLabel || label || '').trim();
|
|
696
|
+
if (!silent) {
|
|
697
|
+
assertCurrent();
|
|
698
|
+
await CursorController.moveTo(point.x, point.y, duration);
|
|
699
|
+
assertCurrent();
|
|
700
|
+
CursorController.setPressing(true);
|
|
701
|
+
await sleep(40);
|
|
702
|
+
}
|
|
703
|
+
try {
|
|
704
|
+
await this.assertTapTargetCurrent(target, point, controlNode);
|
|
705
|
+
assertCurrent();
|
|
706
|
+
const invoked = title ? await NativeBridge.invokeAlertAction(title) : false;
|
|
707
|
+
if (!invoked) {
|
|
708
|
+
await withChromeClearance([point], async () => {
|
|
709
|
+
await this.assertTapTargetCurrent(target, point, controlNode);
|
|
710
|
+
assertCurrent(); return NativeBridge.tap(point);
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
} finally {
|
|
714
|
+
if (!silent) {
|
|
715
|
+
await sleep(120);
|
|
716
|
+
CursorController.setPressing(false);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
this.done(node, this.message('runtime.confirmed', { label: title || label }));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (!silent) {
|
|
724
|
+
assertCurrent();
|
|
725
|
+
await CursorController.moveTo(point.x, point.y, duration);
|
|
726
|
+
assertCurrent();
|
|
727
|
+
CursorController.setPressing(true);
|
|
728
|
+
await sleep(40);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
try {
|
|
732
|
+
await this.assertTapTargetCurrent(target, point, controlNode);
|
|
733
|
+
assertCurrent();
|
|
734
|
+
if (!silent && epoch === this.executionEpoch) {
|
|
735
|
+
CursorController.showAt(point.x, point.y, 0);
|
|
736
|
+
}
|
|
737
|
+
// If an alert is up, prefer title invoke even for generic sheet taps.
|
|
738
|
+
let didAlert = false;
|
|
739
|
+
if (controlNode?.sheet && controlNode.clickable) {
|
|
740
|
+
const title = (controlNode.text || controlNode.accessibilityLabel || label || '').trim();
|
|
741
|
+
if (title) {
|
|
742
|
+
assertCurrent();
|
|
743
|
+
didAlert = await NativeBridge.invokeAlertAction(title);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (!didAlert) {
|
|
747
|
+
await withChromeClearance([point], async () => {
|
|
748
|
+
await this.assertTapTargetCurrent(target, point, controlNode);
|
|
749
|
+
assertCurrent(); return NativeBridge.tap(point);
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
} finally {
|
|
753
|
+
if (!silent) {
|
|
754
|
+
await sleep(160);
|
|
755
|
+
CursorController.setPressing(false);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
this.done(node, this.message('runtime.tapped', { label }));
|
|
760
|
+
} catch (e) {
|
|
761
|
+
this.fail(node, e);
|
|
762
|
+
throw e;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/** Animation and confirmations may outlive the layout that was planned. */
|
|
767
|
+
private async assertTapTargetCurrent(target: TapTarget, point: Point, expected: ViewNode | null): Promise<void> {
|
|
768
|
+
const currentPoint = await this.resolvePoint({
|
|
769
|
+
...target, options: { ...target.options, revealIfNeeded: false },
|
|
770
|
+
});
|
|
771
|
+
if (Math.hypot(currentPoint.x - point.x, currentPoint.y - point.y) > 16) {
|
|
772
|
+
throw new ActionValidationError(this.message('runtime.positionChanged'));
|
|
773
|
+
}
|
|
774
|
+
if (expected && !expected.id.startsWith('ocr:')) {
|
|
775
|
+
const current = this.lastTree ? findNodeById(this.lastTree, expected.id) : null;
|
|
776
|
+
if (!current || current.enabled === false || current.visible === false ||
|
|
777
|
+
current.type !== expected.type || current.text !== expected.text ||
|
|
778
|
+
current.accessibilityLabel !== expected.accessibilityLabel || current.testID !== expected.testID) {
|
|
779
|
+
throw new ActionValidationError(this.message('runtime.contentChanged'));
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/** Refresh node metadata after scroll-based target resolution. */
|
|
785
|
+
private async ensureSafeTapPoint(
|
|
786
|
+
target: TapTarget,
|
|
787
|
+
point: Point,
|
|
788
|
+
controlNode: ViewNode | null,
|
|
789
|
+
): Promise<{ point: Point; node: ViewNode | null }> {
|
|
790
|
+
const tree = this.lastTree;
|
|
791
|
+
const current = tree ? (target.selector ? find(tree, target.selector, tapFindOptions(target)) :
|
|
792
|
+
target.nodeId ? findNodeById(tree, target.nodeId) : controlNode) : controlNode;
|
|
793
|
+
return { point, node: current?.visible === false ? null : current };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/** Reveal by visible swipes, only inside a natively confirmed scrollable list. */
|
|
797
|
+
private async revealVisibleTarget(selector: Selector, label?: string, preferNonInput = false): Promise<ViewNode | null> {
|
|
798
|
+
const epoch = this.executionEpoch;
|
|
799
|
+
const seen = new Set<string>();
|
|
800
|
+
for (const direction of [1, -1]) {
|
|
801
|
+
for (let page = 0; page < 8; page++) {
|
|
802
|
+
const tree = await this.dump({ silent: true });
|
|
803
|
+
if (sensitiveScreenKind(tree)) throw new ActionValidationError(this.message('runtime.privateStep'));
|
|
804
|
+
const hit = find(tree, selector, { preferNonInput }) ||
|
|
805
|
+
(label && allowsSoftLabelFallback(selector) ? findBySoftLabel(tree, label, { preferNonInput }) : null);
|
|
806
|
+
if (hit) return hit;
|
|
807
|
+
const viewport = scrollViewports(tree)
|
|
808
|
+
.filter(({ frame }) => frame.height >= 140 && frame.width >= tree.frame.width * 0.45)
|
|
809
|
+
.sort((a, b) => b.frame.width * b.frame.height - a.frame.width * a.frame.height)[0];
|
|
810
|
+
if (!viewport) return null;
|
|
811
|
+
// Use the list itself, not the first few screen affordances: sticky
|
|
812
|
+
// headers can stay unchanged while later rows are still moving.
|
|
813
|
+
const fingerprint = `${direction}:${JSON.stringify(flattenTree(viewport.node).map(n =>
|
|
814
|
+
[n.id, n.text || n.accessibilityLabel || '', Math.round(n.frame.y)]))}`;
|
|
815
|
+
if (seen.has(fingerprint)) break;
|
|
816
|
+
seen.add(fingerprint);
|
|
817
|
+
this.log('info', this.message('runtime.reveal'));
|
|
818
|
+
const f = viewport.frame;
|
|
819
|
+
const x = f.x + f.width / 2;
|
|
820
|
+
const upper = f.y + f.height * 0.25;
|
|
821
|
+
const lower = f.y + f.height * 0.75;
|
|
822
|
+
this.assertExecutionCurrent(epoch);
|
|
823
|
+
await this.swipe({ x, y: direction > 0 ? lower : upper },
|
|
824
|
+
{ x, y: direction > 0 ? upper : lower }, { durationMs: 420 });
|
|
825
|
+
await sleep(250);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return null;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async swipe(from: Point, to: Point, options?: SwipeOptions): Promise<void> {
|
|
832
|
+
const epoch = this.executionEpoch;
|
|
833
|
+
const assertCurrent = () => this.assertExecutionCurrent(epoch);
|
|
834
|
+
ExecutionGuard.assert('swipe');
|
|
835
|
+
const tree = await this.dump({ silent: true });
|
|
836
|
+
if (sensitiveScreenKind(tree)) throw new ActionValidationError(this.message('runtime.privateStep'));
|
|
837
|
+
const viewport = scrollViewports(tree)
|
|
838
|
+
.filter(({ frame: f }) => from.x >= f.x && from.x <= f.x + f.width &&
|
|
839
|
+
from.y >= f.y && from.y <= f.y + f.height)
|
|
840
|
+
.sort((a, b) => a.frame.width * a.frame.height - b.frame.width * b.frame.height)[0];
|
|
841
|
+
if (viewport) ({ from, to } = boundedScrollGesture(viewport.frame, from, to));
|
|
842
|
+
const silent = options?.silent;
|
|
843
|
+
const node = silent ? { id: '' } : this.track('swipe', this.message('runtime.swipe'));
|
|
844
|
+
const durationMs = options?.durationMs ?? 280;
|
|
845
|
+
try {
|
|
846
|
+
if (!silent) {
|
|
847
|
+
assertCurrent();
|
|
848
|
+
await CursorController.moveTo(from.x, from.y, this.config.cursorDurationMs ?? 320);
|
|
849
|
+
assertCurrent();
|
|
850
|
+
CursorController.setPressing(true);
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
await withChromeClearance([from, to], () =>
|
|
854
|
+
{ assertCurrent(); return NativeBridge.swipe(from, to, durationMs); },
|
|
855
|
+
);
|
|
856
|
+
} finally {
|
|
857
|
+
if (!silent && epoch === this.executionEpoch) {
|
|
858
|
+
CursorController.showAt(to.x, to.y);
|
|
859
|
+
CursorController.setPressing(false);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
this.done(node, this.message('runtime.swiped'));
|
|
863
|
+
} catch (e) {
|
|
864
|
+
this.fail(node, e);
|
|
865
|
+
throw e;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Two-finger pinch at center. scale>1 zoom in, scale<1 zoom out.
|
|
871
|
+
*/
|
|
872
|
+
async pinch(center: Point, scale: number, options?: PinchOptions): Promise<void> {
|
|
873
|
+
const epoch = this.executionEpoch;
|
|
874
|
+
const assertCurrent = () => this.assertExecutionCurrent(epoch);
|
|
875
|
+
ExecutionGuard.assert('pinch');
|
|
876
|
+
const silent = options?.silent;
|
|
877
|
+
const zoomIn = scale >= 1;
|
|
878
|
+
const node = silent
|
|
879
|
+
? { id: '' }
|
|
880
|
+
: this.track('swipe', zoomIn ? this.message('runtime.zoomIn') : this.message('runtime.zoomOut'));
|
|
881
|
+
const durationMs = options?.durationMs ?? 280;
|
|
882
|
+
try {
|
|
883
|
+
if (!silent) {
|
|
884
|
+
assertCurrent();
|
|
885
|
+
await CursorController.moveTo(center.x, center.y, this.config.cursorDurationMs ?? 320);
|
|
886
|
+
assertCurrent();
|
|
887
|
+
CursorController.setPressing(true);
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
await withChromeClearance([center], () =>
|
|
891
|
+
{ assertCurrent(); return NativeBridge.pinch(center, scale, durationMs); },
|
|
892
|
+
);
|
|
893
|
+
} finally {
|
|
894
|
+
if (!silent && epoch === this.executionEpoch) {
|
|
895
|
+
CursorController.showAt(center.x, center.y);
|
|
896
|
+
CursorController.setPressing(false);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
this.done(node, zoomIn ? this.message('runtime.zoomedIn') : this.message('runtime.zoomedOut'));
|
|
900
|
+
} catch (e) {
|
|
901
|
+
this.fail(node, e);
|
|
902
|
+
throw e;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async type(text: string, target: TypeTarget = {}): Promise<void> {
|
|
907
|
+
const epoch = this.executionEpoch;
|
|
908
|
+
const assertCurrent = () => this.assertExecutionCurrent(epoch);
|
|
909
|
+
ExecutionGuard.assert('type');
|
|
910
|
+
if (sensitiveScreenKind(await NativeBridge.dump())) throw new ActionValidationError(this.message('runtime.privateStep'));
|
|
911
|
+
assertCurrent();
|
|
912
|
+
const preview = text.length > 18 ? `${text.slice(0, 18)}…` : text;
|
|
913
|
+
const node = this.track('type', this.message('runtime.input', { text: preview }));
|
|
914
|
+
try {
|
|
915
|
+
// Ensure a field is focused: tap selector / node, or first text field on screen.
|
|
916
|
+
// Native tap must becomeFirstResponder (keyboard); synthetic UIControl events alone do not.
|
|
917
|
+
let focusPoint: Point | null = null;
|
|
918
|
+
if (target.selector || target.nodeId) {
|
|
919
|
+
focusPoint = await this.resolvePoint({
|
|
920
|
+
selector: target.selector,
|
|
921
|
+
nodeId: target.nodeId,
|
|
922
|
+
inputTarget: true,
|
|
923
|
+
});
|
|
924
|
+
assertCurrent();
|
|
925
|
+
await this.tap({
|
|
926
|
+
selector: target.selector,
|
|
927
|
+
nodeId: target.nodeId,
|
|
928
|
+
options: { silent: target.options?.silent },
|
|
929
|
+
label: target.label ?? this.text('runtime.field'),
|
|
930
|
+
inputTarget: true,
|
|
931
|
+
});
|
|
932
|
+
} else {
|
|
933
|
+
const root = this.lastTree ?? (await this.dump({ silent: true }));
|
|
934
|
+
const field = findFirstTextField(root);
|
|
935
|
+
if (field) {
|
|
936
|
+
focusPoint = await this.resolvePoint({ nodeId: field.id });
|
|
937
|
+
assertCurrent();
|
|
938
|
+
await this.tap({
|
|
939
|
+
nodeId: field.id,
|
|
940
|
+
options: { silent: target.options?.silent },
|
|
941
|
+
label: target.label ?? field.accessibilityLabel ?? field.text ?? this.text('runtime.field'),
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
await sleep(160);
|
|
946
|
+
if (focusPoint) {
|
|
947
|
+
try {
|
|
948
|
+
assertCurrent();
|
|
949
|
+
await NativeBridge.focusAt(focusPoint);
|
|
950
|
+
} catch {
|
|
951
|
+
try {
|
|
952
|
+
assertCurrent();
|
|
953
|
+
await NativeBridge.focusNode(target.nodeId ?? '');
|
|
954
|
+
} catch {
|
|
955
|
+
// typeText will try becomeFirstResponder again
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
} else {
|
|
959
|
+
try {
|
|
960
|
+
assertCurrent();
|
|
961
|
+
await NativeBridge.focusNode(target.nodeId ?? '');
|
|
962
|
+
} catch {
|
|
963
|
+
// optional
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
await sleep(80);
|
|
967
|
+
assertCurrent();
|
|
968
|
+
await NativeBridge.typeText(text, target.options?.clear ?? false);
|
|
969
|
+
this.done(node, this.message('runtime.typed', { text: preview }));
|
|
970
|
+
} catch (e) {
|
|
971
|
+
this.fail(node, e);
|
|
972
|
+
throw e;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
async requestConfirm(ctx: SensitiveConfirmContext): Promise<boolean> {
|
|
977
|
+
const label = ctx.label ?? ctx.action;
|
|
978
|
+
const node = this.track('confirm', this.message('runtime.confirm', { label }));
|
|
979
|
+
const ok = await runConfirmationGate(ctx, this.config.confirmation, this.config.onConfirmSensitive);
|
|
980
|
+
TaskStore.updateNode(node.id, {
|
|
981
|
+
status: ok ? 'done' : 'failed',
|
|
982
|
+
title: this.message(ok ? 'runtime.userConfirmed' : 'runtime.userDeclined', { label }),
|
|
983
|
+
});
|
|
984
|
+
return ok;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
hideCursor() {
|
|
988
|
+
CursorController.hide();
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/** Run one agent protocol tool call. */
|
|
992
|
+
async runAction(action: AgentAction): Promise<AgentActionResult> {
|
|
993
|
+
return executeAgentAction(this, action);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
private track(
|
|
997
|
+
kind: Parameters<typeof TaskStore.appendNode>[0]['kind'],
|
|
998
|
+
title: LocalizedText,
|
|
999
|
+
) {
|
|
1000
|
+
if (this.config.observer === false) {
|
|
1001
|
+
return { id: '' };
|
|
1002
|
+
}
|
|
1003
|
+
return TaskStore.appendNode({ kind, title, status: 'running' });
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
private done(node: { id: string }, title: LocalizedText, detail?: string) {
|
|
1007
|
+
if (!node.id || this.config.observer === false) return;
|
|
1008
|
+
TaskStore.updateNode(node.id, { status: 'done', title, detail });
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
private fail(node: { id: string }, e: unknown) {
|
|
1012
|
+
if (!node.id || this.config.observer === false) return;
|
|
1013
|
+
const copy = e instanceof ActionValidationError ? e.localizedMessage : this.message('runtime.operationFailed');
|
|
1014
|
+
TaskStore.updateNode(node.id, { status: 'failed', title: copy });
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
private async resolvePoint(target: TapTarget): Promise<Point> {
|
|
1018
|
+
// A labelled cloud coordinate is a hint, not evidence that a row is visible.
|
|
1019
|
+
// Prefer a fresh selector/label and scroll lookup before dispatching a touch.
|
|
1020
|
+
if (target.point && !target.selector && !target.nodeId) {
|
|
1021
|
+
if (!this.activeGoal) return this.pointWithinWindow(target.point);
|
|
1022
|
+
const label = target.label?.match(/[「“"]([^」”"]+)[」”"]/)?.[1] ||
|
|
1023
|
+
target.label?.replace(/^(点击|轻点|打开)\s*/, '').replace(/按钮$/, '');
|
|
1024
|
+
if (!label) throw new ActionValidationError(this.message('runtime.missingTarget'));
|
|
1025
|
+
return this.resolvePoint({ selector: { match: [{ text: label }] }, label, options: target.options });
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const root = await this.dump({ silent: true });
|
|
1029
|
+
if (sensitiveScreenKind(root)) throw new ActionValidationError(this.message('runtime.privateStep'));
|
|
1030
|
+
|
|
1031
|
+
// An explicit selector is target evidence; a node path is only a hint.
|
|
1032
|
+
// In particular do not replace a selector with a stale recycled row id.
|
|
1033
|
+
if (target.selector) {
|
|
1034
|
+
let node = find(root, target.selector, tapFindOptions(target));
|
|
1035
|
+
const soft = selectorLabel(target.selector);
|
|
1036
|
+
const allowSoft = allowsSoftLabelFallback(target.selector);
|
|
1037
|
+
if (!node && soft && allowSoft) node = findBySoftLabel(root, soft, tapFindOptions(target));
|
|
1038
|
+
if (!node && soft && allowSoft && NativeBridge.isAvailable()) {
|
|
1039
|
+
const point = await this.currentOcrPoint(soft);
|
|
1040
|
+
if (point) return point;
|
|
1041
|
+
}
|
|
1042
|
+
if (!node && target.options?.revealIfNeeded !== false) {
|
|
1043
|
+
node = await this.revealVisibleTarget(target.selector, soft || undefined, tapFindOptions(target).preferNonInput);
|
|
1044
|
+
}
|
|
1045
|
+
if (!node || node.visible === false || node.enabled === false) {
|
|
1046
|
+
throw new ActionValidationError(this.message('runtime.missingTarget'));
|
|
1047
|
+
}
|
|
1048
|
+
return this.pointWithinWindow(centerOfLabel(node, soft));
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (target.nodeId) {
|
|
1052
|
+
if (target.nodeId.startsWith('ocr:')) {
|
|
1053
|
+
const observed = this.ocrTargets.get(target.nodeId);
|
|
1054
|
+
if (!observed) throw new ActionValidationError(this.message('runtime.ocrUnobserved'));
|
|
1055
|
+
const point = await this.currentOcrPoint(observed.text, observed.webview ? root : undefined);
|
|
1056
|
+
if (point) return point;
|
|
1057
|
+
throw new ActionValidationError(this.message('runtime.ocrMissing'));
|
|
1058
|
+
}
|
|
1059
|
+
// host:… is a catalog id, not a dump node — remap to visible text.
|
|
1060
|
+
if (target.nodeId.startsWith('host:')) {
|
|
1061
|
+
const host = HostActionsStore.getById(target.nodeId);
|
|
1062
|
+
const soft = host?.text || host?.label;
|
|
1063
|
+
if (soft) {
|
|
1064
|
+
const hit = target.options?.revealIfNeeded === false
|
|
1065
|
+
? findBySoftLabel(root, soft)
|
|
1066
|
+
: await this.revealVisibleTarget({ match: [{ text: soft }] }, soft);
|
|
1067
|
+
if (hit && hit.visible !== false && hit.enabled !== false) return this.pointWithinWindow(centerOfLabel(hit, soft));
|
|
1068
|
+
}
|
|
1069
|
+
throw new ActionValidationError(this.message('runtime.hostNotVisible', { id: target.nodeId }));
|
|
1070
|
+
}
|
|
1071
|
+
const node = this.validatedNativeNode(root, target.nodeId);
|
|
1072
|
+
// label is narration, in any language. It must not override a real
|
|
1073
|
+
// current node or become an invented selector such as “Click Back”.
|
|
1074
|
+
return this.pointWithinWindow(centerOfLabel(node));
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
throw new ActionValidationError(this.message('runtime.invalidTarget'));
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
private validatedNativeNode(root: ViewNode, nodeId: string): ViewNode {
|
|
1081
|
+
const node = findNodeById(root, nodeId);
|
|
1082
|
+
if (!node || node.visible === false || node.enabled === false) {
|
|
1083
|
+
throw new ActionValidationError(this.message('runtime.nodeUnavailable'));
|
|
1084
|
+
}
|
|
1085
|
+
if (this.activeGoal) {
|
|
1086
|
+
const observed = this.planningTree ? findNodeById(this.planningTree, nodeId) : null;
|
|
1087
|
+
const identity = (item: ViewNode) => JSON.stringify([
|
|
1088
|
+
item.type, item.text || '', item.accessibilityLabel || '', item.testID || '',
|
|
1089
|
+
]);
|
|
1090
|
+
if (!observed || identity(observed) !== identity(node)) {
|
|
1091
|
+
throw new ActionValidationError(this.message('runtime.nodeChanged'));
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return node;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
private async currentOcrPoint(text: string, webviewRoot?: ViewNode): Promise<Point | null> {
|
|
1098
|
+
if (!NativeBridge.isAvailable()) return null;
|
|
1099
|
+
try {
|
|
1100
|
+
const ocr = await NativeBridge.recognizeScreenText({ maxTokens: 32, hideOverlays: true });
|
|
1101
|
+
const hit = findOcrTextHit(ocr.texts || [], text);
|
|
1102
|
+
const frame = hit?.frame;
|
|
1103
|
+
if (!frame || ![frame.x, frame.y, frame.width, frame.height].every(Number.isFinite)) return null;
|
|
1104
|
+
if (webviewRoot && (!hit || !ocrHitInFrames(hit, visibleWebViewFrames(webviewRoot)))) return null;
|
|
1105
|
+
return await this.pointWithinWindow({ x: frame.x + frame.width / 2, y: frame.y + frame.height / 2 });
|
|
1106
|
+
} catch {
|
|
1107
|
+
// No current positioned evidence means no touch, even if an older OCR
|
|
1108
|
+
// read found the same text at a plausible location.
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* A coordinate tap has no node to sanity-check it against, so an invented
|
|
1115
|
+
* point would dispatch into nowhere and only surface later as tap_noop.
|
|
1116
|
+
* Fail loudly instead.
|
|
1117
|
+
*/
|
|
1118
|
+
/**
|
|
1119
|
+
* A tree that bottoms out exactly at the cap was almost certainly cut short,
|
|
1120
|
+
* and the part that gets cut is the page body — the agent is then left with
|
|
1121
|
+
* the shallow chrome and reports the control as missing. Surface it rather
|
|
1122
|
+
* than letting it read as an empty screen.
|
|
1123
|
+
*/
|
|
1124
|
+
private warnIfDepthCapped(tree: ViewNode, cap: number): void {
|
|
1125
|
+
if (!cap) return;
|
|
1126
|
+
let deepest = 0;
|
|
1127
|
+
const walk = (n: ViewNode, d: number) => {
|
|
1128
|
+
if (d > deepest) deepest = d;
|
|
1129
|
+
for (const c of n.children ?? []) walk(c, d + 1);
|
|
1130
|
+
};
|
|
1131
|
+
walk(tree, 0);
|
|
1132
|
+
if (deepest >= cap) {
|
|
1133
|
+
AgentDebug.dumpTruncated(this.config, cap);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* Cheap screen fingerprint used to tell real navigation from a dead action.
|
|
1139
|
+
* The "before" side can reuse the tree the session just dumped for this step:
|
|
1140
|
+
* an extra full walk per tap is work the JS thread does not have to spare
|
|
1141
|
+
* while a task is running.
|
|
1142
|
+
*/
|
|
1143
|
+
private async snapshotLine(reuseLastTree = false): Promise<string> {
|
|
1144
|
+
try {
|
|
1145
|
+
const tree = reuseLastTree
|
|
1146
|
+
? (this.lastTree ?? (await this.dump({ silent: true })))
|
|
1147
|
+
: await this.dump({ silent: true });
|
|
1148
|
+
return tree ? screenSnapshotLine(tree) : '';
|
|
1149
|
+
} catch {
|
|
1150
|
+
return '';
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
private async pointWithinWindow(point: Point): Promise<Point> {
|
|
1155
|
+
if (
|
|
1156
|
+
!Number.isFinite(point.x) ||
|
|
1157
|
+
!Number.isFinite(point.y) ||
|
|
1158
|
+
point.x < 0 ||
|
|
1159
|
+
point.y < 0
|
|
1160
|
+
) {
|
|
1161
|
+
throw new ActionValidationError(this.message('runtime.invalidPoint', { x: point.x, y: point.y }));
|
|
1162
|
+
}
|
|
1163
|
+
const root = this.lastTree ?? (await this.dump({ silent: true }));
|
|
1164
|
+
const w = root?.frame?.width ?? 0;
|
|
1165
|
+
const h = root?.frame?.height ?? 0;
|
|
1166
|
+
if (w > 0 && h > 0 && (point.x > w || point.y > h)) {
|
|
1167
|
+
throw new ActionValidationError(
|
|
1168
|
+
this.message('runtime.outsideWindow'),
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
return point;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/** Resolve the view node for a tap target (for toggle/adjustable routing). */
|
|
1175
|
+
/**
|
|
1176
|
+
* Where to point while a host action runs.
|
|
1177
|
+
*
|
|
1178
|
+
* Best effort by design: the whole reason this path exists is that the dump
|
|
1179
|
+
* sometimes cannot see the control, so a miss here is normal and must not
|
|
1180
|
+
* turn a working host action into a failed step.
|
|
1181
|
+
*/
|
|
1182
|
+
private async hostActionPoint(
|
|
1183
|
+
target: TapTarget,
|
|
1184
|
+
preresolved: ViewNode | null,
|
|
1185
|
+
): Promise<Point | null> {
|
|
1186
|
+
if (preresolved?.frame) return centerOfLabel(preresolved, target.label ?? selectorLabel(target.selector));
|
|
1187
|
+
try {
|
|
1188
|
+
return await this.resolvePoint(target);
|
|
1189
|
+
} catch {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
private async resolveNode(target: TapTarget): Promise<ViewNode | null> {
|
|
1195
|
+
if (target.point && !target.selector && !target.nodeId) return null;
|
|
1196
|
+
const root = await this.dump({ silent: true });
|
|
1197
|
+
if (sensitiveScreenKind(root)) throw new ActionValidationError(this.message('runtime.privateStep'));
|
|
1198
|
+
let node: ViewNode | null = null;
|
|
1199
|
+
if (target.selector) {
|
|
1200
|
+
node = find(root, target.selector, tapFindOptions(target));
|
|
1201
|
+
if (!node) {
|
|
1202
|
+
const soft = selectorLabel(target.selector);
|
|
1203
|
+
if (soft && allowsSoftLabelFallback(target.selector)) node = findBySoftLabel(root, soft, tapFindOptions(target));
|
|
1204
|
+
}
|
|
1205
|
+
} else if (target.nodeId) {
|
|
1206
|
+
if (target.nodeId.startsWith('ocr:')) return null;
|
|
1207
|
+
if (target.nodeId.startsWith('host:')) {
|
|
1208
|
+
const host = HostActionsStore.getById(target.nodeId);
|
|
1209
|
+
const soft = host?.text || host?.label;
|
|
1210
|
+
if (soft) node = findBySoftLabel(root, soft);
|
|
1211
|
+
} else {
|
|
1212
|
+
node = this.validatedNativeNode(root, target.nodeId);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (!node || node.visible === false || node.enabled === false) return null;
|
|
1216
|
+
if (node.toggle || node.adjustable) return node;
|
|
1217
|
+
// Label may resolve to a wrapper; prefer a toggle / adjustable descendant.
|
|
1218
|
+
const nested = findControlDescendant(node);
|
|
1219
|
+
return nested ?? node;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
function tapFindOptions(target: TapTarget): { preferNonInput: boolean } {
|
|
1224
|
+
const inputIntent = /(?:search|input|text)\s*(?:box|field|input)|输入框|搜索框/i.test(target.label || '');
|
|
1225
|
+
return { preferNonInput: !target.inputTarget && !inputIntent };
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/** A fallback must never discard context, identity, location, or an index. */
|
|
1229
|
+
function allowsSoftLabelFallback(selector: Selector): boolean {
|
|
1230
|
+
return selector.index === undefined && selector.match.every(rule =>
|
|
1231
|
+
'text' in rule || 'accessibilityLabel' in rule || ('clickable' in rule && rule.clickable === true));
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function findControlDescendant(node: ViewNode): ViewNode | null {
|
|
1235
|
+
if (node.toggle || node.adjustable) return node;
|
|
1236
|
+
for (const c of node.children) {
|
|
1237
|
+
const hit = findControlDescendant(c);
|
|
1238
|
+
if (hit) return hit;
|
|
1239
|
+
}
|
|
1240
|
+
return null;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
function isAlertActionNode(node: ViewNode): boolean {
|
|
1244
|
+
if (node.type === 'UIAlertAction' || node.id.startsWith('alert/action/')) return true;
|
|
1245
|
+
if (!node.sheet || !node.clickable) return false;
|
|
1246
|
+
const t = (node.text || node.accessibilityLabel || '').trim();
|
|
1247
|
+
return /^(取消|清空|删除|好|确定|确认|允许|不允许|OK|Cancel|Delete|Yes|No|Discard)$/i.test(t);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function selectorLabel(selector?: Selector): string | undefined {
|
|
1251
|
+
if (!selector) return undefined;
|
|
1252
|
+
for (const rule of selector.match) {
|
|
1253
|
+
if ('text' in rule) return rule.text;
|
|
1254
|
+
if ('nearText' in rule) return rule.nearText;
|
|
1255
|
+
if ('accessibilityLabel' in rule) return rule.accessibilityLabel;
|
|
1256
|
+
}
|
|
1257
|
+
return undefined;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function selectorTestID(selector?: Selector): string | undefined {
|
|
1261
|
+
if (!selector) return undefined;
|
|
1262
|
+
for (const rule of selector.match) {
|
|
1263
|
+
if ('testID' in rule) return rule.testID;
|
|
1264
|
+
}
|
|
1265
|
+
return undefined;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
type AdjustableKind = 'slider' | 'stepper' | 'picker' | 'segment' | 'other';
|
|
1269
|
+
|
|
1270
|
+
function classifyAdjustable(node: ViewNode): AdjustableKind | null {
|
|
1271
|
+
const t = node.type.toLowerCase();
|
|
1272
|
+
if (/slider|seekbar|rating/i.test(t)) return 'slider';
|
|
1273
|
+
if (/stepper/i.test(t)) return 'stepper';
|
|
1274
|
+
if (/segment/i.test(t)) return 'segment';
|
|
1275
|
+
if (/picker|datepicker|spinner|numberpicker/i.test(t)) return 'picker';
|
|
1276
|
+
if (node.adjustable) {
|
|
1277
|
+
// Trait-only adjustable: prefer horizontal if wide, else vertical.
|
|
1278
|
+
if (node.frame.width >= node.frame.height * 1.4) return 'slider';
|
|
1279
|
+
return 'picker';
|
|
1280
|
+
}
|
|
1281
|
+
return null;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Map tap intent on an adjustable into a swipe that nudges value.
|
|
1286
|
+
* Uses tap X/Y relative to the control when available.
|
|
1287
|
+
*/
|
|
1288
|
+
function adjustableGesture(
|
|
1289
|
+
node: ViewNode,
|
|
1290
|
+
kind: AdjustableKind,
|
|
1291
|
+
tap: Point,
|
|
1292
|
+
): { from: Point; to: Point; durationMs: number } | null {
|
|
1293
|
+
const f = node.frame;
|
|
1294
|
+
const midX = f.x + f.width / 2;
|
|
1295
|
+
const midY = f.y + f.height / 2;
|
|
1296
|
+
const insetX = Math.max(8, f.width * 0.12);
|
|
1297
|
+
const insetY = Math.max(8, f.height * 0.18);
|
|
1298
|
+
|
|
1299
|
+
if (kind === 'slider') {
|
|
1300
|
+
// Drag from current thumb (if value known) toward the tap X.
|
|
1301
|
+
// Near-center taps used to nudge +28% of track → often slammed to max (e.g. 1.6×).
|
|
1302
|
+
const min = typeof node.min === 'number' ? node.min : 0;
|
|
1303
|
+
const max = typeof node.max === 'number' ? node.max : 1;
|
|
1304
|
+
const span = Math.max(1e-6, max - min);
|
|
1305
|
+
const cur =
|
|
1306
|
+
typeof node.value === 'number' && Number.isFinite(node.value)
|
|
1307
|
+
? node.value
|
|
1308
|
+
: min + span * 0.5;
|
|
1309
|
+
const thumbRatio = Math.max(0, Math.min(1, (cur - min) / span));
|
|
1310
|
+
const trackLeft = f.x + insetX;
|
|
1311
|
+
const trackRight = f.x + f.width - insetX;
|
|
1312
|
+
const trackW = Math.max(1, trackRight - trackLeft);
|
|
1313
|
+
const fromX = trackLeft + thumbRatio * trackW;
|
|
1314
|
+
|
|
1315
|
+
let toX = tap.x;
|
|
1316
|
+
if (Math.abs(toX - midX) < f.width * 0.1) {
|
|
1317
|
+
// Ambiguous center tap: one small step toward + (~12% of range).
|
|
1318
|
+
toX = fromX + trackW * 0.12;
|
|
1319
|
+
}
|
|
1320
|
+
toX = Math.max(trackLeft, Math.min(trackRight, toX));
|
|
1321
|
+
return {
|
|
1322
|
+
from: { x: fromX, y: midY },
|
|
1323
|
+
to: { x: toX, y: midY },
|
|
1324
|
+
durationMs: 200,
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
if (kind === 'stepper') {
|
|
1329
|
+
// Right half → +, left half → − (short horizontal nudge).
|
|
1330
|
+
const goPlus = tap.x >= midX;
|
|
1331
|
+
const from = { x: midX, y: midY };
|
|
1332
|
+
const to = {
|
|
1333
|
+
x: goPlus ? f.x + f.width - insetX : f.x + insetX,
|
|
1334
|
+
y: midY,
|
|
1335
|
+
};
|
|
1336
|
+
return { from, to, durationMs: 160 };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if (kind === 'picker') {
|
|
1340
|
+
// Vertical wheel: tap above mid → scroll up (earlier values), below → down.
|
|
1341
|
+
const goUp = tap.y < midY;
|
|
1342
|
+
const from = { x: midX, y: midY };
|
|
1343
|
+
const to = {
|
|
1344
|
+
x: midX,
|
|
1345
|
+
y: goUp ? f.y + insetY : f.y + f.height - insetY,
|
|
1346
|
+
};
|
|
1347
|
+
return { from, to, durationMs: 260 };
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function findFirstTextField(root: ViewNode): ViewNode | null {
|
|
1354
|
+
let best: ViewNode | null = null;
|
|
1355
|
+
const walk = (n: ViewNode) => {
|
|
1356
|
+
const t = n.type.toLowerCase();
|
|
1357
|
+
if (
|
|
1358
|
+
n.visible &&
|
|
1359
|
+
(/textfield|textinput|edittext|uitextfield/i.test(t) || n.secure === true)
|
|
1360
|
+
) {
|
|
1361
|
+
best = n;
|
|
1362
|
+
}
|
|
1363
|
+
for (const c of n.children) walk(c);
|
|
1364
|
+
};
|
|
1365
|
+
walk(root);
|
|
1366
|
+
return best;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
/**
|
|
1370
|
+
* Before an agent gesture: dismiss chat sheet, and if the tap/swipe lands in
|
|
1371
|
+
* the bottom-right progress/tab dock, hide chrome → wait for RN unmount →
|
|
1372
|
+
* gesture → show chrome again.
|
|
1373
|
+
*/
|
|
1374
|
+
async function withChromeClearance(
|
|
1375
|
+
points: Point[],
|
|
1376
|
+
action: () => Promise<void>,
|
|
1377
|
+
): Promise<void> {
|
|
1378
|
+
if (TaskStore.snapshot().ui.sheetOpen) {
|
|
1379
|
+
TaskStore.closeSheet();
|
|
1380
|
+
await sleep(480);
|
|
1381
|
+
}
|
|
1382
|
+
try {
|
|
1383
|
+
// iOS dispatch temporarily disables overlay hit-testing natively. Keep
|
|
1384
|
+
// the progress host mounted, including when touching beneath the pill.
|
|
1385
|
+
if (Platform.OS === 'ios' || !pointsOverlapProgressChrome(points)) {
|
|
1386
|
+
await action();
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
TaskStore.suppressChrome();
|
|
1390
|
+
// React must commit unmount before native hit-testing; 50ms was too short.
|
|
1391
|
+
await sleep(160);
|
|
1392
|
+
await action();
|
|
1393
|
+
} finally {
|
|
1394
|
+
TaskStore.restoreChrome();
|
|
1395
|
+
// Host Settings/sheet Modals stack above our progress Modal — remount
|
|
1396
|
+
// so stop / pill stay tappable.
|
|
1397
|
+
TaskStore.liftChrome();
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
function sleep(ms: number): Promise<void> {
|
|
1402
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
let shared: KokoAgent | null = null;
|
|
1406
|
+
|
|
1407
|
+
export function getKokoAgent(): KokoAgent {
|
|
1408
|
+
if (!shared) shared = new KokoAgent();
|
|
1409
|
+
return shared;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
export function initKokoAgent(config?: KokoAgentConfig): KokoAgent {
|
|
1413
|
+
shared = new KokoAgent(config);
|
|
1414
|
+
return shared;
|
|
1415
|
+
}
|