@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
@@ -1,847 +0,0 @@
1
- import { createEl } from '../../ui-components.mjs';
2
-
3
- type LiveStatsOptions = {
4
- maxCommentsInput: HTMLInputElement;
5
- linksStat: HTMLSpanElement;
6
- postsStat: HTMLSpanElement;
7
- commentsStat: HTMLSpanElement;
8
- likesStat: HTMLSpanElement;
9
- likesSkipStat: HTMLSpanElement;
10
- repliesStat: HTMLSpanElement;
11
- streamStat: HTMLSpanElement;
12
- shardStatsList: HTMLDivElement;
13
- likedList: HTMLDivElement;
14
- repliedList: HTMLDivElement;
15
- };
16
-
17
- export type LiveStatsController = {
18
- resetLiveStats: () => void;
19
- setExpectedLinksTarget: (target: number) => void;
20
- setShardProfiles: (profiles: string[]) => void;
21
- parseStdoutForEvents: (line: string) => void;
22
- setActiveRunId: (runId: string) => void;
23
- dispose: () => void;
24
- };
25
-
26
- export function createLiveStatsController(opts: LiveStatsOptions): LiveStatsController {
27
- const {
28
- maxCommentsInput,
29
- linksStat,
30
- postsStat,
31
- commentsStat,
32
- likesStat,
33
- likesSkipStat,
34
- repliesStat,
35
- streamStat,
36
- shardStatsList,
37
- likedList,
38
- repliedList,
39
- } = opts;
40
-
41
- type ShardProgress = {
42
- linksCollected: number;
43
- linksTarget: number;
44
- postsProcessed: number;
45
- commentsCollected: number;
46
- likesTotal: number;
47
- likesSkippedTotal: number;
48
- likeDedupSkipped: number;
49
- likeAlreadySkipped: number;
50
- likeGateBlocked: number;
51
- repliesTotal: number;
52
- phase: string;
53
- action: string;
54
- status: 'idle' | 'running' | 'error' | 'completed';
55
- anomaly: string;
56
- updatedAt: number;
57
- };
58
-
59
- const parentDir = (inputPath: string) => {
60
- const p = String(inputPath || '');
61
- const slash = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
62
- return slash > 0 ? p.slice(0, slash) : '';
63
- };
64
-
65
- const liveStats = {
66
- linksCollected: 0,
67
- linksTarget: 0,
68
- postsProcessed: 0,
69
- currentCommentsCollected: 0,
70
- currentCommentsTarget: '不限',
71
- likesTotal: 0,
72
- likesSkippedTotal: 0,
73
- likeDedupSkipped: 0,
74
- likeAlreadySkipped: 0,
75
- likeGateBlocked: 0,
76
- repliesTotal: 0,
77
- eventsPath: '',
78
- noteId: '',
79
- };
80
-
81
- const activeRunIds = new Set<string>();
82
- const runToShard = new Map<string, string>();
83
- const parentRunCurrentShard = new Map<string, string>();
84
- const shardStats = new Map<string, ShardProgress>();
85
- const expectedShardProfiles = new Set<string>();
86
- let activeRunId = '';
87
- let hasStateFeed = false;
88
- let stateUnsubscribe: (() => void) | null = null;
89
- const likedNotes = new Map<string, { count: number; path: string }>();
90
- const repliedNotes = new Map<string, { count: number; path: string }>();
91
-
92
- const renderActionList = (
93
- container: HTMLDivElement,
94
- items: Map<string, { count: number; path: string }>,
95
- emptyText: string,
96
- ) => {
97
- container.innerHTML = '';
98
- if (items.size === 0) {
99
- container.appendChild(createEl('div', { className: 'muted' }, [emptyText]));
100
- return;
101
- }
102
- Array.from(items.entries()).forEach(([noteId, item]) => {
103
- const row = createEl('div', {
104
- className: 'row',
105
- style: 'justify-content:space-between; gap:8px; border:1px solid #1f2937; border-radius:6px; padding:6px 8px;',
106
- });
107
- row.appendChild(createEl('span', { className: 'muted' }, [`${noteId} × ${item.count}`]));
108
- const openBtn = createEl('button', { type: 'button', className: 'secondary', style: 'padding:2px 8px;' }, ['打开目录']) as HTMLButtonElement;
109
- openBtn.onclick = async () => {
110
- const targetPath = String(item.path || '').trim();
111
- if (!targetPath) {
112
- alert('该帖子暂无目录信息');
113
- return;
114
- }
115
- const ret = await window.api.osOpenPath(targetPath);
116
- if (!ret?.ok) alert(`打开失败:${ret?.error || 'unknown_error'}`);
117
- };
118
- row.appendChild(openBtn);
119
- container.appendChild(row);
120
- });
121
- };
122
-
123
- const ensureShardStats = (shardKey: string) => {
124
- const key = String(shardKey || '').trim();
125
- if (!key) return null;
126
- if (!shardStats.has(key)) {
127
- shardStats.set(key, {
128
- linksCollected: 0,
129
- linksTarget: 0,
130
- postsProcessed: 0,
131
- commentsCollected: 0,
132
- likesTotal: 0,
133
- likesSkippedTotal: 0,
134
- likeDedupSkipped: 0,
135
- likeAlreadySkipped: 0,
136
- likeGateBlocked: 0,
137
- repliesTotal: 0,
138
- phase: '',
139
- action: '',
140
- status: 'idle',
141
- anomaly: '',
142
- updatedAt: 0,
143
- });
144
- }
145
- return shardStats.get(key) || null;
146
- };
147
-
148
- const aggregateShardStats = (): ShardProgress => {
149
- if (shardStats.size === 0) {
150
- return {
151
- linksCollected: liveStats.linksCollected,
152
- linksTarget: liveStats.linksTarget,
153
- postsProcessed: liveStats.postsProcessed,
154
- commentsCollected: liveStats.currentCommentsCollected,
155
- likesTotal: liveStats.likesTotal,
156
- likesSkippedTotal: liveStats.likesSkippedTotal,
157
- likeDedupSkipped: liveStats.likeDedupSkipped,
158
- likeAlreadySkipped: liveStats.likeAlreadySkipped,
159
- likeGateBlocked: liveStats.likeGateBlocked,
160
- repliesTotal: liveStats.repliesTotal,
161
- phase: '',
162
- action: '',
163
- status: 'idle',
164
- anomaly: '',
165
- updatedAt: 0,
166
- };
167
- }
168
- const merged: ShardProgress = {
169
- linksCollected: 0,
170
- linksTarget: 0,
171
- postsProcessed: 0,
172
- commentsCollected: 0,
173
- likesTotal: 0,
174
- likesSkippedTotal: 0,
175
- likeDedupSkipped: 0,
176
- likeAlreadySkipped: 0,
177
- likeGateBlocked: 0,
178
- repliesTotal: 0,
179
- phase: '',
180
- action: '',
181
- status: 'idle',
182
- anomaly: '',
183
- updatedAt: 0,
184
- };
185
- shardStats.forEach((item) => {
186
- merged.linksCollected += item.linksCollected;
187
- merged.linksTarget += item.linksTarget;
188
- merged.postsProcessed += item.postsProcessed;
189
- merged.commentsCollected += item.commentsCollected;
190
- merged.likesTotal += item.likesTotal;
191
- merged.likesSkippedTotal += item.likesSkippedTotal;
192
- merged.likeDedupSkipped += item.likeDedupSkipped;
193
- merged.likeAlreadySkipped += item.likeAlreadySkipped;
194
- merged.likeGateBlocked += item.likeGateBlocked;
195
- merged.repliesTotal += item.repliesTotal;
196
- });
197
- if (merged.linksTarget <= 0) merged.linksTarget = liveStats.linksTarget;
198
- return merged;
199
- };
200
-
201
- const formatLineText = (input: string, max = 120) => {
202
- const normalized = String(input || '').replace(/\s+/g, ' ').trim();
203
- if (!normalized) return '';
204
- if (normalized.length <= max) return normalized;
205
- return `${normalized.slice(0, Math.max(1, max - 1))}…`;
206
- };
207
-
208
- const statusLabel = (item: ShardProgress) => {
209
- if (item.status === 'error') return '异常';
210
- if (item.status === 'completed') return '完成';
211
- if (item.status === 'running') return '运行中';
212
- return '待机';
213
- };
214
-
215
- const renderShardStats = () => {
216
- shardStatsList.innerHTML = '';
217
- if (expectedShardProfiles.size === 0 && shardStats.size === 0) {
218
- shardStatsList.appendChild(createEl('div', { className: 'muted' }, ['单账号模式:未检测到分片']));
219
- return;
220
- }
221
-
222
- const merged = aggregateShardStats();
223
- const profileList = expectedShardProfiles.size > 0
224
- ? Array.from(expectedShardProfiles.values())
225
- : Array.from(shardStats.keys());
226
- const shardItems = profileList
227
- .map((profileId) => ensureShardStats(profileId))
228
- .filter((item): item is ShardProgress => Boolean(item));
229
- const runningCount = shardItems.filter((item) => item.status === 'running').length;
230
- const errorCount = shardItems.filter((item) => item.status === 'error').length;
231
- const activePhases = Array.from(
232
- new Set(
233
- shardItems
234
- .map((item) => String(item.phase || '').trim())
235
- .filter(Boolean),
236
- ),
237
- ).slice(0, 3);
238
- const mergedRow = createEl('div', {
239
- style: 'border:1px solid #1f2937; border-radius:6px; padding:6px 8px; background:#0b1220; font-size:12px;',
240
- });
241
- mergedRow.appendChild(createEl('div', { style: 'font-weight:600; margin-bottom:2px;' }, ['合并总览']));
242
- mergedRow.appendChild(
243
- createEl('div', { className: 'muted' }, [
244
- `链接 ${merged.linksCollected}/${merged.linksTarget || 0} · 帖子 ${merged.postsProcessed} · 评论 ${merged.commentsCollected} · 点赞 ${merged.likesTotal} · 跳过赞 ${merged.likesSkippedTotal} · 回复 ${merged.repliesTotal}`,
245
- ]),
246
- );
247
- mergedRow.appendChild(
248
- createEl('div', { className: 'muted' }, [
249
- `跳过明细:去重 ${merged.likeDedupSkipped} · 已赞 ${merged.likeAlreadySkipped} · 限流 ${merged.likeGateBlocked}`,
250
- ]),
251
- );
252
- mergedRow.appendChild(
253
- createEl('div', { className: 'muted' }, [
254
- `运行账号 ${shardItems.length} · 运行中 ${runningCount} · 异常 ${errorCount} · 阶段 ${activePhases.join(' / ') || '等待中'}`,
255
- ]),
256
- );
257
- shardStatsList.appendChild(mergedRow);
258
-
259
- profileList.forEach((profileId) => {
260
- const item = ensureShardStats(profileId);
261
- if (!item) return;
262
- const row = createEl('div', {
263
- style: 'border:1px solid #1f2937; border-radius:6px; padding:6px 8px; font-size:12px;',
264
- });
265
- row.appendChild(createEl('div', { style: 'font-weight:600; margin-bottom:2px;' }, [profileId]));
266
- row.appendChild(
267
- createEl('div', { className: 'muted' }, [
268
- `链接 ${item.linksCollected}/${item.linksTarget || 0} · 帖子 ${item.postsProcessed} · 评论 ${item.commentsCollected} · 点赞 ${item.likesTotal} · 跳过赞 ${item.likesSkippedTotal} · 回复 ${item.repliesTotal}`,
269
- ]),
270
- );
271
- row.appendChild(
272
- createEl('div', { className: 'muted' }, [
273
- `跳过明细:去重 ${item.likeDedupSkipped} · 已赞 ${item.likeAlreadySkipped} · 限流 ${item.likeGateBlocked}`,
274
- ]),
275
- );
276
- row.appendChild(
277
- createEl('div', { className: 'muted' }, [
278
- `阶段 ${item.phase || '未知'} · 状态 ${statusLabel(item)} · 动作 ${item.action || '等待日志'}`,
279
- ]),
280
- );
281
- if (item.anomaly) {
282
- row.appendChild(
283
- createEl('div', { className: 'muted', style: 'color:#fca5a5;' }, [
284
- `异常:${item.anomaly}`,
285
- ]),
286
- );
287
- }
288
- shardStatsList.appendChild(row);
289
- });
290
- };
291
-
292
- const renderLiveStats = () => {
293
- const merged = aggregateShardStats();
294
- linksStat.textContent = `链接采集:${merged.linksCollected}/${merged.linksTarget || liveStats.linksTarget || 0}`;
295
- postsStat.textContent = `帖子处理:${merged.postsProcessed}`;
296
- commentsStat.textContent = `当前帖子评论:${liveStats.currentCommentsCollected}/${liveStats.currentCommentsTarget}`;
297
- likesStat.textContent = `总点赞:${merged.likesTotal}`;
298
- likesSkipStat.textContent = `点赞跳过:${merged.likesSkippedTotal}(去重${merged.likeDedupSkipped}/已赞${merged.likeAlreadySkipped}/限流${merged.likeGateBlocked})`;
299
- repliesStat.textContent = `总回复:${merged.repliesTotal}`;
300
-
301
- const sourceHint = activeRunIds.size > 0 ? `run=${activeRunIds.size}` : 'run=0';
302
- const shardHint = `shard=${Math.max(expectedShardProfiles.size, shardStats.size, 1)}`;
303
- const eventsHint = liveStats.eventsPath ? `, events=${liveStats.eventsPath}` : '';
304
- const feed = hasStateFeed ? 'state+cmd-event' : 'cmd-event';
305
- streamStat.textContent = `数据源:${feed}(${sourceHint}, ${shardHint}${eventsHint})`;
306
-
307
- renderShardStats();
308
- renderActionList(likedList, likedNotes, '暂无点赞命中');
309
- renderActionList(repliedList, repliedNotes, '暂无回复命中');
310
- };
311
-
312
- const extractEventsPath = (line: string) => {
313
- const quoted = line.match(/(?:events=|eventsPath\s*[:=]\s*)(?:"([^"]+run-events(?:\.[A-Za-z0-9_-]+)?\.jsonl)"|'([^']+run-events(?:\.[A-Za-z0-9_-]+)?\.jsonl)')/i);
314
- if (quoted?.[1] || quoted?.[2]) return String(quoted[1] || quoted[2] || '').trim();
315
-
316
- const plain = line.match(/(?:events=|eventsPath\s*[:=]\s*)([^\s]+run-events(?:\.[A-Za-z0-9_-]+)?\.jsonl)/i);
317
- if (!plain?.[1]) return '';
318
- return String(plain[1]).trim();
319
- };
320
-
321
- const parseStdoutForEvents = (line: string) => {
322
- const rawText = String(line || '');
323
- if (!rawText.trim()) return;
324
-
325
- const prefixedRid = rawText.match(/^\[rid:([A-Za-z0-9_-]+)\]/i);
326
- const prefixedRunId = String(prefixedRid?.[1] || '').trim();
327
- if (prefixedRunId) activeRunIds.add(prefixedRunId);
328
-
329
- const runIdMatch = rawText.match(/runId\s*[:=]\s*([A-Za-z0-9_-]+)/);
330
- const rawRunId = String(runIdMatch?.[1] || '').trim();
331
- if (rawRunId) activeRunIds.add(rawRunId);
332
-
333
- const shardHintMatch = rawText.match(/\[shard-hint\]\s*profiles=([A-Za-z0-9_,-]+)/i);
334
- if (shardHintMatch?.[1]) {
335
- String(shardHintMatch[1])
336
- .split(',')
337
- .map((x) => x.trim())
338
- .filter(Boolean)
339
- .forEach((profileId) => {
340
- expectedShardProfiles.add(profileId);
341
- ensureShardStats(profileId);
342
- if (prefixedRunId) {
343
- parentRunCurrentShard.set(prefixedRunId, profileId);
344
- runToShard.set(prefixedRunId, profileId);
345
- }
346
- });
347
- }
348
-
349
- const profileByLine = rawText.match(/(?:\bprofile\s*[=:]\s*|Profile\s*[::]\s*)([A-Za-z0-9_-]+)/);
350
- const profileId = String(profileByLine?.[1] || '').trim();
351
- if (profileId) {
352
- expectedShardProfiles.add(profileId);
353
- ensureShardStats(profileId);
354
- if (prefixedRunId) {
355
- parentRunCurrentShard.set(prefixedRunId, profileId);
356
- runToShard.set(prefixedRunId, profileId);
357
- }
358
- if (rawRunId) runToShard.set(rawRunId, profileId);
359
- }
360
-
361
- const loggerChild = rawText.match(/\[Logger\]\s+runId=([A-Za-z0-9_-]+)/);
362
- const childRunId = String(loggerChild?.[1] || '').trim();
363
- if (childRunId && prefixedRunId) {
364
- const currentShard = parentRunCurrentShard.get(prefixedRunId) || runToShard.get(prefixedRunId) || '';
365
- if (currentShard) {
366
- runToShard.set(childRunId, currentShard);
367
- ensureShardStats(currentShard);
368
- }
369
- }
370
-
371
- const eventsPath = extractEventsPath(rawText);
372
- if (eventsPath) liveStats.eventsPath = eventsPath;
373
-
374
- const currentShard =
375
- (rawRunId ? runToShard.get(rawRunId) : '') ||
376
- (prefixedRunId ? runToShard.get(prefixedRunId) : '') ||
377
- (prefixedRunId ? parentRunCurrentShard.get(prefixedRunId) : '') ||
378
- (expectedShardProfiles.size === 1 ? Array.from(expectedShardProfiles)[0] : '');
379
- const shardStat = currentShard ? ensureShardStats(currentShard) : null;
380
-
381
- const text = rawText
382
- .replace(/^\[rid:[^\]]+\]\s*/i, '')
383
- .replace(/^\[(?:stdout|stderr)\]\s*/i, '')
384
- .trim();
385
- const isStderr = /\[stderr\]/i.test(rawText);
386
-
387
- const phaseTag = text.match(/^\[([A-Za-z0-9:_-]+)\]/);
388
- const normalizedPhase = String(phaseTag?.[1] || '').trim();
389
- const actionText = formatLineText(text.replace(/^\[[^\]]+\]\s*/, ''), 140);
390
- const exitCodeMatch = text.match(/^\[exit\]\s*code=([^\s]+)/i);
391
- const looksLikeError =
392
- isStderr ||
393
- /(?:^|\s)(?:error|exception|err_|failed|failure|失败|异常|❌)(?:\s|:|$)/i.test(text) ||
394
- (exitCodeMatch?.[1] && String(exitCodeMatch[1]).trim() !== '0');
395
- if (shardStat) {
396
- if (normalizedPhase) shardStat.phase = normalizedPhase;
397
- if (actionText) shardStat.action = actionText;
398
- if (exitCodeMatch?.[1]) {
399
- shardStat.status = String(exitCodeMatch[1]).trim() === '0' ? 'completed' : 'error';
400
- } else {
401
- shardStat.status = looksLikeError ? 'error' : 'running';
402
- }
403
- shardStat.anomaly = looksLikeError ? formatLineText(actionText || text, 160) : '';
404
- shardStat.updatedAt = Date.now();
405
- }
406
-
407
- const rigidGateBlocked = text.match(/\[Phase2Collect\]\s*Rigid gate blocked click index=(\d+):\s*(.+)$/i);
408
- if (rigidGateBlocked && shardStat) {
409
- const idx = Number(rigidGateBlocked[1] || -1);
410
- const reason = formatLineText(String(rigidGateBlocked[2] || '').trim(), 80) || 'unknown';
411
- shardStat.phase = 'Phase2Collect';
412
- shardStat.status = 'running';
413
- shardStat.action = `开帖点击被阻断,自动重试(index=${idx >= 0 ? idx : '?'})`;
414
- shardStat.anomaly = `阻断原因:${reason}`;
415
- shardStat.updatedAt = Date.now();
416
- }
417
-
418
- const postClickGateFailed = text.match(/\[Phase2Collect\]\s*Post-click gate FAILED:\s*explore=(\w+)\s*xsec=(\w+)/i);
419
- if (postClickGateFailed && shardStat) {
420
- const hasExplore = String(postClickGateFailed[1] || '').toLowerCase() === 'true';
421
- const hasXsec = String(postClickGateFailed[2] || '').toLowerCase() === 'true';
422
- shardStat.phase = 'Phase2Collect';
423
- shardStat.status = 'running';
424
- shardStat.action = '开帖后校验未通过,正在切换点击策略';
425
- shardStat.anomaly = `阻断原因:post-click gate failed (explore=${hasExplore} xsec=${hasXsec})`;
426
- shardStat.updatedAt = Date.now();
427
- }
428
-
429
- const clickDecision = text.match(/\[Phase2Collect\]\s*Click decision:\s*strategy=([a-z0-9:_-]+)\s+mode=([a-z0-9:_-]+)\s+focus=(\w+)\s+active=([^\s]+)/i);
430
- if (clickDecision && shardStat) {
431
- const strategy = String(clickDecision[1] || '').trim() || 'unknown';
432
- const mode = String(clickDecision[2] || '').trim() || 'unknown';
433
- const focus = String(clickDecision[3] || '').trim() || 'unknown';
434
- const active = formatLineText(String(clickDecision[4] || '').trim(), 80) || 'unknown';
435
- shardStat.phase = 'Phase2Collect';
436
- shardStat.status = 'running';
437
- shardStat.action = `Click decision: ${strategy} (${mode})`;
438
- shardStat.anomaly = `focus=${focus} active=${active}`;
439
- shardStat.updatedAt = Date.now();
440
- }
441
-
442
- const focusEnsure = text.match(/\[Phase2Collect\]\s*Focus ensure:\s*strategy=([a-z0-9:_-]+)\s+ok=(\w+)\s+beforeFocus=(\w+)\s+beforeActive=([^\s]+)\s+afterFocus=(\w+)\s+afterActive=([^\s]+)/i);
443
- if (focusEnsure && shardStat) {
444
- const strategy = String(focusEnsure[1] || '').trim() || 'unknown';
445
- const ok = String(focusEnsure[2] || '').trim() || 'unknown';
446
- const beforeFocus = String(focusEnsure[3] || '').trim() || 'unknown';
447
- const beforeActive = formatLineText(String(focusEnsure[4] || '').trim(), 80) || 'unknown';
448
- const afterFocus = String(focusEnsure[5] || '').trim() || 'unknown';
449
- const afterActive = formatLineText(String(focusEnsure[6] || '').trim(), 80) || 'unknown';
450
- shardStat.phase = 'Phase2Collect';
451
- shardStat.status = 'running';
452
- shardStat.action = `Focus ensure: ${strategy} ok=${ok}`;
453
- shardStat.anomaly = `before=${beforeFocus}/${beforeActive} after=${afterFocus}/${afterActive}`;
454
- shardStat.updatedAt = Date.now();
455
- }
456
-
457
- const clickStrategyFailed = text.match(/\[Phase2Collect\]\s*Click strategy failed:\s*strategy=([a-z_]+)\s+reason=(.+)$/i);
458
- if (clickStrategyFailed && shardStat) {
459
- const strategy = String(clickStrategyFailed[1] || '').trim() || 'unknown';
460
- const reason = formatLineText(String(clickStrategyFailed[2] || '').trim(), 120) || 'unknown';
461
- shardStat.phase = 'Phase2Collect';
462
- shardStat.status = 'running';
463
- shardStat.action = `点击未执行(${strategy})`;
464
- shardStat.anomaly = `阻断原因:click dispatch failed (${reason})`;
465
- shardStat.updatedAt = Date.now();
466
- }
467
-
468
- const clickStrategyNoOpen = text.match(/\[Phase2Collect\]\s*Click strategy no-open:\s*strategy=([a-z_]+)\s+url=(.+?)\s+waitedMs=(\d+)/i);
469
- if (clickStrategyNoOpen && shardStat) {
470
- const strategy = String(clickStrategyNoOpen[1] || '').trim() || 'unknown';
471
- const url = formatLineText(String(clickStrategyNoOpen[2] || '').trim(), 100) || 'n/a';
472
- const waitedMs = Number(clickStrategyNoOpen[3] || 0);
473
- shardStat.phase = 'Phase2Collect';
474
- shardStat.status = 'running';
475
- shardStat.action = `点击已发出但未开帖(${strategy},${waitedMs}ms)`;
476
- shardStat.anomaly = `阻断原因:no explore/xsec after click (url=${url})`;
477
- shardStat.updatedAt = Date.now();
478
- }
479
-
480
- const protocolFill = text.match(/\[Phase2Search\]\s*protocol fill:\s*selector="([^"]+)"\s*success=(\w+)(?:\s+error=(.+))?/i);
481
- if (protocolFill && shardStat) {
482
- const selector = formatLineText(String(protocolFill[1] || '').trim(), 80) || 'n/a';
483
- const ok = String(protocolFill[2] || '').trim() || 'unknown';
484
- const err = formatLineText(String(protocolFill[3] || '').trim(), 120) || '';
485
- shardStat.phase = 'Phase2Search';
486
- shardStat.status = ok === 'true' ? 'running' : 'error';
487
- shardStat.action = `Protocol fill (browser:fill)=${ok}`;
488
- shardStat.anomaly = err ? `selector=${selector} error=${err}` : `selector=${selector}`;
489
- shardStat.updatedAt = Date.now();
490
- }
491
-
492
- const protocolType = text.match(/\[Phase2Search\]\s*protocol input:\s*container_type\s*success=(\w+)(?:\s+error=(.+))?/i);
493
- if (protocolType && shardStat) {
494
- const ok = String(protocolType[1] || '').trim() || 'unknown';
495
- const err = formatLineText(String(protocolType[2] || '').trim(), 120) || '';
496
- shardStat.phase = 'Phase2Search';
497
- shardStat.status = ok === 'true' ? 'running' : 'error';
498
- shardStat.action = `Protocol input (container:type)=${ok}`;
499
- shardStat.anomaly = err ? `error=${err}` : '';
500
- shardStat.updatedAt = Date.now();
501
- }
502
-
503
- const phase2Fatal = text.match(/(?:❌\s*)?Phase\s*2\s*失败[::]\s*(.+)$/i);
504
- if (phase2Fatal && shardStat) {
505
- const reason = formatLineText(String(phase2Fatal[1] || '').trim(), 160);
506
- shardStat.phase = 'Phase2Collect';
507
- shardStat.status = 'error';
508
- shardStat.action = 'Phase2 终止';
509
- shardStat.anomaly = reason || 'Phase2 执行失败';
510
- shardStat.updatedAt = Date.now();
511
- }
512
-
513
- const likeGateMatch = text.match(/\[Phase3Interact\]\s*Like Gate:\s*(\d+)\s*\/\s*(\d+)\s*(✅|❌)?/i);
514
- if (likeGateMatch && shardStat) {
515
- const gateCurrent = Number(likeGateMatch[1] || 0);
516
- const gateLimit = Number(likeGateMatch[2] || 0);
517
- const gateOk = String(likeGateMatch[3] || '').includes('✅');
518
- shardStat.phase = 'Phase3Interact';
519
- shardStat.action = `Like Gate ${gateCurrent}/${gateLimit} ${gateOk ? '许可通过' : '受限'}`;
520
- shardStat.status = gateOk ? 'running' : 'error';
521
- shardStat.anomaly = gateOk ? '' : `点赞速率限制 ${gateCurrent}/${gateLimit}`;
522
- shardStat.updatedAt = Date.now();
523
- }
524
-
525
- const phase3RoundMatch = text.match(/\[Phase3Interact\]\s*round=(\d+)/i);
526
- if (phase3RoundMatch) {
527
- const round = Number(phase3RoundMatch[1] || 0);
528
- const readToken = (key: string) => {
529
- const m = text.match(new RegExp(`${key}=(\\d+)`, 'i'));
530
- return Number(m?.[1] || 0);
531
- };
532
- const ruleHits = readToken('ruleHits');
533
- const gateBlocked = readToken('gateBlocked');
534
- const dedupSkipped = readToken('dedup');
535
- const alreadyLikedSkipped = readToken('alreadyLiked');
536
- const newLikes = readToken('newLikes');
537
- const likedTotalMatch = text.match(/likedTotal=(\d+)\s*\/\s*(\d+)/i);
538
- const likedTotal = Number(likedTotalMatch?.[1] || 0);
539
- const likedLimit = Number(likedTotalMatch?.[2] || 0);
540
- const endReasonMatch = text.match(/\bend=([a-z_]+)/i);
541
- const endReason = String(endReasonMatch?.[1] || '').trim();
542
- const roundSkipped = Math.max(0, dedupSkipped) + Math.max(0, alreadyLikedSkipped) + Math.max(0, gateBlocked);
543
- if (Number.isFinite(likedTotal)) {
544
- liveStats.likesTotal = Math.max(liveStats.likesTotal, likedTotal);
545
- }
546
- if (roundSkipped > 0) {
547
- liveStats.likesSkippedTotal += roundSkipped;
548
- liveStats.likeDedupSkipped += Math.max(0, dedupSkipped);
549
- liveStats.likeAlreadySkipped += Math.max(0, alreadyLikedSkipped);
550
- liveStats.likeGateBlocked += Math.max(0, gateBlocked);
551
- }
552
- if (shardStat) {
553
- shardStat.likesTotal = Math.max(shardStat.likesTotal, likedTotal);
554
- if (roundSkipped > 0) {
555
- shardStat.likesSkippedTotal += roundSkipped;
556
- shardStat.likeDedupSkipped += Math.max(0, dedupSkipped);
557
- shardStat.likeAlreadySkipped += Math.max(0, alreadyLikedSkipped);
558
- shardStat.likeGateBlocked += Math.max(0, gateBlocked);
559
- }
560
- shardStat.phase = 'Phase3Interact';
561
- shardStat.status = gateBlocked > 0 ? 'error' : 'running';
562
- shardStat.anomaly = gateBlocked > 0 ? `点赞限流阻塞 ${gateBlocked}` : '';
563
- shardStat.action = `Round ${round}: 命中${ruleHits} 新增赞${newLikes} 跳过${roundSkipped}(去重${dedupSkipped}/已赞${alreadyLikedSkipped}/限流${gateBlocked}) 累计${likedTotal}/${likedLimit}${endReason ? ` ${endReason}` : ''}`;
564
- shardStat.updatedAt = Date.now();
565
- }
566
- }
567
-
568
- const phase2ProgressMatch = text.match(/\[Phase2Collect(?:Links)?\][^\d]*(\d+)\s*\/\s*(\d+)/);
569
- if (phase2ProgressMatch) {
570
- const collected = Number(phase2ProgressMatch[1] || liveStats.linksCollected || 0);
571
- const target = Number(phase2ProgressMatch[2] || liveStats.linksTarget || 0);
572
- liveStats.linksCollected = Math.max(liveStats.linksCollected, collected);
573
- liveStats.linksTarget = Math.max(liveStats.linksTarget, target);
574
- if (shardStat) {
575
- shardStat.linksCollected = Math.max(shardStat.linksCollected, collected);
576
- shardStat.linksTarget = Math.max(shardStat.linksTarget, target);
577
- }
578
- }
579
-
580
- const linksReadyMatch = text.match(/\[Links\][^\d]*(\d+)\s*\/\s*(\d+)/);
581
- if (linksReadyMatch) {
582
- const collected = Number(linksReadyMatch[1] || 0);
583
- const target = Number(linksReadyMatch[2] || 0);
584
- liveStats.linksCollected = Math.max(liveStats.linksCollected, collected);
585
- liveStats.linksTarget = Math.max(liveStats.linksTarget, target);
586
- if (shardStat) {
587
- shardStat.linksCollected = Math.max(shardStat.linksCollected, collected);
588
- shardStat.linksTarget = Math.max(shardStat.linksTarget, target);
589
- }
590
- }
591
-
592
- const noteProgressMatch = text.match(/^\[(\d+)\s*\/\s*(\d+)\]\s+slot-\d+\(tab-\d+\)\s+note=([A-Za-z0-9]+)/);
593
- if (noteProgressMatch) {
594
- const processed = Number(noteProgressMatch[1] || liveStats.postsProcessed || 0);
595
- const target = Number(noteProgressMatch[2] || liveStats.linksTarget || 0);
596
- liveStats.postsProcessed = processed;
597
- liveStats.linksTarget = Math.max(liveStats.linksTarget, target);
598
- liveStats.noteId = String(noteProgressMatch[3] || '').trim();
599
- liveStats.currentCommentsCollected = 0;
600
- liveStats.currentCommentsTarget = Number(maxCommentsInput.value || 0) > 0
601
- ? String(Math.floor(Number(maxCommentsInput.value || 0)))
602
- : '不限';
603
- if (shardStat) {
604
- shardStat.postsProcessed = Math.max(shardStat.postsProcessed, processed);
605
- shardStat.linksTarget = Math.max(shardStat.linksTarget, target);
606
- }
607
- }
608
-
609
- const postsSummary = text.match(/-\s*处理帖子[::]\s*(\d+)/);
610
- if (postsSummary) {
611
- const processed = Number(postsSummary[1] || liveStats.postsProcessed || 0);
612
- liveStats.postsProcessed = processed;
613
- if (shardStat) shardStat.postsProcessed = Math.max(shardStat.postsProcessed, processed);
614
- }
615
-
616
- const commentsSummary = text.match(/-\s*(?:评论总量|comments?)[::]\s*(\d+)/i);
617
- if (commentsSummary) {
618
- const comments = Number(commentsSummary[1] || liveStats.currentCommentsCollected || 0);
619
- liveStats.currentCommentsCollected = comments;
620
- if (shardStat) shardStat.commentsCollected = Math.max(shardStat.commentsCollected, comments);
621
- }
622
-
623
- const likesSummary = text.match(/-\s*(?:点赞总量|likes?)[::]\s*(\d+)/i);
624
- if (likesSummary) {
625
- const likes = Number(likesSummary[1] || liveStats.likesTotal || 0);
626
- liveStats.likesTotal = likes;
627
- if (shardStat) shardStat.likesTotal = Math.max(shardStat.likesTotal, likes);
628
- }
629
-
630
- const repliesSummary = text.match(/-\s*(?:回复总量|replies?)[::]\s*(\d+)/i);
631
- if (repliesSummary) {
632
- const replies = Number(repliesSummary[1] || liveStats.repliesTotal || 0);
633
- liveStats.repliesTotal = replies;
634
- if (shardStat) shardStat.repliesTotal = Math.max(shardStat.repliesTotal, replies);
635
- }
636
-
637
- const donePath = text.match(/likeEvidenceDir\s*[:=]\s*(?:"([^"]+)"|'([^']+)'|([^\s]+))/);
638
- if ((donePath?.[1] || donePath?.[2] || donePath?.[3]) && liveStats.noteId) {
639
- const p = String(donePath[1] || donePath[2] || donePath[3] || '').trim();
640
- likedNotes.set(liveStats.noteId, {
641
- count: Math.max(1, liveStats.likesTotal),
642
- path: parentDir(p) || p,
643
- });
644
- }
645
-
646
- renderLiveStats();
647
- };
648
-
649
- const applyStatePatch = (patch: any, runId?: string) => {
650
- if (!patch || typeof patch !== 'object') return;
651
- const rid = String(runId || '').trim();
652
- const shardKey = (rid ? runToShard.get(rid) : '') || (expectedShardProfiles.size === 1 ? Array.from(expectedShardProfiles)[0] : '');
653
- const shardStat = shardKey ? ensureShardStats(shardKey) : null;
654
- const progress =
655
- patch.progress && typeof patch.progress === 'object'
656
- ? patch.progress
657
- : (('current' in patch) || ('processed' in patch) || ('total' in patch) || ('percent' in patch))
658
- ? patch
659
- : null;
660
- if (progress) {
661
- const processed = Number(progress.processed ?? progress.current);
662
- const total = Number(progress.total);
663
- if (Number.isFinite(processed)) liveStats.linksCollected = Math.max(0, Math.floor(processed));
664
- if (Number.isFinite(total)) liveStats.linksTarget = Math.max(liveStats.linksTarget, Math.floor(total));
665
- if (shardStat) {
666
- if (Number.isFinite(processed)) shardStat.linksCollected = Math.max(shardStat.linksCollected, Math.floor(processed));
667
- if (Number.isFinite(total)) shardStat.linksTarget = Math.max(shardStat.linksTarget, Math.floor(total));
668
- }
669
- }
670
- const stats =
671
- patch.stats && typeof patch.stats === 'object'
672
- ? patch.stats
673
- : (('notesProcessed' in patch) || ('commentsCollected' in patch) || ('likesPerformed' in patch) || ('repliesGenerated' in patch))
674
- ? patch
675
- : null;
676
- if (stats) {
677
- const notes = Number(stats.notesProcessed);
678
- const comments = Number(stats.commentsCollected);
679
- const likes = Number(stats.likesPerformed);
680
- const likesSkipped = Number((stats as any).likesSkippedTotal ?? (stats as any).likesSkipped ?? (stats as any).likeSkipped);
681
- const likeDedupSkipped = Number((stats as any).likeDedupSkipped ?? (stats as any).dedupSkipped);
682
- const likeAlreadySkipped = Number((stats as any).likeAlreadySkipped ?? (stats as any).alreadyLikedSkipped);
683
- const likeGateBlocked = Number((stats as any).likeGateBlocked ?? (stats as any).gateBlocked);
684
- const replies = Number(stats.repliesGenerated);
685
- if (Number.isFinite(notes)) liveStats.postsProcessed = Math.max(liveStats.postsProcessed, Math.floor(notes));
686
- if (Number.isFinite(comments)) liveStats.currentCommentsCollected = Math.max(liveStats.currentCommentsCollected, Math.floor(comments));
687
- if (Number.isFinite(likes)) liveStats.likesTotal = Math.max(liveStats.likesTotal, Math.floor(likes));
688
- if (Number.isFinite(likesSkipped)) liveStats.likesSkippedTotal = Math.max(liveStats.likesSkippedTotal, Math.floor(likesSkipped));
689
- if (Number.isFinite(likeDedupSkipped)) liveStats.likeDedupSkipped = Math.max(liveStats.likeDedupSkipped, Math.floor(likeDedupSkipped));
690
- if (Number.isFinite(likeAlreadySkipped)) liveStats.likeAlreadySkipped = Math.max(liveStats.likeAlreadySkipped, Math.floor(likeAlreadySkipped));
691
- if (Number.isFinite(likeGateBlocked)) liveStats.likeGateBlocked = Math.max(liveStats.likeGateBlocked, Math.floor(likeGateBlocked));
692
- if (Number.isFinite(replies)) liveStats.repliesTotal = Math.max(liveStats.repliesTotal, Math.floor(replies));
693
- if (shardStat) {
694
- if (Number.isFinite(notes)) shardStat.postsProcessed = Math.max(shardStat.postsProcessed, Math.floor(notes));
695
- if (Number.isFinite(comments)) shardStat.commentsCollected = Math.max(shardStat.commentsCollected, Math.floor(comments));
696
- if (Number.isFinite(likes)) shardStat.likesTotal = Math.max(shardStat.likesTotal, Math.floor(likes));
697
- if (Number.isFinite(likesSkipped)) shardStat.likesSkippedTotal = Math.max(shardStat.likesSkippedTotal, Math.floor(likesSkipped));
698
- if (Number.isFinite(likeDedupSkipped)) shardStat.likeDedupSkipped = Math.max(shardStat.likeDedupSkipped, Math.floor(likeDedupSkipped));
699
- if (Number.isFinite(likeAlreadySkipped)) shardStat.likeAlreadySkipped = Math.max(shardStat.likeAlreadySkipped, Math.floor(likeAlreadySkipped));
700
- if (Number.isFinite(likeGateBlocked)) shardStat.likeGateBlocked = Math.max(shardStat.likeGateBlocked, Math.floor(likeGateBlocked));
701
- if (Number.isFinite(replies)) shardStat.repliesTotal = Math.max(shardStat.repliesTotal, Math.floor(replies));
702
- }
703
- }
704
- if (shardStat) {
705
- const phase = String((patch as any)?.phase || '').trim();
706
- const status = String((patch as any)?.status || '').trim().toLowerCase();
707
- const errText =
708
- String((patch as any)?.lastError?.message || (patch as any)?.error || '').trim();
709
- if (phase) shardStat.phase = phase;
710
- if (status === 'failed' || status === 'error') {
711
- shardStat.status = 'error';
712
- shardStat.anomaly = formatLineText(errText || status, 160);
713
- } else if (status === 'completed' || status === 'done' || status === 'success') {
714
- shardStat.status = 'completed';
715
- shardStat.anomaly = '';
716
- } else if (progress || stats) {
717
- shardStat.status = 'running';
718
- if (!errText) shardStat.anomaly = '';
719
- }
720
- const action = String((patch as any)?.message || (patch as any)?.step || '').trim();
721
- if (action) shardStat.action = formatLineText(action, 140);
722
- if ((progress || stats || phase || status || action) && !shardStat.updatedAt) shardStat.updatedAt = Date.now();
723
- if (progress || stats) shardStat.updatedAt = Date.now();
724
- }
725
- hasStateFeed = true;
726
- renderLiveStats();
727
- };
728
-
729
- const setActiveRunId = (runId: string) => {
730
- const next = String(runId || '').trim();
731
- if (!next) return;
732
- activeRunId = next;
733
- activeRunIds.add(next);
734
- if (expectedShardProfiles.size === 1 && !runToShard.has(next)) {
735
- runToShard.set(next, Array.from(expectedShardProfiles)[0]);
736
- }
737
- if (typeof window.api?.stateGetTask === 'function') {
738
- void window.api.stateGetTask(next).then((task: any) => {
739
- applyStatePatch(task, next);
740
- }).catch(() => null);
741
- }
742
- renderLiveStats();
743
- };
744
-
745
- const setShardProfiles = (profiles: string[]) => {
746
- expectedShardProfiles.clear();
747
- (Array.isArray(profiles) ? profiles : [])
748
- .map((x) => String(x || '').trim())
749
- .filter(Boolean)
750
- .forEach((profileId) => {
751
- expectedShardProfiles.add(profileId);
752
- ensureShardStats(profileId);
753
- });
754
- if (expectedShardProfiles.size === 1 && activeRunId && !runToShard.has(activeRunId)) {
755
- runToShard.set(activeRunId, Array.from(expectedShardProfiles)[0]);
756
- }
757
- renderLiveStats();
758
- };
759
-
760
- const resetLiveStats = () => {
761
- liveStats.linksCollected = 0;
762
- liveStats.linksTarget = 0;
763
- liveStats.postsProcessed = 0;
764
- liveStats.currentCommentsCollected = 0;
765
- liveStats.currentCommentsTarget = Number(maxCommentsInput.value || 0) > 0
766
- ? String(Math.floor(Number(maxCommentsInput.value || 0)))
767
- : '不限';
768
- liveStats.likesTotal = 0;
769
- liveStats.likesSkippedTotal = 0;
770
- liveStats.likeDedupSkipped = 0;
771
- liveStats.likeAlreadySkipped = 0;
772
- liveStats.likeGateBlocked = 0;
773
- liveStats.repliesTotal = 0;
774
- liveStats.eventsPath = '';
775
- liveStats.noteId = '';
776
- activeRunId = '';
777
- hasStateFeed = false;
778
- activeRunIds.clear();
779
- runToShard.clear();
780
- parentRunCurrentShard.clear();
781
- shardStats.clear();
782
- expectedShardProfiles.clear();
783
- likedNotes.clear();
784
- repliedNotes.clear();
785
- renderLiveStats();
786
- };
787
-
788
- const setExpectedLinksTarget = (target: number) => {
789
- const normalized = Number.isFinite(target) ? Math.max(0, Math.floor(target)) : 0;
790
- if (normalized <= 0) return;
791
- liveStats.linksTarget = Math.max(liveStats.linksTarget, normalized);
792
- renderLiveStats();
793
- };
794
-
795
- maxCommentsInput.addEventListener('change', () => {
796
- liveStats.currentCommentsTarget = Number(maxCommentsInput.value || 0) > 0
797
- ? String(Math.floor(Number(maxCommentsInput.value || 0)))
798
- : '不限';
799
- renderLiveStats();
800
- });
801
-
802
- if (typeof window.api?.onStateUpdate === 'function') {
803
- stateUnsubscribe = window.api.onStateUpdate((update: any) => {
804
- const rid = String(update?.runId || '').trim();
805
- if (!rid) return;
806
- const t = String(update?.type || '').trim();
807
- const patch = update?.data || {};
808
- const looksProgress =
809
- t === 'progress' ||
810
- Number.isFinite(Number((patch as any)?.processed)) ||
811
- Number.isFinite(Number((patch as any)?.current)) ||
812
- Number.isFinite(Number((patch as any)?.total));
813
- const looksStats =
814
- t === 'stats' ||
815
- Number.isFinite(Number((patch as any)?.notesProcessed)) ||
816
- Number.isFinite(Number((patch as any)?.commentsCollected)) ||
817
- Number.isFinite(Number((patch as any)?.likesPerformed)) ||
818
- Number.isFinite(Number((patch as any)?.repliesGenerated));
819
- if (activeRunIds.size > 0 && !activeRunIds.has(rid) && !(looksProgress || looksStats)) return;
820
- if (!activeRunId) activeRunId = rid;
821
- activeRunIds.add(rid);
822
- if (t === 'progress') {
823
- applyStatePatch({ progress: patch }, rid);
824
- } else if (t === 'stats') {
825
- applyStatePatch({ stats: patch }, rid);
826
- } else {
827
- applyStatePatch(patch, rid);
828
- }
829
- });
830
- }
831
-
832
- const dispose = () => {
833
- if (typeof stateUnsubscribe === 'function') stateUnsubscribe();
834
- stateUnsubscribe = null;
835
- };
836
-
837
- resetLiveStats();
838
-
839
- return {
840
- resetLiveStats,
841
- setExpectedLinksTarget,
842
- setShardProfiles,
843
- parseStdoutForEvents,
844
- setActiveRunId,
845
- dispose,
846
- };
847
- }