@web-auto/webauto 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/apps/desktop-console/default-settings.json +1 -0
  2. package/apps/desktop-console/dist/main/index.mjs +1618 -0
  3. package/apps/desktop-console/{src → dist}/main/preload.mjs +10 -0
  4. package/apps/desktop-console/dist/renderer/index.js +3063 -0
  5. package/apps/desktop-console/entry/ui-console.mjs +299 -0
  6. package/apps/webauto/entry/account.mjs +356 -0
  7. package/apps/webauto/entry/lib/account-detect.mjs +160 -0
  8. package/apps/webauto/entry/lib/account-store.mjs +587 -0
  9. package/apps/webauto/entry/lib/profilepool.mjs +1 -1
  10. package/apps/webauto/entry/xhs-install.mjs +27 -3
  11. package/apps/webauto/entry/xhs-status.mjs +152 -0
  12. package/apps/webauto/entry/xhs-unified.mjs +595 -17
  13. package/bin/webauto.mjs +247 -12
  14. package/dist/apps/webauto/server.js +66 -0
  15. package/dist/modules/camo-backend/src/index.js +575 -0
  16. package/dist/modules/camo-backend/src/internal/BrowserSession.js +817 -0
  17. package/dist/modules/camo-backend/src/internal/ElementRegistry.js +61 -0
  18. package/dist/modules/camo-backend/src/internal/ProfileLock.js +85 -0
  19. package/dist/modules/camo-backend/src/internal/SessionManager.js +172 -0
  20. package/dist/modules/camo-backend/src/internal/container-matcher.js +852 -0
  21. package/dist/modules/camo-backend/src/internal/engine-manager.js +258 -0
  22. package/dist/modules/camo-backend/src/internal/fingerprint.js +203 -0
  23. package/dist/modules/camo-backend/src/internal/pageRuntime.js +29 -0
  24. package/dist/modules/camo-backend/src/internal/runtimeInjector.js +30 -0
  25. package/dist/modules/camo-backend/src/internal/state-bus.js +46 -0
  26. package/dist/modules/camo-backend/src/internal/storage-paths.js +36 -0
  27. package/dist/modules/camo-backend/src/internal/ws-server.js +1202 -0
  28. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +423 -0
  29. package/dist/modules/camo-runtime/src/utils/config.mjs +77 -0
  30. package/dist/modules/container-registry/src/index.js +184 -0
  31. package/dist/modules/logging/src/index.js +92 -0
  32. package/dist/modules/operations/src/builtin.js +27 -0
  33. package/dist/modules/operations/src/container-binding.js +75 -0
  34. package/dist/modules/operations/src/executor.js +146 -0
  35. package/dist/modules/operations/src/operations/click.js +167 -0
  36. package/dist/modules/operations/src/operations/extract.js +204 -0
  37. package/dist/modules/operations/src/operations/find-child.js +17 -0
  38. package/dist/modules/operations/src/operations/highlight.js +138 -0
  39. package/dist/modules/operations/src/operations/key.js +61 -0
  40. package/dist/modules/operations/src/operations/navigate.js +148 -0
  41. package/dist/modules/operations/src/operations/scroll.js +126 -0
  42. package/dist/modules/operations/src/operations/type.js +190 -0
  43. package/dist/modules/operations/src/queue.js +100 -0
  44. package/dist/modules/operations/src/registry.js +11 -0
  45. package/dist/modules/operations/src/system/mouse.js +33 -0
  46. package/dist/modules/state/src/atomic-json.js +33 -0
  47. package/dist/modules/workflow/blocks/AnchorVerificationBlock.js +71 -0
  48. package/dist/modules/workflow/blocks/BehaviorRandomizer.js +26 -0
  49. package/dist/modules/workflow/blocks/CallWorkflowBlock.js +38 -0
  50. package/dist/modules/workflow/blocks/CloseDetailBlock.js +209 -0
  51. package/dist/modules/workflow/blocks/CollectBatch.js +137 -0
  52. package/dist/modules/workflow/blocks/CollectCommentsBlock.js +415 -0
  53. package/dist/modules/workflow/blocks/CollectSearchListBlock.js +599 -0
  54. package/dist/modules/workflow/blocks/CollectWeiboPosts.js +229 -0
  55. package/dist/modules/workflow/blocks/DetectPageStateBlock.js +259 -0
  56. package/dist/modules/workflow/blocks/EnsureLoginBlock.js +162 -0
  57. package/dist/modules/workflow/blocks/EnsureSession.js +426 -0
  58. package/dist/modules/workflow/blocks/ErrorClassifier.js +164 -0
  59. package/dist/modules/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  60. package/dist/modules/workflow/blocks/ExpandCommentsBlock.js +1032 -0
  61. package/dist/modules/workflow/blocks/ExtractDetailBlock.js +310 -0
  62. package/dist/modules/workflow/blocks/ExtractPostFields.js +88 -0
  63. package/dist/modules/workflow/blocks/GenerateSmartReplyBlock.js +68 -0
  64. package/dist/modules/workflow/blocks/GoToSearchBlock.js +497 -0
  65. package/dist/modules/workflow/blocks/GracefulFallbackBlock.js +104 -0
  66. package/dist/modules/workflow/blocks/HighlightBlock.js +66 -0
  67. package/dist/modules/workflow/blocks/InitAutoScroll.js +65 -0
  68. package/dist/modules/workflow/blocks/LoadContainerDefinition.js +50 -0
  69. package/dist/modules/workflow/blocks/LoadContainerIndex.js +43 -0
  70. package/dist/modules/workflow/blocks/LocateAndGuardBlock.js +176 -0
  71. package/dist/modules/workflow/blocks/LoginRecoveryBlock.js +242 -0
  72. package/dist/modules/workflow/blocks/MatchContainers.js +64 -0
  73. package/dist/modules/workflow/blocks/MonitoringBlock.js +190 -0
  74. package/dist/modules/workflow/blocks/OpenDetailBlock.js +1240 -0
  75. package/dist/modules/workflow/blocks/OrganizeXhsNotesBlock.js +117 -0
  76. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +270 -0
  77. package/dist/modules/workflow/blocks/PickSinglePost.js +69 -0
  78. package/dist/modules/workflow/blocks/ProgressTracker.js +125 -0
  79. package/dist/modules/workflow/blocks/RecordFixtureBlock.js +44 -0
  80. package/dist/modules/workflow/blocks/RenderMarkdown.js +48 -0
  81. package/dist/modules/workflow/blocks/SaveFile.js +54 -0
  82. package/dist/modules/workflow/blocks/ScrollNextBatch.js +72 -0
  83. package/dist/modules/workflow/blocks/SessionHealthBlock.js +73 -0
  84. package/dist/modules/workflow/blocks/StartBrowserService.js +45 -0
  85. package/dist/modules/workflow/blocks/ValidateContainerDefinition.js +67 -0
  86. package/dist/modules/workflow/blocks/ValidateExtract.js +35 -0
  87. package/dist/modules/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  88. package/dist/modules/workflow/blocks/WaitStable.js +74 -0
  89. package/dist/modules/workflow/blocks/WarmupCommentsBlock.js +120 -0
  90. package/dist/modules/workflow/blocks/WorkflowExecutor.js +156 -0
  91. package/dist/modules/workflow/blocks/XiaohongshuCollectFromLinksBlock.js +1004 -0
  92. package/dist/modules/workflow/blocks/XiaohongshuCollectLinksBlock.js +1049 -0
  93. package/dist/modules/workflow/blocks/XiaohongshuFullCollectBlock.js +782 -0
  94. package/dist/modules/workflow/blocks/helpers/anchorVerify.js +198 -0
  95. package/dist/modules/workflow/blocks/helpers/asyncWorkQueue.js +53 -0
  96. package/dist/modules/workflow/blocks/helpers/commentScroller.js +334 -0
  97. package/dist/modules/workflow/blocks/helpers/commentSectionLocator.js +126 -0
  98. package/dist/modules/workflow/blocks/helpers/containerAnchors.js +301 -0
  99. package/dist/modules/workflow/blocks/helpers/debugArtifacts.js +6 -0
  100. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +29 -0
  101. package/dist/modules/workflow/blocks/helpers/expandCommentsController.js +53 -0
  102. package/dist/modules/workflow/blocks/helpers/expandCommentsExtractor.js +129 -0
  103. package/dist/modules/workflow/blocks/helpers/macosVisionOcrPlugin.js +116 -0
  104. package/dist/modules/workflow/blocks/helpers/mergeXhsMarkdown.js +109 -0
  105. package/dist/modules/workflow/blocks/helpers/openDetailController.js +56 -0
  106. package/dist/modules/workflow/blocks/helpers/openDetailTypes.js +7 -0
  107. package/dist/modules/workflow/blocks/helpers/openDetailViewport.js +474 -0
  108. package/dist/modules/workflow/blocks/helpers/openDetailWaiter.js +104 -0
  109. package/dist/modules/workflow/blocks/helpers/operationLogger.js +195 -0
  110. package/dist/modules/workflow/blocks/helpers/persistedNotes.js +107 -0
  111. package/dist/modules/workflow/blocks/helpers/replyExpander.js +260 -0
  112. package/dist/modules/workflow/blocks/helpers/scrollIntoView.js +138 -0
  113. package/dist/modules/workflow/blocks/helpers/searchExecutor.js +328 -0
  114. package/dist/modules/workflow/blocks/helpers/searchGate.js +46 -0
  115. package/dist/modules/workflow/blocks/helpers/searchPageState.js +164 -0
  116. package/dist/modules/workflow/blocks/helpers/searchResultWaiter.js +64 -0
  117. package/dist/modules/workflow/blocks/helpers/simpleAnchor.js +134 -0
  118. package/dist/modules/workflow/blocks/helpers/smartReply.js +40 -0
  119. package/dist/modules/workflow/blocks/helpers/systemInput.js +635 -0
  120. package/dist/modules/workflow/blocks/helpers/targetCountMode.js +9 -0
  121. package/dist/modules/workflow/blocks/helpers/xhsCliArgs.js +80 -0
  122. package/dist/modules/workflow/blocks/helpers/xhsCommentDom.js +805 -0
  123. package/dist/modules/workflow/blocks/helpers/xhsNoteOrganizer.js +140 -0
  124. package/dist/modules/workflow/blocks/restore/RestorePhaseBlock.js +204 -0
  125. package/dist/modules/workflow/config/workflowRegistry.js +32 -0
  126. package/dist/modules/workflow/definitions/batch-collect-workflow.js +63 -0
  127. package/dist/modules/workflow/definitions/scroll-extract-workflow.js +74 -0
  128. package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow-v2.js +81 -0
  129. package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow.js +57 -0
  130. package/dist/modules/workflow/definitions/xiaohongshu-full-collect-workflow-v3.js +68 -0
  131. package/dist/modules/workflow/definitions/xiaohongshu-note-collect.js +49 -0
  132. package/dist/modules/workflow/definitions/xiaohongshu-phase1-workflow-v3.js +30 -0
  133. package/dist/modules/workflow/definitions/xiaohongshu-phase2-links-workflow-v3.js +40 -0
  134. package/dist/modules/workflow/definitions/xiaohongshu-phase3-collect-workflow-v1.js +54 -0
  135. package/dist/modules/workflow/definitions/xiaohongshu-phase34-from-links-workflow-v3.js +25 -0
  136. package/dist/modules/workflow/src/WeiboEventDrivenWorkflowRunner.js +308 -0
  137. package/dist/modules/workflow/src/context.js +70 -0
  138. package/dist/modules/workflow/src/index.js +5 -0
  139. package/dist/modules/workflow/src/orchestrator.js +230 -0
  140. package/dist/modules/workflow/src/runner.js +55 -0
  141. package/dist/modules/workflow/src/runtime.js +70 -0
  142. package/dist/modules/workflow/workflows/WeiboFeedExtractionWorkflow.js +359 -0
  143. package/dist/modules/workflow/workflows/XiaohongshuLoginWorkflow.js +110 -0
  144. package/dist/modules/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  145. package/dist/modules/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  146. package/dist/modules/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  147. package/dist/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  148. package/dist/modules/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  149. package/dist/modules/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  150. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  151. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  152. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  153. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  154. package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  155. package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  156. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  157. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  158. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  159. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  160. package/dist/modules/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  161. package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  162. package/dist/modules/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  163. package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  164. package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  165. package/dist/modules/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  166. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +42 -0
  167. package/dist/modules/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  168. package/dist/modules/xiaohongshu/app/src/index.js +9 -0
  169. package/dist/modules/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  170. package/dist/modules/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  171. package/dist/services/controller/src/controller.js +1476 -0
  172. package/dist/services/controller/src/index.js +2 -0
  173. package/dist/services/controller/src/payload-normalizer.js +129 -0
  174. package/dist/services/shared/heartbeat.js +120 -0
  175. package/dist/services/shared/lib/errorHandler.js +2 -0
  176. package/dist/services/shared/serviceProcessLogger.js +139 -0
  177. package/dist/services/unified-api/RemoteBrowserSession.js +176 -0
  178. package/dist/services/unified-api/RemoteSessionManager.js +148 -0
  179. package/dist/services/unified-api/container-operations-handler.js +115 -0
  180. package/dist/services/unified-api/server.js +652 -0
  181. package/dist/services/unified-api/state-registry.js +274 -0
  182. package/dist/services/unified-api/task-persistence.js +66 -0
  183. package/dist/services/unified-api/task-state.js +130 -0
  184. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +12 -5
  185. package/modules/xiaohongshu/app/pnpm-lock.yaml +24 -0
  186. package/package.json +37 -9
  187. package/.beads/README.md +0 -81
  188. package/.beads/config.yaml +0 -67
  189. package/.beads/interactions.jsonl +0 -0
  190. package/.beads/issues.jsonl +0 -180
  191. package/.beads/metadata.json +0 -4
  192. package/.claude/settings.local.json +0 -10
  193. package/.github/workflows/ci.yml +0 -55
  194. package/AGENTS.md +0 -253
  195. package/apps/desktop-console/README.md +0 -27
  196. package/apps/desktop-console/package-lock.json +0 -897
  197. package/apps/desktop-console/package.json +0 -20
  198. package/apps/desktop-console/scripts/build-and-install.mjs +0 -19
  199. package/apps/desktop-console/scripts/build.mjs +0 -45
  200. package/apps/desktop-console/scripts/test-preload.mjs +0 -13
  201. package/apps/desktop-console/src/main/config.mts +0 -26
  202. package/apps/desktop-console/src/main/core-daemon-manager.mts +0 -131
  203. package/apps/desktop-console/src/main/desktop-settings.mts +0 -267
  204. package/apps/desktop-console/src/main/heartbeat-watchdog.mts +0 -50
  205. package/apps/desktop-console/src/main/heartbeat-watchdog.test.mts +0 -68
  206. package/apps/desktop-console/src/main/index-streaming.test.mts +0 -20
  207. package/apps/desktop-console/src/main/index.mts +0 -980
  208. package/apps/desktop-console/src/main/profile-store.mts +0 -239
  209. package/apps/desktop-console/src/main/profile-store.test.mts +0 -54
  210. package/apps/desktop-console/src/main/state-bridge.mts +0 -114
  211. package/apps/desktop-console/src/main/task-state-types.ts +0 -32
  212. package/apps/desktop-console/src/renderer/hooks/use-task-state.mts +0 -120
  213. package/apps/desktop-console/src/renderer/index.mts +0 -133
  214. package/apps/desktop-console/src/renderer/index.test.mts +0 -34
  215. package/apps/desktop-console/src/renderer/path-helpers.mts +0 -46
  216. package/apps/desktop-console/src/renderer/path-helpers.test.mts +0 -14
  217. package/apps/desktop-console/src/renderer/tabs/debug.mts +0 -48
  218. package/apps/desktop-console/src/renderer/tabs/debug.test.mts +0 -22
  219. package/apps/desktop-console/src/renderer/tabs/logs.mts +0 -421
  220. package/apps/desktop-console/src/renderer/tabs/logs.test.mts +0 -27
  221. package/apps/desktop-console/src/renderer/tabs/preflight.mts +0 -486
  222. package/apps/desktop-console/src/renderer/tabs/preflight.test.mts +0 -33
  223. package/apps/desktop-console/src/renderer/tabs/profile-pool.mts +0 -213
  224. package/apps/desktop-console/src/renderer/tabs/results.mts +0 -171
  225. package/apps/desktop-console/src/renderer/tabs/run.test.mts +0 -63
  226. package/apps/desktop-console/src/renderer/tabs/runtime.mts +0 -151
  227. package/apps/desktop-console/src/renderer/tabs/settings.mts +0 -146
  228. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/account-flow.mts +0 -486
  229. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/guide-browser-check.mts +0 -56
  230. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/helpers.mts +0 -262
  231. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/layout-block.mts +0 -430
  232. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/live-stats.mts +0 -847
  233. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/run-flow.mts +0 -443
  234. package/apps/desktop-console/src/renderer/tabs/xiaohongshu-state.mts +0 -425
  235. package/apps/desktop-console/src/renderer/tabs/xiaohongshu.mts +0 -497
  236. package/apps/desktop-console/src/renderer/tabs/xiaohongshu.test.mts +0 -291
  237. package/apps/desktop-console/src/renderer/ui-components.mts +0 -31
  238. package/docs/README_camoufox_chinese.md +0 -141
  239. package/docs/USAGE_V3.md +0 -163
  240. package/docs/arch/OCR_MACOS_PLUGIN.md +0 -39
  241. package/docs/arch/PORTS.md +0 -40
  242. package/docs/arch/REGRESSION_CHECKLIST.md +0 -121
  243. package/docs/arch/SEARCH_GATE.md +0 -224
  244. package/docs/arch/VIEWPORT_SAFETY.md +0 -182
  245. package/docs/arch/XIAOHONGSHU_OFFLINE_MOCK_DESIGN.md +0 -267
  246. package/docs/xiaohongshu-container-driven-summary.md +0 -221
  247. package/docs/xiaohongshu-full-collect-runbook.md +0 -134
  248. package/docs/xiaohongshu-next-steps.md +0 -228
  249. package/docs/xiaohongshu-quickstart.md +0 -73
  250. package/docs/xiaohongshu-workflow-summary.md +0 -227
  251. package/modules/container-registry/tests/container-registry.test.ts +0 -16
  252. package/modules/logging/tests/logging.test.ts +0 -38
  253. package/modules/operations/tests/operations.test.ts +0 -22
  254. package/modules/operations/tests/viewport-filter.test.ts +0 -161
  255. package/modules/operations/tests/visible-only.test.ts +0 -250
  256. package/modules/session-manager/tests/session-manager.test.ts +0 -23
  257. package/modules/state/src/atomic-json.test.ts +0 -30
  258. package/modules/state/src/paths.test.ts +0 -59
  259. package/modules/state/src/xiaohongshu-collect-state.test.ts +0 -259
  260. package/modules/workflow/blocks/AnchorVerificationBlock.d.ts.map +0 -1
  261. package/modules/workflow/blocks/AnchorVerificationBlock.js.map +0 -1
  262. package/modules/workflow/blocks/DetectPageStateBlock.d.ts.map +0 -1
  263. package/modules/workflow/blocks/DetectPageStateBlock.js.map +0 -1
  264. package/modules/workflow/blocks/ErrorRecoveryBlock.d.ts.map +0 -1
  265. package/modules/workflow/blocks/ErrorRecoveryBlock.js.map +0 -1
  266. package/modules/workflow/blocks/WaitSearchPermitBlock.d.ts.map +0 -1
  267. package/modules/workflow/blocks/WaitSearchPermitBlock.js.map +0 -1
  268. package/modules/workflow/blocks/helpers/containerAnchors.d.ts.map +0 -1
  269. package/modules/workflow/blocks/helpers/containerAnchors.js.map +0 -1
  270. package/modules/workflow/blocks/helpers/downloadPaths.test.ts +0 -62
  271. package/modules/workflow/blocks/helpers/mergeXhsMarkdown.test.ts +0 -121
  272. package/modules/workflow/blocks/helpers/operationLogger.d.ts.map +0 -1
  273. package/modules/workflow/blocks/helpers/operationLogger.js.map +0 -1
  274. package/modules/workflow/blocks/helpers/persistedNotes.test.ts +0 -268
  275. package/modules/workflow/blocks/helpers/searchPageState.d.ts.map +0 -1
  276. package/modules/workflow/blocks/helpers/searchPageState.js.map +0 -1
  277. package/modules/workflow/blocks/helpers/targetCountMode.test.ts +0 -29
  278. package/modules/workflow/blocks/helpers/xhsCliArgs.test.ts +0 -75
  279. package/modules/workflow/tests/smartReply.test.ts +0 -32
  280. package/modules/xiaohongshu/app/src/blocks/Phase3Interact.matcher.test.ts +0 -33
  281. package/modules/xiaohongshu/app/src/utils/__tests__/checkpoints.test.ts +0 -141
  282. package/modules/xiaohongshu/app/tests/commentMatchDsl.test.ts +0 -50
  283. package/modules/xiaohongshu/app/tests/commentMatcher.test.ts +0 -46
  284. package/modules/xiaohongshu/app/tests/sharding.test.ts +0 -31
  285. package/package-scripts.json +0 -8
  286. package/runtime/infra/utils/README.md +0 -13
  287. package/runtime/infra/utils/scripts/README.md +0 -0
  288. package/runtime/infra/utils/scripts/development/eval-in-session.mjs +0 -40
  289. package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +0 -35
  290. package/runtime/infra/utils/scripts/service/kill-port.mjs +0 -24
  291. package/runtime/infra/utils/scripts/service/start-api.mjs +0 -39
  292. package/runtime/infra/utils/scripts/service/start-browser-service.mjs +0 -106
  293. package/runtime/infra/utils/scripts/service/stop-api.mjs +0 -18
  294. package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +0 -104
  295. package/runtime/infra/utils/scripts/test-services.mjs +0 -94
  296. package/services/shared/heartbeat.test.ts +0 -102
  297. package/services/unified-api/__tests__/task-state.test.ts +0 -95
  298. package/sitecustomize.py +0 -19
  299. package/tests/README.md +0 -194
  300. package/tests/e2e/workflows/weibo-feed-extraction.test.ts +0 -171
  301. package/tests/fixtures/data/container-definitions.json +0 -67
  302. package/tests/fixtures/pages/simple-page.html +0 -69
  303. package/tests/integration/01-test-container-match.mjs +0 -188
  304. package/tests/integration/02-test-dom-branch.mjs +0 -161
  305. package/tests/integration/03-test-container-operation-system.mjs +0 -91
  306. package/tests/integration/05-test-container-lifecycle-events.mjs +0 -224
  307. package/tests/integration/05-test-container-lifecycle-with-events.mjs +0 -250
  308. package/tests/integration/06-test-container-dom-tree-drawing.mjs +0 -256
  309. package/tests/integration/07-test-weibo-container-lifecycle.mjs +0 -355
  310. package/tests/integration/08-test-weibo-feed-workflow.test.mjs +0 -164
  311. package/tests/integration/10-test-visual-analyzer.mjs +0 -312
  312. package/tests/integration/11-test-visual-loop.mjs +0 -284
  313. package/tests/integration/12-test-simple-visual-loop.mjs +0 -242
  314. package/tests/integration/13-test-visual-robust.mjs +0 -185
  315. package/tests/integration/14-test-visual-highlight-loop.mjs +0 -271
  316. package/tests/integration/inspect-page.mjs +0 -50
  317. package/tests/integration/run-all-tests.mjs +0 -95
  318. package/tests/patch_verification/CODEX_PATCH_TEST.md +0 -103
  319. package/tests/patch_verification/PHASE2_ANALYSIS.md +0 -179
  320. package/tests/patch_verification/PHASE2_OPTIMIZATION_REPORT.md +0 -55
  321. package/tests/patch_verification/PHASE2_TO_PHASE4_SUMMARY.md +0 -126
  322. package/tests/patch_verification/QUICK_TEST_SEQUENCE.md +0 -262
  323. package/tests/patch_verification/README.md +0 -143
  324. package/tests/patch_verification/RUN_TESTS.md +0 -60
  325. package/tests/patch_verification/TEST_EXECUTION.md +0 -99
  326. package/tests/patch_verification/TEST_PLAN.md +0 -328
  327. package/tests/patch_verification/TEST_RESULTS.md +0 -34
  328. package/tests/patch_verification/TOOL_TEST_PLAN.md +0 -48
  329. package/tests/patch_verification/run-tool-test.mjs +0 -121
  330. package/tests/patch_verification/temp_test_files/test01.txt +0 -1
  331. package/tests/patch_verification/temp_test_files/test02.txt +0 -3
  332. package/tests/patch_verification/temp_test_files/test02_gnu.txt +0 -3
  333. package/tests/patch_verification/temp_test_files/test03.txt +0 -1
  334. package/tests/patch_verification/temp_test_files/test03_multiline.txt +0 -5
  335. package/tests/patch_verification/temp_test_files/test04_function.ts +0 -5
  336. package/tests/patch_verification/temp_test_files/test05_import.ts +0 -4
  337. package/tests/patch_verification/temp_test_files/test06_special_chars.txt +0 -4
  338. package/tests/patch_verification/temp_test_files/test07_indentation.ts +0 -5
  339. package/tests/patch_verification/temp_test_files/test08_mismatch.txt +0 -1
  340. package/tests/patch_verification/temp_test_files/test_add_02.txt +0 -3
  341. package/tests/patch_verification/temp_test_files/test_simple.txt +0 -1
  342. package/tests/runner/TestReporter.mjs +0 -57
  343. package/tests/runner/TestRunner.mjs +0 -244
  344. package/tests/unit/commands/profile.test.mjs +0 -10
  345. package/tests/unit/container/change-notifier.test.mjs +0 -181
  346. package/tests/unit/lifecycle/session-registry.test.mjs +0 -135
  347. package/tests/unit/operations/registry.test.ts +0 -73
  348. package/tests/unit/utils/browser-service.test.mjs +0 -153
  349. package/tests/unit/utils/config.test.mjs +0 -166
  350. package/tests/unit/utils/fingerprint.test.mjs +0 -166
  351. package/tsconfig.json +0 -31
  352. package/tsconfig.services.json +0 -26
  353. /package/apps/desktop-console/{src → dist}/renderer/index.html +0 -0
  354. /package/apps/desktop-console/{src/renderer/tabs → dist/renderer}/run.mts +0 -0
