@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,703 @@
1
+ /**
2
+ * Phase 2 Block: 执行搜索
3
+ *
4
+ * 职责:通过容器系统执行搜索操作(全系统级操作)
5
+ */
6
+ import { detectXhsCheckpoint, ensureXhsCheckpoint } from '../utils/checkpoints.js';
7
+ import { controllerAction, delay } from '../utils/controllerAction.js';
8
+ function isDebugArtifactsEnabled() {
9
+ return (process.env.WEBAUTO_DEBUG === '1' ||
10
+ process.env.WEBAUTO_DEBUG_ARTIFACTS === '1' ||
11
+ process.env.WEBAUTO_DEBUG_SCREENSHOT === '1');
12
+ }
13
+ // controllerAction/delay are shared utilities (with a safer timeout) to avoid
14
+ // per-block drift that breaks regression flows.
15
+ async function readSearchInputValue(profile, unifiedApiUrl) {
16
+ const value = await controllerAction('browser:execute', {
17
+ profile,
18
+ script: `(() => {
19
+ const root =
20
+ document.querySelector('#search-input') ||
21
+ document.querySelector("input[type='search']") ||
22
+ document.querySelector("input[placeholder*='搜索'], input[placeholder*='关键字']");
23
+ if (!root) return null;
24
+
25
+ // 兼容:站点更新后 #search-input 可能是 wrapper/div,而不是 input
26
+ const el =
27
+ ('value' in root)
28
+ ? root
29
+ : (root.querySelector('input, textarea, [contenteditable="true"], [contenteditable=""]') || root);
30
+
31
+ try {
32
+ if (el && typeof el === 'object' && 'value' in el) {
33
+ // @ts-ignore
34
+ const v = el.value;
35
+ return typeof v === 'string' ? v : String(v ?? '');
36
+ }
37
+ const text = (el && 'textContent' in el) ? (el.textContent ?? '') : '';
38
+ return typeof text === 'string' ? text : String(text ?? '');
39
+ } catch {
40
+ return null;
41
+ }
42
+ })()`,
43
+ }, unifiedApiUrl).then((res) => res?.result || res?.data?.result || null);
44
+ return typeof value === 'string' ? value : null;
45
+ }
46
+ async function readActiveInputValue(profile, unifiedApiUrl) {
47
+ const value = await controllerAction('browser:execute', {
48
+ profile,
49
+ script: `(() => {
50
+ const el = document.activeElement;
51
+ if (!el) return null;
52
+ const input = (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)
53
+ ? el
54
+ : (el.querySelector?.('input, textarea') || el);
55
+ if (input && 'value' in input) {
56
+ // @ts-ignore
57
+ const v = input.value;
58
+ return typeof v === 'string' ? v : String(v ?? '');
59
+ }
60
+ const text = (input && 'textContent' in input) ? (input.textContent ?? '') : '';
61
+ return typeof text === 'string' ? text : String(text ?? '');
62
+ })()`,
63
+ }, unifiedApiUrl).then((res) => res?.result || res?.data?.result || null);
64
+ return typeof value === 'string' ? value : null;
65
+ }
66
+ async function probeSearchInputState(profile, unifiedApiUrl, keyword) {
67
+ const state = await controllerAction('browser:execute', {
68
+ profile,
69
+ script: `(() => {
70
+ const norm = (v) => (typeof v === 'string' ? v.trim() : String(v ?? '').trim());
71
+ const editableSelector = "input, textarea, [contenteditable='true'], [contenteditable=''], [role='textbox']";
72
+ const toValue = (el) => {
73
+ try {
74
+ if (!el) return '';
75
+ if ('value' in el) {
76
+ const v = el.value;
77
+ return typeof v === 'string' ? v : String(v ?? '');
78
+ }
79
+ const t = el.textContent ?? '';
80
+ return typeof t === 'string' ? t : String(t ?? '');
81
+ } catch {
82
+ return '';
83
+ }
84
+ };
85
+ const isVisible = (el) => {
86
+ try {
87
+ if (!el || !el.getBoundingClientRect) return false;
88
+ const r = el.getBoundingClientRect();
89
+ if (!(r.width > 0 && r.height > 0)) return false;
90
+ return r.bottom >= 0 && r.right >= 0 && r.top <= window.innerHeight && r.left <= window.innerWidth;
91
+ } catch {
92
+ return false;
93
+ }
94
+ };
95
+ const descriptor = (el) => {
96
+ const bits = [
97
+ el?.id || '',
98
+ el?.className || '',
99
+ el?.getAttribute?.('name') || '',
100
+ el?.getAttribute?.('placeholder') || '',
101
+ el?.getAttribute?.('aria-label') || '',
102
+ el?.getAttribute?.('data-placeholder') || '',
103
+ ]
104
+ .map((x) => String(x || '').toLowerCase())
105
+ .filter(Boolean);
106
+ return bits.join(' ');
107
+ };
108
+ const hasSearchHint = (el) => /search|搜索|关键/.test(descriptor(el));
109
+ const activeElement = document.activeElement;
110
+ const activeEditable = activeElement && (activeElement.matches?.(editableSelector)
111
+ ? activeElement
112
+ : activeElement.querySelector?.(editableSelector));
113
+
114
+ /** @type {Array<{value: string, source: string, score: number}>} */
115
+ const candidates = [];
116
+ const seen = new Set();
117
+ const pushCandidate = (el, source) => {
118
+ if (!el || seen.has(el)) return;
119
+ seen.add(el);
120
+ if (!isVisible(el)) return;
121
+ const value = toValue(el);
122
+ const rect = el.getBoundingClientRect?.() || { top: 9999, width: 0 };
123
+ const score =
124
+ (el === activeEditable ? 80 : 0) +
125
+ (hasSearchHint(el) ? 40 : 0) +
126
+ (rect.top < 180 ? 20 : 0) +
127
+ (rect.width > 120 ? 10 : 0);
128
+ candidates.push({ value: String(value || ''), source, score });
129
+ };
130
+
131
+ if (activeEditable) pushCandidate(activeEditable, 'active');
132
+
133
+ const root = document.querySelector('#search-input');
134
+ if (root) {
135
+ const nested = root.matches?.(editableSelector)
136
+ ? root
137
+ : root.querySelector?.(editableSelector);
138
+ if (nested) pushCandidate(nested, 'search_root');
139
+ }
140
+
141
+ const primary = document.querySelectorAll(
142
+ "#search-input, input[type='search'], input[placeholder*='搜索'], input[placeholder*='关键字'], input[aria-label*='搜索'], input[name*='search']",
143
+ );
144
+ primary.forEach((node, idx) => {
145
+ const el = node.matches?.(editableSelector)
146
+ ? node
147
+ : node.querySelector?.(editableSelector);
148
+ if (el) pushCandidate(el, \`primary_\${idx + 1}\`);
149
+ });
150
+
151
+ const editable = document.querySelectorAll(editableSelector);
152
+ editable.forEach((node, idx) => {
153
+ pushCandidate(node, \`editable_\${idx + 1}\`);
154
+ });
155
+
156
+ candidates.sort((a, b) => b.score - a.score);
157
+ const normalizedKeyword = norm(keyword);
158
+ const exact = candidates.find((c) => norm(c.value) === normalizedKeyword) || null;
159
+ const nonEmpty = candidates.find((c) => norm(c.value).length > 0) || null;
160
+ const best = exact || nonEmpty || candidates[0] || null;
161
+ const activeValue = activeEditable ? toValue(activeEditable) : null;
162
+
163
+ return {
164
+ ok: !!exact,
165
+ value: best ? String(best.value || '') : null,
166
+ source: best ? String(best.source || '') : null,
167
+ activeValue: typeof activeValue === 'string' ? activeValue : (activeValue == null ? null : String(activeValue)),
168
+ candidates: candidates.slice(0, 5).map((c) => \`\${c.source}:\${String(c.value || '').slice(0, 60)}\`),
169
+ };
170
+ })()`,
171
+ }, unifiedApiUrl).then((res) => res?.result || res?.data?.result || null);
172
+ return {
173
+ ok: Boolean(state?.ok),
174
+ value: typeof state?.value === 'string' ? state.value : null,
175
+ source: typeof state?.source === 'string' ? state.source : null,
176
+ activeValue: typeof state?.activeValue === 'string' ? state.activeValue : null,
177
+ candidates: Array.isArray(state?.candidates) ? state.candidates.map((x) => String(x)) : [],
178
+ };
179
+ }
180
+ async function systemFillSearchInputValue(profile, unifiedApiUrl, keyword) {
181
+ // System-level requirement: prefer keyboard/mouse. `browser:execute` is JS mutation and should be avoided.
182
+ // We implement a system-level "fill" as: select-all + delete + type (with retries).
183
+ const trySelectAllDeleteType = async (modifier) => {
184
+ await controllerAction('keyboard:down', { profileId: profile, key: modifier }, unifiedApiUrl).catch(() => { });
185
+ await controllerAction('keyboard:press', { profileId: profile, key: 'A' }, unifiedApiUrl).catch(() => { });
186
+ await controllerAction('keyboard:up', { profileId: profile, key: modifier }, unifiedApiUrl).catch(() => { });
187
+ await delay(60);
188
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Backspace' }, unifiedApiUrl).catch(() => { });
189
+ await delay(60);
190
+ await controllerAction('keyboard:type', { profileId: profile, text: keyword, delay: 70 }, unifiedApiUrl).catch(() => { });
191
+ };
192
+ const checkBoth = async () => {
193
+ const probe = await probeSearchInputState(profile, unifiedApiUrl, keyword).catch(() => null);
194
+ if (probe?.ok) {
195
+ return { ok: true, source: probe.source || 'probeSearchInputState', value: probe.value || '' };
196
+ }
197
+ const v1 = await readSearchInputValue(profile, unifiedApiUrl).catch(() => null);
198
+ if (v1 && v1.trim() === keyword)
199
+ return { ok: true, source: 'readSearchInputValue', value: v1 };
200
+ const v2 = await readActiveInputValue(profile, unifiedApiUrl).catch(() => null);
201
+ if (v2 && v2.trim() === keyword)
202
+ return { ok: true, source: 'readActiveInputValue', value: v2 };
203
+ return { ok: false, value: probe?.value || v1 || v2 || '' };
204
+ };
205
+ // Try a few times; Camoufox can drop events if focus is flaky.
206
+ for (let i = 0; i < 3; i++) {
207
+ await trySelectAllDeleteType('Meta');
208
+ await delay(250);
209
+ const c1 = await checkBoth();
210
+ if (c1.ok)
211
+ return { ok: true, method: 'meta', source: c1.source };
212
+ await trySelectAllDeleteType('Control');
213
+ await delay(250);
214
+ const c2 = await checkBoth();
215
+ if (c2.ok)
216
+ return { ok: true, method: 'control', source: c2.source };
217
+ }
218
+ const finalCheck = await checkBoth();
219
+ return { ok: false, reason: `mismatch:${String(finalCheck.value ?? '')}` };
220
+ }
221
+ async function submitHomeSearchViaContainer(profile, unifiedApiUrl) {
222
+ // Fallback only: do NOT type keyword again (it can append duplicated text).
223
+ // Just click search button once.
224
+ try {
225
+ await controllerAction('container:operation', { containerId: 'xiaohongshu_home.search_button', operationId: 'click', sessionId: profile, timeoutMs: 10000 }, unifiedApiUrl).catch(() => { });
226
+ console.log('[Phase2Search] submit via click fallback');
227
+ return true;
228
+ }
229
+ catch {
230
+ return false;
231
+ }
232
+ }
233
+ async function canSubmitSearch(profile, unifiedApiUrl, keyword) {
234
+ const probe = await probeSearchInputState(profile, unifiedApiUrl, keyword).catch(() => null);
235
+ if (probe?.ok) {
236
+ return {
237
+ ok: true,
238
+ value: probe.value ?? null,
239
+ source: probe.source ?? 'probeSearchInputState',
240
+ activeValue: probe.activeValue ?? null,
241
+ candidates: Array.isArray(probe.candidates) ? probe.candidates : [],
242
+ };
243
+ }
244
+ const value = await readSearchInputValue(profile, unifiedApiUrl).catch(() => null);
245
+ if (typeof value === 'string' && value.trim() === keyword)
246
+ return { ok: true, value, source: 'readSearchInputValue', activeValue: null, candidates: [] };
247
+ const activeValue = await readActiveInputValue(profile, unifiedApiUrl).catch(() => null);
248
+ if (typeof activeValue === 'string' && activeValue.trim() === keyword)
249
+ return { ok: true, value: activeValue, source: 'readActiveInputValue', activeValue, candidates: [] };
250
+ return {
251
+ ok: false,
252
+ value: (probe?.value ?? value ?? activeValue ?? null),
253
+ source: (probe?.source ?? null),
254
+ activeValue: (probe?.activeValue ?? activeValue ?? null),
255
+ candidates: Array.isArray(probe?.candidates) ? probe.candidates : [],
256
+ };
257
+ }
258
+ async function browserFillSearchInputValue(profile, unifiedApiUrl, keyword, searchInputContainerId) {
259
+ const selector = "#search-input input, #search-input textarea, #search-input [contenteditable='true'], input[type='search'], input[placeholder*='鎼滅储'], input[placeholder*='鍏抽敭'], input[aria-label*='鎼滅储']";
260
+ const fillRes = await controllerAction('browser:fill', { profile, selector, text: keyword }, unifiedApiUrl).catch((e) => ({ success: false, error: String(e?.message || e || 'browser_fill_failed') }));
261
+ const fillOk = Boolean(fillRes?.success ?? fillRes?.ok ?? false);
262
+ const fillErr = String(fillRes?.error || '').trim();
263
+ console.log(`[Phase2Search] protocol fill: selector="${selector}" success=${fillOk}${fillErr ? ` error=${fillErr}` : ''}`);
264
+ if (fillOk) {
265
+ return { success: true, mode: 'browser:fill', selector };
266
+ }
267
+ if (searchInputContainerId !== 'dom_fallback_search_input') {
268
+ const typeRes = await controllerAction('container:operation', {
269
+ containerId: searchInputContainerId,
270
+ operationId: 'type',
271
+ sessionId: profile,
272
+ config: {
273
+ text: keyword,
274
+ value: keyword,
275
+ clear_first: true,
276
+ human_typing: true,
277
+ pause_after: 300,
278
+ },
279
+ }, unifiedApiUrl).catch((e) => ({ success: false, error: String(e?.message || e || 'container_type_failed') }));
280
+ const typeOk = Boolean(typeRes?.success ?? typeRes?.ok ?? false);
281
+ const typeErr = String(typeRes?.error || '').trim();
282
+ console.log(`[Phase2Search] protocol input: container_type success=${typeOk}${typeErr ? ` error=${typeErr}` : ''}`);
283
+ if (typeOk) {
284
+ const ok = await canSubmitSearch(profile, unifiedApiUrl, keyword).catch(() => ({ ok: false }));
285
+ if (ok?.ok)
286
+ return { success: true, mode: 'container:type', selector };
287
+ }
288
+ }
289
+ // Fallback: system-level input
290
+ if (searchInputContainerId !== 'dom_fallback_search_input') {
291
+ await controllerAction('container:operation', { containerId: searchInputContainerId, operationId: 'click', sessionId: profile }, unifiedApiUrl).catch(() => { });
292
+ }
293
+ await delay(200);
294
+ await clearSearchInput(profile, unifiedApiUrl);
295
+ await delay(120);
296
+ await controllerAction('keyboard:type', { profileId: profile, text: keyword }, unifiedApiUrl).catch(() => { });
297
+ await delay(200);
298
+ const ok = await canSubmitSearch(profile, unifiedApiUrl, keyword).catch(() => ({ ok: false }));
299
+ return { success: !!ok?.ok, mode: 'keyboard:type', selector };
300
+ }
301
+ async function clearSearchInput(profile, unifiedApiUrl) {
302
+ // Step 1: JS-based clear first (most reliable for Camoufox/Firefox)
303
+ const jsCleared = await controllerAction('browser:execute', {
304
+ profile,
305
+ script: `(() => {
306
+ const root = document.querySelector('#search-input') ||
307
+ document.querySelector("input[type='search']") ||
308
+ document.querySelector("input[placeholder*='搜索'], input[placeholder*='关键字']");
309
+ if (!root) return false;
310
+ root.value = '';
311
+ root.dispatchEvent(new Event('input', { bubbles: true }));
312
+ root.dispatchEvent(new Event('change', { bubbles: true }));
313
+ return true;
314
+ })()`,
315
+ }, unifiedApiUrl).then(res => res?.result || res?.data?.result || false).catch(() => false);
316
+ await delay(100);
317
+ const v1 = await readSearchInputValue(profile, unifiedApiUrl);
318
+ if (!v1 || !v1.trim())
319
+ return;
320
+ // Step 2+: Fallback to system keyboard if needed
321
+ const tryCombo = async (combo) => {
322
+ const mod = combo.startsWith('Meta') ? 'Meta' : 'Control';
323
+ await controllerAction('keyboard:down', { profileId: profile, key: mod }, unifiedApiUrl).catch(() => { });
324
+ await controllerAction('keyboard:press', { profileId: profile, key: 'A' }, unifiedApiUrl).catch(() => { });
325
+ await controllerAction('keyboard:up', { profileId: profile, key: mod }, unifiedApiUrl).catch(() => { });
326
+ await delay(80);
327
+ // Use both Delete and Backspace to cover different input implementations.
328
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Backspace' }, unifiedApiUrl).catch(() => { });
329
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Delete' }, unifiedApiUrl).catch(() => { });
330
+ };
331
+ // 1) Cmd+A (Mac) then delete
332
+ await tryCombo('Meta+A');
333
+ await delay(120);
334
+ let v = await readSearchInputValue(profile, unifiedApiUrl);
335
+ if (!v || !v.trim())
336
+ return;
337
+ // 2) Ctrl+A then delete
338
+ await tryCombo('Control+A');
339
+ await delay(120);
340
+ v = await readSearchInputValue(profile, unifiedApiUrl);
341
+ if (!v || !v.trim())
342
+ return;
343
+ // 3) Fallback: repeated Backspace
344
+ for (let i = 0; i < 80; i++) {
345
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Backspace' }, unifiedApiUrl).catch(() => { });
346
+ if (i % 10 === 0) {
347
+ await delay(40);
348
+ v = await readSearchInputValue(profile, unifiedApiUrl);
349
+ if (!v || !v.trim())
350
+ return;
351
+ }
352
+ }
353
+ }
354
+ export async function execute(input) {
355
+ const { keyword, profile = 'xiaohongshu_fresh', unifiedApiUrl = 'http://127.0.0.1:7701', } = input;
356
+ const debugArtifactsEnabled = isDebugArtifactsEnabled();
357
+ console.log(`[Phase2Search] 执行搜索(容器驱动): ${keyword}`);
358
+ // Ensure we are in a safe starting state (home/search). Recover from detail/comments if needed.
359
+ const ensureRes = await ensureXhsCheckpoint({
360
+ sessionId: profile,
361
+ target: 'search_ready',
362
+ serviceUrl: unifiedApiUrl,
363
+ timeoutMs: 15000,
364
+ allowOneLevelUpFallback: true,
365
+ });
366
+ if (!ensureRes.success && ensureRes.reached !== 'home_ready' && ensureRes.reached !== 'search_ready') {
367
+ throw new Error(`[Phase2Search] ensure checkpoint failed: reached=${ensureRes.reached} url=${ensureRes.url}`);
368
+ }
369
+ let currentUrl = await controllerAction('browser:execute', { profile, script: 'window.location.href' }, unifiedApiUrl).then((res) => res?.result || res?.data?.result || '');
370
+ // 开发期硬门禁:每个大环节开始先定位,不做容错兜底。
371
+ const det = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
372
+ console.log(`[Phase2Search] locate: checkpoint=${det.checkpoint} url=${det.url}`);
373
+ if (det.checkpoint === 'risk_control' || det.checkpoint === 'login_guard' || det.checkpoint === 'offsite') {
374
+ throw new Error(`[Phase2Search] hard_stop checkpoint=${det.checkpoint} url=${det.url}`);
375
+ }
376
+ async function waitCheckpoint(maxWaitMs) {
377
+ const start = Date.now();
378
+ let last = det;
379
+ while (Date.now() - start < maxWaitMs) {
380
+ await delay(500);
381
+ last = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
382
+ if (last.checkpoint !== 'detail_ready' && last.checkpoint !== 'comments_ready')
383
+ return last;
384
+ }
385
+ return last;
386
+ }
387
+ async function exitDetailOrCommentsState() {
388
+ let d = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
389
+ if (d.checkpoint !== 'detail_ready' && d.checkpoint !== 'comments_ready')
390
+ return d;
391
+ // 详情/评论态:先尝试点击关闭按钮(更贴近用户行为),然后等待状态变化。
392
+ console.log('[Phase2Search] 处于详情/评论态,尝试点击关闭按钮关闭详情页...');
393
+ try {
394
+ const r = await controllerAction('container:operation', { containerId: 'xiaohongshu_detail.close_button', operationId: 'click', sessionId: profile, timeoutMs: 15000 }, unifiedApiUrl);
395
+ console.log(`[Phase2Search] close_button click: success=${Boolean(r?.success !== false)}`);
396
+ }
397
+ catch {
398
+ console.log('[Phase2Search] close_button click failed (ignored)');
399
+ }
400
+ d = await waitCheckpoint(8000);
401
+ if (d.checkpoint !== 'detail_ready' && d.checkpoint !== 'comments_ready')
402
+ return d;
403
+ // 若仍在详情/评论态:使用 ESC 退出(系统级),每次后等待状态稳定。
404
+ for (let i = 0; i < 2; i += 1) {
405
+ console.log(`[Phase2Search] still in ${d.checkpoint}, press ESC to exit (round=${i + 1})`);
406
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Escape' }, unifiedApiUrl);
407
+ d = await waitCheckpoint(8000);
408
+ if (d.checkpoint !== 'detail_ready' && d.checkpoint !== 'comments_ready')
409
+ return d;
410
+ }
411
+ // 最终仍失败:留证据(截图长度),停止(不刷新)。
412
+ let shotLen = 0;
413
+ if (isDebugArtifactsEnabled()) {
414
+ const shot = await controllerAction('browser:screenshot', { profileId: profile, fullPage: false }, unifiedApiUrl).then((res) => res?.data || res?.result || res?.data?.data || '');
415
+ shotLen = typeof shot === 'string' ? shot.length : 0;
416
+ }
417
+ throw new Error(`[Phase2Search] 关闭详情页后仍未回到搜索/首页(checkpoint=${d.checkpoint})。停止(避免刷新)。URL=${d.url} screenshot_len=${shotLen}`);
418
+ }
419
+ // If starting from detail/comments state, exit to a stable checkpoint before doing anything else.
420
+ if (det.checkpoint === 'detail_ready' || det.checkpoint === 'comments_ready') {
421
+ await exitDetailOrCommentsState();
422
+ }
423
+ // 检查当前页面是否可搜索(优先用 DOM signals,禁止任何刷新/导航)
424
+ const domCheck = await controllerAction('browser:execute', {
425
+ profile,
426
+ script: `(function(){
427
+ const root =
428
+ document.querySelector('#search-input') ||
429
+ document.querySelector('input[type="search"]') ||
430
+ document.querySelector('input[placeholder*="搜索"], input[placeholder*="关键字"]');
431
+ const el = root && ('value' in root)
432
+ ? root
433
+ : (root ? (root.querySelector('input, textarea, [contenteditable="true"], [contenteditable=""]') || root) : null);
434
+ const rect = el && el.getBoundingClientRect ? el.getBoundingClientRect() : null;
435
+ const hasSearchInput = !!el && !!rect && rect.width > 0 && rect.height > 0;
436
+ const hasDetailMask = !!document.querySelector('.detail-mask, .note-detail-mask, .content-mask');
437
+ return {
438
+ hasSearchInput,
439
+ hasDetailMask,
440
+ rect: rect ? { x1: rect.left, y1: rect.top, x2: rect.right, y2: rect.bottom } : null,
441
+ url: window.location.href,
442
+ };
443
+ })()`,
444
+ }, unifiedApiUrl).then(res => res?.result || res?.data?.result || {});
445
+ const hasSearchInput = Boolean(domCheck?.hasSearchInput);
446
+ const hasDetailMask = Boolean(domCheck?.hasDetailMask);
447
+ const domRect = domCheck?.rect || null;
448
+ // Priority 1: If detail mask exists or we're still in detail/comments, close the modal.
449
+ // NOTE: On XHS, ESC is unreliable in Camoufox; prefer clicking the close button (system click via container).
450
+ if (hasDetailMask || det.checkpoint === 'detail_ready' || det.checkpoint === 'comments_ready') {
451
+ console.log('[Phase2Search] 处于详情/评论态,尝试点击关闭按钮关闭详情页...');
452
+ // Best-effort click the close button by container operation (system-level click).
453
+ // If container is missing (layout changed), we fail fast (no refresh) with evidence.
454
+ const clickRes = await controllerAction('container:operation', { containerId: 'xiaohongshu_detail.modal_shell', operationId: 'click', sessionId: profile }, unifiedApiUrl);
455
+ console.log(`[Phase2Search] close_button click: success=${Boolean(clickRes?.success)}`);
456
+ await delay(1500);
457
+ const detAfterClose = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
458
+ if (detAfterClose.checkpoint !== 'search_ready' && detAfterClose.checkpoint !== 'home_ready') {
459
+ throw new Error(`[Phase2Search] 关闭详情页后仍未回到搜索/首页(checkpoint=${detAfterClose.checkpoint})。停止(避免刷新)。URL=${currentUrl}`);
460
+ }
461
+ }
462
+ // Priority 2: Without search input we cannot search
463
+ if (!hasSearchInput) {
464
+ throw new Error(`[Phase2Search] 未检测到搜索输入框,无法执行搜索。URL=${currentUrl}`);
465
+ }
466
+ const probeHighlight = async (containerId) => {
467
+ try {
468
+ const res = await controllerAction('container:operation', { containerId, operationId: 'highlight', sessionId: profile }, unifiedApiUrl);
469
+ return res?.success ? res : null;
470
+ }
471
+ catch {
472
+ return null;
473
+ }
474
+ };
475
+ // Do not infer page type from URL (XHS can keep /explore/<id> while showing search UI).
476
+ // Instead, probe containers first; if all fail, fallback to DOM detection (no refresh/goto).
477
+ let searchInputContainerId = '';
478
+ let highlightResult = null;
479
+ let useDomFallback = false;
480
+ const preferredOrder = det.checkpoint === 'search_ready'
481
+ ? ['xiaohongshu_search.search_bar', 'xiaohongshu_home.search_input']
482
+ : ['xiaohongshu_home.search_input', 'xiaohongshu_search.search_bar'];
483
+ for (const candidate of preferredOrder) {
484
+ highlightResult = await probeHighlight(candidate);
485
+ if (highlightResult) {
486
+ searchInputContainerId = candidate;
487
+ break;
488
+ }
489
+ }
490
+ // Fallback: if container highlight fails, use DOM rect (no refresh/goto)
491
+ if (!searchInputContainerId && domRect) {
492
+ useDomFallback = true;
493
+ searchInputContainerId = 'dom_fallback_search_input';
494
+ highlightResult = { success: true, data: { rect: domRect } };
495
+ console.log('[Phase2Search] 容器 highlight 失败,使用 DOM rect 作为搜索框');
496
+ }
497
+ if (!searchInputContainerId) {
498
+ throw new Error(`[Phase2Search] 未识别页面状态,无法定位搜索框。当前 URL: ${currentUrl}`);
499
+ }
500
+ const isSearchResult = searchInputContainerId === 'xiaohongshu_search.search_bar';
501
+ const isHome = searchInputContainerId === 'xiaohongshu_home.search_input' || searchInputContainerId === 'dom_fallback_search_input';
502
+ console.log(`[Phase2Search] 当前页面: ${isSearchResult ? 'search_result' : 'home'},使用容器 ${searchInputContainerId}`);
503
+ // 验证搜索框可用性(先高亮确认)
504
+ if (!highlightResult) {
505
+ console.log(`[Phase2Search] highlight start: ${searchInputContainerId}`);
506
+ highlightResult = await controllerAction('container:operation', { containerId: searchInputContainerId, operationId: 'highlight', sessionId: profile }, unifiedApiUrl);
507
+ }
508
+ console.log(`[Phase2Search] highlight done: success=${Boolean(highlightResult?.success)}`);
509
+ if (!highlightResult?.success) {
510
+ throw new Error(`[Phase2Search] 搜索框不可用: ${searchInputContainerId}`);
511
+ }
512
+ await delay(500);
513
+ // Camoufox: prefer system-level coordinate click to reliably focus inputs.
514
+ // Using container:operation click can hang in some cases.
515
+ const anchor = highlightResult?.data || highlightResult;
516
+ const rect = anchor?.rect;
517
+ if (rect?.x1 !== undefined && rect?.y1 !== undefined && rect?.x2 !== undefined && rect?.y2 !== undefined) {
518
+ const width = Number(rect.x2) - Number(rect.x1);
519
+ const height = Number(rect.y2) - Number(rect.y1);
520
+ if (!Number.isFinite(width) || !Number.isFinite(height) || width < 8 || height < 8) {
521
+ console.log(`[Phase2Search] rect invalid for coordinate click (w=${width}, h=${height}), fallback to container click`);
522
+ if (!useDomFallback) {
523
+ await controllerAction('container:operation', { containerId: searchInputContainerId, operationId: 'click', sessionId: profile, timeoutMs: 10000 }, unifiedApiUrl).catch(() => { });
524
+ }
525
+ await delay(260);
526
+ }
527
+ else {
528
+ const cx = (Number(rect.x1) + Number(rect.x2)) / 2;
529
+ const cy = (Number(rect.y1) + Number(rect.y2)) / 2;
530
+ console.log(`[Phase2Search] mouse:click at (${cx.toFixed(1)}, ${cy.toFixed(1)})`);
531
+ await controllerAction('mouse:click', { profileId: profile, x: cx, y: cy, clicks: 1 }, unifiedApiUrl);
532
+ // Give the input time to receive focus in Camoufox.
533
+ await delay(300);
534
+ // Try selecting text via mouse (more reliable than keyboard shortcuts on some builds).
535
+ await controllerAction('mouse:click', { profileId: profile, x: cx, y: cy, clicks: 2 }, unifiedApiUrl).catch(() => { });
536
+ await delay(200);
537
+ }
538
+ }
539
+ else {
540
+ console.warn(`[Phase2Search] missing rect for system click, fallback to container click`);
541
+ if (!useDomFallback) {
542
+ await controllerAction('container:operation', { containerId: searchInputContainerId, operationId: 'click', sessionId: profile, timeoutMs: 10000 }, unifiedApiUrl).catch(() => { });
543
+ await delay(260);
544
+ }
545
+ }
546
+ // If the input already contains the same keyword, do not force clearing.
547
+ // Camoufox sometimes ignores deletion events in certain states; treat "already correct" as success.
548
+ const beforeClear = await readSearchInputValue(profile, unifiedApiUrl).catch(() => null);
549
+ const inputAlreadyMatches = typeof beforeClear === 'string' && beforeClear.trim() === keyword;
550
+ if (inputAlreadyMatches) {
551
+ console.log('[Phase2Search] input already matches keyword, will clear and retype to ensure focus');
552
+ }
553
+ // Always clear and type to ensure focus and input state
554
+ // Force clear via JS first (most reliable)
555
+ await controllerAction('browser:execute', {
556
+ profile,
557
+ script: `(() => {
558
+ const root = document.querySelector('#search-input') ||
559
+ document.querySelector("input[type='search']") ||
560
+ document.querySelector("input[placeholder*='搜索'], input[placeholder*='关键字']");
561
+ if (root) {
562
+ root.value = '';
563
+ root.dispatchEvent(new Event('input', { bubbles: true }));
564
+ }
565
+ return true;
566
+ })()`,
567
+ }, unifiedApiUrl).catch(() => { });
568
+ await delay(100);
569
+ await clearSearchInput(profile, unifiedApiUrl);
570
+ const clearedValue = await readSearchInputValue(profile, unifiedApiUrl);
571
+ if (typeof clearedValue === 'string' && clearedValue.trim() && clearedValue.trim() !== keyword) {
572
+ let shotLen = 0;
573
+ if (debugArtifactsEnabled) {
574
+ const shot = await controllerAction('browser:screenshot', { profileId: profile, fullPage: false }, unifiedApiUrl).then((res) => res?.data || res?.result || res?.data?.data || '');
575
+ shotLen = typeof shot === 'string' ? shot.length : 0;
576
+ }
577
+ throw new Error(`[Phase2Search] 清空输入框失败(组合键 + 退格后仍有残留)。value="${clearedValue}" screenshot_len=${shotLen}`);
578
+ }
579
+ // Camoufox: keyboard typing can be flaky (focus/IME). Use a fill-style set + input/change events.
580
+ const fillRes = await browserFillSearchInputValue(profile, unifiedApiUrl, keyword, searchInputContainerId);
581
+ const fillSuccess = Boolean(fillRes?.success !== false);
582
+ console.log(`[Phase2Search] browser:fill done: success=${fillSuccess}`);
583
+ if (!fillSuccess) {
584
+ const fallback = await systemFillSearchInputValue(profile, unifiedApiUrl, keyword);
585
+ console.log(`[Phase2Search] keyboard fill fallback: ok=${Boolean(fallback?.ok)} reason=${fallback?.reason || ''}`);
586
+ }
587
+ await delay(450);
588
+ // If we skipped typing, the input already contains keyword; proceed to submit.
589
+ // 强制验证:提交前必须确认 input 值等于 keyword,否则直接失败(不点击搜索按钮)
590
+ const canSubmit = await canSubmitSearch(profile, unifiedApiUrl, keyword);
591
+ const beforeSubmitValue = await readSearchInputValue(profile, unifiedApiUrl).catch(() => null);
592
+ const activeBeforeSubmitValue = await readActiveInputValue(profile, unifiedApiUrl).catch(() => null);
593
+ console.log(`[Phase2Search] Before submit: input value="${String(beforeSubmitValue)}" active="${String(activeBeforeSubmitValue)}" keyword="${keyword}" probe=${String(canSubmit.source)} candidates=${(canSubmit.candidates || []).join(' | ')}`);
594
+ if (String(canSubmit.activeValue || activeBeforeSubmitValue || '') !== String(keyword) && searchInputContainerId !== 'dom_fallback_search_input') {
595
+ await controllerAction('container:operation', { containerId: searchInputContainerId, operationId: 'click', sessionId: profile }, unifiedApiUrl).catch(() => { });
596
+ await delay(300);
597
+ }
598
+ if (!canSubmit.ok) {
599
+ let shotLen = 0;
600
+ if (debugArtifactsEnabled) {
601
+ const shot = await controllerAction('browser:screenshot', { profileId: profile, fullPage: false }, unifiedApiUrl).then((res) => res?.data || res?.result || res?.data?.data || '');
602
+ shotLen = typeof shot === 'string' ? shot.length : 0;
603
+ }
604
+ throw new Error(`[Phase2Search] 提交前 input 值不等于关键字:expected="${keyword}" actual="${String(canSubmit.value)}" source=${String(canSubmit.source)} active="${String(canSubmit.activeValue)}" candidates=${(canSubmit.candidates || []).join(' | ')} screenshot_len=${shotLen}。停止执行(不点击搜索按钮)。`);
605
+ }
606
+ if (isHome) {
607
+ // Trigger Enter first; fallback to click only when Enter does not navigate to search result.
608
+ console.log('[Phase2Search] submit via Enter');
609
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Enter' }, unifiedApiUrl).catch(() => { });
610
+ await delay(1200);
611
+ const afterEnter = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
612
+ if (afterEnter.checkpoint !== 'search_ready') {
613
+ const ok = await submitHomeSearchViaContainer(profile, unifiedApiUrl);
614
+ if (!ok) {
615
+ console.log('[Phase2Search] submit via search_button failed');
616
+ }
617
+ }
618
+ else {
619
+ console.log('[Phase2Search] Enter submit accepted, skip click fallback');
620
+ }
621
+ }
622
+ else {
623
+ // search_result:single Enter submit only
624
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Enter' }, unifiedApiUrl).catch(() => { });
625
+ }
626
+ // 等待搜索结果页加载:最多 15 秒。
627
+ // 注意:XHS 在 Camoufox 下可能出现“URL 仍停留 /explore/<id>,但 DOM 已是搜索结果页”的壳页行为。
628
+ // 这里仅做节奏控制,最终成功判定仍以 DOM/容器信号为准。
629
+ await delay(15000);
630
+ // 验证是否到达搜索结果页(不依赖 URL,使用 DOM + checkpoint),失败则最多重试 3 次 Enter 提交
631
+ let finalUrl = currentUrl;
632
+ let success = false;
633
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
634
+ const checkpoint = await detectXhsCheckpoint({ sessionId: profile, serviceUrl: unifiedApiUrl });
635
+ const pageCheck = await controllerAction('browser:execute', {
636
+ profile,
637
+ script: `(function(){
638
+ const hasTabs = !!document.querySelector('.tabs, .filter-tabs, [role="tablist"]');
639
+ const hasResultList = !!document.querySelector('.feeds-container, .search-result-list, .note-list');
640
+ const hasSearchInput = !!document.querySelector('#search-input, input[type="search"]');
641
+ return {
642
+ hasTabs,
643
+ hasResultList,
644
+ hasSearchInput,
645
+ url: window.location.href,
646
+ };
647
+ })()`,
648
+ }, unifiedApiUrl).then(res => res?.result || res?.data?.result || null);
649
+ finalUrl = pageCheck?.url || finalUrl;
650
+ const urlStr = String(finalUrl || '');
651
+ const urlLooksSearch = urlStr.includes('/search_result') || urlStr.includes('search_result');
652
+ success = checkpoint.checkpoint === 'search_ready' && Boolean(pageCheck?.hasSearchInput) && (pageCheck?.hasTabs || pageCheck?.hasResultList || urlLooksSearch);
653
+ console.log(`[Phase2Search] 完成: success=${success} checkpoint=${checkpoint.checkpoint} url=${finalUrl} hasTabs=${pageCheck?.hasTabs} hasResultList=${pageCheck?.hasResultList} attempt=${attempt}`);
654
+ if (success)
655
+ break;
656
+ if (attempt < 3) {
657
+ console.log(`[Phase2Search] retry search submit (attempt=${attempt + 1})`);
658
+ // 重新聚焦输入框(系统级点击)再输入关键字,避免焦点丢失
659
+ try {
660
+ const rect = await controllerAction('browser:execute', {
661
+ profile,
662
+ script: `(function(){
663
+ const el = document.querySelector('#search-input') || document.querySelector('input[type="search"]') || document.querySelector('input[placeholder*="搜索"], input[placeholder*="关键字"]');
664
+ if (!el) return null;
665
+ const r = el.getBoundingClientRect();
666
+ return { x1: r.left, y1: r.top, x2: r.right, y2: r.bottom };
667
+ })()`,
668
+ }, unifiedApiUrl).then(res => res?.result || res?.data?.result || null);
669
+ if (rect && rect.x1 !== undefined) {
670
+ const cx = (Number(rect.x1) + Number(rect.x2)) / 2;
671
+ const cy = (Number(rect.y1) + Number(rect.y2)) / 2;
672
+ await controllerAction('mouse:click', { profileId: profile, x: cx, y: cy, clicks: 1 }, unifiedApiUrl).catch(() => { });
673
+ await delay(200);
674
+ }
675
+ }
676
+ catch {
677
+ // ignore refocus errors
678
+ }
679
+ await systemFillSearchInputValue(profile, unifiedApiUrl, keyword).catch(() => { });
680
+ await delay(300);
681
+ await controllerAction('keyboard:press', { profileId: profile, key: 'Enter' }, unifiedApiUrl);
682
+ await delay(5000);
683
+ }
684
+ }
685
+ if (!success) {
686
+ const ensureFinal = await ensureXhsCheckpoint({
687
+ sessionId: profile,
688
+ target: 'search_ready',
689
+ serviceUrl: unifiedApiUrl,
690
+ timeoutMs: 12000,
691
+ allowOneLevelUpFallback: false,
692
+ });
693
+ success = ensureFinal.success && ensureFinal.reached === 'search_ready';
694
+ finalUrl = ensureFinal.url || finalUrl;
695
+ console.log(`[Phase2Search] ensure final: success=${success} reached=${ensureFinal.reached} url=${finalUrl}`);
696
+ }
697
+ return {
698
+ success,
699
+ finalUrl,
700
+ keyword,
701
+ };
702
+ }
703
+ //# sourceMappingURL=Phase2SearchBlock.js.map