@web-auto/webauto 0.1.1 → 0.1.3

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 +263 -15
  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 +38 -10
  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,198 @@
1
+ /**
2
+ * 锚点验证辅助函数
3
+ * 用于 Block 中验证容器锚点 + 高亮 + Rect 回环
4
+ */
5
+ /**
6
+ * 验证锚点容器:找到 + 高亮 + 获取 Rect
7
+ */
8
+ export async function verifyAnchor(containerId, sessionId, serviceUrl = 'http://127.0.0.1:7701') {
9
+ try {
10
+ // 1. 高亮容器
11
+ const highlighted = await highlightContainer(containerId, sessionId, serviceUrl);
12
+ if (!highlighted) {
13
+ return {
14
+ found: false,
15
+ highlighted: false,
16
+ error: `Failed to highlight container: ${containerId}`
17
+ };
18
+ }
19
+ // 2. 获取 Rect
20
+ const rect = await getContainerRect(containerId, sessionId, serviceUrl);
21
+ if (!rect || rect.width === 0 || rect.height === 0) {
22
+ return {
23
+ found: true,
24
+ highlighted: true,
25
+ rect,
26
+ error: `Container ${containerId} has invalid rect: ${JSON.stringify(rect)}`
27
+ };
28
+ }
29
+ return {
30
+ found: true,
31
+ highlighted: true,
32
+ rect
33
+ };
34
+ }
35
+ catch (error) {
36
+ return {
37
+ found: false,
38
+ highlighted: false,
39
+ error: `verifyAnchor failed: ${error.message}`
40
+ };
41
+ }
42
+ }
43
+ /**
44
+ * 高亮容器
45
+ */
46
+ export async function highlightContainer(containerId, sessionId, serviceUrl = 'http://127.0.0.1:7701', options = {}) {
47
+ try {
48
+ const { style = '3px solid #ff4444', duration = 2000 } = options;
49
+ const response = await fetch(`${serviceUrl}/v1/controller/action`, {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json' },
52
+ body: JSON.stringify({
53
+ action: 'container:operation',
54
+ payload: {
55
+ containerId,
56
+ operationId: 'highlight',
57
+ config: { style, duration },
58
+ sessionId
59
+ }
60
+ })
61
+ });
62
+ if (!response.ok) {
63
+ console.error(`[highlightContainer] HTTP ${response.status}: ${await response.text()}`);
64
+ return false;
65
+ }
66
+ const data = await response.json();
67
+ return data.success === true;
68
+ }
69
+ catch (error) {
70
+ console.error(`[highlightContainer] Error:`, error.message);
71
+ return false;
72
+ }
73
+ }
74
+ /**
75
+ * 获取容器的 Rect(通过 browser:execute + getBoundingClientRect)
76
+ */
77
+ export async function getContainerRect(containerId, sessionId, serviceUrl = 'http://127.0.0.1:7701') {
78
+ try {
79
+ // 先获取当前页面 URL,避免 containers:match 依赖 session-manager CLI 查询会话
80
+ let currentUrl = null;
81
+ try {
82
+ const urlResp = await fetch(`${serviceUrl}/v1/controller/action`, {
83
+ method: 'POST',
84
+ headers: { 'Content-Type': 'application/json' },
85
+ body: JSON.stringify({
86
+ action: 'browser:execute',
87
+ payload: {
88
+ profile: sessionId,
89
+ script: 'location.href',
90
+ },
91
+ }),
92
+ signal: AbortSignal.timeout ? AbortSignal.timeout(8000) : undefined,
93
+ });
94
+ if (urlResp.ok) {
95
+ const urlData = await urlResp.json();
96
+ currentUrl = urlData.data?.result || urlData.result || null;
97
+ }
98
+ }
99
+ catch (e) {
100
+ console.warn('[getContainerRect] failed to read current url:', e?.message || e);
101
+ }
102
+ // 先通过 containers:match 获取容器的 selector
103
+ const matchResponse = await fetch(`${serviceUrl}/v1/controller/action`, {
104
+ method: 'POST',
105
+ headers: { 'Content-Type': 'application/json' },
106
+ body: JSON.stringify({
107
+ action: 'containers:match',
108
+ payload: {
109
+ profile: sessionId,
110
+ ...(currentUrl ? { url: currentUrl } : {}),
111
+ maxDepth: 5,
112
+ maxChildren: 20
113
+ }
114
+ }),
115
+ // 避免长时间挂起:为 containers:match 增加超时
116
+ // Node >=16 支持 AbortSignal.timeout
117
+ signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined
118
+ });
119
+ if (!matchResponse.ok) {
120
+ console.error(`[getContainerRect] containers:match failed: ${matchResponse.status}`);
121
+ return null;
122
+ }
123
+ const matchData = await matchResponse.json();
124
+ const tree = matchData.data?.snapshot?.container_tree || matchData.snapshot?.container_tree;
125
+ // 递归查找容器
126
+ function findContainer(node, targetId) {
127
+ if (!node)
128
+ return null;
129
+ if (node.id === targetId || node.defId === targetId)
130
+ return node;
131
+ if (Array.isArray(node.children)) {
132
+ for (const child of node.children) {
133
+ const found = findContainer(child, targetId);
134
+ if (found)
135
+ return found;
136
+ }
137
+ }
138
+ return null;
139
+ }
140
+ const container = findContainer(tree, containerId);
141
+ if (!container) {
142
+ console.error(`[getContainerRect] Container ${containerId} not found in container_tree`);
143
+ return null;
144
+ }
145
+ const selector = typeof container.selector === 'string'
146
+ ? container.selector
147
+ : Array.isArray(container.selectors) && container.selectors.length
148
+ ? container.selectors[0].css
149
+ : undefined;
150
+ if (!selector) {
151
+ console.error(`[getContainerRect] Container ${containerId} has no selector; selectors field:`, JSON.stringify(container.selectors || container.selector || null));
152
+ return null;
153
+ }
154
+ // 通过 browser:execute 获取 Rect
155
+ const script = `
156
+ (() => {
157
+ const el = document.querySelector('${selector.replace(/'/g, "\\'")}');
158
+ if (!el) return null;
159
+ const rect = el.getBoundingClientRect();
160
+ return {
161
+ x: rect.x,
162
+ y: rect.y,
163
+ width: rect.width,
164
+ height: rect.height
165
+ };
166
+ })()
167
+ `;
168
+ const execResponse = await fetch(`${serviceUrl}/v1/controller/action`, {
169
+ method: 'POST',
170
+ headers: { 'Content-Type': 'application/json' },
171
+ body: JSON.stringify({
172
+ action: 'browser:execute',
173
+ payload: {
174
+ profile: sessionId,
175
+ script
176
+ }
177
+ }),
178
+ // browser:execute 也加一个防御性超时
179
+ signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined
180
+ });
181
+ if (!execResponse.ok) {
182
+ console.error(`[getContainerRect] browser:execute failed: ${execResponse.status}`);
183
+ return null;
184
+ }
185
+ const execData = await execResponse.json();
186
+ const result = execData.data?.result || execData.result || null;
187
+ // 兼容 promise 结果为 { ok, rect } 的脚本返回格式
188
+ if (result && typeof result === 'object' && 'rect' in result && !('x' in result)) {
189
+ return result.rect || null;
190
+ }
191
+ return result;
192
+ }
193
+ catch (error) {
194
+ console.error(`[getContainerRect] Error:`, error.message);
195
+ return null;
196
+ }
197
+ }
198
+ //# sourceMappingURL=anchorVerify.js.map
@@ -0,0 +1,53 @@
1
+ export class AsyncWorkQueue {
2
+ concurrency;
3
+ label;
4
+ running = 0;
5
+ pending = [];
6
+ drainResolvers = [];
7
+ constructor(opts = {}) {
8
+ this.concurrency = Math.max(1, Math.floor(opts.concurrency ?? 1));
9
+ this.label = opts.label || 'queue';
10
+ }
11
+ getPendingCount() {
12
+ return this.pending.length;
13
+ }
14
+ getRunningCount() {
15
+ return this.running;
16
+ }
17
+ async enqueue(fn) {
18
+ return await new Promise((resolve, reject) => {
19
+ this.pending.push({ run: fn, resolve, reject });
20
+ this.pump();
21
+ });
22
+ }
23
+ async drain() {
24
+ if (this.pending.length === 0 && this.running === 0)
25
+ return;
26
+ await new Promise((resolve) => {
27
+ this.drainResolvers.push(resolve);
28
+ this.pump();
29
+ });
30
+ }
31
+ pump() {
32
+ while (this.running < this.concurrency && this.pending.length > 0) {
33
+ const job = this.pending.shift();
34
+ this.running += 1;
35
+ Promise.resolve()
36
+ .then(job.run)
37
+ .then(job.resolve, job.reject)
38
+ .finally(() => {
39
+ this.running -= 1;
40
+ if (this.pending.length === 0 && this.running === 0) {
41
+ const resolvers = this.drainResolvers.slice();
42
+ this.drainResolvers = [];
43
+ for (const r of resolvers)
44
+ r();
45
+ }
46
+ else {
47
+ this.pump();
48
+ }
49
+ });
50
+ }
51
+ }
52
+ }
53
+ //# sourceMappingURL=asyncWorkQueue.js.map
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Comment Scroller Helper
3
+ *
4
+ * 共享滚动循环:WarmupCommentsBlock / ExpandCommentsBlock
5
+ * - 只负责“滚到评论底部(或空态)”这一件事
6
+ * - 所有操作通过 systemInput(browser-service)发送系统事件
7
+ */
8
+ import { computeVisibleFocusPoint, getCommentEndState, getCommentStats, getScrollContainerInfo, getScrollContainerState, getViewport, getViewportFirstComment, isInputFocused, locateCommentsFocusPoint, } from './xhsCommentDom.js';
9
+ import { systemClickAt, systemHoverAt, systemMouseWheel, isDevMode } from './systemInput.js';
10
+ // Back-compat re-exports (older blocks import these from commentScroller.js)
11
+ export { getScrollStats, getViewport } from './xhsCommentDom.js';
12
+ export { systemMouseWheel } from './systemInput.js';
13
+ export async function scrollCommentSection(options) {
14
+ const { controllerUrl, profile, browserServiceUrl = 'http://127.0.0.1:7704', browserWsUrl = process.env.WEBAUTO_BROWSER_WS_URL || 'ws://127.0.0.1:8765', maxRounds, focusRect, logPrefix = '[WarmupComments]', activateComments, expand, } = options;
15
+ const log = (msg) => console.log(`${logPrefix} ${msg}`);
16
+ const warn = (msg) => console.warn(`${logPrefix} ${msg}`);
17
+ const viewport = await getViewport(controllerUrl, profile);
18
+ let focusPoint = focusRect ? computeVisibleFocusPoint(focusRect, viewport) : null;
19
+ const domFocus = await locateCommentsFocusPoint(controllerUrl, profile);
20
+ if (domFocus) {
21
+ focusPoint = domFocus;
22
+ await systemClickAt(profile, domFocus.x, domFocus.y, browserServiceUrl, 'comment_focus_dom');
23
+ await new Promise((r) => setTimeout(r, 450));
24
+ }
25
+ if (focusPoint) {
26
+ await systemHoverAt(profile, focusPoint.x, focusPoint.y, browserServiceUrl);
27
+ await new Promise((r) => setTimeout(r, 180));
28
+ if (await isInputFocused(controllerUrl, profile)) {
29
+ log('检测到输入框焦点,点击评论区以切换焦点...');
30
+ await systemClickAt(profile, focusPoint.x, focusPoint.y, browserServiceUrl, 'comment_blur_input');
31
+ await new Promise((r) => setTimeout(r, 350));
32
+ }
33
+ }
34
+ let lastCount = 0;
35
+ let targetTotal = null;
36
+ let stuckBounceFailures = 0;
37
+ const initialStats = await getCommentStats(controllerUrl, profile);
38
+ lastCount = initialStats.count;
39
+ targetTotal = initialStats.total;
40
+ if (initialStats.count === 0 &&
41
+ typeof initialStats.total === 'number' &&
42
+ Number.isFinite(initialStats.total) &&
43
+ initialStats.total > 0 &&
44
+ activateComments) {
45
+ log(`count=0 but headerTotal=${initialStats.total}, try activate comments`);
46
+ try {
47
+ await activateComments('count_zero_but_total_positive');
48
+ }
49
+ catch (e) {
50
+ warn(`activate comments failed: ${e?.message || e}`);
51
+ }
52
+ const domFocus2 = await locateCommentsFocusPoint(controllerUrl, profile);
53
+ if (domFocus2) {
54
+ focusPoint = domFocus2;
55
+ await systemClickAt(profile, domFocus2.x, domFocus2.y, browserServiceUrl, 'comment_focus_after_activate');
56
+ await new Promise((r) => setTimeout(r, 450));
57
+ }
58
+ await new Promise((r) => setTimeout(r, 600));
59
+ const after = await getCommentStats(controllerUrl, profile);
60
+ log(`after activate: count=${after.count} total=${after.total} hasMore=${after.hasMore}`);
61
+ lastCount = after.count;
62
+ targetTotal = after.total;
63
+ }
64
+ // 一些详情页会先渲染“评论区骨架/占位”但 headerTotal 不可用(null),需要先点一次 comment_button 激活
65
+ if (initialStats.count === 0 &&
66
+ (initialStats.total === null || initialStats.total === 0) &&
67
+ !initialStats.hasMore &&
68
+ activateComments) {
69
+ const endBefore = await getCommentEndState(controllerUrl, profile);
70
+ const noMoreBefore = Boolean(endBefore.endMarkerVisible || endBefore.emptyStateVisible);
71
+ if (!noMoreBefore) {
72
+ log(`count=0 total=${initialStats.total}, try activate comments (total_null_or_zero)`);
73
+ try {
74
+ await activateComments('count_zero_total_null_or_zero');
75
+ }
76
+ catch (e) {
77
+ warn(`activate comments failed: ${e?.message || e}`);
78
+ }
79
+ const domFocus2 = await locateCommentsFocusPoint(controllerUrl, profile);
80
+ if (domFocus2) {
81
+ focusPoint = domFocus2;
82
+ await systemClickAt(profile, domFocus2.x, domFocus2.y, browserServiceUrl, 'comment_focus_after_activate');
83
+ await new Promise((r) => setTimeout(r, 450));
84
+ }
85
+ await new Promise((r) => setTimeout(r, 900));
86
+ const after = await getCommentStats(controllerUrl, profile);
87
+ const endAfter = await getCommentEndState(controllerUrl, profile);
88
+ log(`after activate(total_null_or_zero): count=${after.count} total=${after.total} hasMore=${after.hasMore} endMarker=${endAfter.endMarkerVisible} empty=${endAfter.emptyStateVisible}`);
89
+ lastCount = after.count;
90
+ targetTotal = after.total;
91
+ if (after.count === 0 &&
92
+ (after.total === null || after.total === 0) &&
93
+ !after.hasMore &&
94
+ (endAfter.endMarkerVisible || endAfter.emptyStateVisible)) {
95
+ return {
96
+ reachedEnd: true,
97
+ totalFromHeader: typeof after.total === 'number' ? after.total : null,
98
+ finalCount: 0,
99
+ rounds: 0,
100
+ focusPoint,
101
+ };
102
+ }
103
+ }
104
+ }
105
+ if (initialStats.count === 0 &&
106
+ (initialStats.total === null || initialStats.total === 0) &&
107
+ !initialStats.hasMore) {
108
+ // 严格结束条件:仅当 end_marker / empty_state 可见才视为结束
109
+ const endState = await getCommentEndState(controllerUrl, profile);
110
+ const noMore = Boolean(endState.endMarkerVisible || endState.emptyStateVisible);
111
+ log(`initial no-comments probe: endMarker=${endState.endMarkerVisible} empty=${endState.emptyStateVisible} noMore=${noMore}`);
112
+ if (noMore) {
113
+ return {
114
+ reachedEnd: true,
115
+ totalFromHeader: typeof initialStats.total === 'number' ? initialStats.total : null,
116
+ finalCount: 0,
117
+ rounds: 0,
118
+ focusPoint,
119
+ };
120
+ }
121
+ }
122
+ const dynamicMaxRounds = typeof maxRounds === 'number' && maxRounds > 0
123
+ ? maxRounds
124
+ : targetTotal && targetTotal > 0
125
+ ? Math.min(Math.max(Math.ceil(targetTotal / 6) * 3, 36), 900)
126
+ : 96;
127
+ let noEffectStreak = 0;
128
+ let roundsRan = 0;
129
+ for (let i = 0; i < dynamicMaxRounds; i++) {
130
+ roundsRan = i + 1;
131
+ const viewportBefore = await getViewportFirstComment(controllerUrl, profile);
132
+ const scrollInfo = await getScrollContainerInfo(controllerUrl, profile);
133
+ if (scrollInfo && viewport.innerWidth && viewport.innerHeight) {
134
+ const fx = clamp(scrollInfo.x, 20, viewport.innerWidth - 20);
135
+ const fy = clamp(scrollInfo.y, 120, viewport.innerHeight - 120);
136
+ focusPoint = { x: fx, y: fy };
137
+ log(`round=${i} refreshed focus: (${scrollInfo.x}, ${scrollInfo.y}), scrollTop=${scrollInfo.scrollTop}/${scrollInfo.scrollHeight}`);
138
+ await systemHoverAt(profile, fx, fy, browserServiceUrl);
139
+ }
140
+ const scrollTopBefore = scrollInfo?.scrollTop ?? null;
141
+ const scrollHeightBefore = scrollInfo?.scrollHeight ?? null;
142
+ const clientHeightBefore = scrollInfo?.clientHeight ?? null;
143
+ if (expand) {
144
+ try {
145
+ await expand({ round: i, focusPoint });
146
+ }
147
+ catch (e) {
148
+ const msg = String(e?.message || e || '');
149
+ // 开发阶段:风控/验证码/误点保护触发时必须停下,不允许吞错继续滚
150
+ if (msg.includes('captcha_modal_detected'))
151
+ throw e;
152
+ if (msg.includes('unsafe_click_image_in_detail')) {
153
+ if (isDevMode())
154
+ throw e;
155
+ warn(`round=${i} expand skipped unsafe click (continue): ${msg}`);
156
+ }
157
+ warn(`round=${i} expand failed: ${msg}`);
158
+ }
159
+ }
160
+ // 单次滚动不超过 800px(视口安全约束),但尽量加大步长以更快触达底部 end_marker
161
+ const deltaY = 520 + Math.floor(Math.random() * 260);
162
+ try {
163
+ await systemMouseWheel({
164
+ profileId: profile,
165
+ deltaY,
166
+ focusPoint,
167
+ browserServiceUrl,
168
+ browserWsUrl,
169
+ context: 'comment_scroll',
170
+ });
171
+ log(`round=${i} system wheel deltaY=${deltaY}`);
172
+ }
173
+ catch (e) {
174
+ const msg = String(e?.message || e || '');
175
+ if (msg.includes('captcha_modal_detected'))
176
+ throw e;
177
+ warn(`round=${i} system wheel failed: ${msg}`);
178
+ }
179
+ await new Promise((r) => setTimeout(r, 650 + Math.random() * 650));
180
+ const stats = await getCommentStats(controllerUrl, profile);
181
+ const currentCount = stats.count;
182
+ const viewportAfter = await getViewportFirstComment(controllerUrl, profile);
183
+ if (viewportBefore || viewportAfter) {
184
+ log(`round=${i} viewportFirst before=${JSON.stringify(viewportBefore)} after=${JSON.stringify(viewportAfter)}`);
185
+ }
186
+ let scrolled = null;
187
+ try {
188
+ const afterState = await getScrollContainerState(controllerUrl, profile);
189
+ const scrollTopAfter = afterState?.scrollTop ?? null;
190
+ const scrollHeightAfter = afterState?.scrollHeight ?? null;
191
+ const clientHeightAfter = afterState?.clientHeight ?? null;
192
+ const canScroll = (scrollHeightAfter ?? scrollHeightBefore ?? 0) -
193
+ (clientHeightAfter ?? clientHeightBefore ?? 0) >
194
+ 12;
195
+ if (canScroll && scrollTopBefore !== null && scrollTopAfter !== null) {
196
+ scrolled = Math.abs(scrollTopAfter - scrollTopBefore) > 2;
197
+ }
198
+ }
199
+ catch {
200
+ // ignore
201
+ }
202
+ const keyBefore = viewportBefore?.key || '';
203
+ const keyAfter = viewportAfter?.key || '';
204
+ const firstChanged = Boolean(keyBefore && keyAfter && keyBefore !== keyAfter);
205
+ if (scrolled === false && !firstChanged && currentCount <= lastCount) {
206
+ noEffectStreak += 1;
207
+ warn(`round=${i} ⚠️ scroll seems ineffective (streak=${noEffectStreak})`);
208
+ // 卡住时:先回滚(向上滚)几次,再继续向下滚(避免虚拟列表“卡在同一批节点”)
209
+ if (noEffectStreak >= 2) {
210
+ const endStateBeforeBounce = await getCommentEndState(controllerUrl, profile);
211
+ const noMoreBeforeBounce = Boolean(endStateBeforeBounce.endMarkerVisible || endStateBeforeBounce.emptyStateVisible);
212
+ if (noMoreBeforeBounce) {
213
+ log(`round=${i} endMarker=${endStateBeforeBounce.endMarkerVisible}, empty=${endStateBeforeBounce.emptyStateVisible} -> stop before bounce`);
214
+ lastCount = currentCount;
215
+ break;
216
+ }
217
+ // 规则:如果“回滚->再下滚”的尝试累计 3 次仍无法推进,则视为结束(避免无限卡死)
218
+ let stuckScrollBefore = null;
219
+ let stuckFirstBefore = null;
220
+ try {
221
+ stuckScrollBefore = await getScrollContainerState(controllerUrl, profile);
222
+ }
223
+ catch {
224
+ stuckScrollBefore = null;
225
+ }
226
+ try {
227
+ stuckFirstBefore = await getViewportFirstComment(controllerUrl, profile);
228
+ }
229
+ catch {
230
+ stuckFirstBefore = null;
231
+ }
232
+ const stuckTopBefore = stuckScrollBefore?.scrollTop ?? null;
233
+ const fallbackFocus = (await locateCommentsFocusPoint(controllerUrl, profile)) || focusPoint;
234
+ if (fallbackFocus)
235
+ focusPoint = fallbackFocus;
236
+ const fp = focusPoint || fallbackFocus;
237
+ if (fp) {
238
+ await systemHoverAt(profile, fp.x, fp.y, browserServiceUrl);
239
+ await new Promise((r) => setTimeout(r, 150));
240
+ await systemClickAt(profile, fp.x, fp.y, browserServiceUrl, 'comment_scroll_recovery');
241
+ await new Promise((r) => setTimeout(r, 220));
242
+ }
243
+ const upCount = 3 + Math.floor(Math.random() * 4);
244
+ // 回滚 3-6 次
245
+ for (let k = 0; k < upCount; k += 1) {
246
+ await systemMouseWheel({
247
+ profileId: profile,
248
+ deltaY: -(320 + Math.floor(Math.random() * 160)),
249
+ focusPoint,
250
+ browserServiceUrl,
251
+ browserWsUrl,
252
+ context: 'comment_scroll_recovery_up',
253
+ });
254
+ await new Promise((r) => setTimeout(r, 500 + Math.random() * 400));
255
+ }
256
+ // 再向下 3 次
257
+ for (let k = 0; k < 3; k += 1) {
258
+ await systemMouseWheel({
259
+ profileId: profile,
260
+ deltaY: 540 + Math.floor(Math.random() * 220),
261
+ focusPoint,
262
+ browserServiceUrl,
263
+ browserWsUrl,
264
+ context: 'comment_scroll_recovery_down',
265
+ });
266
+ await new Promise((r) => setTimeout(r, 600 + Math.random() * 450));
267
+ }
268
+ let stuckScrollAfter = null;
269
+ let stuckFirstAfter = null;
270
+ try {
271
+ stuckScrollAfter = await getScrollContainerState(controllerUrl, profile);
272
+ }
273
+ catch {
274
+ stuckScrollAfter = null;
275
+ }
276
+ try {
277
+ stuckFirstAfter = await getViewportFirstComment(controllerUrl, profile);
278
+ }
279
+ catch {
280
+ stuckFirstAfter = null;
281
+ }
282
+ const stuckTopAfter = stuckScrollAfter?.scrollTop ?? null;
283
+ const topMoved = stuckTopBefore !== null &&
284
+ stuckTopAfter !== null &&
285
+ Math.abs(Number(stuckTopAfter) - Number(stuckTopBefore)) > 2;
286
+ const firstMoved = Boolean(stuckFirstBefore?.key && stuckFirstAfter?.key && stuckFirstBefore.key !== stuckFirstAfter.key);
287
+ if (!topMoved && !firstMoved) {
288
+ stuckBounceFailures += 1;
289
+ warn(`round=${i} ⚠️ stuck bounce failed (attempt=${stuckBounceFailures}/3)`);
290
+ if (stuckBounceFailures >= 3) {
291
+ warn(`round=${i} 🛑 cannot scroll after 3 bounce attempts, treat as end`);
292
+ return {
293
+ reachedEnd: true,
294
+ endedByStuck: true,
295
+ totalFromHeader: targetTotal,
296
+ finalCount: currentCount,
297
+ rounds: i + 1,
298
+ focusPoint,
299
+ };
300
+ }
301
+ }
302
+ else {
303
+ stuckBounceFailures = 0;
304
+ }
305
+ noEffectStreak = 0;
306
+ }
307
+ }
308
+ else {
309
+ noEffectStreak = 0;
310
+ }
311
+ // “到底算结束”:严格仅以 end_marker / empty_state 为准
312
+ const endState = await getCommentEndState(controllerUrl, profile);
313
+ const noMore = Boolean(endState.endMarkerVisible || endState.emptyStateVisible);
314
+ log(`round=${i} count=${currentCount}, total=${targetTotal}, hasMoreHint=${stats.hasMore}, endMarker=${endState.endMarkerVisible}, empty=${endState.emptyStateVisible}, noMore=${noMore}`);
315
+ lastCount = currentCount;
316
+ if (noMore) {
317
+ log('stop conditions met');
318
+ break;
319
+ }
320
+ }
321
+ const finalStats = await getCommentStats(controllerUrl, profile);
322
+ const finalEndState = await getCommentEndState(controllerUrl, profile);
323
+ return {
324
+ reachedEnd: Boolean(finalEndState.endMarkerVisible || finalEndState.emptyStateVisible),
325
+ totalFromHeader: finalStats.total,
326
+ finalCount: finalStats.count,
327
+ rounds: roundsRan,
328
+ focusPoint,
329
+ };
330
+ }
331
+ function clamp(n, min, max) {
332
+ return Math.min(Math.max(n, min), max);
333
+ }
334
+ //# sourceMappingURL=commentScroller.js.map