@web-auto/webauto 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/apps/desktop-console/default-settings.json +1 -0
  2. package/apps/desktop-console/dist/main/index.mjs +1618 -0
  3. package/apps/desktop-console/{src → dist}/main/preload.mjs +10 -0
  4. package/apps/desktop-console/dist/renderer/index.js +3063 -0
  5. package/apps/desktop-console/entry/ui-console.mjs +299 -0
  6. package/apps/webauto/entry/account.mjs +356 -0
  7. package/apps/webauto/entry/lib/account-detect.mjs +160 -0
  8. package/apps/webauto/entry/lib/account-store.mjs +587 -0
  9. package/apps/webauto/entry/lib/profilepool.mjs +1 -1
  10. package/apps/webauto/entry/xhs-install.mjs +27 -3
  11. package/apps/webauto/entry/xhs-status.mjs +152 -0
  12. package/apps/webauto/entry/xhs-unified.mjs +595 -17
  13. package/bin/webauto.mjs +247 -12
  14. package/dist/apps/webauto/server.js +66 -0
  15. package/dist/modules/camo-backend/src/index.js +575 -0
  16. package/dist/modules/camo-backend/src/internal/BrowserSession.js +817 -0
  17. package/dist/modules/camo-backend/src/internal/ElementRegistry.js +61 -0
  18. package/dist/modules/camo-backend/src/internal/ProfileLock.js +85 -0
  19. package/dist/modules/camo-backend/src/internal/SessionManager.js +172 -0
  20. package/dist/modules/camo-backend/src/internal/container-matcher.js +852 -0
  21. package/dist/modules/camo-backend/src/internal/engine-manager.js +258 -0
  22. package/dist/modules/camo-backend/src/internal/fingerprint.js +203 -0
  23. package/dist/modules/camo-backend/src/internal/pageRuntime.js +29 -0
  24. package/dist/modules/camo-backend/src/internal/runtimeInjector.js +30 -0
  25. package/dist/modules/camo-backend/src/internal/state-bus.js +46 -0
  26. package/dist/modules/camo-backend/src/internal/storage-paths.js +36 -0
  27. package/dist/modules/camo-backend/src/internal/ws-server.js +1202 -0
  28. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +423 -0
  29. package/dist/modules/camo-runtime/src/utils/config.mjs +77 -0
  30. package/dist/modules/container-registry/src/index.js +184 -0
  31. package/dist/modules/logging/src/index.js +92 -0
  32. package/dist/modules/operations/src/builtin.js +27 -0
  33. package/dist/modules/operations/src/container-binding.js +75 -0
  34. package/dist/modules/operations/src/executor.js +146 -0
  35. package/dist/modules/operations/src/operations/click.js +167 -0
  36. package/dist/modules/operations/src/operations/extract.js +204 -0
  37. package/dist/modules/operations/src/operations/find-child.js +17 -0
  38. package/dist/modules/operations/src/operations/highlight.js +138 -0
  39. package/dist/modules/operations/src/operations/key.js +61 -0
  40. package/dist/modules/operations/src/operations/navigate.js +148 -0
  41. package/dist/modules/operations/src/operations/scroll.js +126 -0
  42. package/dist/modules/operations/src/operations/type.js +190 -0
  43. package/dist/modules/operations/src/queue.js +100 -0
  44. package/dist/modules/operations/src/registry.js +11 -0
  45. package/dist/modules/operations/src/system/mouse.js +33 -0
  46. package/dist/modules/state/src/atomic-json.js +33 -0
  47. package/dist/modules/workflow/blocks/AnchorVerificationBlock.js +71 -0
  48. package/dist/modules/workflow/blocks/BehaviorRandomizer.js +26 -0
  49. package/dist/modules/workflow/blocks/CallWorkflowBlock.js +38 -0
  50. package/dist/modules/workflow/blocks/CloseDetailBlock.js +209 -0
  51. package/dist/modules/workflow/blocks/CollectBatch.js +137 -0
  52. package/dist/modules/workflow/blocks/CollectCommentsBlock.js +415 -0
  53. package/dist/modules/workflow/blocks/CollectSearchListBlock.js +599 -0
  54. package/dist/modules/workflow/blocks/CollectWeiboPosts.js +229 -0
  55. package/dist/modules/workflow/blocks/DetectPageStateBlock.js +259 -0
  56. package/dist/modules/workflow/blocks/EnsureLoginBlock.js +162 -0
  57. package/dist/modules/workflow/blocks/EnsureSession.js +426 -0
  58. package/dist/modules/workflow/blocks/ErrorClassifier.js +164 -0
  59. package/dist/modules/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  60. package/dist/modules/workflow/blocks/ExpandCommentsBlock.js +1032 -0
  61. package/dist/modules/workflow/blocks/ExtractDetailBlock.js +310 -0
  62. package/dist/modules/workflow/blocks/ExtractPostFields.js +88 -0
  63. package/dist/modules/workflow/blocks/GenerateSmartReplyBlock.js +68 -0
  64. package/dist/modules/workflow/blocks/GoToSearchBlock.js +497 -0
  65. package/dist/modules/workflow/blocks/GracefulFallbackBlock.js +104 -0
  66. package/dist/modules/workflow/blocks/HighlightBlock.js +66 -0
  67. package/dist/modules/workflow/blocks/InitAutoScroll.js +65 -0
  68. package/dist/modules/workflow/blocks/LoadContainerDefinition.js +50 -0
  69. package/dist/modules/workflow/blocks/LoadContainerIndex.js +43 -0
  70. package/dist/modules/workflow/blocks/LocateAndGuardBlock.js +176 -0
  71. package/dist/modules/workflow/blocks/LoginRecoveryBlock.js +242 -0
  72. package/dist/modules/workflow/blocks/MatchContainers.js +64 -0
  73. package/dist/modules/workflow/blocks/MonitoringBlock.js +190 -0
  74. package/dist/modules/workflow/blocks/OpenDetailBlock.js +1240 -0
  75. package/dist/modules/workflow/blocks/OrganizeXhsNotesBlock.js +117 -0
  76. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +270 -0
  77. package/dist/modules/workflow/blocks/PickSinglePost.js +69 -0
  78. package/dist/modules/workflow/blocks/ProgressTracker.js +125 -0
  79. package/dist/modules/workflow/blocks/RecordFixtureBlock.js +44 -0
  80. package/dist/modules/workflow/blocks/RenderMarkdown.js +48 -0
  81. package/dist/modules/workflow/blocks/SaveFile.js +54 -0
  82. package/dist/modules/workflow/blocks/ScrollNextBatch.js +72 -0
  83. package/dist/modules/workflow/blocks/SessionHealthBlock.js +73 -0
  84. package/dist/modules/workflow/blocks/StartBrowserService.js +45 -0
  85. package/dist/modules/workflow/blocks/ValidateContainerDefinition.js +67 -0
  86. package/dist/modules/workflow/blocks/ValidateExtract.js +35 -0
  87. package/dist/modules/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  88. package/dist/modules/workflow/blocks/WaitStable.js +74 -0
  89. package/dist/modules/workflow/blocks/WarmupCommentsBlock.js +120 -0
  90. package/dist/modules/workflow/blocks/WorkflowExecutor.js +156 -0
  91. package/dist/modules/workflow/blocks/XiaohongshuCollectFromLinksBlock.js +1004 -0
  92. package/dist/modules/workflow/blocks/XiaohongshuCollectLinksBlock.js +1049 -0
  93. package/dist/modules/workflow/blocks/XiaohongshuFullCollectBlock.js +782 -0
  94. package/dist/modules/workflow/blocks/helpers/anchorVerify.js +198 -0
  95. package/dist/modules/workflow/blocks/helpers/asyncWorkQueue.js +53 -0
  96. package/dist/modules/workflow/blocks/helpers/commentScroller.js +334 -0
  97. package/dist/modules/workflow/blocks/helpers/commentSectionLocator.js +126 -0
  98. package/dist/modules/workflow/blocks/helpers/containerAnchors.js +301 -0
  99. package/dist/modules/workflow/blocks/helpers/debugArtifacts.js +6 -0
  100. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +29 -0
  101. package/dist/modules/workflow/blocks/helpers/expandCommentsController.js +53 -0
  102. package/dist/modules/workflow/blocks/helpers/expandCommentsExtractor.js +129 -0
  103. package/dist/modules/workflow/blocks/helpers/macosVisionOcrPlugin.js +116 -0
  104. package/dist/modules/workflow/blocks/helpers/mergeXhsMarkdown.js +109 -0
  105. package/dist/modules/workflow/blocks/helpers/openDetailController.js +56 -0
  106. package/dist/modules/workflow/blocks/helpers/openDetailTypes.js +7 -0
  107. package/dist/modules/workflow/blocks/helpers/openDetailViewport.js +474 -0
  108. package/dist/modules/workflow/blocks/helpers/openDetailWaiter.js +104 -0
  109. package/dist/modules/workflow/blocks/helpers/operationLogger.js +195 -0
  110. package/dist/modules/workflow/blocks/helpers/persistedNotes.js +107 -0
  111. package/dist/modules/workflow/blocks/helpers/replyExpander.js +260 -0
  112. package/dist/modules/workflow/blocks/helpers/scrollIntoView.js +138 -0
  113. package/dist/modules/workflow/blocks/helpers/searchExecutor.js +328 -0
  114. package/dist/modules/workflow/blocks/helpers/searchGate.js +46 -0
  115. package/dist/modules/workflow/blocks/helpers/searchPageState.js +164 -0
  116. package/dist/modules/workflow/blocks/helpers/searchResultWaiter.js +64 -0
  117. package/dist/modules/workflow/blocks/helpers/simpleAnchor.js +134 -0
  118. package/dist/modules/workflow/blocks/helpers/smartReply.js +40 -0
  119. package/dist/modules/workflow/blocks/helpers/systemInput.js +635 -0
  120. package/dist/modules/workflow/blocks/helpers/targetCountMode.js +9 -0
  121. package/dist/modules/workflow/blocks/helpers/xhsCliArgs.js +80 -0
  122. package/dist/modules/workflow/blocks/helpers/xhsCommentDom.js +805 -0
  123. package/dist/modules/workflow/blocks/helpers/xhsNoteOrganizer.js +140 -0
  124. package/dist/modules/workflow/blocks/restore/RestorePhaseBlock.js +204 -0
  125. package/dist/modules/workflow/config/workflowRegistry.js +32 -0
  126. package/dist/modules/workflow/definitions/batch-collect-workflow.js +63 -0
  127. package/dist/modules/workflow/definitions/scroll-extract-workflow.js +74 -0
  128. package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow-v2.js +81 -0
  129. package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow.js +57 -0
  130. package/dist/modules/workflow/definitions/xiaohongshu-full-collect-workflow-v3.js +68 -0
  131. package/dist/modules/workflow/definitions/xiaohongshu-note-collect.js +49 -0
  132. package/dist/modules/workflow/definitions/xiaohongshu-phase1-workflow-v3.js +30 -0
  133. package/dist/modules/workflow/definitions/xiaohongshu-phase2-links-workflow-v3.js +40 -0
  134. package/dist/modules/workflow/definitions/xiaohongshu-phase3-collect-workflow-v1.js +54 -0
  135. package/dist/modules/workflow/definitions/xiaohongshu-phase34-from-links-workflow-v3.js +25 -0
  136. package/dist/modules/workflow/src/WeiboEventDrivenWorkflowRunner.js +308 -0
  137. package/dist/modules/workflow/src/context.js +70 -0
  138. package/dist/modules/workflow/src/index.js +5 -0
  139. package/dist/modules/workflow/src/orchestrator.js +230 -0
  140. package/dist/modules/workflow/src/runner.js +55 -0
  141. package/dist/modules/workflow/src/runtime.js +70 -0
  142. package/dist/modules/workflow/workflows/WeiboFeedExtractionWorkflow.js +359 -0
  143. package/dist/modules/workflow/workflows/XiaohongshuLoginWorkflow.js +110 -0
  144. package/dist/modules/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  145. package/dist/modules/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  146. package/dist/modules/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  147. package/dist/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  148. package/dist/modules/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  149. package/dist/modules/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  150. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  151. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  152. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  153. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  154. package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  155. package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  156. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  157. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  158. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  159. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  160. package/dist/modules/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  161. package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  162. package/dist/modules/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  163. package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  164. package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  165. package/dist/modules/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  166. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +42 -0
  167. package/dist/modules/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  168. package/dist/modules/xiaohongshu/app/src/index.js +9 -0
  169. package/dist/modules/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  170. package/dist/modules/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  171. package/dist/services/controller/src/controller.js +1476 -0
  172. package/dist/services/controller/src/index.js +2 -0
  173. package/dist/services/controller/src/payload-normalizer.js +129 -0
  174. package/dist/services/shared/heartbeat.js +120 -0
  175. package/dist/services/shared/lib/errorHandler.js +2 -0
  176. package/dist/services/shared/serviceProcessLogger.js +139 -0
  177. package/dist/services/unified-api/RemoteBrowserSession.js +176 -0
  178. package/dist/services/unified-api/RemoteSessionManager.js +148 -0
  179. package/dist/services/unified-api/container-operations-handler.js +115 -0
  180. package/dist/services/unified-api/server.js +652 -0
  181. package/dist/services/unified-api/state-registry.js +274 -0
  182. package/dist/services/unified-api/task-persistence.js +66 -0
  183. package/dist/services/unified-api/task-state.js +130 -0
  184. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +12 -5
  185. package/modules/xiaohongshu/app/pnpm-lock.yaml +24 -0
  186. package/package.json +37 -9
  187. package/.beads/README.md +0 -81
  188. package/.beads/config.yaml +0 -67
  189. package/.beads/interactions.jsonl +0 -0
  190. package/.beads/issues.jsonl +0 -180
  191. package/.beads/metadata.json +0 -4
  192. package/.claude/settings.local.json +0 -10
  193. package/.github/workflows/ci.yml +0 -55
  194. package/AGENTS.md +0 -253
  195. package/apps/desktop-console/README.md +0 -27
  196. package/apps/desktop-console/package-lock.json +0 -897
  197. package/apps/desktop-console/package.json +0 -20
  198. package/apps/desktop-console/scripts/build-and-install.mjs +0 -19
  199. package/apps/desktop-console/scripts/build.mjs +0 -45
  200. package/apps/desktop-console/scripts/test-preload.mjs +0 -13
  201. package/apps/desktop-console/src/main/config.mts +0 -26
  202. package/apps/desktop-console/src/main/core-daemon-manager.mts +0 -131
  203. package/apps/desktop-console/src/main/desktop-settings.mts +0 -267
  204. package/apps/desktop-console/src/main/heartbeat-watchdog.mts +0 -50
  205. package/apps/desktop-console/src/main/heartbeat-watchdog.test.mts +0 -68
  206. package/apps/desktop-console/src/main/index-streaming.test.mts +0 -20
  207. package/apps/desktop-console/src/main/index.mts +0 -980
  208. package/apps/desktop-console/src/main/profile-store.mts +0 -239
  209. package/apps/desktop-console/src/main/profile-store.test.mts +0 -54
  210. package/apps/desktop-console/src/main/state-bridge.mts +0 -114
  211. package/apps/desktop-console/src/main/task-state-types.ts +0 -32
  212. package/apps/desktop-console/src/renderer/hooks/use-task-state.mts +0 -120
  213. package/apps/desktop-console/src/renderer/index.mts +0 -133
  214. package/apps/desktop-console/src/renderer/index.test.mts +0 -34
  215. package/apps/desktop-console/src/renderer/path-helpers.mts +0 -46
  216. package/apps/desktop-console/src/renderer/path-helpers.test.mts +0 -14
  217. package/apps/desktop-console/src/renderer/tabs/debug.mts +0 -48
  218. package/apps/desktop-console/src/renderer/tabs/debug.test.mts +0 -22
  219. package/apps/desktop-console/src/renderer/tabs/logs.mts +0 -421
  220. package/apps/desktop-console/src/renderer/tabs/logs.test.mts +0 -27
  221. package/apps/desktop-console/src/renderer/tabs/preflight.mts +0 -486
  222. package/apps/desktop-console/src/renderer/tabs/preflight.test.mts +0 -33
  223. package/apps/desktop-console/src/renderer/tabs/profile-pool.mts +0 -213
  224. package/apps/desktop-console/src/renderer/tabs/results.mts +0 -171
  225. package/apps/desktop-console/src/renderer/tabs/run.test.mts +0 -63
  226. package/apps/desktop-console/src/renderer/tabs/runtime.mts +0 -151
  227. package/apps/desktop-console/src/renderer/tabs/settings.mts +0 -146
  228. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/account-flow.mts +0 -486
  229. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/guide-browser-check.mts +0 -56
  230. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/helpers.mts +0 -262
  231. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/layout-block.mts +0 -430
  232. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/live-stats.mts +0 -847
  233. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/run-flow.mts +0 -443
  234. package/apps/desktop-console/src/renderer/tabs/xiaohongshu-state.mts +0 -425
  235. package/apps/desktop-console/src/renderer/tabs/xiaohongshu.mts +0 -497
  236. package/apps/desktop-console/src/renderer/tabs/xiaohongshu.test.mts +0 -291
  237. package/apps/desktop-console/src/renderer/ui-components.mts +0 -31
  238. package/docs/README_camoufox_chinese.md +0 -141
  239. package/docs/USAGE_V3.md +0 -163
  240. package/docs/arch/OCR_MACOS_PLUGIN.md +0 -39
  241. package/docs/arch/PORTS.md +0 -40
  242. package/docs/arch/REGRESSION_CHECKLIST.md +0 -121
  243. package/docs/arch/SEARCH_GATE.md +0 -224
  244. package/docs/arch/VIEWPORT_SAFETY.md +0 -182
  245. package/docs/arch/XIAOHONGSHU_OFFLINE_MOCK_DESIGN.md +0 -267
  246. package/docs/xiaohongshu-container-driven-summary.md +0 -221
  247. package/docs/xiaohongshu-full-collect-runbook.md +0 -134
  248. package/docs/xiaohongshu-next-steps.md +0 -228
  249. package/docs/xiaohongshu-quickstart.md +0 -73
  250. package/docs/xiaohongshu-workflow-summary.md +0 -227
  251. package/modules/container-registry/tests/container-registry.test.ts +0 -16
  252. package/modules/logging/tests/logging.test.ts +0 -38
  253. package/modules/operations/tests/operations.test.ts +0 -22
  254. package/modules/operations/tests/viewport-filter.test.ts +0 -161
  255. package/modules/operations/tests/visible-only.test.ts +0 -250
  256. package/modules/session-manager/tests/session-manager.test.ts +0 -23
  257. package/modules/state/src/atomic-json.test.ts +0 -30
  258. package/modules/state/src/paths.test.ts +0 -59
  259. package/modules/state/src/xiaohongshu-collect-state.test.ts +0 -259
  260. package/modules/workflow/blocks/AnchorVerificationBlock.d.ts.map +0 -1
  261. package/modules/workflow/blocks/AnchorVerificationBlock.js.map +0 -1
  262. package/modules/workflow/blocks/DetectPageStateBlock.d.ts.map +0 -1
  263. package/modules/workflow/blocks/DetectPageStateBlock.js.map +0 -1
  264. package/modules/workflow/blocks/ErrorRecoveryBlock.d.ts.map +0 -1
  265. package/modules/workflow/blocks/ErrorRecoveryBlock.js.map +0 -1
  266. package/modules/workflow/blocks/WaitSearchPermitBlock.d.ts.map +0 -1
  267. package/modules/workflow/blocks/WaitSearchPermitBlock.js.map +0 -1
  268. package/modules/workflow/blocks/helpers/containerAnchors.d.ts.map +0 -1
  269. package/modules/workflow/blocks/helpers/containerAnchors.js.map +0 -1
  270. package/modules/workflow/blocks/helpers/downloadPaths.test.ts +0 -62
  271. package/modules/workflow/blocks/helpers/mergeXhsMarkdown.test.ts +0 -121
  272. package/modules/workflow/blocks/helpers/operationLogger.d.ts.map +0 -1
  273. package/modules/workflow/blocks/helpers/operationLogger.js.map +0 -1
  274. package/modules/workflow/blocks/helpers/persistedNotes.test.ts +0 -268
  275. package/modules/workflow/blocks/helpers/searchPageState.d.ts.map +0 -1
  276. package/modules/workflow/blocks/helpers/searchPageState.js.map +0 -1
  277. package/modules/workflow/blocks/helpers/targetCountMode.test.ts +0 -29
  278. package/modules/workflow/blocks/helpers/xhsCliArgs.test.ts +0 -75
  279. package/modules/workflow/tests/smartReply.test.ts +0 -32
  280. package/modules/xiaohongshu/app/src/blocks/Phase3Interact.matcher.test.ts +0 -33
  281. package/modules/xiaohongshu/app/src/utils/__tests__/checkpoints.test.ts +0 -141
  282. package/modules/xiaohongshu/app/tests/commentMatchDsl.test.ts +0 -50
  283. package/modules/xiaohongshu/app/tests/commentMatcher.test.ts +0 -46
  284. package/modules/xiaohongshu/app/tests/sharding.test.ts +0 -31
  285. package/package-scripts.json +0 -8
  286. package/runtime/infra/utils/README.md +0 -13
  287. package/runtime/infra/utils/scripts/README.md +0 -0
  288. package/runtime/infra/utils/scripts/development/eval-in-session.mjs +0 -40
  289. package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +0 -35
  290. package/runtime/infra/utils/scripts/service/kill-port.mjs +0 -24
  291. package/runtime/infra/utils/scripts/service/start-api.mjs +0 -39
  292. package/runtime/infra/utils/scripts/service/start-browser-service.mjs +0 -106
  293. package/runtime/infra/utils/scripts/service/stop-api.mjs +0 -18
  294. package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +0 -104
  295. package/runtime/infra/utils/scripts/test-services.mjs +0 -94
  296. package/services/shared/heartbeat.test.ts +0 -102
  297. package/services/unified-api/__tests__/task-state.test.ts +0 -95
  298. package/sitecustomize.py +0 -19
  299. package/tests/README.md +0 -194
  300. package/tests/e2e/workflows/weibo-feed-extraction.test.ts +0 -171
  301. package/tests/fixtures/data/container-definitions.json +0 -67
  302. package/tests/fixtures/pages/simple-page.html +0 -69
  303. package/tests/integration/01-test-container-match.mjs +0 -188
  304. package/tests/integration/02-test-dom-branch.mjs +0 -161
  305. package/tests/integration/03-test-container-operation-system.mjs +0 -91
  306. package/tests/integration/05-test-container-lifecycle-events.mjs +0 -224
  307. package/tests/integration/05-test-container-lifecycle-with-events.mjs +0 -250
  308. package/tests/integration/06-test-container-dom-tree-drawing.mjs +0 -256
  309. package/tests/integration/07-test-weibo-container-lifecycle.mjs +0 -355
  310. package/tests/integration/08-test-weibo-feed-workflow.test.mjs +0 -164
  311. package/tests/integration/10-test-visual-analyzer.mjs +0 -312
  312. package/tests/integration/11-test-visual-loop.mjs +0 -284
  313. package/tests/integration/12-test-simple-visual-loop.mjs +0 -242
  314. package/tests/integration/13-test-visual-robust.mjs +0 -185
  315. package/tests/integration/14-test-visual-highlight-loop.mjs +0 -271
  316. package/tests/integration/inspect-page.mjs +0 -50
  317. package/tests/integration/run-all-tests.mjs +0 -95
  318. package/tests/patch_verification/CODEX_PATCH_TEST.md +0 -103
  319. package/tests/patch_verification/PHASE2_ANALYSIS.md +0 -179
  320. package/tests/patch_verification/PHASE2_OPTIMIZATION_REPORT.md +0 -55
  321. package/tests/patch_verification/PHASE2_TO_PHASE4_SUMMARY.md +0 -126
  322. package/tests/patch_verification/QUICK_TEST_SEQUENCE.md +0 -262
  323. package/tests/patch_verification/README.md +0 -143
  324. package/tests/patch_verification/RUN_TESTS.md +0 -60
  325. package/tests/patch_verification/TEST_EXECUTION.md +0 -99
  326. package/tests/patch_verification/TEST_PLAN.md +0 -328
  327. package/tests/patch_verification/TEST_RESULTS.md +0 -34
  328. package/tests/patch_verification/TOOL_TEST_PLAN.md +0 -48
  329. package/tests/patch_verification/run-tool-test.mjs +0 -121
  330. package/tests/patch_verification/temp_test_files/test01.txt +0 -1
  331. package/tests/patch_verification/temp_test_files/test02.txt +0 -3
  332. package/tests/patch_verification/temp_test_files/test02_gnu.txt +0 -3
  333. package/tests/patch_verification/temp_test_files/test03.txt +0 -1
  334. package/tests/patch_verification/temp_test_files/test03_multiline.txt +0 -5
  335. package/tests/patch_verification/temp_test_files/test04_function.ts +0 -5
  336. package/tests/patch_verification/temp_test_files/test05_import.ts +0 -4
  337. package/tests/patch_verification/temp_test_files/test06_special_chars.txt +0 -4
  338. package/tests/patch_verification/temp_test_files/test07_indentation.ts +0 -5
  339. package/tests/patch_verification/temp_test_files/test08_mismatch.txt +0 -1
  340. package/tests/patch_verification/temp_test_files/test_add_02.txt +0 -3
  341. package/tests/patch_verification/temp_test_files/test_simple.txt +0 -1
  342. package/tests/runner/TestReporter.mjs +0 -57
  343. package/tests/runner/TestRunner.mjs +0 -244
  344. package/tests/unit/commands/profile.test.mjs +0 -10
  345. package/tests/unit/container/change-notifier.test.mjs +0 -181
  346. package/tests/unit/lifecycle/session-registry.test.mjs +0 -135
  347. package/tests/unit/operations/registry.test.ts +0 -73
  348. package/tests/unit/utils/browser-service.test.mjs +0 -153
  349. package/tests/unit/utils/config.test.mjs +0 -166
  350. package/tests/unit/utils/fingerprint.test.mjs +0 -166
  351. package/tsconfig.json +0 -31
  352. package/tsconfig.services.json +0 -26
  353. /package/apps/desktop-console/{src → dist}/renderer/index.html +0 -0
  354. /package/apps/desktop-console/{src/renderer/tabs → dist/renderer}/run.mts +0 -0