@@ -0,0 +1,328 @@
1
+ /**
2
+ * SearchExecutor helper
3
+ *
4
+ * 处理搜索输入、焦点检查和搜索执行
5
+ */
6
+ import os from 'node:os';
7
+ import { logControllerActionError, logControllerActionResult, logControllerActionStart, } from './operationLogger.js';
8
+ export async function controllerAction(controllerUrl, action, payload = {}) {
9
+ const opId = logControllerActionStart(action, payload, { source: 'searchExecutor' });
10
+ try {
11
+ const response = await fetch(controllerUrl, {
12
+ method: 'POST',
13
+ headers: { 'Content-Type': 'application/json' },
14
+ body: JSON.stringify({ action, payload }),
15
+ signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined
16
+ });
17
+ if (!response.ok) {
18
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
19
+ }
20
+ const data = await response.json();
21
+ const result = data.data || data;
22
+ logControllerActionResult(opId, action, result, { source: 'searchExecutor' });
23
+ return result;
24
+ }
25
+ catch (error) {
26
+ logControllerActionError(opId, action, error, payload, { source: 'searchExecutor' });
27
+ throw error;
28
+ }
29
+ }
30
+ export async function verifySearchBarAnchor(config) {
31
+ const { profile, controllerUrl, searchInputContainerId } = config;
32
+ try {
33
+ const { verifyAnchorByContainerId } = await import('./containerAnchors.js');
34
+ const anchor = await verifyAnchorByContainerId(searchInputContainerId, profile, controllerUrl.replace('/v1/controller/action', ''));
35
+ if (!anchor.found) {
36
+ return {
37
+ found: false,
38
+ error: anchor.error || 'anchor_not_found',
39
+ selector: anchor.selector
40
+ };
41
+ }
42
+ return {
43
+ found: true,
44
+ selector: anchor.selector || '#search-input',
45
+ rect: anchor.rect
46
+ };
47
+ }
48
+ catch (error) {
49
+ return { found: false, error: error.message };
50
+ }
51
+ }
52
+ export async function isSearchInputFocused(config, selector) {
53
+ const { profile, controllerUrl } = config;
54
+ if (!selector)
55
+ return false;
56
+ const script = `(() => {
57
+ const el = document.querySelector(${JSON.stringify(selector)});
58
+ if (!el) return false;
59
+ const active = document.activeElement;
60
+ if (!active) return false;
61
+ return active === el || (el instanceof Element && el.contains(active));
62
+ })()`;
63
+ const response = await fetch(controllerUrl, {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({
67
+ action: 'browser:execute',
68
+ payload: {
69
+ profile,
70
+ script,
71
+ },
72
+ }),
73
+ });
74
+ const data = await response.json();
75
+ return Boolean(data.data?.result ?? data.result);
76
+ }
77
+ export async function readSearchInputValue(config, selector) {
78
+ const { profile, controllerUrl } = config;
79
+ const sel = selector ||
80
+ '#search-input, input[type="search"], .search-input';
81
+ const script = `(() => {
82
+ const el = document.querySelector(${JSON.stringify(sel)});
83
+ if (!el) return '';
84
+ try {
85
+ if ('value' in el) return String(el.value || '');
86
+ if (el.isContentEditable) return String(el.textContent || '');
87
+ return String(el.textContent || '');
88
+ } catch {
89
+ return '';
90
+ }
91
+ })()`;
92
+ const response = await fetch(controllerUrl, {
93
+ method: 'POST',
94
+ headers: { 'Content-Type': 'application/json' },
95
+ body: JSON.stringify({
96
+ action: 'browser:execute',
97
+ payload: {
98
+ profile,
99
+ script,
100
+ },
101
+ }),
102
+ });
103
+ const data = await response.json();
104
+ const value = data.data?.result ?? data.result ?? '';
105
+ return typeof value === 'string' ? value : '';
106
+ }
107
+ export async function executeSearch(config, anchorSelector) {
108
+ const { profile, controllerUrl, searchInputContainerId, keyword } = config;
109
+ try {
110
+ console.log(`[SearchExecutor] Typing keyword "${keyword}"...`);
111
+ const selector = anchorSelector ||
112
+ '#search-input, input[type="search"], .search-input';
113
+ // ✅ 系统级输入:禁止 container:operation type(底层为 session.fill,属于非系统行为)
114
+ // 依赖上游已完成 focus;这里额外做一次清空 + 输入 + Enter
115
+ // 注意:在 mac 上 Control+A 可能导致光标跳到行首,反而造成“关键字拼接”。
116
+ let typedByContainer = false;
117
+ try {
118
+ const typeResp = await controllerAction(controllerUrl, 'container:operation', {
119
+ containerId: searchInputContainerId,
120
+ operationId: 'type',
121
+ sessionId: profile,
122
+ config: {
123
+ selector,
124
+ text: keyword,
125
+ clear_first: true,
126
+ human_typing: true,
127
+ pause_after: 320,
128
+ fullyVisible: true,
129
+ },
130
+ });
131
+ const ok = Boolean(typeResp?.success !== false && (typeResp?.data?.success ?? true));
132
+ if (!ok) {
133
+ console.warn('[SearchExecutor] container type failed', typeResp);
134
+ }
135
+ else {
136
+ typedByContainer = true;
137
+ }
138
+ }
139
+ catch (error) {
140
+ console.warn('[SearchExecutor] container type failed', error?.message || String(error));
141
+ }
142
+ if (!typedByContainer) {
143
+ const platform = os.platform();
144
+ const selectAllKey = platform === 'darwin' ? 'Meta+A' : 'Control+A';
145
+ await controllerAction(controllerUrl, 'keyboard:press', { profileId: profile, key: selectAllKey }).catch(() => { });
146
+ await controllerAction(controllerUrl, 'keyboard:press', { profileId: profile, key: 'Backspace' }).catch(() => { });
147
+ await controllerAction(controllerUrl, 'keyboard:press', { profileId: profile, key: 'Delete' }).catch(() => { });
148
+ await controllerAction(controllerUrl, 'keyboard:type', {
149
+ profileId: profile,
150
+ text: keyword,
151
+ delay: 80 + Math.floor(Math.random() * 60),
152
+ }).catch(() => { });
153
+ }
154
+ // 操作之间要等待:给输入法/站内联想一点稳定时间
155
+ await new Promise((r) => setTimeout(r, 420));
156
+ // 仅用于诊断:记录“相关搜索/推荐词”是否出现及其内容(不做点击/纠错)
157
+ let suggestionProbe = null;
158
+ try {
159
+ const raw = await controllerAction(controllerUrl, 'browser:execute', {
160
+ profile,
161
+ script: `(() => {
162
+ const wrap = document.querySelector('.query-note-wrapper');
163
+ if (!wrap) return { visible: false, items: [], active: null };
164
+ const r = wrap.getBoundingClientRect();
165
+ const visible =
166
+ r.width > 10 && r.height > 10 && r.bottom > 0 && r.top < (window.innerHeight || 0);
167
+ const items = Array.from(wrap.querySelectorAll('.item-text'))
168
+ .map((el) => (el && el.textContent ? el.textContent.trim() : ''))
169
+ .filter(Boolean)
170
+ .slice(0, 8);
171
+ const activeEl =
172
+ wrap.querySelector('.rec-query.active, .rec-query.selected, .rec-query.current, [aria-selected=\"true\"]') ||
173
+ null;
174
+ const active = activeEl && activeEl.textContent ? activeEl.textContent.trim().slice(0, 80) : null;
175
+ return { visible, items, active };
176
+ })()`,
177
+ });
178
+ suggestionProbe = raw?.result ?? raw?.data?.result ?? raw ?? null;
179
+ }
180
+ catch {
181
+ suggestionProbe = null;
182
+ }
183
+ // 开发阶段:必须保证输入框中确实出现目标 keyword,否则直接失败(不做无脑重试)
184
+ let activeValue = '';
185
+ try {
186
+ const raw = await controllerAction(controllerUrl, 'browser:execute', {
187
+ profile,
188
+ script: `(() => {
189
+ const el = document.activeElement;
190
+ if (!el) return '';
191
+ const tag = (el.tagName || '').toLowerCase();
192
+ if (tag === 'input' || tag === 'textarea') {
193
+ const v = el.value || '';
194
+ return (typeof v === 'string') ? v : String(v || '');
195
+ }
196
+ if (el.isContentEditable) return (el.textContent || '');
197
+ return '';
198
+ })()`,
199
+ });
200
+ activeValue = raw?.result ?? raw?.data?.result ?? raw ?? '';
201
+ }
202
+ catch {
203
+ activeValue = '';
204
+ }
205
+ const rawValue = await readSearchInputValue(config, selector).catch(() => '');
206
+ const trimmedExpected = String(keyword || '').trim();
207
+ const trimmedActive = typeof activeValue === 'string' ? activeValue.trim() : '';
208
+ const trimmedRaw = typeof rawValue === 'string' ? rawValue.trim() : '';
209
+ const matches = (trimmedActive && trimmedActive === trimmedExpected) || (trimmedRaw && trimmedRaw === trimmedExpected);
210
+ const valueMismatch = !matches;
211
+ if (valueMismatch) {
212
+ console.warn(`[SearchExecutor] keyword not detected in input, continue to press Enter. active="${trimmedActive}", selectorValue="${trimmedRaw}", expected="${trimmedExpected}"`);
213
+ }
214
+ async function readUrl() {
215
+ try {
216
+ const raw = await controllerAction(controllerUrl, 'browser:execute', {
217
+ profile,
218
+ script: 'location.href',
219
+ });
220
+ const v = raw?.result ?? raw?.data?.result ?? raw ?? '';
221
+ return typeof v === 'string' ? v : String(v || '');
222
+ }
223
+ catch {
224
+ return '';
225
+ }
226
+ }
227
+ async function waitForSearchResultUrl(maxWaitMs) {
228
+ const start = Date.now();
229
+ let last = '';
230
+ while (Date.now() - start < maxWaitMs) {
231
+ last = await readUrl();
232
+ if (typeof last === 'string' && last.includes('/search_result')) {
233
+ return { ok: true, url: last };
234
+ }
235
+ await new Promise((r) => setTimeout(r, 400));
236
+ }
237
+ return { ok: false, url: last };
238
+ }
239
+ // 中文输入法下 Enter 可能先“确认输入”而不触发搜索:最多 2 次 Enter(不循环重搜)
240
+ let navigated = false;
241
+ let lastUrl = '';
242
+ const useSearchButton = searchInputContainerId === 'xiaohongshu_home.search_input';
243
+ if (useSearchButton) {
244
+ console.log('[SearchExecutor] Trigger search via home search button ...');
245
+ try {
246
+ const clickResp = await controllerAction(controllerUrl, 'container:operation', {
247
+ containerId: 'xiaohongshu_home.search_button',
248
+ operationId: 'click',
249
+ sessionId: profile,
250
+ config: { fullyVisible: true, useSystemMouse: true },
251
+ });
252
+ const ok = Boolean(clickResp?.success !== false && (clickResp?.data?.success ?? true));
253
+ if (!ok) {
254
+ console.warn('[SearchExecutor] search button click failed', clickResp);
255
+ }
256
+ }
257
+ catch (error) {
258
+ console.warn('[SearchExecutor] search button click failed', error?.message || String(error));
259
+ }
260
+ await new Promise((r) => setTimeout(r, 650));
261
+ const waited = await waitForSearchResultUrl(9000);
262
+ lastUrl = waited.url;
263
+ navigated = waited.ok;
264
+ }
265
+ if (!navigated) {
266
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
267
+ console.log(`[SearchExecutor] Press Enter (attempt=${attempt}/2) ...`);
268
+ await controllerAction(controllerUrl, 'keyboard:press', { profileId: profile, key: 'Enter' }).catch(() => { });
269
+ await new Promise((r) => setTimeout(r, 450));
270
+ const waited = await waitForSearchResultUrl(7000);
271
+ lastUrl = waited.url;
272
+ if (waited.ok) {
273
+ navigated = true;
274
+ break;
275
+ }
276
+ }
277
+ }
278
+ console.log('[SearchExecutor] Search triggered, waiting for results...');
279
+ if (!navigated) {
280
+ return {
281
+ success: false,
282
+ error: `search_not_navigated_after_enter (url="${lastUrl || ''}")`,
283
+ debug: {
284
+ selector,
285
+ searchInputContainerId,
286
+ suggestions: suggestionProbe,
287
+ valueMismatch,
288
+ activeValue: trimmedActive,
289
+ selectorValue: trimmedRaw,
290
+ },
291
+ };
292
+ }
293
+ return {
294
+ success: true,
295
+ debug: {
296
+ selector,
297
+ searchInputContainerId,
298
+ suggestions: suggestionProbe,
299
+ valueMismatch,
300
+ activeValue: trimmedActive,
301
+ selectorValue: trimmedRaw,
302
+ },
303
+ };
304
+ }
305
+ catch (error) {
306
+ console.error(`[SearchExecutor] Search failed: ${error.message}`);
307
+ return { success: false, error: error.message };
308
+ }
309
+ }
310
+ export async function performSystemClickFocus(config, selector) {
311
+ const { profile, controllerUrl, searchInputContainerId } = config;
312
+ try {
313
+ const clickResp = await controllerAction(controllerUrl, 'container:operation', {
314
+ containerId: searchInputContainerId,
315
+ operationId: 'click',
316
+ config: { selector, useSystemMouse: true, fullyVisible: true },
317
+ sessionId: profile
318
+ });
319
+ console.log('[SearchExecutor] System click on search bar executed', clickResp);
320
+ const focused = await isSearchInputFocused(config, selector);
321
+ return { success: true, focused };
322
+ }
323
+ catch (error) {
324
+ console.warn('[SearchExecutor] System click on search bar failed:', error.message);
325
+ return { success: false, focused: false, error: error.message };
326
+ }
327
+ }
328
+ //# sourceMappingURL=searchExecutor.js.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * SearchGate helper
3
+ *
4
+ * 处理搜索节流请求,避免因频繁搜索触发风控
5
+ */
6
+ export async function waitSearchPermit(config) {
7
+ const { profileId, windowMs = 60_000, maxCount = 2, gateUrl = process.env.WEBAUTO_SEARCH_GATE_URL || 'http://127.0.0.1:7790/permit', maxRetries = 5 } = config;
8
+ async function requestOnce() {
9
+ const response = await fetch(gateUrl, {
10
+ method: 'POST',
11
+ headers: { 'Content-Type': 'application/json' },
12
+ body: JSON.stringify({
13
+ profileId,
14
+ windowMs,
15
+ maxCount
16
+ }),
17
+ signal: AbortSignal.timeout ? AbortSignal.timeout(5000) : undefined
18
+ });
19
+ if (!response.ok) {
20
+ throw new Error(`SearchGate HTTP ${response.status}`);
21
+ }
22
+ const data = await response.json();
23
+ return {
24
+ allowed: Boolean(data.allowed),
25
+ waitMs: Number(data.waitMs || 0)
26
+ };
27
+ }
28
+ try {
29
+ for (let i = 0; i < maxRetries; i++) {
30
+ const { allowed, waitMs } = await requestOnce();
31
+ if (allowed) {
32
+ console.log('[SearchGate] Permit granted');
33
+ return;
34
+ }
35
+ const safeWait = Math.min(Math.max(waitMs, 5_000), 65_000);
36
+ console.log(`[SearchGate] Throttling, wait ${safeWait}ms`);
37
+ await new Promise(resolve => setTimeout(resolve, safeWait));
38
+ }
39
+ throw new Error('SearchGate throttling: too many retries');
40
+ }
41
+ catch (err) {
42
+ console.error('[SearchGate] Not available:', err.message);
43
+ throw new Error('SearchGate not available, 请先在另一终端运行 node scripts/search-gate-server.mjs');
44
+ }
45
+ }
46
+ //# sourceMappingURL=searchGate.js.map
@@ -0,0 +1,164 @@
1
+ /**
2
+ * SearchPageState helper
3
+ *
4
+ * 处理搜索页面状态验证和 URL 关键词校验
5
+ */
6
+ function safeDecodeURIComponent(value) {
7
+ try {
8
+ return decodeURIComponent(value);
9
+ }
10
+ catch {
11
+ return value;
12
+ }
13
+ }
14
+ export function urlKeywordEquals(url, keyword) {
15
+ const raw = url || '';
16
+ if (!raw.includes('/search_result'))
17
+ return false;
18
+ try {
19
+ const u = new URL(raw);
20
+ const kw = u.searchParams.get('keyword');
21
+ if (!kw)
22
+ return false;
23
+ const decoded = safeDecodeURIComponent(safeDecodeURIComponent(kw)).trim();
24
+ return decoded === String(keyword || '').trim();
25
+ }
26
+ catch {
27
+ const decodedUrl = safeDecodeURIComponent(safeDecodeURIComponent(raw));
28
+ if (!decodedUrl.includes('/search_result'))
29
+ return false;
30
+ try {
31
+ const u2 = new URL(decodedUrl);
32
+ const kw2 = u2.searchParams.get('keyword');
33
+ if (!kw2)
34
+ return false;
35
+ const decoded2 = safeDecodeURIComponent(safeDecodeURIComponent(kw2)).trim();
36
+ return decoded2 === String(keyword || '').trim();
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ }
43
+ export async function getCurrentUrl(config) {
44
+ const { profile, controllerUrl } = config;
45
+ const response = await fetch(controllerUrl, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({
49
+ action: 'browser:execute',
50
+ payload: {
51
+ profile,
52
+ script: 'location.href'
53
+ }
54
+ })
55
+ });
56
+ const data = await response.json();
57
+ return data.data?.result || '';
58
+ }
59
+ export async function ensureHomePage(config) {
60
+ const { profile, controllerUrl } = config;
61
+ const url = await getCurrentUrl({ profile, controllerUrl });
62
+ if (!url.includes('xiaohongshu.com')) {
63
+ throw new Error(`Not on xiaohongshu.com (current url=${url || 'unknown'}), please navigate manually before searching.`);
64
+ }
65
+ try {
66
+ const detailState = await fetch(controllerUrl, {
67
+ method: 'POST',
68
+ headers: { 'Content-Type': 'application/json' },
69
+ body: JSON.stringify({
70
+ action: 'browser:execute',
71
+ payload: {
72
+ profile,
73
+ script: `(() => {
74
+ const selectors = [
75
+ '.note-detail-mask',
76
+ '.note-detail-page',
77
+ '.note-detail-dialog',
78
+ '.note-detail',
79
+ '.detail-container',
80
+ '.media-container'
81
+ ];
82
+ const isVisible = (el) => {
83
+ if (!el) return false;
84
+ const style = window.getComputedStyle(el);
85
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
86
+ const r = el.getBoundingClientRect();
87
+ if (!r.width || !r.height) return false;
88
+ if (r.bottom <= 0 || r.top >= window.innerHeight) return false;
89
+ return true;
90
+ };
91
+ let visibleOverlay = null;
92
+ for (const sel of selectors) {
93
+ const el = document.querySelector(sel);
94
+ if (el && isVisible(el)) {
95
+ visibleOverlay = el;
96
+ break;
97
+ }
98
+ }
99
+ return { hasDetailOverlayVisible: !!visibleOverlay };
100
+ })()`
101
+ },
102
+ }),
103
+ }).then((r) => r.json());
104
+ const payload = detailState.data?.result ?? detailState.result ?? {};
105
+ if (payload.hasDetailOverlayVisible) {
106
+ throw new Error('Currently in visible detail overlay, please exit detail before searching.');
107
+ }
108
+ }
109
+ catch (err) {
110
+ if (err?.message?.includes('Currently in detail overlay')) {
111
+ throw err;
112
+ }
113
+ console.warn('[SearchPageState] Detail-overlay check failed:', err?.message || err);
114
+ }
115
+ const onSearchPage = url.includes('/search_result');
116
+ const onCaptchaPage = url.includes('captcha') || url.includes('verify');
117
+ if (onCaptchaPage) {
118
+ throw new Error('Detected CAPTCHA page, please solve it manually.');
119
+ }
120
+ return {
121
+ success: true,
122
+ url,
123
+ onSearchPage,
124
+ onCaptchaPage
125
+ };
126
+ }
127
+ export async function probeSearchPageState(config) {
128
+ const { profile, controllerUrl } = config;
129
+ try {
130
+ const response = await fetch(controllerUrl, {
131
+ method: 'POST',
132
+ headers: { 'Content-Type': 'application/json' },
133
+ body: JSON.stringify({
134
+ action: 'browser:execute',
135
+ payload: {
136
+ profile,
137
+ script: `(() => {
138
+ const cards = document.querySelectorAll('.note-item').length;
139
+ const emptyEl =
140
+ document.querySelector('[class*="no-result"], [class*="noResult"], [class*="empty"], .search-empty, .empty') ||
141
+ null;
142
+ const emptyText = (emptyEl ? (emptyEl.textContent || '') : '').trim();
143
+ const bodyText = (document.body && document.body.innerText ? document.body.innerText.slice(0, 1200) : '');
144
+ const hasNoResultText =
145
+ emptyText.includes('没找到相关内容') ||
146
+ emptyText.includes('换个词试试') ||
147
+ bodyText.includes('没找到相关内容') ||
148
+ bodyText.includes('换个词试试');
149
+ return { cards, hasNoResultText };
150
+ })()`
151
+ },
152
+ }),
153
+ signal: AbortSignal.timeout ? AbortSignal.timeout(5000) : undefined,
154
+ });
155
+ const data = await response.json().catch(() => ({}));
156
+ const payload = data.data?.result ?? data.result ?? {};
157
+ const cards = Number(payload.cards || 0);
158
+ return { hasItems: cards > 0, hasNoResultText: Boolean(payload.hasNoResultText) };
159
+ }
160
+ catch {
161
+ return { hasItems: false, hasNoResultText: false };
162
+ }
163
+ }
164
+ //# sourceMappingURL=searchPageState.js.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * SearchResultWaiter helper
3
+ *
4
+ * 处理搜索结果等待和列表锚点验证
5
+ */
6
+ import { getCurrentUrl, urlKeywordEquals, probeSearchPageState } from './searchPageState.js';
7
+ async function verifySearchResultListAnchor(profile, controllerUrl) {
8
+ try {
9
+ const serviceUrl = controllerUrl.replace('/v1/controller/action', '');
10
+ const { verifyAnchorByContainerId } = await import('./containerAnchors.js');
11
+ const anchor = await verifyAnchorByContainerId('xiaohongshu_search.search_result_list', profile, serviceUrl);
12
+ if (!anchor.found) {
13
+ return {
14
+ found: false,
15
+ error: anchor.error || 'anchor_not_found',
16
+ selector: anchor.selector,
17
+ rect: anchor.rect
18
+ };
19
+ }
20
+ return {
21
+ found: true,
22
+ selector: anchor.selector,
23
+ rect: anchor.rect
24
+ };
25
+ }
26
+ catch (error) {
27
+ return { found: false, error: error.message };
28
+ }
29
+ }
30
+ export async function waitForSearchResultsReady(config) {
31
+ const { profile, controllerUrl, keyword, maxWaitMs = 30000 } = config;
32
+ const start = Date.now();
33
+ let lastUrl = '';
34
+ while (Date.now() - start < maxWaitMs) {
35
+ lastUrl = await getCurrentUrl({ profile, controllerUrl });
36
+ if (lastUrl.includes('captcha') || lastUrl.includes('verify')) {
37
+ return { ok: false, noResults: false, url: lastUrl };
38
+ }
39
+ const urlLooksRight = lastUrl.includes('/search_result') && urlKeywordEquals(lastUrl, keyword);
40
+ if (!urlLooksRight) {
41
+ await new Promise(resolve => setTimeout(resolve, 800));
42
+ continue;
43
+ }
44
+ const listAnchor = await verifySearchResultListAnchor(profile, controllerUrl);
45
+ if (listAnchor.found && listAnchor.rect) {
46
+ return {
47
+ ok: true,
48
+ noResults: false,
49
+ listAnchor: { selector: listAnchor.selector, rect: listAnchor.rect },
50
+ url: lastUrl,
51
+ };
52
+ }
53
+ const probe = await probeSearchPageState({ profile, controllerUrl });
54
+ if (probe.hasNoResultText) {
55
+ return { ok: false, noResults: true, url: lastUrl };
56
+ }
57
+ if (probe.hasItems) {
58
+ return { ok: true, noResults: false, url: lastUrl };
59
+ }
60
+ await new Promise(resolve => setTimeout(resolve, 1000));
61
+ }
62
+ return { ok: false, noResults: false, url: lastUrl };
63
+ }
64
+ //# sourceMappingURL=searchResultWaiter.js.map