@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,599 @@
1
+ /**
2
+ * Workflow Block: CollectSearchListBlock (v3 with timeout protection)
3
+ *
4
+ * 从搜索结果列表中收集笔记条目(支持滚动加载)
5
+ *
6
+ * v3 改进(2025-01-07):
7
+ * - 为 containers:match 添加 5 秒超时保护
8
+ * - 超时或失败时降级到固定容器 ID(基于 URL 判断)
9
+ * - 与 P0 目标一致:降低对 containers:match 的依赖
10
+ */
11
+ import { logControllerActionError, logControllerActionResult, logControllerActionStart, } from './helpers/operationLogger.js';
12
+ export async function execute(input) {
13
+ const { sessionId, targetCount = 20, maxScrollRounds = 10, serviceUrl = 'http://127.0.0.1:7701' } = input;
14
+ const profile = sessionId;
15
+ const controllerUrl = `${serviceUrl}/v1/controller/action`;
16
+ const MATCH_TIMEOUT_MS = 12_000;
17
+ function deriveNoteIdFromUrl(url) {
18
+ if (!url)
19
+ return undefined;
20
+ try {
21
+ const m = url.match(/\/(explore|search_result)\/([^/?#]+)/);
22
+ return m ? m[2] : undefined;
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ async function controllerAction(action, payload = {}) {
29
+ const opId = logControllerActionStart(action, payload, { source: 'CollectSearchListBlock' });
30
+ try {
31
+ const response = await fetch(controllerUrl, {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify({ action, payload })
35
+ });
36
+ if (!response.ok) {
37
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
38
+ }
39
+ const data = await response.json();
40
+ const result = data.data || data;
41
+ logControllerActionResult(opId, action, result, { source: 'CollectSearchListBlock' });
42
+ return result;
43
+ }
44
+ catch (error) {
45
+ logControllerActionError(opId, action, error, payload, { source: 'CollectSearchListBlock' });
46
+ throw error;
47
+ }
48
+ }
49
+ async function getCurrentUrl() {
50
+ const response = await fetch(controllerUrl, {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify({
54
+ action: 'browser:execute',
55
+ payload: { profile, script: 'location.href' }
56
+ })
57
+ });
58
+ const data = await response.json();
59
+ return data.data?.result || '';
60
+ }
61
+ async function probeNoResultOrHasItems() {
62
+ try {
63
+ const res = await controllerAction('browser:execute', {
64
+ profile,
65
+ script: `(() => {
66
+ const cards = document.querySelectorAll('.note-item').length;
67
+ const emptyEl =
68
+ document.querySelector('[class*="no-result"], [class*="noResult"], [class*="empty"], .search-empty, .empty') ||
69
+ null;
70
+ const emptyText = (emptyEl ? (emptyEl.textContent || '') : '').trim();
71
+ const bodyText = (document.body && document.body.innerText ? document.body.innerText.slice(0, 1200) : '');
72
+ const hasNoResultText =
73
+ emptyText.includes('没找到相关内容') ||
74
+ emptyText.includes('换个词试试') ||
75
+ bodyText.includes('没找到相关内容') ||
76
+ bodyText.includes('换个词试试');
77
+ return { cards, hasNoResultText };
78
+ })()`,
79
+ });
80
+ const payload = res?.result ?? res?.data?.result ?? res ?? {};
81
+ const cards = Number(payload?.cards ?? 0);
82
+ return { hasItems: cards > 0, hasNoResultText: Boolean(payload?.hasNoResultText) };
83
+ }
84
+ catch {
85
+ return { hasItems: false, hasNoResultText: false };
86
+ }
87
+ }
88
+ async function waitForSearchItemsIfNeeded(currentUrl) {
89
+ if (!currentUrl.includes('/search_result'))
90
+ return { ok: true, noResults: false };
91
+ const start = Date.now();
92
+ while (Date.now() - start < 25_000) {
93
+ const probe = await probeNoResultOrHasItems();
94
+ if (probe.hasNoResultText)
95
+ return { ok: false, noResults: true };
96
+ if (probe.hasItems)
97
+ return { ok: true, noResults: false };
98
+ await new Promise((r) => setTimeout(r, 1000));
99
+ }
100
+ // 超时:继续走采集逻辑(可能页面慢/容器异常),由下游判断是否 0 item
101
+ return { ok: true, noResults: false };
102
+ }
103
+ function findContainer(tree, pattern) {
104
+ if (!tree)
105
+ return null;
106
+ if (pattern.test(tree.id || tree.defId || ''))
107
+ return tree;
108
+ if (Array.isArray(tree.children)) {
109
+ for (const child of tree.children) {
110
+ const found = findContainer(child, pattern);
111
+ if (found)
112
+ return found;
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+ function collectContainers(tree, pattern, result = []) {
118
+ if (!tree)
119
+ return result;
120
+ if (pattern.test(tree.id || tree.defId || '')) {
121
+ result.push(tree);
122
+ }
123
+ if (Array.isArray(tree.children)) {
124
+ for (const child of tree.children) {
125
+ collectContainers(child, pattern, result);
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+ /**
131
+ * 滚动列表容器(遵循视口安全原则)
132
+ *
133
+ * 基于列表容器锚点的 selector,在容器内部执行 scrollBy;
134
+ * 单次滚动距离不超过 800px,滚动前后都会做 Rect 与滚动量校验。
135
+ */
136
+ async function scrollListContainer(containerId, direction = 'down') {
137
+ try {
138
+ // ✅ 系统级滚动:通过容器 scroll operation 触发真实滚轮/键盘滚动(禁止 JS scrollBy)
139
+ const op = await controllerAction('container:operation', {
140
+ containerId,
141
+ operationId: 'scroll',
142
+ sessionId: profile,
143
+ config: { direction, amount: 800 },
144
+ });
145
+ const payload = op?.data ?? op;
146
+ const ok = Boolean(payload?.success ?? payload?.data?.success ?? op?.success);
147
+ await new Promise((resolve) => setTimeout(resolve, 1000));
148
+ return ok;
149
+ }
150
+ catch (error) {
151
+ console.warn(`[CollectSearchList] Scroll error: ${error.message}`);
152
+ return false;
153
+ }
154
+ }
155
+ async function scrollPageFallback(direction = 'down') {
156
+ try {
157
+ // ✅ 系统级滚动:PageDown / PageUp(禁止 JS scrollBy)
158
+ const before = await controllerAction('browser:execute', {
159
+ profile,
160
+ script: '({ y: window.scrollY || document.documentElement.scrollTop || 0 })'
161
+ });
162
+ const beforeY = before?.result?.y ?? before?.data?.result?.y ?? before?.y ?? 0;
163
+ await controllerAction('keyboard:press', {
164
+ profileId: profile,
165
+ key: direction === 'up' ? 'PageUp' : 'PageDown',
166
+ });
167
+ await new Promise((resolve) => setTimeout(resolve, 1200));
168
+ const after = await controllerAction('browser:execute', {
169
+ profile,
170
+ script: '({ y: window.scrollY || document.documentElement.scrollTop || 0 })'
171
+ });
172
+ const afterY = after?.result?.y ?? after?.data?.result?.y ?? after?.y ?? 0;
173
+ const scrolled = Number(afterY) - Number(beforeY);
174
+ console.log(`[CollectSearchList] Page scrolled (${direction}): ${beforeY} -> ${afterY} (+${scrolled}px)`);
175
+ return Math.abs(scrolled) > 0;
176
+ }
177
+ catch (error) {
178
+ console.warn(`[CollectSearchList] Page scroll error: ${error.message}`);
179
+ return false;
180
+ }
181
+ }
182
+ try {
183
+ // 1. 匹配搜索根容器(带超时保护)
184
+ const currentUrl = await getCurrentUrl();
185
+ let tree = null;
186
+ let matchTimeout = false;
187
+ try {
188
+ const matchResult = await Promise.race([
189
+ controllerAction('containers:match', {
190
+ profile,
191
+ url: currentUrl,
192
+ maxDepth: 8,
193
+ maxChildren: 20
194
+ }),
195
+ new Promise((_, reject) => setTimeout(() => reject(new Error('containers:match timeout')), MATCH_TIMEOUT_MS))
196
+ ]);
197
+ tree = matchResult.snapshot?.container_tree || matchResult.container_tree;
198
+ }
199
+ catch (err) {
200
+ if (err.message?.includes('timeout')) {
201
+ console.warn(`[CollectSearchList] containers:match 超时(${MATCH_TIMEOUT_MS}ms),使用降级方案`);
202
+ }
203
+ else {
204
+ console.warn('[CollectSearchList] containers:match 失败:', err.message);
205
+ }
206
+ matchTimeout = true;
207
+ }
208
+ // 2. 确定列表容器 ID
209
+ let listContainerId = 'xiaohongshu_search.search_result_list';
210
+ let listContainer = null;
211
+ if (matchTimeout || !tree) {
212
+ // 降级方案:根据 URL 使用固定容器 ID
213
+ console.warn('[CollectSearchList] 使用降级方案:直接使用已知容器 ID');
214
+ if (currentUrl.includes('/search_result')) {
215
+ listContainerId = 'xiaohongshu_search.search_result_list';
216
+ }
217
+ else {
218
+ listContainerId = 'xiaohongshu_home.feed_list';
219
+ }
220
+ listContainer = { id: listContainerId };
221
+ }
222
+ else {
223
+ // 正常流程:从容器树查找
224
+ listContainer = findContainer(tree, /xiaohongshu_search\.search_result_list$/);
225
+ if (!listContainer) {
226
+ const homeFeed = findContainer(tree, /xiaohongshu_home\.feed_list$/);
227
+ if (homeFeed) {
228
+ listContainer = homeFeed;
229
+ listContainerId = homeFeed.id || 'xiaohongshu_home.feed_list';
230
+ console.warn(`[CollectSearchList] 使用 fallback: ${listContainerId}`);
231
+ }
232
+ else {
233
+ // 最后降级
234
+ console.warn('[CollectSearchList] 容器树中未找到列表容器,使用固定 ID');
235
+ listContainerId = currentUrl.includes('/search_result')
236
+ ? 'xiaohongshu_search.search_result_list'
237
+ : 'xiaohongshu_home.feed_list';
238
+ listContainer = { id: listContainerId };
239
+ matchTimeout = true;
240
+ }
241
+ }
242
+ else {
243
+ listContainerId = listContainer.id;
244
+ }
245
+ }
246
+ if (!listContainer) {
247
+ throw new Error('未找到搜索结果列表容器');
248
+ }
249
+ // 2.05 等待搜索结果页内容就绪(有卡片或明确无结果)
250
+ const pageReady = await waitForSearchItemsIfNeeded(currentUrl);
251
+ if (!pageReady.ok && pageReady.noResults) {
252
+ return {
253
+ success: false,
254
+ items: [],
255
+ count: 0,
256
+ scrollRounds: 0,
257
+ usedFallback: matchTimeout,
258
+ anchor: {
259
+ listContainerId,
260
+ verified: false,
261
+ },
262
+ error: 'search_no_results',
263
+ };
264
+ }
265
+ // 2.1 高亮列表容器
266
+ try {
267
+ await controllerAction('container:operation', {
268
+ containerId: listContainer.id,
269
+ operationId: 'highlight',
270
+ config: { style: '3px solid #ff4444', duration: 2000 },
271
+ sessionId: profile
272
+ });
273
+ console.log(`[CollectSearchList] Highlighted list: ${listContainer.id}`);
274
+ }
275
+ catch (error) {
276
+ console.warn('[CollectSearchList] Highlight failed:', error.message);
277
+ }
278
+ // 2.2 获取列表 Rect
279
+ let listRect;
280
+ try {
281
+ const { verifyAnchorByContainerId } = await import('./helpers/containerAnchors.js');
282
+ const anchor = await verifyAnchorByContainerId(listContainerId, profile, serviceUrl);
283
+ if (anchor.found && anchor.rect) {
284
+ listRect = anchor.rect;
285
+ console.log(`[CollectSearchList] List rect: ${JSON.stringify(listRect)}`);
286
+ }
287
+ }
288
+ catch (error) {
289
+ console.warn('[CollectSearchList] Get rect failed:', error.message);
290
+ }
291
+ // 3. 滚动 + 采集循环(基于列表容器锚点,不做页面级滚动)
292
+ const items = [];
293
+ const seenContainerIds = new Set();
294
+ let scrollRound = 0;
295
+ let consecutiveNoNewItems = 0;
296
+ let bounceAttempts = 0;
297
+ // 滚动-采集循环:
298
+ // - 达到 targetCount 退出
299
+ // - 或连续 5 轮无新增(包含尝试过向上/向下 bounce)退出
300
+ // - 或达到 maxScrollRounds(用于“仅采集当前视口”模式,如 maxScrollRounds=1)
301
+ while (items.length < targetCount) {
302
+ scrollRound += 1;
303
+ console.log(`[CollectSearchList] Round ${scrollRound}, collected=${items.length}/${targetCount}`);
304
+ const beforeCount = items.length;
305
+ // 3.2 批量从 DOM 提取所有 .note-item 的信息(一次性查询),并标记是否在当前视口内
306
+ try {
307
+ const batchExtractResult = await controllerAction('browser:execute', {
308
+ profile,
309
+ script: `
310
+ (function () {
311
+ var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
312
+ var viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
313
+ // 视口安全边距:避免顶部 sticky tab/筛选条与底部悬浮层导致“看起来在视口内但实际被遮挡”
314
+ var safeTop = 180;
315
+ var safeBottom = 140;
316
+ var safeLeft = 24;
317
+ var safeRight = 24;
318
+ var cards = Array.from(document.querySelectorAll('.note-item'));
319
+ return cards.map(function(card, idx) {
320
+ var rect = card.getBoundingClientRect();
321
+ var titleEl = card.querySelector('.footer .title span') || card.querySelector('.footer .title') || card.querySelector('[class*="title"]');
322
+ var coverEl = card.querySelector('a.cover') || null;
323
+ var hasQueryWrapper = !!card.querySelector('.query-note-wrapper');
324
+ // 严格:只允许点击 a.cover(封面)。禁止使用其它 a[href](可能是作者/推荐词/标签等,会误跳转)。
325
+ var linkEl = coverEl;
326
+ var hrefAttr = linkEl ? (linkEl.getAttribute('href') || '') : '';
327
+ var href = linkEl ? (linkEl.href || hrefAttr || '') : '';
328
+ var match = href.match(/\\/(explore|search_result)\\/([^?]+)/);
329
+ var noteId = match ? match[2] : '';
330
+ var cardText = (card.textContent || '').trim();
331
+ var hasAdBadge =
332
+ !!card.querySelector('[class*="ad"], [class*="Ad"], [class*="promo"], [class*="Promote"], [data-ad], [data-promote]') ||
333
+ cardText.indexOf('广告') !== -1 ||
334
+ cardText.indexOf('推广') !== -1 ||
335
+ cardText.indexOf('赞助') !== -1;
336
+ // 仅采集**完全处于视口内**的卡片与封面(双重校验),避免点击到离屏或半离屏元素
337
+ var coverRect = null;
338
+ if (coverEl) {
339
+ // ✅ 优先使用封面媒体(img/video)的 rect,避免 a.cover 覆盖过大导致误点到头像/作者区
340
+ var media = coverEl.querySelector('img,video');
341
+ var target = media || coverEl;
342
+ var cr = target.getBoundingClientRect();
343
+ if (cr && cr.width && cr.height) {
344
+ coverRect = { x: cr.x, y: cr.y, width: cr.width, height: cr.height };
345
+ }
346
+ }
347
+ // clickRect:用于返回给上层点击坐标/rect(优先封面 rect;无封面时回退卡片 rect)
348
+ var clickRect = coverRect || { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
349
+ var cardCx = rect ? (rect.x + rect.width / 2) : 0;
350
+ var cardCy = rect ? (rect.y + rect.height / 2) : 0;
351
+ var inViewportCard =
352
+ rect &&
353
+ rect.width > 0 &&
354
+ rect.height > 0 &&
355
+ cardCx >= safeLeft &&
356
+ cardCx <= (viewportWidth - safeRight) &&
357
+ cardCy >= safeTop &&
358
+ cardCy <= (viewportHeight - safeBottom);
359
+ var coverCx = coverRect ? (coverRect.x + coverRect.width / 2) : 0;
360
+ var coverCy = coverRect ? (coverRect.y + coverRect.height / 2) : 0;
361
+ var inViewportCover =
362
+ coverRect &&
363
+ coverRect.width > 0 &&
364
+ coverRect.height > 0 &&
365
+ coverCx >= safeLeft &&
366
+ coverCx <= (viewportWidth - safeRight) &&
367
+ coverCy >= safeTop &&
368
+ coverCy <= (viewportHeight - safeBottom);
369
+ var inViewport = Boolean(inViewportCard && inViewportCover);
370
+ return {
371
+ index: idx,
372
+ title: titleEl ? titleEl.textContent.trim() : '',
373
+ detail_url: href,
374
+ href_attr: hrefAttr,
375
+ note_id: noteId,
376
+ hasToken: href.indexOf('xsec_token=') !== -1,
377
+ rect: {
378
+ x: clickRect.x,
379
+ y: clickRect.y,
380
+ width: clickRect.width,
381
+ height: clickRect.height
382
+ },
383
+ card_rect: {
384
+ x: rect.x,
385
+ y: rect.y,
386
+ width: rect.width,
387
+ height: rect.height
388
+ },
389
+ cover_rect: coverRect,
390
+ isAd: hasAdBadge,
391
+ hasQueryWrapper: hasQueryWrapper,
392
+ inViewport: inViewport,
393
+ inViewportCard: inViewportCard,
394
+ inViewportCover: inViewportCover
395
+ };
396
+ });
397
+ })();
398
+ `
399
+ });
400
+ const extractedItems = batchExtractResult.result || batchExtractResult.data?.result || [];
401
+ if (!Array.isArray(extractedItems)) {
402
+ console.warn('[CollectSearchList] Batch extract returned non-array');
403
+ }
404
+ else if (extractedItems.length === 0 && scrollRound === 1 && currentUrl.includes('/search_result')) {
405
+ // 首轮为空:再等 1 次(页面可能仍在加载/虚拟列表尚未渲染)
406
+ const probe = await waitForSearchItemsIfNeeded(currentUrl);
407
+ if (!probe.ok && probe.noResults) {
408
+ return {
409
+ success: false,
410
+ items: [],
411
+ count: 0,
412
+ scrollRounds: scrollRound,
413
+ usedFallback: matchTimeout,
414
+ anchor: {
415
+ listContainerId: listContainer.id,
416
+ listRect,
417
+ verified: false,
418
+ },
419
+ error: 'search_no_results',
420
+ };
421
+ }
422
+ }
423
+ else {
424
+ for (const extracted of extractedItems) {
425
+ // 只关心当前视口内的卡片
426
+ if (extracted && extracted.inViewport === false) {
427
+ continue;
428
+ }
429
+ // 过滤广告/推广卡片:这些卡片可能不是真正的笔记详情入口,误点会污染采集
430
+ if (extracted?.isAd) {
431
+ continue;
432
+ }
433
+ // 禁止:搜索结果里的“大家都在搜/相关搜索”等 query-note-wrapper 块(不是帖子,点击会跳转到推荐关键词/搜索)
434
+ if (extracted?.hasQueryWrapper) {
435
+ continue;
436
+ }
437
+ // 必须有 cover_rect(a.cover)才允许进入点击流程;否则宁可不采集,避免误点作者/推荐词
438
+ if (!extracted?.cover_rect) {
439
+ continue;
440
+ }
441
+ // 仅采集“可识别 noteId 的真实帖子卡片”,避免空卡/推荐词/占位导致误点跳转
442
+ const noteId = typeof extracted.note_id === 'string' ? extracted.note_id.trim() : '';
443
+ const detailUrlRaw = typeof extracted.detail_url === 'string' ? extracted.detail_url.trim() : '';
444
+ if (!noteId || !/\/(explore|search_result)\//.test(detailUrlRaw)) {
445
+ continue;
446
+ }
447
+ const uniqueKey = noteId;
448
+ if (seenContainerIds.has(uniqueKey))
449
+ continue;
450
+ seenContainerIds.add(uniqueKey);
451
+ const detailUrl = detailUrlRaw;
452
+ const hasToken = /[?&]xsec_token=/.test(detailUrl);
453
+ const safeDetailUrl = hasToken ? detailUrl : undefined;
454
+ // 即使没有 token 也可以收集(后续通过 OpenDetail 点击触发,而不是直接 URL 导航)
455
+ // 只要有 domIndex 和 noteId/title 即可
456
+ items.push({
457
+ containerId: 'xiaohongshu_search.search_result_item',
458
+ domIndex: extracted.index,
459
+ noteId,
460
+ title: extracted.title,
461
+ detailUrl: safeDetailUrl,
462
+ safeDetailUrl,
463
+ hasToken,
464
+ hrefAttr: typeof extracted.href_attr === 'string' ? extracted.href_attr : undefined,
465
+ isAd: Boolean(extracted?.isAd),
466
+ rect: extracted?.cover_rect &&
467
+ typeof extracted.cover_rect.x === 'number' &&
468
+ typeof extracted.cover_rect.y === 'number' &&
469
+ typeof extracted.cover_rect.width === 'number' &&
470
+ typeof extracted.cover_rect.height === 'number'
471
+ ? extracted.cover_rect
472
+ : undefined,
473
+ raw: extracted,
474
+ });
475
+ if (items.length >= targetCount)
476
+ break;
477
+ }
478
+ console.log(`[CollectSearchList] Batch extracted ${extractedItems.length} items from DOM(viewport items=${items.length})`);
479
+ }
480
+ }
481
+ catch (error) {
482
+ console.warn(`[CollectSearchList] Batch extract failed: ${error.message}`);
483
+ }
484
+ const newItemsCount = items.length - beforeCount;
485
+ console.log(`[CollectSearchList] Round ${scrollRound}: +${newItemsCount} items (total ${items.length})`);
486
+ // 如果设置了 maxScrollRounds,则在达到轮数后不再继续滚动,由上层决定是否滚动页面
487
+ if (maxScrollRounds > 0 && scrollRound >= maxScrollRounds) {
488
+ console.log(`[CollectSearchList] Reached maxScrollRounds=${maxScrollRounds}, stop further scrolling`);
489
+ break;
490
+ }
491
+ // 3.3 终止条件 + bounce 滚动策略
492
+ if (newItemsCount === 0) {
493
+ consecutiveNoNewItems += 1;
494
+ // 连续 5 轮都没有新增,视为已经耗尽
495
+ if (consecutiveNoNewItems >= 5) {
496
+ console.log('[CollectSearchList] No new items for 5 rounds, stopping');
497
+ break;
498
+ }
499
+ // 连续 2 轮无新增且还有目标缺口时,尝试一次“先向上再向下”的 bounce 滚动
500
+ if (consecutiveNoNewItems >= 2 && bounceAttempts < 2 && items.length < targetCount) {
501
+ bounceAttempts += 1;
502
+ console.log(`[CollectSearchList] No new items for ${consecutiveNoNewItems} rounds, bounce attempt #${bounceAttempts}`);
503
+ // 先向上滚动 2 次(小幅回拉)
504
+ for (let i = 0; i < 2; i += 1) {
505
+ const upScrolled = await scrollListContainer(listContainer.id, 'up');
506
+ if (!upScrolled) {
507
+ console.warn('[CollectSearchList] Upward bounce scroll failed or reached top');
508
+ break;
509
+ }
510
+ }
511
+ // 再向下滚动 4 次(继续加载后续内容)
512
+ for (let i = 0; i < 4; i += 1) {
513
+ const downScrolled = await scrollListContainer(listContainer.id, 'down');
514
+ if (!downScrolled) {
515
+ console.warn('[CollectSearchList] Downward bounce scroll failed or reached bottom');
516
+ break;
517
+ }
518
+ }
519
+ // 进行了一轮 bounce 后,继续下一轮采集(不在本轮再做普通滚动)
520
+ continue;
521
+ }
522
+ }
523
+ else {
524
+ // 一旦出现新增,重置无新增计数
525
+ consecutiveNoNewItems = 0;
526
+ }
527
+ if (items.length >= targetCount) {
528
+ console.log(`[CollectSearchList] Reached target ${targetCount}`);
529
+ break;
530
+ }
531
+ // 3.4 正常向下滚动(单步),持续拉新
532
+ if (items.length < targetCount) {
533
+ console.log(`[CollectSearchList] Need more items (${items.length}/${targetCount}), scrolling down...`);
534
+ const scrolled = await scrollListContainer(listContainer.id, 'down');
535
+ if (!scrolled) {
536
+ console.warn('[CollectSearchList] Page scroll failed or reached bottom');
537
+ // 如果已经连续多轮没有新增并且滚不动了,可以提前退出
538
+ if (consecutiveNoNewItems >= 2) {
539
+ break;
540
+ }
541
+ }
542
+ }
543
+ }
544
+ // 4. 高亮第一个 item
545
+ let firstItemRect;
546
+ let firstItemContainerId;
547
+ if (items.length > 0) {
548
+ firstItemContainerId = items[0].containerId;
549
+ try {
550
+ await controllerAction('container:operation', {
551
+ containerId: firstItemContainerId,
552
+ operationId: 'highlight',
553
+ config: { style: '3px solid #00ff00', duration: 2000 },
554
+ sessionId: profile
555
+ });
556
+ console.log(`[CollectSearchList] Highlighted first item: ${firstItemContainerId}`);
557
+ const { verifyAnchorByContainerId } = await import('./helpers/containerAnchors.js');
558
+ const itemAnchor = await verifyAnchorByContainerId(firstItemContainerId, profile, serviceUrl);
559
+ if (itemAnchor.found && itemAnchor.rect) {
560
+ firstItemRect = itemAnchor.rect;
561
+ console.log(`[CollectSearchList] First item rect: ${JSON.stringify(firstItemRect)}`);
562
+ }
563
+ }
564
+ catch (error) {
565
+ console.warn('[CollectSearchList] Highlight/rect first item failed:', error.message);
566
+ }
567
+ }
568
+ const listRectValid = listRect && listRect.y > 100 && listRect.height > 0;
569
+ const firstItemRectValid = firstItemRect && firstItemRect.width > 0 && firstItemRect.height > 0;
570
+ const verified = listRectValid && firstItemRectValid;
571
+ console.log(`[CollectSearchList] Rect validation: list=${listRectValid}, firstItem=${firstItemRectValid}, verified=${verified}`);
572
+ return {
573
+ success: true,
574
+ items,
575
+ count: items.length,
576
+ scrollRounds: scrollRound,
577
+ usedFallback: matchTimeout,
578
+ firstItemContainerId: firstItemContainerId || (items[0]?.containerId ?? undefined),
579
+ anchor: {
580
+ listContainerId: listContainer.id,
581
+ listRect,
582
+ firstItemContainerId,
583
+ firstItemRect,
584
+ verified
585
+ }
586
+ };
587
+ }
588
+ catch (error) {
589
+ return {
590
+ success: false,
591
+ items: [],
592
+ count: 0,
593
+ scrollRounds: 0,
594
+ anchor: undefined,
595
+ error: `CollectSearchList failed: ${error.message}`
596
+ };
597
+ }
598
+ }
599
+ //# sourceMappingURL=CollectSearchListBlock.js.map