@@ -1,980 +0,0 @@
1
- import electron from 'electron';
2
- const { app, BrowserWindow, ipcMain, shell, clipboard } = electron;
3
-
4
- import { spawn } from 'node:child_process';
5
- import os from 'node:os';
6
- import path from 'node:path';
7
- import { fileURLToPath, pathToFileURL } from 'node:url';
8
- import { mkdirSync, promises as fs } from 'node:fs';
9
-
10
- import { readDesktopConsoleSettings, resolveDefaultDownloadRoot, writeDesktopConsoleSettings } from './desktop-settings.mts';
11
- import type { DesktopConsoleSettings } from './desktop-settings.mts';
12
- import { startCoreDaemon, stopCoreDaemon } from './core-daemon-manager.mts';
13
- import { createProfileStore } from './profile-store.mts';
14
- import { decideWatchdogAction, resolveUiHeartbeatTimeoutMs } from './heartbeat-watchdog.mts';
15
-
16
- type CmdEvent =
17
- | { type: 'started'; runId: string; title: string; pid: number; ts: number }
18
- | { type: 'stdout'; runId: string; line: string; ts: number }
19
- | { type: 'stderr'; runId: string; line: string; ts: number }
20
- | { type: 'exit'; runId: string; exitCode: number | null; signal: string | null; ts: number };
21
-
22
- type SpawnSpec = {
23
- title: string;
24
- cwd: string;
25
- args: string[];
26
- env?: Record<string, string>;
27
- groupKey?: string;
28
- };
29
-
30
- type RunJsonSpec = {
31
- title: string;
32
- cwd: string;
33
- args: string[];
34
- env?: Record<string, string>;
35
- timeoutMs?: number;
36
- };
37
-
38
- type UiSettings = DesktopConsoleSettings;
39
- import { stateBridge } from './state-bridge.mts';
40
-
41
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
42
- const APP_ROOT = path.resolve(__dirname, '../..'); // apps/desktop-console/dist/main -> apps/desktop-console
43
- const REPO_ROOT = path.resolve(APP_ROOT, '../..');
44
- const DESKTOP_HEARTBEAT_FILE = path.join(
45
- os.homedir(),
46
- '.webauto',
47
- 'run',
48
- 'desktop-console-heartbeat.json',
49
- );
50
- const profileStore = createProfileStore({ repoRoot: REPO_ROOT });
51
- const XHS_SCRIPTS_ROOT = path.join(REPO_ROOT, 'scripts', 'xiaohongshu');
52
- const XHS_FULL_COLLECT_RE = /collect-content\.mjs$/;
53
-
54
- function configureElectronPaths() {
55
- try {
56
- const downloadRoot = resolveDefaultDownloadRoot();
57
- const baseDir = path.dirname(downloadRoot);
58
- const userDataRoot = path.join(baseDir, 'desktop-console');
59
- const cacheRoot = path.join(userDataRoot, 'cache');
60
- const gpuCacheRoot = path.join(cacheRoot, 'gpu');
61
-
62
- try { mkdirSync(cacheRoot, { recursive: true }); } catch {}
63
- try { mkdirSync(gpuCacheRoot, { recursive: true }); } catch {}
64
-
65
- app.setPath('userData', userDataRoot);
66
- app.setPath('cache', cacheRoot);
67
- app.commandLine.appendSwitch('disk-cache-dir', cacheRoot);
68
- app.commandLine.appendSwitch('gpu-cache-dir', gpuCacheRoot);
69
- } catch (err) {
70
- console.warn('[desktop-console] failed to configure cache paths', err);
71
- }
72
- }
73
-
74
- function now() {
75
- return Date.now();
76
- }
77
-
78
- class GroupQueue {
79
- private running = false;
80
- private queue: Array<() => Promise<void>> = [];
81
-
82
- enqueue(job: () => Promise<void>) {
83
- this.queue.push(job);
84
- void this.pump();
85
- }
86
-
87
- private async pump() {
88
- if (this.running) return;
89
- this.running = true;
90
- try {
91
- while (this.queue.length > 0) {
92
- const job = this.queue.shift()!;
93
- await job();
94
- }
95
- } finally {
96
- this.running = false;
97
- }
98
- }
99
- }
100
-
101
- const groupQueues = new Map<string, GroupQueue>();
102
- const runs = new Map<string, { child: ReturnType<typeof spawn>; title: string; startedAt: number }>();
103
-
104
- const UI_HEARTBEAT_TIMEOUT_MS = resolveUiHeartbeatTimeoutMs(process.env);
105
- let lastUiHeartbeatAt = Date.now();
106
- let heartbeatWatchdog: NodeJS.Timeout | null = null;
107
- let heartbeatTimeoutHandled = false;
108
- let coreServicesStopRequested = false;
109
- let coreServiceHeartbeatTimer: NodeJS.Timeout | null = null;
110
- let coreServiceHeartbeatStopped = false;
111
-
112
- async function writeCoreServiceHeartbeat(status: 'running' | 'stopped') {
113
- const filePath = String(process.env.WEBAUTO_HEARTBEAT_FILE || DESKTOP_HEARTBEAT_FILE).trim() || DESKTOP_HEARTBEAT_FILE;
114
- const payload = {
115
- pid: process.pid,
116
- ts: new Date().toISOString(),
117
- status,
118
- source: 'desktop-console',
119
- };
120
- try {
121
- await fs.mkdir(path.dirname(filePath), { recursive: true });
122
- await fs.writeFile(filePath, JSON.stringify(payload), 'utf8');
123
- } catch {
124
- // ignore heartbeat write errors
125
- }
126
- }
127
-
128
- function startCoreServiceHeartbeat() {
129
- const filePath = String(process.env.WEBAUTO_HEARTBEAT_FILE || DESKTOP_HEARTBEAT_FILE).trim() || DESKTOP_HEARTBEAT_FILE;
130
- process.env.WEBAUTO_HEARTBEAT_FILE = filePath;
131
- if (!process.env.WEBAUTO_HEARTBEAT_INTERVAL_MS) process.env.WEBAUTO_HEARTBEAT_INTERVAL_MS = '5000';
132
- if (!process.env.WEBAUTO_HEARTBEAT_STALE_MS) process.env.WEBAUTO_HEARTBEAT_STALE_MS = '45000';
133
-
134
- coreServiceHeartbeatStopped = false;
135
- void writeCoreServiceHeartbeat('running');
136
- if (coreServiceHeartbeatTimer) clearInterval(coreServiceHeartbeatTimer);
137
- coreServiceHeartbeatTimer = setInterval(() => {
138
- if (coreServiceHeartbeatStopped) return;
139
- void writeCoreServiceHeartbeat('running');
140
- }, 5000);
141
- coreServiceHeartbeatTimer.unref();
142
- }
143
-
144
- function stopCoreServiceHeartbeat() {
145
- if (coreServiceHeartbeatStopped) return;
146
- coreServiceHeartbeatStopped = true;
147
- if (coreServiceHeartbeatTimer) {
148
- clearInterval(coreServiceHeartbeatTimer);
149
- coreServiceHeartbeatTimer = null;
150
- }
151
- void writeCoreServiceHeartbeat('stopped');
152
- }
153
-
154
- let stateBridgeStarted = false;
155
- function ensureStateBridge() {
156
- if (stateBridgeStarted) return;
157
- const w = getWin();
158
- if (w) { stateBridge.start(w); stateBridgeStarted = true; }
159
- }
160
-
161
- let win: BrowserWindow | null = null;
162
-
163
- configureElectronPaths();
164
-
165
- function getWin() {
166
- if (!win || win.isDestroyed()) return null;
167
- return win;
168
- }
169
-
170
- function isUiOperational() {
171
- const w = getWin();
172
- if (!w) return false;
173
- const wc = w.webContents;
174
- if (!wc || wc.isDestroyed()) return false;
175
- if (typeof wc.isCrashed === 'function' && wc.isCrashed()) return false;
176
- return true;
177
- }
178
-
179
- function sendEvent(evt: CmdEvent) {
180
- if (!win || win.isDestroyed()) return;
181
- win.webContents.send('cmd:event', evt);
182
- }
183
-
184
- function markUiHeartbeat(source = 'renderer') {
185
- lastUiHeartbeatAt = Date.now();
186
- heartbeatTimeoutHandled = false;
187
- return { ok: true, ts: new Date(lastUiHeartbeatAt).toISOString(), source };
188
- }
189
-
190
- function terminateRunProcess(runId: string, reason = 'manual') {
191
- const run = runs.get(runId);
192
- if (!run) return false;
193
- const child = run.child;
194
- const pid = Number(child.pid || 0);
195
-
196
- try {
197
- if (process.platform === 'win32') {
198
- if (pid > 0) {
199
- spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
200
- }
201
- } else {
202
- if (pid > 0) {
203
- // Best-effort: terminate direct children first, then root process.
204
- spawn('pkill', ['-TERM', '-P', String(pid)], { stdio: 'ignore' }).on('error', () => {});
205
- }
206
- }
207
- } catch {
208
- // ignore
209
- }
210
-
211
- try {
212
- child.kill('SIGTERM');
213
- } catch {
214
- // ignore
215
- }
216
-
217
- sendEvent({ type: 'stderr', runId, line: `[watchdog] kill requested (${reason})`, ts: now() });
218
- return true;
219
- }
220
-
221
- async function stopCoreServicesBestEffort(reason: string) {
222
- if (coreServicesStopRequested) return;
223
- coreServicesStopRequested = true;
224
- try {
225
- await stopCoreDaemon();
226
- } catch (err) {
227
- console.warn(`[desktop-console] core-daemon stop failed (${reason})`, err);
228
- }
229
- }
230
-
231
- function killAllRuns(reason = 'ui_heartbeat_timeout') {
232
- for (const runId of Array.from(runs.keys())) {
233
- terminateRunProcess(runId, reason);
234
- }
235
- if (reason === 'ui_heartbeat_timeout' || reason === 'window_closed') {
236
- void stopCoreServicesBestEffort(reason);
237
- }
238
- }
239
-
240
- function ensureHeartbeatWatchdog() {
241
- if (heartbeatWatchdog) return;
242
- heartbeatWatchdog = setInterval(() => {
243
- const staleMs = Date.now() - lastUiHeartbeatAt;
244
- const decision = decideWatchdogAction({
245
- staleMs,
246
- timeoutMs: UI_HEARTBEAT_TIMEOUT_MS,
247
- alreadyHandled: heartbeatTimeoutHandled,
248
- runCount: runs.size,
249
- uiOperational: isUiOperational(),
250
- });
251
- heartbeatTimeoutHandled = decision.nextHandled;
252
-
253
- if (decision.action === 'none') {
254
- if (decision.reason === 'stale_ui_alive') {
255
- console.warn(
256
- `[desktop-heartbeat] stale ${staleMs}ms > ${UI_HEARTBEAT_TIMEOUT_MS}ms, UI still alive, skip kill (likely timer throttling)`,
257
- );
258
- }
259
- return;
260
- }
261
-
262
- if (decision.action === 'kill_runs') {
263
- console.warn(`[desktop-heartbeat] stale ${staleMs}ms > ${UI_HEARTBEAT_TIMEOUT_MS}ms, killing ${runs.size} run(s)`);
264
- killAllRuns('ui_heartbeat_timeout');
265
- return;
266
- }
267
-
268
- console.warn(`[desktop-heartbeat] stale ${staleMs}ms > ${UI_HEARTBEAT_TIMEOUT_MS}ms, stopping core services`);
269
- void stopCoreServicesBestEffort('heartbeat_stop_only');
270
- }, 5_000);
271
- heartbeatWatchdog.unref();
272
- }
273
-
274
- function getQueue(groupKey: string) {
275
- const key = groupKey || 'default';
276
- let q = groupQueues.get(key);
277
- if (!q) {
278
- q = new GroupQueue();
279
- groupQueues.set(key, q);
280
- }
281
- return q;
282
- }
283
-
284
- function generateRunId() {
285
- return `run_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
286
- }
287
-
288
- type StreamEventType = 'stdout' | 'stderr';
289
-
290
- function createLineEmitter(runId: string, type: StreamEventType) {
291
- let pending = '';
292
-
293
- const emit = (line: string) => {
294
- const normalized = String(line || '').replace(/\r$/, '');
295
- if (!normalized) return;
296
- sendEvent({ type, runId, line: normalized, ts: now() });
297
- };
298
-
299
- return {
300
- push(chunk: Buffer) {
301
- pending += chunk.toString('utf8');
302
- let idx = pending.indexOf('\n');
303
- while (idx >= 0) {
304
- const line = pending.slice(0, idx);
305
- pending = pending.slice(idx + 1);
306
- emit(line);
307
- idx = pending.indexOf('\n');
308
- }
309
- },
310
- flush() {
311
- if (!pending) return;
312
- emit(pending);
313
- pending = '';
314
- },
315
- };
316
- }
317
-
318
- function resolveNodeBin() {
319
- const explicit = String(process.env.WEBAUTO_NODE_BIN || '').trim();
320
- if (explicit) return explicit;
321
- const npmNode = String(process.env.npm_node_execpath || '').trim();
322
- if (npmNode) return npmNode;
323
- return process.platform === 'win32' ? 'node.exe' : 'node';
324
- }
325
-
326
- function resolveCwd(input?: string) {
327
- const raw = String(input || '').trim();
328
- if (!raw) return REPO_ROOT;
329
- return path.isAbsolute(raw) ? raw : path.resolve(REPO_ROOT, raw);
330
- }
331
-
332
- let cachedStateMod: any = null;
333
- async function getStateModule() {
334
- if (cachedStateMod) return cachedStateMod;
335
- try {
336
- const p = path.join(REPO_ROOT, 'dist', 'modules', 'state', 'src', 'xiaohongshu-collect-state.js');
337
- cachedStateMod = await import(pathToFileURL(p).href);
338
- return cachedStateMod;
339
- } catch {
340
- cachedStateMod = null;
341
- return null;
342
- }
343
- }
344
-
345
- async function spawnCommand(spec: SpawnSpec) {
346
- const runId = generateRunId();
347
- const groupKey = spec.groupKey || 'xiaohongshu';
348
- const q = getQueue(groupKey);
349
- const cwd = resolveCwd(spec.cwd);
350
-
351
- q.enqueue(
352
- () =>
353
- new Promise<void>((resolve) => {
354
- let finished = false;
355
- let exitCode: number | null = null;
356
- let exitSignal: string | null = null;
357
- const finalize = (code: number | null, signal: string | null) => {
358
- if (finished) return;
359
- finished = true;
360
- sendEvent({ type: 'exit', runId, exitCode: code, signal, ts: now() });
361
- runs.delete(runId);
362
- resolve();
363
- };
364
-
365
- const child = spawn(resolveNodeBin(), spec.args, {
366
- cwd,
367
- env: {
368
- ...process.env,
369
- WEBAUTO_DAEMON: '1',
370
- WEBAUTO_UI_HEARTBEAT: '1',
371
- ...(spec.env || {}),
372
- },
373
- stdio: ['ignore', 'pipe', 'pipe'],
374
- windowsHide: true,
375
- });
376
-
377
- runs.set(runId, { child, title: spec.title, startedAt: now() });
378
- sendEvent({ type: 'started', runId, title: spec.title, pid: child.pid ?? -1, ts: now() });
379
-
380
- const stdoutLines = createLineEmitter(runId, 'stdout');
381
- const stderrLines = createLineEmitter(runId, 'stderr');
382
-
383
- child.stdout?.on('data', (chunk: Buffer) => {
384
- stdoutLines.push(chunk);
385
- });
386
- child.stderr?.on('data', (chunk: Buffer) => {
387
- stderrLines.push(chunk);
388
- });
389
- child.on('error', (err: any) => {
390
- sendEvent({ type: 'stderr', runId, line: `[spawn-error] ${err?.message || String(err)}`, ts: now() });
391
- finalize(null, 'error');
392
- });
393
- child.on('exit', (code, signal) => {
394
- exitCode = code;
395
- exitSignal = signal;
396
- });
397
- child.on('close', (code, signal) => {
398
- stdoutLines.flush();
399
- stderrLines.flush();
400
- finalize(exitCode ?? code ?? null, exitSignal ?? signal ?? null);
401
- });
402
- }),
403
- );
404
-
405
- return { runId };
406
- }
407
-
408
- async function runJson(spec: RunJsonSpec) {
409
- const timeoutMs = typeof spec.timeoutMs === 'number' ? spec.timeoutMs : 20_000;
410
- const cwd = resolveCwd(spec.cwd);
411
- const child = spawn(resolveNodeBin(), spec.args, {
412
- cwd,
413
- env: { ...process.env, ...(spec.env || {}) },
414
- stdio: ['ignore', 'pipe', 'pipe'],
415
- windowsHide: true,
416
- });
417
-
418
- const stdout: Buffer[] = [];
419
- const stderr: Buffer[] = [];
420
-
421
- child.stdout?.on('data', (c: Buffer) => stdout.push(c));
422
- child.stderr?.on('data', (c: Buffer) => stderr.push(c));
423
-
424
- const timer = setTimeout(() => {
425
- try {
426
- child.kill('SIGTERM');
427
- } catch {
428
- // ignore
429
- }
430
- }, timeoutMs);
431
-
432
- const { code } = await new Promise<{ code: number | null }>((resolve) => {
433
- child.on('exit', (c) => resolve({ code: c }));
434
- });
435
- clearTimeout(timer);
436
-
437
- const out = Buffer.concat(stdout).toString('utf8').trim();
438
- const err = Buffer.concat(stderr).toString('utf8').trim();
439
-
440
- if (code !== 0) {
441
- return { ok: false, code, stdout: out, stderr: err };
442
- }
443
-
444
- try {
445
- const json = JSON.parse(out);
446
- return { ok: true, code, json };
447
- } catch {
448
- return { ok: true, code, stdout: out, stderr: err };
449
- }
450
- }
451
-
452
- async function scanResults(input: { downloadRoot?: string }) {
453
- const downloadRoot = String(input.downloadRoot || resolveDefaultDownloadRoot());
454
- const root = path.join(downloadRoot, 'xiaohongshu');
455
-
456
- const result: any = { ok: true, root, entries: [] as any[] };
457
- try {
458
- const stateMod = await getStateModule();
459
- const envDirs = await fs.readdir(root, { withFileTypes: true });
460
- for (const envEnt of envDirs) {
461
- if (!envEnt.isDirectory()) continue;
462
- const env = envEnt.name;
463
- const envPath = path.join(root, env);
464
- const keywordDirs = await fs.readdir(envPath, { withFileTypes: true });
465
- for (const kwEnt of keywordDirs) {
466
- if (!kwEnt.isDirectory()) continue;
467
- const keyword = kwEnt.name;
468
- const kwPath = path.join(envPath, keyword);
469
- const stat = await fs.stat(kwPath).catch(() => null);
470
- let stateSummary: any = null;
471
- if (stateMod?.loadXhsCollectState) {
472
- try {
473
- const state = await stateMod.loadXhsCollectState({ keyword, env, downloadRoot });
474
- stateSummary = {
475
- status: state?.status,
476
- links: state?.listCollection?.collectedUrls?.length || 0,
477
- target: state?.listCollection?.targetCount || 0,
478
- completed: state?.detailCollection?.completed || 0,
479
- failed: state?.detailCollection?.failed || 0,
480
- updatedAt: state?.lastUpdateTime || null,
481
- };
482
- } catch {
483
- // ignore
484
- }
485
- }
486
- result.entries.push({ env, keyword, path: kwPath, mtimeMs: stat?.mtimeMs || 0, state: stateSummary });
487
- }
488
- }
489
- result.entries.sort((a: any, b: any) => (b.mtimeMs || 0) - (a.mtimeMs || 0));
490
- } catch (e: any) {
491
- result.ok = false;
492
- result.error = e?.message || String(e);
493
- }
494
- return result;
495
- }
496
-
497
- async function listXhsFullCollectScripts() {
498
- try {
499
- const entries = await fs.readdir(XHS_SCRIPTS_ROOT, { withFileTypes: true });
500
- const scripts = entries
501
- .filter((ent) => ent.isFile() && XHS_FULL_COLLECT_RE.test(ent.name))
502
- .map((ent) => {
503
- const name = ent.name;
504
- return {
505
- id: `xhs:${name}`,
506
- label: `Full Collect (${name})`,
507
- path: path.join(XHS_SCRIPTS_ROOT, name),
508
- };
509
- });
510
- return { ok: true, scripts };
511
- } catch (err: any) {
512
- return { ok: false, error: err?.message || String(err), scripts: [] };
513
- }
514
- }
515
-
516
- async function readTextPreview(input: { path: string; maxBytes?: number; maxLines?: number }) {
517
- const filePath = String(input.path || '');
518
- const maxBytes = typeof input.maxBytes === 'number' ? input.maxBytes : 80_000;
519
- const maxLines = typeof input.maxLines === 'number' ? input.maxLines : 200;
520
- try {
521
- const raw = await fs.readFile(filePath, 'utf8');
522
- const clipped = raw.slice(0, maxBytes);
523
- const lines = clipped.split(/\r?\n/g).slice(0, maxLines);
524
- return { ok: true, path: filePath, text: lines.join('\n') };
525
- } catch (err: any) {
526
- if (err?.code === 'ENOENT') return { ok: false, path: filePath, error: 'not_found' };
527
- return { ok: false, path: filePath, error: err?.message || String(err) };
528
- }
529
- }
530
-
531
-
532
-
533
- async function readTextTail(input: { path: string; fromOffset?: number; maxBytes?: number }) {
534
- const filePath = String(input?.path || '');
535
- const requestedOffset = typeof input?.fromOffset === 'number' ? Math.max(0, Math.floor(input.fromOffset)) : 0;
536
- const maxBytes = typeof input?.maxBytes === 'number' ? Math.max(1024, Math.floor(input.maxBytes)) : 256_000;
537
-
538
- const st = await fs.stat(filePath);
539
- const size = Number(st?.size || 0);
540
- const fromOffset = requestedOffset > size ? 0 : requestedOffset;
541
- const toRead = Math.max(0, Math.min(maxBytes, size - fromOffset));
542
- if (toRead <= 0) {
543
- return { ok: true, path: filePath, text: '', fromOffset, nextOffset: fromOffset, fileSize: size };
544
- }
545
-
546
- const fh = await fs.open(filePath, 'r');
547
- try {
548
- const buf = Buffer.allocUnsafe(toRead);
549
- const { bytesRead } = await fh.read(buf, 0, toRead, fromOffset);
550
- const text = buf.subarray(0, bytesRead).toString('utf8');
551
- return {
552
- ok: true,
553
- path: filePath,
554
- text,
555
- fromOffset,
556
- nextOffset: fromOffset + bytesRead,
557
- fileSize: size,
558
- };
559
- } finally {
560
- await fh.close();
561
- }
562
- }
563
-
564
- async function readFileBase64(input: { path: string; maxBytes?: number }) {
565
- const filePath = String(input.path || '');
566
- const maxBytes = typeof input.maxBytes === 'number' ? input.maxBytes : 8_000_000;
567
- const buf = await fs.readFile(filePath);
568
- if (buf.byteLength > maxBytes) {
569
- return { ok: false, error: `file too large: ${buf.byteLength}` };
570
- }
571
- return { ok: true, data: buf.toString('base64') };
572
- }
573
-
574
- async function listDir(input: { root: string; recursive?: boolean; maxEntries?: number }) {
575
- const root = String(input?.root || '');
576
- const recursive = Boolean(input?.recursive);
577
- const maxEntries = typeof input?.maxEntries === 'number' ? input.maxEntries : 2000;
578
- const entries: Array<{
579
- path: string;
580
- rel: string;
581
- name: string;
582
- isDir: boolean;
583
- size: number;
584
- mtimeMs: number;
585
- }> = [];
586
-
587
- const stack: string[] = [root];
588
- while (stack.length > 0 && entries.length < maxEntries) {
589
- const dir = stack.pop()!;
590
- const items = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
591
- for (const ent of items) {
592
- if (entries.length >= maxEntries) break;
593
- const full = path.join(dir, ent.name);
594
- const st = await fs.stat(full).catch(() => null);
595
- entries.push({
596
- path: full,
597
- rel: path.relative(root, full),
598
- name: ent.name,
599
- isDir: ent.isDirectory(),
600
- size: st?.size || 0,
601
- mtimeMs: st?.mtimeMs || 0,
602
- });
603
- if (recursive && ent.isDirectory()) stack.push(full);
604
- }
605
- }
606
-
607
- entries.sort((a, b) => {
608
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
609
- return b.mtimeMs - a.mtimeMs;
610
- });
611
-
612
- return { ok: true, root, entries, truncated: entries.length >= maxEntries };
613
- }
614
-
615
- function createWindow() {
616
- win = new BrowserWindow({
617
- title: "WebAuto Desktop v0.1.1",
618
- width: 1280,
619
- height: 900,
620
- minWidth: 920,
621
- minHeight: 800,
622
- webPreferences: {
623
- preload: path.join(APP_ROOT, 'dist', 'main', 'preload.mjs'),
624
- contextIsolation: true,
625
- nodeIntegration: false,
626
- sandbox: false,
627
- // Prevent renderer timer throttling when app loses focus; heartbeat must remain stable.
628
- backgroundThrottling: false,
629
- },
630
- });
631
-
632
- const htmlPath = path.join(APP_ROOT, 'dist', 'renderer', 'index.html');
633
- void win.loadFile(htmlPath);
634
- ensureStateBridge();
635
- }
636
-
637
- app.on('window-all-closed', () => {
638
- killAllRuns('window_closed');
639
- // macOS 下关闭窗口后也退出应用,避免命令行挂起
640
- app.quit();
641
- });
642
-
643
- // 确保窗口关闭时命令行能退出
644
- app.on('before-quit', () => {
645
- killAllRuns('before_quit');
646
- stopCoreServiceHeartbeat();
647
- void stopCoreServicesBestEffort('before_quit');
648
- if (heartbeatWatchdog) {
649
- clearInterval(heartbeatWatchdog);
650
- heartbeatWatchdog = null;
651
- }
652
- });
653
-
654
- app.on('will-quit', () => {
655
- killAllRuns('will_quit');
656
- stopCoreServiceHeartbeat();
657
- void stopCoreServicesBestEffort('will_quit');
658
- stateBridge.stop();
659
- });
660
-
661
- app.whenReady().then(async () => {
662
- startCoreServiceHeartbeat();
663
- const started = await startCoreDaemon().catch(() => false);
664
- if (!started) {
665
- console.warn('[desktop-console] core services are not healthy at startup');
666
- }
667
- markUiHeartbeat('main_ready');
668
- ensureHeartbeatWatchdog();
669
- createWindow();
670
- });
671
-
672
- ipcMain.on('preload:test', () => {
673
- console.log('[preload-test] window.api OK');
674
- // give renderer a moment to flush
675
- setTimeout(() => app.quit(), 200);
676
- });
677
-
678
- ipcMain.handle('settings:get', async () => readDesktopConsoleSettings({ appRoot: APP_ROOT, repoRoot: REPO_ROOT }));
679
- ipcMain.handle('settings:set', async (_evt, next) => {
680
- const updated = await writeDesktopConsoleSettings({ appRoot: APP_ROOT, repoRoot: REPO_ROOT }, next || {});
681
- // Broadcast to all tabs so they can refresh aliases/colors without manual reload.
682
- const w = getWin();
683
- if (w) w.webContents.send('settings:changed', updated);
684
- return updated;
685
- });
686
-
687
- ipcMain.handle('ai:listModels', async (_evt, input: { baseUrl: string; apiKey: string; path?: string }) => {
688
- try {
689
- const baseUrl = String(input?.baseUrl || '').trim().replace(/\/+$/, '');
690
- const apiKey = String(input?.apiKey || '').trim();
691
- const apiPath = String(input?.path || '/v1/models').trim() || '/v1/models';
692
- if (!baseUrl) return { ok: false, models: [], rawCount: 0, error: 'baseUrl is required' };
693
-
694
- const headers: Record<string, string> = { 'Content-Type': 'application/json' };
695
- if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
696
-
697
- const res = await fetch(`${baseUrl}${apiPath}`, {
698
- method: 'GET',
699
- headers,
700
- signal: AbortSignal.timeout(15000),
701
- });
702
- const json = await res.json().catch(() => ({} as any));
703
- if (!res.ok) {
704
- return {
705
- ok: false,
706
- models: [],
707
- rawCount: 0,
708
- error: (json as any)?.error?.message || `HTTP ${res.status}`,
709
- };
710
- }
711
-
712
- const data = Array.isArray((json as any)?.data) ? (json as any).data : [];
713
- const models = data.map((m: any) => String(m?.id || '')).filter(Boolean);
714
- return { ok: true, models, rawCount: data.length };
715
- } catch (e: any) {
716
- return { ok: false, models: [], rawCount: 0, error: e?.message || String(e) };
717
- }
718
- });
719
-
720
- ipcMain.handle(
721
- 'ai:testChatCompletion',
722
- async (_evt, input: { baseUrl: string; apiKey: string; model: string; timeoutMs?: number }) => {
723
- const startedAt = Date.now();
724
- try {
725
- const baseUrl = String(input?.baseUrl || '').trim().replace(/\/+$/, '');
726
- const apiKey = String(input?.apiKey || '').trim();
727
- const model = String(input?.model || '').trim();
728
- const timeoutMs = Math.max(5000, Number(input?.timeoutMs || 25000));
729
-
730
- if (!baseUrl) return { ok: false, latencyMs: 0, model, error: 'baseUrl is required' };
731
- if (!model) return { ok: false, latencyMs: 0, model, error: 'model is required' };
732
-
733
- const headers: Record<string, string> = { 'Content-Type': 'application/json' };
734
- if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
735
-
736
- const res = await fetch(`${baseUrl}/v1/chat/completions`, {
737
- method: 'POST',
738
- headers,
739
- body: JSON.stringify({
740
- model,
741
- messages: [{ role: 'user', content: 'ping' }],
742
- max_tokens: 8,
743
- temperature: 0,
744
- }),
745
- signal: AbortSignal.timeout(timeoutMs),
746
- });
747
-
748
- const json = await res.json().catch(() => ({} as any));
749
- if (!res.ok) {
750
- return {
751
- ok: false,
752
- latencyMs: Date.now() - startedAt,
753
- model,
754
- error: (json as any)?.error?.message || `HTTP ${res.status}`,
755
- };
756
- }
757
-
758
- return {
759
- ok: true,
760
- latencyMs: Date.now() - startedAt,
761
- model,
762
- };
763
- } catch (e: any) {
764
- return {
765
- ok: false,
766
- latencyMs: Date.now() - startedAt,
767
- model: String(input?.model || ''),
768
- error: e?.message || String(e),
769
- };
770
- }
771
- },
772
- );
773
-
774
- ipcMain.handle('desktop:heartbeat', async () => markUiHeartbeat());
775
-
776
- ipcMain.handle('cmd:spawn', async (_evt, spec: SpawnSpec) => {
777
- markUiHeartbeat('cmd_spawn');
778
- const title = String(spec?.title || 'command');
779
- const cwd = String(spec?.cwd || REPO_ROOT);
780
- const args = Array.isArray(spec?.args) ? spec.args : [];
781
- return spawnCommand({ title, cwd, args, env: spec.env, groupKey: spec.groupKey });
782
- });
783
-
784
- ipcMain.handle('cmd:kill', async (_evt, input: { runId: string }) => {
785
- const runId = String(input?.runId || '');
786
- const r = runs.get(runId);
787
- if (!r) return { ok: false, error: 'not found' };
788
- try {
789
- const ok = terminateRunProcess(runId, 'manual_stop');
790
- return { ok };
791
- } catch (e: any) {
792
- return { ok: false, error: e?.message || String(e) };
793
- }
794
- });
795
-
796
- ipcMain.handle('cmd:runJson', async (_evt, spec: RunJsonSpec) => {
797
- const cwd = String(spec?.cwd || REPO_ROOT);
798
- const args = Array.isArray(spec?.args) ? spec.args : [];
799
- return runJson({ ...spec, cwd, args });
800
- });
801
-
802
- ipcMain.handle('results:scan', async (_evt, spec: { downloadRoot?: string }) => scanResults(spec || {}));
803
- ipcMain.handle('fs:listDir', async (_evt, spec: { root: string; recursive?: boolean; maxEntries?: number }) => listDir(spec));
804
- ipcMain.handle('fs:readTextPreview', async (_evt, spec: { path: string; maxBytes?: number; maxLines?: number }) =>
805
- readTextPreview(spec),
806
- );
807
- ipcMain.handle('fs:readTextTail', async (_evt, spec: { path: string; fromOffset?: number; maxBytes?: number }) =>
808
- readTextTail(spec),
809
- );
810
- ipcMain.handle('fs:readFileBase64', async (_evt, spec: { path: string; maxBytes?: number }) => readFileBase64(spec));
811
- ipcMain.handle('profiles:list', async () => profileStore.listProfiles());
812
- ipcMain.handle('profiles:scan', async () => profileStore.scanProfiles());
813
- ipcMain.handle('scripts:xhsFullCollect', async () => listXhsFullCollectScripts());
814
- ipcMain.handle('profile:create', async (_evt, input: { profileId: string }) => profileStore.profileCreate(input || ({} as any)));
815
- ipcMain.handle('profile:delete', async (_evt, input: { profileId: string; deleteFingerprint?: boolean }) =>
816
- profileStore.profileDelete(input || ({} as any)),
817
- );
818
- ipcMain.handle('fingerprint:delete', async (_evt, input: { profileId: string }) => profileStore.fingerprintDelete(input || ({} as any)));
819
- ipcMain.handle('fingerprint:regenerate', async (_evt, input: { profileId: string; platform?: 'windows' | 'macos' | 'random' }) =>
820
- profileStore.fingerprintRegenerate(input || ({} as any)),
821
- );
822
- ipcMain.handle('os:openPath', async (_evt, input: { path: string }) => {
823
- const p = String(input?.path || '');
824
- const r = await shell.openPath(p);
825
- return { ok: !r, error: r || null };
826
- });
827
-
828
- ipcMain.handle('clipboard:writeText', async (_evt, input: { text: string }) => {
829
- try {
830
- clipboard.writeText(String(input?.text || ''));
831
- return { ok: true };
832
- } catch (err: any) {
833
- return { ok: false, error: err?.message || String(err) };
834
- }
835
- });
836
-
837
- // ---- Runtime Dashboard APIs ----
838
-
839
- async function unifiedGet(pathname: string) {
840
- const base = String((await readDesktopConsoleSettings({ appRoot: APP_ROOT, repoRoot: REPO_ROOT }))?.unifiedApiUrl || 'http://127.0.0.1:7701');
841
- const url = `${base}${pathname}`;
842
- const res = await fetch(url, { signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined });
843
- const json = await res.json().catch(() => ({}));
844
- if (!res.ok) throw new Error(json?.error || `HTTP ${res.status}`);
845
- return json;
846
- }
847
-
848
- async function unifiedAction(action: string, payload: any) {
849
- const base = String((await readDesktopConsoleSettings({ appRoot: APP_ROOT, repoRoot: REPO_ROOT }))?.unifiedApiUrl || 'http://127.0.0.1:7701');
850
- const res = await fetch(`${base}/v1/controller/action`, {
851
- method: 'POST',
852
- headers: { 'Content-Type': 'application/json' },
853
- body: JSON.stringify({ action, payload }),
854
- signal: AbortSignal.timeout ? AbortSignal.timeout(20000) : undefined,
855
- });
856
- const json = await res.json().catch(() => ({}));
857
- if (!res.ok || json?.success === false || json?.ok === false) throw new Error(json?.error || 'unified action failed');
858
- return json;
859
- }
860
-
861
- ipcMain.handle('runtime:listSessions', async () => {
862
- // Prefer live sessions from controller; StateRegistry may lag behind.
863
- const data = await unifiedAction('session:list', {}).catch(() => null);
864
- const sessions = data?.data?.sessions || data?.sessions || [];
865
- if (!Array.isArray(sessions)) return [];
866
- const now = new Date().toISOString();
867
- return sessions
868
- .map((s: any) => ({
869
- profileId: String(s?.profileId || s?.profile_id || s?.sessionId || s?.session_id || ''),
870
- sessionId: String(s?.sessionId || s?.session_id || s?.profileId || s?.profile_id || ''),
871
- currentUrl: String(s?.currentUrl || s?.current_url || ''),
872
- lastPhase: String(s?.lastPhase || s?.phase || 'phase1'),
873
- lastActiveAt: String(s?.lastActiveAt || now),
874
- status: 'running',
875
- }))
876
- .filter((s: any) => s.profileId);
877
- });
878
-
879
- ipcMain.handle('runtime:focus', async (_evt, input: { profileId: string }) => {
880
- const profileId = String(input?.profileId || '').trim();
881
- if (!profileId) return { ok: false, error: 'missing profileId' };
882
- // Best-effort: some controllers may not implement browser:focus; fail gracefully.
883
- const focusRes = await unifiedAction('browser:focus', { profile: profileId }).catch(() => ({ ok: false }));
884
- // Add a temporary highlight overlay in the page to help locate the window.
885
- await unifiedAction('browser:execute', {
886
- profile: profileId,
887
- script: `(() => {
888
- try {
889
- const id = '__webauto_focus_ring__';
890
- let el = document.getElementById(id);
891
- if (!el) {
892
- el = document.createElement('div');
893
- el.id = id;
894
- el.style.position = 'fixed';
895
- el.style.left = '8px';
896
- el.style.top = '8px';
897
- el.style.right = '8px';
898
- el.style.bottom = '8px';
899
- el.style.border = '3px solid #2b67ff';
900
- el.style.borderRadius = '10px';
901
- el.style.zIndex = '2147483647';
902
- el.style.pointerEvents = 'none';
903
- document.body.appendChild(el);
904
- }
905
- el.style.display = 'block';
906
- setTimeout(() => { try { el.remove(); } catch {} }, 1500);
907
- return true;
908
- } catch {
909
- return false;
910
- }
911
- })()`
912
- }).catch(() => null);
913
- return focusRes;
914
- });
915
-
916
- ipcMain.handle('runtime:kill', async (_evt, input: { profileId: string }) => {
917
- const profileId = String(input?.profileId || '').trim();
918
- if (!profileId) return { ok: false, error: 'missing profileId' };
919
- return unifiedAction('session:delete', { profileId }).catch((err) => ({ ok: false, error: err?.message || String(err) }));
920
- });
921
-
922
- ipcMain.handle('runtime:restartPhase1', async (_evt, input: { profileId: string }) => {
923
- const profileId = String(input?.profileId || '').trim();
924
- if (!profileId) return { ok: false, error: 'missing profileId' };
925
- const args = [path.join(REPO_ROOT, 'scripts', 'xiaohongshu', 'phase1-boot.mjs'), '--profile', profileId, '--headless', 'false'];
926
- return spawnCommand({ title: `Phase1 restart ${profileId}`, cwd: REPO_ROOT, args, groupKey: 'phase1' });
927
- });
928
-
929
- ipcMain.handle('runtime:setBrowserTitle', async (_evt, input: { profileId: string; title: string }) => {
930
- const profileId = String(input?.profileId || '').trim();
931
- const title = String(input?.title || '').trim();
932
- if (!profileId || !title) return { ok: false, error: 'missing profileId/title' };
933
- return unifiedAction('browser:execute', {
934
- profile: profileId,
935
- script: `(() => { try { document.title = ${JSON.stringify(title)}; return true; } catch { return false; } })()`,
936
- }).catch((err) => ({ ok: false, error: err?.message || String(err) }));
937
- });
938
-
939
- ipcMain.handle('runtime:setHeaderBar', async (_evt, input: { profileId: string; label: string; color: string }) => {
940
- const profileId = String(input?.profileId || '').trim();
941
- const label = String(input?.label || '').trim();
942
- const color = String(input?.color || '').trim();
943
- if (!profileId || !label || !color) return { ok: false, error: 'missing profileId/label/color' };
944
- const safeColor = /^#[0-9a-fA-F]{6}$/.test(color) ? color : '#2b67ff';
945
- return unifiedAction('browser:execute', {
946
- profile: profileId,
947
- script: `(() => {
948
- try {
949
- const id = '__webauto_header_bar__';
950
- let bar = document.getElementById(id);
951
- if (!bar) {
952
- bar = document.createElement('div');
953
- bar.id = id;
954
- bar.style.position = 'fixed';
955
- bar.style.left = '0';
956
- bar.style.top = '0';
957
- bar.style.right = '0';
958
- bar.style.height = '22px';
959
- bar.style.zIndex = '2147483647';
960
- bar.style.display = 'flex';
961
- bar.style.alignItems = 'center';
962
- bar.style.padding = '0 10px';
963
- bar.style.fontSize = '12px';
964
- bar.style.fontFamily = 'system-ui, sans-serif';
965
- bar.style.fontWeight = '600';
966
- bar.style.color = '#fff';
967
- bar.style.pointerEvents = 'none';
968
- document.body.appendChild(bar);
969
- const html = document.documentElement;
970
- if (html) html.style.scrollPaddingTop = '22px';
971
- }
972
- bar.style.background = ${JSON.stringify(safeColor)};
973
- bar.textContent = ${JSON.stringify(label)};
974
- return true;
975
- } catch {
976
- return false;
977
- }
978
- })()`
979
- }).catch((err) => ({ ok: false, error: err?.message || String(err) }));
980
- });