@web-auto/webauto 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/apps/desktop-console/default-settings.json +1 -0
  2. package/apps/desktop-console/dist/main/index.mjs +1618 -0
  3. package/apps/desktop-console/{src → dist}/main/preload.mjs +10 -0
  4. package/apps/desktop-console/dist/renderer/index.js +3063 -0
  5. package/apps/desktop-console/entry/ui-console.mjs +299 -0
  6. package/apps/webauto/entry/account.mjs +356 -0
  7. package/apps/webauto/entry/lib/account-detect.mjs +160 -0
  8. package/apps/webauto/entry/lib/account-store.mjs +587 -0
  9. package/apps/webauto/entry/lib/profilepool.mjs +1 -1
  10. package/apps/webauto/entry/xhs-install.mjs +27 -3
  11. package/apps/webauto/entry/xhs-status.mjs +152 -0
  12. package/apps/webauto/entry/xhs-unified.mjs +595 -17
  13. package/bin/webauto.mjs +247 -12
  14. package/dist/apps/webauto/server.js +66 -0
  15. package/dist/modules/camo-backend/src/index.js +575 -0
  16. package/dist/modules/camo-backend/src/internal/BrowserSession.js +817 -0
  17. package/dist/modules/camo-backend/src/internal/ElementRegistry.js +61 -0
  18. package/dist/modules/camo-backend/src/internal/ProfileLock.js +85 -0
  19. package/dist/modules/camo-backend/src/internal/SessionManager.js +172 -0
  20. package/dist/modules/camo-backend/src/internal/container-matcher.js +852 -0
  21. package/dist/modules/camo-backend/src/internal/engine-manager.js +258 -0
  22. package/dist/modules/camo-backend/src/internal/fingerprint.js +203 -0
  23. package/dist/modules/camo-backend/src/internal/pageRuntime.js +29 -0
  24. package/dist/modules/camo-backend/src/internal/runtimeInjector.js +30 -0
  25. package/dist/modules/camo-backend/src/internal/state-bus.js +46 -0
  26. package/dist/modules/camo-backend/src/internal/storage-paths.js +36 -0
  27. package/dist/modules/camo-backend/src/internal/ws-server.js +1202 -0
  28. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +423 -0
  29. package/dist/modules/camo-runtime/src/utils/config.mjs +77 -0
  30. package/dist/modules/container-registry/src/index.js +184 -0
  31. package/dist/modules/logging/src/index.js +92 -0
  32. package/dist/modules/operations/src/builtin.js +27 -0
  33. package/dist/modules/operations/src/container-binding.js +75 -0
  34. package/dist/modules/operations/src/executor.js +146 -0
  35. package/dist/modules/operations/src/operations/click.js +167 -0
  36. package/dist/modules/operations/src/operations/extract.js +204 -0
  37. package/dist/modules/operations/src/operations/find-child.js +17 -0
  38. package/dist/modules/operations/src/operations/highlight.js +138 -0
  39. package/dist/modules/operations/src/operations/key.js +61 -0
  40. package/dist/modules/operations/src/operations/navigate.js +148 -0
  41. package/dist/modules/operations/src/operations/scroll.js +126 -0
  42. package/dist/modules/operations/src/operations/type.js +190 -0
  43. package/dist/modules/operations/src/queue.js +100 -0
  44. package/dist/modules/operations/src/registry.js +11 -0
  45. package/dist/modules/operations/src/system/mouse.js +33 -0
  46. package/dist/modules/state/src/atomic-json.js +33 -0
  47. package/dist/modules/workflow/blocks/AnchorVerificationBlock.js +71 -0
  48. package/dist/modules/workflow/blocks/BehaviorRandomizer.js +26 -0
  49. package/dist/modules/workflow/blocks/CallWorkflowBlock.js +38 -0
  50. package/dist/modules/workflow/blocks/CloseDetailBlock.js +209 -0
  51. package/dist/modules/workflow/blocks/CollectBatch.js +137 -0
  52. package/dist/modules/workflow/blocks/CollectCommentsBlock.js +415 -0
  53. package/dist/modules/workflow/blocks/CollectSearchListBlock.js +599 -0
  54. package/dist/modules/workflow/blocks/CollectWeiboPosts.js +229 -0
  55. package/dist/modules/workflow/blocks/DetectPageStateBlock.js +259 -0
  56. package/dist/modules/workflow/blocks/EnsureLoginBlock.js +162 -0
  57. package/dist/modules/workflow/blocks/EnsureSession.js +426 -0
  58. package/dist/modules/workflow/blocks/ErrorClassifier.js +164 -0
  59. package/dist/modules/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  60. package/dist/modules/workflow/blocks/ExpandCommentsBlock.js +1032 -0
  61. package/dist/modules/workflow/blocks/ExtractDetailBlock.js +310 -0
  62. package/dist/modules/workflow/blocks/ExtractPostFields.js +88 -0
  63. package/dist/modules/workflow/blocks/GenerateSmartReplyBlock.js +68 -0
  64. package/dist/modules/workflow/blocks/GoToSearchBlock.js +497 -0
  65. package/dist/modules/workflow/blocks/GracefulFallbackBlock.js +104 -0
  66. package/dist/modules/workflow/blocks/HighlightBlock.js +66 -0
  67. package/dist/modules/workflow/blocks/InitAutoScroll.js +65 -0
  68. package/dist/modules/workflow/blocks/LoadContainerDefinition.js +50 -0
  69. package/dist/modules/workflow/blocks/LoadContainerIndex.js +43 -0
  70. package/dist/modules/workflow/blocks/LocateAndGuardBlock.js +176 -0
  71. package/dist/modules/workflow/blocks/LoginRecoveryBlock.js +242 -0
  72. package/dist/modules/workflow/blocks/MatchContainers.js +64 -0
  73. package/dist/modules/workflow/blocks/MonitoringBlock.js +190 -0
  74. package/dist/modules/workflow/blocks/OpenDetailBlock.js +1240 -0
  75. package/dist/modules/workflow/blocks/OrganizeXhsNotesBlock.js +117 -0
  76. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +270 -0
  77. package/dist/modules/workflow/blocks/PickSinglePost.js +69 -0
  78. package/dist/modules/workflow/blocks/ProgressTracker.js +125 -0
  79. package/dist/modules/workflow/blocks/RecordFixtureBlock.js +44 -0
  80. package/dist/modules/workflow/blocks/RenderMarkdown.js +48 -0
  81. package/dist/modules/workflow/blocks/SaveFile.js +54 -0
  82. package/dist/modules/workflow/blocks/ScrollNextBatch.js +72 -0
  83. package/dist/modules/workflow/blocks/SessionHealthBlock.js +73 -0
  84. package/dist/modules/workflow/blocks/StartBrowserService.js +45 -0
  85. package/dist/modules/workflow/blocks/ValidateContainerDefinition.js +67 -0
  86. package/dist/modules/workflow/blocks/ValidateExtract.js +35 -0
  87. package/dist/modules/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  88. package/dist/modules/workflow/blocks/WaitStable.js +74 -0
  89. package/dist/modules/workflow/blocks/WarmupCommentsBlock.js +120 -0
  90. package/dist/modules/workflow/blocks/WorkflowExecutor.js +156 -0
  91. package/dist/modules/workflow/blocks/XiaohongshuCollectFromLinksBlock.js +1004 -0
  92. package/dist/modules/workflow/blocks/XiaohongshuCollectLinksBlock.js +1049 -0
  93. package/dist/modules/workflow/blocks/XiaohongshuFullCollectBlock.js +782 -0
  94. package/dist/modules/workflow/blocks/helpers/anchorVerify.js +198 -0
  95. package/dist/modules/workflow/blocks/helpers/asyncWorkQueue.js +53 -0
  96. package/dist/modules/workflow/blocks/helpers/commentScroller.js +334 -0
  97. package/dist/modules/workflow/blocks/helpers/commentSectionLocator.js +126 -0
  98. package/dist/modules/workflow/blocks/helpers/containerAnchors.js +301 -0
  99. package/dist/modules/workflow/blocks/helpers/debugArtifacts.js +6 -0
  100. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +29 -0
  101. package/dist/modules/workflow/blocks/helpers/expandCommentsController.js +53 -0
  102. package/dist/modules/workflow/blocks/helpers/expandCommentsExtractor.js +129 -0
  103. package/dist/modules/workflow/blocks/helpers/macosVisionOcrPlugin.js +116 -0
  104. package/dist/modules/workflow/blocks/helpers/mergeXhsMarkdown.js +109 -0
  105. package/dist/modules/workflow/blocks/helpers/openDetailController.js +56 -0
  106. package/dist/modules/workflow/blocks/helpers/openDetailTypes.js +7 -0
  107. package/dist/modules/workflow/blocks/helpers/openDetailViewport.js +474 -0
  108. package/dist/modules/workflow/blocks/helpers/openDetailWaiter.js +104 -0
  109. package/dist/modules/workflow/blocks/helpers/operationLogger.js +195 -0
  110. package/dist/modules/workflow/blocks/helpers/persistedNotes.js +107 -0
  111. package/dist/modules/workflow/blocks/helpers/replyExpander.js +260 -0
  112. package/dist/modules/workflow/blocks/helpers/scrollIntoView.js +138 -0
  113. package/dist/modules/workflow/blocks/helpers/searchExecutor.js +328 -0
  114. package/dist/modules/workflow/blocks/helpers/searchGate.js +46 -0
  115. package/dist/modules/workflow/blocks/helpers/searchPageState.js +164 -0
  116. package/dist/modules/workflow/blocks/helpers/searchResultWaiter.js +64 -0
  117. package/dist/modules/workflow/blocks/helpers/simpleAnchor.js +134 -0
  118. package/dist/modules/workflow/blocks/helpers/smartReply.js +40 -0
  119. package/dist/modules/workflow/blocks/helpers/systemInput.js +635 -0
  120. package/dist/modules/workflow/blocks/helpers/targetCountMode.js +9 -0
  121. package/dist/modules/workflow/blocks/helpers/xhsCliArgs.js +80 -0
  122. package/dist/modules/workflow/blocks/helpers/xhsCommentDom.js +805 -0
  123. package/dist/modules/workflow/blocks/helpers/xhsNoteOrganizer.js +140 -0
  124. package/dist/modules/workflow/blocks/restore/RestorePhaseBlock.js +204 -0
  125. package/dist/modules/workflow/config/workflowRegistry.js +32 -0
  126. package/dist/modules/workflow/definitions/batch-collect-workflow.js +63 -0
  127. package/dist/modules/workflow/definitions/scroll-extract-workflow.js +74 -0
  128. package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow-v2.js +81 -0
  129. package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow.js +57 -0
  130. package/dist/modules/workflow/definitions/xiaohongshu-full-collect-workflow-v3.js +68 -0
  131. package/dist/modules/workflow/definitions/xiaohongshu-note-collect.js +49 -0
  132. package/dist/modules/workflow/definitions/xiaohongshu-phase1-workflow-v3.js +30 -0
  133. package/dist/modules/workflow/definitions/xiaohongshu-phase2-links-workflow-v3.js +40 -0
  134. package/dist/modules/workflow/definitions/xiaohongshu-phase3-collect-workflow-v1.js +54 -0
  135. package/dist/modules/workflow/definitions/xiaohongshu-phase34-from-links-workflow-v3.js +25 -0
  136. package/dist/modules/workflow/src/WeiboEventDrivenWorkflowRunner.js +308 -0
  137. package/dist/modules/workflow/src/context.js +70 -0
  138. package/dist/modules/workflow/src/index.js +5 -0
  139. package/dist/modules/workflow/src/orchestrator.js +230 -0
  140. package/dist/modules/workflow/src/runner.js +55 -0
  141. package/dist/modules/workflow/src/runtime.js +70 -0
  142. package/dist/modules/workflow/workflows/WeiboFeedExtractionWorkflow.js +359 -0
  143. package/dist/modules/workflow/workflows/XiaohongshuLoginWorkflow.js +110 -0
  144. package/dist/modules/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  145. package/dist/modules/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  146. package/dist/modules/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  147. package/dist/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  148. package/dist/modules/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  149. package/dist/modules/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  150. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  151. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  152. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  153. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  154. package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  155. package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  156. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  157. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  158. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  159. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  160. package/dist/modules/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  161. package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  162. package/dist/modules/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  163. package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  164. package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  165. package/dist/modules/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  166. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +42 -0
  167. package/dist/modules/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  168. package/dist/modules/xiaohongshu/app/src/index.js +9 -0
  169. package/dist/modules/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  170. package/dist/modules/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  171. package/dist/services/controller/src/controller.js +1476 -0
  172. package/dist/services/controller/src/index.js +2 -0
  173. package/dist/services/controller/src/payload-normalizer.js +129 -0
  174. package/dist/services/shared/heartbeat.js +120 -0
  175. package/dist/services/shared/lib/errorHandler.js +2 -0
  176. package/dist/services/shared/serviceProcessLogger.js +139 -0
  177. package/dist/services/unified-api/RemoteBrowserSession.js +176 -0
  178. package/dist/services/unified-api/RemoteSessionManager.js +148 -0
  179. package/dist/services/unified-api/container-operations-handler.js +115 -0
  180. package/dist/services/unified-api/server.js +652 -0
  181. package/dist/services/unified-api/state-registry.js +274 -0
  182. package/dist/services/unified-api/task-persistence.js +66 -0
  183. package/dist/services/unified-api/task-state.js +130 -0
  184. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +12 -5
  185. package/modules/xiaohongshu/app/pnpm-lock.yaml +24 -0
  186. package/package.json +37 -9
  187. package/.beads/README.md +0 -81
  188. package/.beads/config.yaml +0 -67
  189. package/.beads/interactions.jsonl +0 -0
  190. package/.beads/issues.jsonl +0 -180
  191. package/.beads/metadata.json +0 -4
  192. package/.claude/settings.local.json +0 -10
  193. package/.github/workflows/ci.yml +0 -55
  194. package/AGENTS.md +0 -253
  195. package/apps/desktop-console/README.md +0 -27
  196. package/apps/desktop-console/package-lock.json +0 -897
  197. package/apps/desktop-console/package.json +0 -20
  198. package/apps/desktop-console/scripts/build-and-install.mjs +0 -19
  199. package/apps/desktop-console/scripts/build.mjs +0 -45
  200. package/apps/desktop-console/scripts/test-preload.mjs +0 -13
  201. package/apps/desktop-console/src/main/config.mts +0 -26
  202. package/apps/desktop-console/src/main/core-daemon-manager.mts +0 -131
  203. package/apps/desktop-console/src/main/desktop-settings.mts +0 -267
  204. package/apps/desktop-console/src/main/heartbeat-watchdog.mts +0 -50
  205. package/apps/desktop-console/src/main/heartbeat-watchdog.test.mts +0 -68
  206. package/apps/desktop-console/src/main/index-streaming.test.mts +0 -20
  207. package/apps/desktop-console/src/main/index.mts +0 -980
  208. package/apps/desktop-console/src/main/profile-store.mts +0 -239
  209. package/apps/desktop-console/src/main/profile-store.test.mts +0 -54
  210. package/apps/desktop-console/src/main/state-bridge.mts +0 -114
  211. package/apps/desktop-console/src/main/task-state-types.ts +0 -32
  212. package/apps/desktop-console/src/renderer/hooks/use-task-state.mts +0 -120
  213. package/apps/desktop-console/src/renderer/index.mts +0 -133
  214. package/apps/desktop-console/src/renderer/index.test.mts +0 -34
  215. package/apps/desktop-console/src/renderer/path-helpers.mts +0 -46
  216. package/apps/desktop-console/src/renderer/path-helpers.test.mts +0 -14
  217. package/apps/desktop-console/src/renderer/tabs/debug.mts +0 -48
  218. package/apps/desktop-console/src/renderer/tabs/debug.test.mts +0 -22
  219. package/apps/desktop-console/src/renderer/tabs/logs.mts +0 -421
  220. package/apps/desktop-console/src/renderer/tabs/logs.test.mts +0 -27
  221. package/apps/desktop-console/src/renderer/tabs/preflight.mts +0 -486
  222. package/apps/desktop-console/src/renderer/tabs/preflight.test.mts +0 -33
  223. package/apps/desktop-console/src/renderer/tabs/profile-pool.mts +0 -213
  224. package/apps/desktop-console/src/renderer/tabs/results.mts +0 -171
  225. package/apps/desktop-console/src/renderer/tabs/run.test.mts +0 -63
  226. package/apps/desktop-console/src/renderer/tabs/runtime.mts +0 -151
  227. package/apps/desktop-console/src/renderer/tabs/settings.mts +0 -146
  228. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/account-flow.mts +0 -486
  229. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/guide-browser-check.mts +0 -56
  230. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/helpers.mts +0 -262
  231. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/layout-block.mts +0 -430
  232. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/live-stats.mts +0 -847
  233. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/run-flow.mts +0 -443
  234. package/apps/desktop-console/src/renderer/tabs/xiaohongshu-state.mts +0 -425
  235. package/apps/desktop-console/src/renderer/tabs/xiaohongshu.mts +0 -497
  236. package/apps/desktop-console/src/renderer/tabs/xiaohongshu.test.mts +0 -291
  237. package/apps/desktop-console/src/renderer/ui-components.mts +0 -31
  238. package/docs/README_camoufox_chinese.md +0 -141
  239. package/docs/USAGE_V3.md +0 -163
  240. package/docs/arch/OCR_MACOS_PLUGIN.md +0 -39
  241. package/docs/arch/PORTS.md +0 -40
  242. package/docs/arch/REGRESSION_CHECKLIST.md +0 -121
  243. package/docs/arch/SEARCH_GATE.md +0 -224
  244. package/docs/arch/VIEWPORT_SAFETY.md +0 -182
  245. package/docs/arch/XIAOHONGSHU_OFFLINE_MOCK_DESIGN.md +0 -267
  246. package/docs/xiaohongshu-container-driven-summary.md +0 -221
  247. package/docs/xiaohongshu-full-collect-runbook.md +0 -134
  248. package/docs/xiaohongshu-next-steps.md +0 -228
  249. package/docs/xiaohongshu-quickstart.md +0 -73
  250. package/docs/xiaohongshu-workflow-summary.md +0 -227
  251. package/modules/container-registry/tests/container-registry.test.ts +0 -16
  252. package/modules/logging/tests/logging.test.ts +0 -38
  253. package/modules/operations/tests/operations.test.ts +0 -22
  254. package/modules/operations/tests/viewport-filter.test.ts +0 -161
  255. package/modules/operations/tests/visible-only.test.ts +0 -250
  256. package/modules/session-manager/tests/session-manager.test.ts +0 -23
  257. package/modules/state/src/atomic-json.test.ts +0 -30
  258. package/modules/state/src/paths.test.ts +0 -59
  259. package/modules/state/src/xiaohongshu-collect-state.test.ts +0 -259
  260. package/modules/workflow/blocks/AnchorVerificationBlock.d.ts.map +0 -1
  261. package/modules/workflow/blocks/AnchorVerificationBlock.js.map +0 -1
  262. package/modules/workflow/blocks/DetectPageStateBlock.d.ts.map +0 -1
  263. package/modules/workflow/blocks/DetectPageStateBlock.js.map +0 -1
  264. package/modules/workflow/blocks/ErrorRecoveryBlock.d.ts.map +0 -1
  265. package/modules/workflow/blocks/ErrorRecoveryBlock.js.map +0 -1
  266. package/modules/workflow/blocks/WaitSearchPermitBlock.d.ts.map +0 -1
  267. package/modules/workflow/blocks/WaitSearchPermitBlock.js.map +0 -1
  268. package/modules/workflow/blocks/helpers/containerAnchors.d.ts.map +0 -1
  269. package/modules/workflow/blocks/helpers/containerAnchors.js.map +0 -1
  270. package/modules/workflow/blocks/helpers/downloadPaths.test.ts +0 -62
  271. package/modules/workflow/blocks/helpers/mergeXhsMarkdown.test.ts +0 -121
  272. package/modules/workflow/blocks/helpers/operationLogger.d.ts.map +0 -1
  273. package/modules/workflow/blocks/helpers/operationLogger.js.map +0 -1
  274. package/modules/workflow/blocks/helpers/persistedNotes.test.ts +0 -268
  275. package/modules/workflow/blocks/helpers/searchPageState.d.ts.map +0 -1
  276. package/modules/workflow/blocks/helpers/searchPageState.js.map +0 -1
  277. package/modules/workflow/blocks/helpers/targetCountMode.test.ts +0 -29
  278. package/modules/workflow/blocks/helpers/xhsCliArgs.test.ts +0 -75
  279. package/modules/workflow/tests/smartReply.test.ts +0 -32
  280. package/modules/xiaohongshu/app/src/blocks/Phase3Interact.matcher.test.ts +0 -33
  281. package/modules/xiaohongshu/app/src/utils/__tests__/checkpoints.test.ts +0 -141
  282. package/modules/xiaohongshu/app/tests/commentMatchDsl.test.ts +0 -50
  283. package/modules/xiaohongshu/app/tests/commentMatcher.test.ts +0 -46
  284. package/modules/xiaohongshu/app/tests/sharding.test.ts +0 -31
  285. package/package-scripts.json +0 -8
  286. package/runtime/infra/utils/README.md +0 -13
  287. package/runtime/infra/utils/scripts/README.md +0 -0
  288. package/runtime/infra/utils/scripts/development/eval-in-session.mjs +0 -40
  289. package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +0 -35
  290. package/runtime/infra/utils/scripts/service/kill-port.mjs +0 -24
  291. package/runtime/infra/utils/scripts/service/start-api.mjs +0 -39
  292. package/runtime/infra/utils/scripts/service/start-browser-service.mjs +0 -106
  293. package/runtime/infra/utils/scripts/service/stop-api.mjs +0 -18
  294. package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +0 -104
  295. package/runtime/infra/utils/scripts/test-services.mjs +0 -94
  296. package/services/shared/heartbeat.test.ts +0 -102
  297. package/services/unified-api/__tests__/task-state.test.ts +0 -95
  298. package/sitecustomize.py +0 -19
  299. package/tests/README.md +0 -194
  300. package/tests/e2e/workflows/weibo-feed-extraction.test.ts +0 -171
  301. package/tests/fixtures/data/container-definitions.json +0 -67
  302. package/tests/fixtures/pages/simple-page.html +0 -69
  303. package/tests/integration/01-test-container-match.mjs +0 -188
  304. package/tests/integration/02-test-dom-branch.mjs +0 -161
  305. package/tests/integration/03-test-container-operation-system.mjs +0 -91
  306. package/tests/integration/05-test-container-lifecycle-events.mjs +0 -224
  307. package/tests/integration/05-test-container-lifecycle-with-events.mjs +0 -250
  308. package/tests/integration/06-test-container-dom-tree-drawing.mjs +0 -256
  309. package/tests/integration/07-test-weibo-container-lifecycle.mjs +0 -355
  310. package/tests/integration/08-test-weibo-feed-workflow.test.mjs +0 -164
  311. package/tests/integration/10-test-visual-analyzer.mjs +0 -312
  312. package/tests/integration/11-test-visual-loop.mjs +0 -284
  313. package/tests/integration/12-test-simple-visual-loop.mjs +0 -242
  314. package/tests/integration/13-test-visual-robust.mjs +0 -185
  315. package/tests/integration/14-test-visual-highlight-loop.mjs +0 -271
  316. package/tests/integration/inspect-page.mjs +0 -50
  317. package/tests/integration/run-all-tests.mjs +0 -95
  318. package/tests/patch_verification/CODEX_PATCH_TEST.md +0 -103
  319. package/tests/patch_verification/PHASE2_ANALYSIS.md +0 -179
  320. package/tests/patch_verification/PHASE2_OPTIMIZATION_REPORT.md +0 -55
  321. package/tests/patch_verification/PHASE2_TO_PHASE4_SUMMARY.md +0 -126
  322. package/tests/patch_verification/QUICK_TEST_SEQUENCE.md +0 -262
  323. package/tests/patch_verification/README.md +0 -143
  324. package/tests/patch_verification/RUN_TESTS.md +0 -60
  325. package/tests/patch_verification/TEST_EXECUTION.md +0 -99
  326. package/tests/patch_verification/TEST_PLAN.md +0 -328
  327. package/tests/patch_verification/TEST_RESULTS.md +0 -34
  328. package/tests/patch_verification/TOOL_TEST_PLAN.md +0 -48
  329. package/tests/patch_verification/run-tool-test.mjs +0 -121
  330. package/tests/patch_verification/temp_test_files/test01.txt +0 -1
  331. package/tests/patch_verification/temp_test_files/test02.txt +0 -3
  332. package/tests/patch_verification/temp_test_files/test02_gnu.txt +0 -3
  333. package/tests/patch_verification/temp_test_files/test03.txt +0 -1
  334. package/tests/patch_verification/temp_test_files/test03_multiline.txt +0 -5
  335. package/tests/patch_verification/temp_test_files/test04_function.ts +0 -5
  336. package/tests/patch_verification/temp_test_files/test05_import.ts +0 -4
  337. package/tests/patch_verification/temp_test_files/test06_special_chars.txt +0 -4
  338. package/tests/patch_verification/temp_test_files/test07_indentation.ts +0 -5
  339. package/tests/patch_verification/temp_test_files/test08_mismatch.txt +0 -1
  340. package/tests/patch_verification/temp_test_files/test_add_02.txt +0 -3
  341. package/tests/patch_verification/temp_test_files/test_simple.txt +0 -1
  342. package/tests/runner/TestReporter.mjs +0 -57
  343. package/tests/runner/TestRunner.mjs +0 -244
  344. package/tests/unit/commands/profile.test.mjs +0 -10
  345. package/tests/unit/container/change-notifier.test.mjs +0 -181
  346. package/tests/unit/lifecycle/session-registry.test.mjs +0 -135
  347. package/tests/unit/operations/registry.test.ts +0 -73
  348. package/tests/unit/utils/browser-service.test.mjs +0 -153
  349. package/tests/unit/utils/config.test.mjs +0 -166
  350. package/tests/unit/utils/fingerprint.test.mjs +0 -166
  351. package/tsconfig.json +0 -31
  352. package/tsconfig.services.json +0 -26
  353. /package/apps/desktop-console/{src → dist}/renderer/index.html +0 -0
  354. /package/apps/desktop-console/{src/renderer/tabs → dist/renderer}/run.mts +0 -0
@@ -0,0 +1,1618 @@
1
+ // src/main/index.mts
2
+ import electron from "electron";
3
+ import { spawn as spawn2 } from "node:child_process";
4
+ import os4 from "node:os";
5
+ import path5 from "node:path";
6
+ import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL3 } from "node:url";
7
+ import { mkdirSync, promises as fs3 } from "node:fs";
8
+
9
+ // src/main/desktop-settings.mts
10
+ import { promises as fs } from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ import { pathToFileURL } from "node:url";
14
+ function resolveHomeDir() {
15
+ const homeDir = process.platform === "win32" ? process.env.USERPROFILE || os.homedir() || "" : process.env.HOME || os.homedir() || "";
16
+ if (!homeDir) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7528\u6237\u4E3B\u76EE\u5F55\uFF1AHOME/USERPROFILE \u672A\u8BBE\u7F6E");
17
+ return homeDir;
18
+ }
19
+ function resolveLegacySettingsPath() {
20
+ return path.join(resolveHomeDir(), ".webauto", "ui-settings.console.json");
21
+ }
22
+ function resolveDefaultDownloadRoot() {
23
+ if (process.platform === "win32") return "D:\\webauto";
24
+ return path.join(resolveHomeDir(), ".webauto", "download");
25
+ }
26
+ function normalizeAiReplyConfig(raw) {
27
+ if (!raw || typeof raw !== "object") {
28
+ return {
29
+ enabled: false,
30
+ provider: "openai-compatible",
31
+ baseUrl: "http://127.0.0.1:5520",
32
+ apiKey: "",
33
+ model: "iflow.glm-5",
34
+ temperature: 0.7,
35
+ maxChars: 20,
36
+ timeoutMs: 25e3,
37
+ stylePreset: "friendly",
38
+ styleCustom: ""
39
+ };
40
+ }
41
+ const stylePresets = ["friendly", "professional", "humorous", "concise", "custom"];
42
+ const preset = String(raw.stylePreset || "friendly");
43
+ return {
44
+ enabled: Boolean(raw.enabled ?? false),
45
+ provider: "openai-compatible",
46
+ baseUrl: String(raw.baseUrl || "http://127.0.0.1:5520"),
47
+ apiKey: String(raw.apiKey || ""),
48
+ model: String(raw.model || "iflow.glm-5"),
49
+ temperature: Math.max(0, Math.min(2, Number(raw.temperature ?? 0.7))),
50
+ maxChars: Math.max(5, Math.min(500, Math.floor(Number(raw.maxChars ?? 20)))),
51
+ timeoutMs: Math.max(5e3, Math.floor(Number(raw.timeoutMs ?? 25e3))),
52
+ stylePreset: stylePresets.includes(preset) ? preset : "friendly",
53
+ styleCustom: String(raw.styleCustom || "")
54
+ };
55
+ }
56
+ function normalizeSettings(defaults, input) {
57
+ const aliasesRaw = input.profileAliases ?? defaults.profileAliases ?? {};
58
+ const aliases = {};
59
+ if (aliasesRaw && typeof aliasesRaw === "object") {
60
+ for (const [k, v] of Object.entries(aliasesRaw)) {
61
+ const key = String(k || "").trim();
62
+ const val = String(v || "").trim();
63
+ if (!key) continue;
64
+ if (!val) continue;
65
+ aliases[key] = val;
66
+ }
67
+ }
68
+ const merged = {
69
+ unifiedApiUrl: String(input.unifiedApiUrl || defaults.unifiedApiUrl || "http://127.0.0.1:7701"),
70
+ browserServiceUrl: String(input.browserServiceUrl || defaults.browserServiceUrl || "http://127.0.0.1:7704"),
71
+ searchGateUrl: String(input.searchGateUrl || defaults.searchGateUrl || "http://127.0.0.1:7790"),
72
+ downloadRoot: String(input.downloadRoot || defaults.downloadRoot || resolveDefaultDownloadRoot()),
73
+ defaultEnv: String(input.defaultEnv || defaults.defaultEnv || "debug") === "prod" ? "prod" : "debug",
74
+ defaultKeyword: String(input.defaultKeyword ?? defaults.defaultKeyword ?? ""),
75
+ defaultTarget: Math.max(1, Math.floor(Number(input.defaultTarget ?? defaults.defaultTarget ?? 20) || 20)),
76
+ defaultDryRun: Boolean(input.defaultDryRun ?? defaults.defaultDryRun ?? false),
77
+ timeouts: {
78
+ loginTimeoutSec: Math.max(
79
+ 30,
80
+ Math.floor(
81
+ Number(
82
+ input.timeouts?.loginTimeoutSec ?? defaults.timeouts?.loginTimeoutSec ?? 900
83
+ )
84
+ )
85
+ ),
86
+ cmdTimeoutSec: Math.max(
87
+ 0,
88
+ Math.floor(
89
+ Number(
90
+ input.timeouts?.cmdTimeoutSec ?? defaults.timeouts?.cmdTimeoutSec ?? 0
91
+ )
92
+ )
93
+ )
94
+ },
95
+ profileAliases: aliases,
96
+ profileColors: normalizeColorMap(input.profileColors ?? defaults.profileColors ?? {}),
97
+ aiReply: normalizeAiReplyConfig(input.aiReply ?? defaults.aiReply ?? {}),
98
+ lastCrawlConfig: input.lastCrawlConfig ?? defaults.lastCrawlConfig ?? void 0
99
+ };
100
+ return merged;
101
+ }
102
+ function normalizeColorMap(raw) {
103
+ const out = {};
104
+ if (!raw || typeof raw !== "object") return out;
105
+ for (const [k, v] of Object.entries(raw)) {
106
+ const key = String(k || "").trim();
107
+ const val = String(v || "").trim();
108
+ if (!key || !val) continue;
109
+ if (!/^#[0-9a-fA-F]{6}$/.test(val)) continue;
110
+ out[key] = val;
111
+ }
112
+ return out;
113
+ }
114
+ async function readDefaultSettingsFromAppRoot(appRoot) {
115
+ const defaultsPath = path.join(appRoot, "default-settings.json");
116
+ let raw = {};
117
+ try {
118
+ const text = await fs.readFile(defaultsPath, "utf8");
119
+ raw = JSON.parse(text) || {};
120
+ } catch {
121
+ raw = {};
122
+ }
123
+ const base = {
124
+ unifiedApiUrl: raw.unifiedApiUrl,
125
+ browserServiceUrl: raw.browserServiceUrl,
126
+ searchGateUrl: raw.searchGateUrl,
127
+ defaultEnv: raw.defaultEnv,
128
+ defaultKeyword: raw.defaultKeyword,
129
+ timeouts: raw.timeouts
130
+ };
131
+ const downloadRoot = typeof raw.downloadRoot === "string" ? String(raw.downloadRoot) : process.platform === "win32" && typeof raw.downloadRootWindows === "string" ? String(raw.downloadRootWindows) : process.platform !== "win32" && typeof raw.downloadRootPosix === "string" ? String(raw.downloadRootPosix) : Array.isArray(raw.downloadRootParts) ? path.join(resolveHomeDir(), ...raw.downloadRootParts.map((x) => String(x))) : resolveDefaultDownloadRoot();
132
+ return normalizeSettings({ ...base, downloadRoot }, {});
133
+ }
134
+ async function readLegacySettings() {
135
+ const legacyPath = resolveLegacySettingsPath();
136
+ try {
137
+ const text = await fs.readFile(legacyPath, "utf8");
138
+ const parsed = JSON.parse(text) || {};
139
+ if (!parsed || typeof parsed !== "object") return null;
140
+ return parsed;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+ async function tryLoadConfigApi(repoRoot) {
146
+ const distEntry = path.join(repoRoot, "dist", "modules", "config", "index.js");
147
+ try {
148
+ await fs.access(distEntry);
149
+ } catch {
150
+ return null;
151
+ }
152
+ try {
153
+ const mod = await import(pathToFileURL(distEntry).href);
154
+ if (!mod?.loadConfig || !mod?.saveConfig || !mod?.loader?.ensureExists) return null;
155
+ return mod;
156
+ } catch {
157
+ return null;
158
+ }
159
+ }
160
+ async function readDesktopConsoleSettings(input) {
161
+ const defaults = await readDefaultSettingsFromAppRoot(input.appRoot);
162
+ const configApi = await tryLoadConfigApi(input.repoRoot);
163
+ if (!configApi) {
164
+ const legacy2 = await readLegacySettings();
165
+ return normalizeSettings(defaults, legacy2 || {});
166
+ }
167
+ await configApi.loader.ensureExists();
168
+ const cfg = await configApi.loadConfig();
169
+ const fromConfig = cfg && cfg.desktopConsole ? cfg.desktopConsole : null;
170
+ if (fromConfig) {
171
+ return normalizeSettings(defaults, fromConfig);
172
+ }
173
+ const legacy = await readLegacySettings();
174
+ if (legacy) {
175
+ const migrated = normalizeSettings(defaults, legacy);
176
+ const nextCfg2 = configApi.loader.merge(cfg, { desktopConsole: migrated });
177
+ await configApi.saveConfig(nextCfg2);
178
+ return migrated;
179
+ }
180
+ const seeded = normalizeSettings(defaults, {});
181
+ const nextCfg = configApi.loader.merge(cfg, { desktopConsole: seeded });
182
+ await configApi.saveConfig(nextCfg);
183
+ return seeded;
184
+ }
185
+ async function writeDesktopConsoleSettings(input, next) {
186
+ const current = await readDesktopConsoleSettings(input);
187
+ const defaults = await readDefaultSettingsFromAppRoot(input.appRoot);
188
+ const merged = normalizeSettings(defaults, { ...current, ...next, timeouts: { ...current.timeouts, ...next.timeouts || {} } });
189
+ const configApi = await tryLoadConfigApi(input.repoRoot);
190
+ if (configApi) {
191
+ await configApi.loader.ensureExists();
192
+ const cfg = await configApi.loadConfig();
193
+ const nextCfg = configApi.loader.merge(cfg, { desktopConsole: merged });
194
+ await configApi.saveConfig(nextCfg);
195
+ return merged;
196
+ }
197
+ const legacyPath = resolveLegacySettingsPath();
198
+ await fs.mkdir(path.dirname(legacyPath), { recursive: true });
199
+ await fs.writeFile(legacyPath, JSON.stringify(merged, null, 2), "utf8");
200
+ return merged;
201
+ }
202
+ async function saveCrawlConfig(input, config) {
203
+ const current = await readDesktopConsoleSettings(input);
204
+ const next = { ...current, lastCrawlConfig: config };
205
+ await writeDesktopConsoleSettings(input, next);
206
+ }
207
+ async function loadCrawlConfig(input) {
208
+ const current = await readDesktopConsoleSettings(input);
209
+ return current.lastCrawlConfig || null;
210
+ }
211
+ async function exportConfigToFile(filePath, config) {
212
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
213
+ const content = JSON.stringify(config, null, 2);
214
+ const isWindows = process.platform === "win32";
215
+ if (isWindows) {
216
+ const BOM = "\uFEFF";
217
+ await fs.writeFile(filePath, BOM + content, "utf8");
218
+ } else {
219
+ await fs.writeFile(filePath, content, "utf8");
220
+ }
221
+ return { ok: true, path: filePath };
222
+ }
223
+ async function importConfigFromFile(filePath) {
224
+ const content = await fs.readFile(filePath, "utf8");
225
+ const cleanContent = content.replace(/^\uFEFF/, "");
226
+ const config = JSON.parse(cleanContent);
227
+ return { ok: true, config };
228
+ }
229
+
230
+ // src/main/core-daemon-manager.mts
231
+ import { spawn } from "child_process";
232
+ import path2 from "path";
233
+ import { fileURLToPath } from "url";
234
+ var REPO_ROOT = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "../../../..");
235
+ var CORE_HEALTH_URLS = ["http://127.0.0.1:7701/health", "http://127.0.0.1:7704/health"];
236
+ var START_API_SCRIPT = path2.join(REPO_ROOT, "runtime", "infra", "utils", "scripts", "service", "start-api.mjs");
237
+ var STOP_API_SCRIPT = path2.join(REPO_ROOT, "runtime", "infra", "utils", "scripts", "service", "stop-api.mjs");
238
+ function sleep(ms) {
239
+ return new Promise((resolve) => setTimeout(resolve, ms));
240
+ }
241
+ function resolveNodeBin() {
242
+ const explicit = String(process.env.WEBAUTO_NODE_BIN || "").trim();
243
+ if (explicit) return explicit;
244
+ const npmNode = String(process.env.npm_node_execpath || "").trim();
245
+ if (npmNode) return npmNode;
246
+ return process.platform === "win32" ? "node.exe" : "node";
247
+ }
248
+ function resolveNpxBin() {
249
+ return process.platform === "win32" ? "npx.cmd" : "npx";
250
+ }
251
+ async function checkHttpHealth(url) {
252
+ try {
253
+ const res = await fetch(url, { signal: AbortSignal.timeout(1e3) });
254
+ return res.ok;
255
+ } catch {
256
+ return false;
257
+ }
258
+ }
259
+ async function areCoreServicesHealthy() {
260
+ const health = await Promise.all(CORE_HEALTH_URLS.map((url) => checkHttpHealth(url)));
261
+ return health.every(Boolean);
262
+ }
263
+ async function runNodeScript(scriptPath, timeoutMs) {
264
+ return new Promise((resolve) => {
265
+ const nodeBin = resolveNodeBin();
266
+ const child = spawn(nodeBin, [scriptPath], {
267
+ cwd: REPO_ROOT,
268
+ stdio: "ignore",
269
+ windowsHide: true,
270
+ detached: false,
271
+ env: {
272
+ ...process.env,
273
+ BROWSER_SERVICE_AUTO_EXIT: "0"
274
+ }
275
+ });
276
+ const timer = setTimeout(() => {
277
+ try {
278
+ child.kill("SIGTERM");
279
+ } catch {
280
+ }
281
+ resolve(false);
282
+ }, timeoutMs);
283
+ child.once("error", () => {
284
+ clearTimeout(timer);
285
+ resolve(false);
286
+ });
287
+ child.once("exit", (code) => {
288
+ clearTimeout(timer);
289
+ resolve(code === 0);
290
+ });
291
+ });
292
+ }
293
+ async function runCommand(command, args, timeoutMs) {
294
+ return new Promise((resolve) => {
295
+ const child = spawn(command, args, {
296
+ cwd: REPO_ROOT,
297
+ stdio: "ignore",
298
+ windowsHide: true,
299
+ detached: false,
300
+ env: {
301
+ ...process.env
302
+ }
303
+ });
304
+ const timer = setTimeout(() => {
305
+ try {
306
+ child.kill("SIGTERM");
307
+ } catch {
308
+ }
309
+ resolve(false);
310
+ }, timeoutMs);
311
+ child.once("error", () => {
312
+ clearTimeout(timer);
313
+ resolve(false);
314
+ });
315
+ child.once("exit", (code) => {
316
+ clearTimeout(timer);
317
+ resolve(code === 0);
318
+ });
319
+ });
320
+ }
321
+ async function startCoreDaemon() {
322
+ if (await areCoreServicesHealthy()) return true;
323
+ const startedApi = await runNodeScript(START_API_SCRIPT, 4e4);
324
+ if (!startedApi) {
325
+ console.error("[CoreDaemonManager] Failed to start unified API service");
326
+ return false;
327
+ }
328
+ const startedBrowser = await runCommand(resolveNpxBin(), ["--yes", "@web-auto/camo", "init"], 4e4);
329
+ if (!startedBrowser) {
330
+ console.error("[CoreDaemonManager] Failed to start camo browser backend");
331
+ return false;
332
+ }
333
+ for (let i = 0; i < 20; i += 1) {
334
+ if (await areCoreServicesHealthy()) return true;
335
+ await sleep(300);
336
+ }
337
+ console.error("[CoreDaemonManager] Services still unhealthy after start");
338
+ return false;
339
+ }
340
+ async function stopCoreDaemon() {
341
+ const stoppedApi = await runNodeScript(STOP_API_SCRIPT, 2e4);
342
+ if (!stoppedApi) {
343
+ console.error("[CoreDaemonManager] Failed to stop core services");
344
+ return false;
345
+ }
346
+ return true;
347
+ }
348
+
349
+ // src/main/profile-store.mts
350
+ import os2 from "node:os";
351
+ import path3 from "node:path";
352
+ import { promises as fs2 } from "node:fs";
353
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
354
+ function resolveHomeDir2(opts) {
355
+ if (opts.homeDir) return opts.homeDir;
356
+ const homeDir = process.platform === "win32" ? process.env.USERPROFILE || os2.homedir() || "" : process.env.HOME || os2.homedir() || "";
357
+ if (!homeDir) throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7528\u6237\u4E3B\u76EE\u5F55\uFF1AHOME/USERPROFILE \u672A\u8BBE\u7F6E");
358
+ return homeDir;
359
+ }
360
+ function resolvePortableRoot(opts) {
361
+ const root = String(process.env.WEBAUTO_PORTABLE_ROOT || process.env.WEBAUTO_ROOT || "").trim();
362
+ if (!root) return "";
363
+ return path3.join(root, ".webauto");
364
+ }
365
+ function isWithinDir(root, target) {
366
+ const rel = path3.relative(root, target);
367
+ return rel === "" || !rel.startsWith("..") && !path3.isAbsolute(rel);
368
+ }
369
+ function validateProfileId(profileId) {
370
+ const id = String(profileId || "").trim();
371
+ if (!id) throw new Error("profileId \u4E0D\u80FD\u4E3A\u7A7A");
372
+ if (id === "." || id === "..") throw new Error("profileId \u975E\u6CD5");
373
+ if (/[\\/]/.test(id)) throw new Error("profileId \u4E0D\u80FD\u5305\u542B\u8DEF\u5F84\u5206\u9694\u7B26");
374
+ return id;
375
+ }
376
+ function resolveRoots(opts) {
377
+ const homeDir = resolveHomeDir2(opts);
378
+ const envProfiles = String(process.env.WEBAUTO_PATHS_PROFILES || "").trim();
379
+ const envFingerprints = String(process.env.WEBAUTO_PATHS_FINGERPRINTS || "").trim();
380
+ const portable = resolvePortableRoot(opts);
381
+ return {
382
+ profilesRoot: envProfiles || (portable ? path3.join(portable, "profiles") : path3.join(homeDir, ".webauto", "profiles")),
383
+ fingerprintsRoot: envFingerprints || (portable ? path3.join(portable, "fingerprints") : path3.join(homeDir, ".webauto", "fingerprints"))
384
+ };
385
+ }
386
+ function resolveProfileDir(opts, profileId) {
387
+ const { profilesRoot } = resolveRoots(opts);
388
+ const profileDir = path3.join(profilesRoot, profileId);
389
+ if (!isWithinDir(profilesRoot, profileDir)) throw new Error("unsafe profile path");
390
+ return { profilesRoot, profileDir };
391
+ }
392
+ function resolveFingerprintPath(opts, profileId) {
393
+ const { fingerprintsRoot } = resolveRoots(opts);
394
+ const fingerprintPath = path3.join(fingerprintsRoot, `${profileId}.json`);
395
+ if (!isWithinDir(fingerprintsRoot, fingerprintPath)) throw new Error("unsafe fingerprint path");
396
+ return { fingerprintsRoot, fingerprintPath };
397
+ }
398
+ function createProfileStore(opts) {
399
+ let cachedFingerprintMod = null;
400
+ async function getFingerprintModule() {
401
+ if (cachedFingerprintMod) return cachedFingerprintMod;
402
+ const p = path3.join(opts.repoRoot, "dist", "modules", "camo-backend", "src", "internal", "fingerprint.js");
403
+ cachedFingerprintMod = await import(pathToFileURL2(p).href);
404
+ return cachedFingerprintMod;
405
+ }
406
+ async function listProfiles() {
407
+ const { profilesRoot } = resolveRoots(opts);
408
+ const entries = [];
409
+ try {
410
+ const dirs = await fs2.readdir(profilesRoot, { withFileTypes: true });
411
+ for (const ent of dirs) {
412
+ if (!ent.isDirectory()) continue;
413
+ const name = ent.name;
414
+ if (!name || name.startsWith(".")) continue;
415
+ entries.push(name);
416
+ }
417
+ } catch {
418
+ }
419
+ entries.sort((a, b) => a.localeCompare(b));
420
+ return { ok: true, root: profilesRoot, profiles: entries };
421
+ }
422
+ async function scanProfiles() {
423
+ const { profilesRoot, fingerprintsRoot } = resolveRoots(opts);
424
+ const entries = [];
425
+ const dirs = await fs2.readdir(profilesRoot, { withFileTypes: true }).catch(() => []);
426
+ for (const ent of dirs) {
427
+ if (!ent.isDirectory()) continue;
428
+ const profileId = ent.name;
429
+ if (!profileId || profileId.startsWith(".")) continue;
430
+ const profileDir = path3.join(profilesRoot, profileId);
431
+ const profileStat = await fs2.stat(profileDir).catch(() => null);
432
+ const fingerprintPath = path3.join(fingerprintsRoot, `${profileId}.json`);
433
+ const fpStat = await fs2.stat(fingerprintPath).catch(() => null);
434
+ let fingerprint = null;
435
+ if (fpStat?.isFile()) {
436
+ try {
437
+ const raw = await fs2.readFile(fingerprintPath, "utf8");
438
+ const parsed = JSON.parse(raw || "{}");
439
+ if (parsed && typeof parsed === "object") fingerprint = parsed;
440
+ } catch {
441
+ fingerprint = null;
442
+ }
443
+ }
444
+ entries.push({
445
+ profileId,
446
+ profileDir,
447
+ profileMtimeMs: profileStat?.mtimeMs || 0,
448
+ fingerprintPath,
449
+ fingerprintMtimeMs: fpStat?.mtimeMs || null,
450
+ fingerprint
451
+ });
452
+ }
453
+ entries.sort((a, b) => {
454
+ if (a.profileMtimeMs !== b.profileMtimeMs) return b.profileMtimeMs - a.profileMtimeMs;
455
+ return a.profileId.localeCompare(b.profileId);
456
+ });
457
+ return { ok: true, profilesRoot, fingerprintsRoot, entries };
458
+ }
459
+ async function profileCreate(input) {
460
+ const profileId = validateProfileId(input?.profileId);
461
+ const { profileDir } = resolveProfileDir(opts, profileId);
462
+ await fs2.mkdir(profileDir, { recursive: true });
463
+ return { ok: true, profileId, profileDir };
464
+ }
465
+ async function profileDelete(input) {
466
+ const profileId = validateProfileId(input?.profileId);
467
+ const { profileDir } = resolveProfileDir(opts, profileId);
468
+ await fs2.rm(profileDir, { recursive: true, force: true });
469
+ if (input?.deleteFingerprint) {
470
+ const { fingerprintPath } = resolveFingerprintPath(opts, profileId);
471
+ await fs2.rm(fingerprintPath, { force: true });
472
+ }
473
+ return { ok: true, profileId };
474
+ }
475
+ async function fingerprintDelete(input) {
476
+ const profileId = validateProfileId(input?.profileId);
477
+ const { fingerprintPath } = resolveFingerprintPath(opts, profileId);
478
+ await fs2.rm(fingerprintPath, { force: true });
479
+ return { ok: true, profileId };
480
+ }
481
+ async function fingerprintRegenerate(input) {
482
+ const profileId = validateProfileId(input?.profileId);
483
+ const mod = await getFingerprintModule();
484
+ const platform = input?.platform === "windows" || input?.platform === "macos" ? input.platform : null;
485
+ const { fingerprintPath } = resolveFingerprintPath(opts, profileId);
486
+ const fingerprint = mod.generateFingerprint(profileId, { platform });
487
+ const saved = await mod.saveFingerprint(fingerprintPath, fingerprint);
488
+ if (!saved) throw new Error("failed to save fingerprint");
489
+ return {
490
+ ok: true,
491
+ profileId,
492
+ fingerprintPath,
493
+ fingerprint: {
494
+ platform: fingerprint?.platform,
495
+ originalPlatform: fingerprint?.originalPlatform,
496
+ osVersion: fingerprint?.osVersion,
497
+ userAgent: fingerprint?.userAgent,
498
+ viewport: fingerprint?.viewport,
499
+ fingerprintSalt: fingerprint?.fingerprintSalt
500
+ }
501
+ };
502
+ }
503
+ return {
504
+ listProfiles,
505
+ scanProfiles,
506
+ profileCreate,
507
+ profileDelete,
508
+ fingerprintDelete,
509
+ fingerprintRegenerate
510
+ };
511
+ }
512
+
513
+ // src/main/heartbeat-watchdog.mts
514
+ var DEFAULT_UI_HEARTBEAT_TIMEOUT_MS = 5 * 6e4;
515
+ function resolveUiHeartbeatTimeoutMs(env = process.env) {
516
+ const raw = Number.parseInt(String(env.WEBAUTO_UI_HEARTBEAT_TIMEOUT_MS || "").trim(), 10);
517
+ if (Number.isFinite(raw) && raw >= 3e4 && raw <= 60 * 6e4) {
518
+ return raw;
519
+ }
520
+ return DEFAULT_UI_HEARTBEAT_TIMEOUT_MS;
521
+ }
522
+ function decideWatchdogAction(input) {
523
+ if (input.staleMs <= input.timeoutMs) {
524
+ return { action: "none", nextHandled: false, reason: "healthy" };
525
+ }
526
+ if (input.alreadyHandled) {
527
+ return { action: "none", nextHandled: true, reason: "already_handled" };
528
+ }
529
+ if (input.uiOperational) {
530
+ return { action: "none", nextHandled: true, reason: "stale_ui_alive" };
531
+ }
532
+ if (input.runCount > 0) {
533
+ return { action: "kill_runs", nextHandled: true, reason: "stale_ui_unavailable_with_runs" };
534
+ }
535
+ return { action: "stop_core_services", nextHandled: true, reason: "stale_ui_unavailable_idle" };
536
+ }
537
+
538
+ // src/main/state-bridge.mts
539
+ import { ipcMain } from "electron";
540
+ import WebSocket from "ws";
541
+ var UNIFIED_API_WS = process.env.WEBAUTO_UNIFIED_WS || "ws://127.0.0.1:7701/ws";
542
+ var API_URL = "http://127.0.0.1:7701";
543
+ var StateBridge = class {
544
+ ws = null;
545
+ reconnectTimer = null;
546
+ win = null;
547
+ tasks = /* @__PURE__ */ new Map();
548
+ handlersRegistered = false;
549
+ start(win2) {
550
+ this.win = win2;
551
+ this.connect();
552
+ this.setupIPCHandlers();
553
+ }
554
+ connect() {
555
+ if (this.ws) {
556
+ this.ws.close();
557
+ this.ws = null;
558
+ }
559
+ try {
560
+ this.ws = new WebSocket(UNIFIED_API_WS);
561
+ this.ws.on("open", () => {
562
+ console.log("[StateBridge] connected to", UNIFIED_API_WS);
563
+ this.ws?.send(JSON.stringify({ type: "subscribe", topic: "task:*" }));
564
+ });
565
+ this.ws.on("message", (data) => {
566
+ try {
567
+ const msg = JSON.parse(data.toString());
568
+ if (msg.type === "task:update" && msg.data) {
569
+ this.handleTaskUpdate(msg.data);
570
+ }
571
+ } catch (err) {
572
+ console.warn("[StateBridge] parse error:", err);
573
+ }
574
+ });
575
+ this.ws.on("close", () => {
576
+ console.log("[StateBridge] disconnected, reconnecting...");
577
+ this.scheduleReconnect();
578
+ });
579
+ this.ws.on("error", (err) => {
580
+ console.warn("[StateBridge] error:", err.message);
581
+ });
582
+ } catch (err) {
583
+ console.warn("[StateBridge] connect failed:", err);
584
+ this.scheduleReconnect();
585
+ }
586
+ }
587
+ scheduleReconnect() {
588
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
589
+ this.reconnectTimer = setTimeout(() => this.connect(), 3e3);
590
+ }
591
+ handleTaskUpdate(update) {
592
+ const task = this.tasks.get(update.runId);
593
+ if (task) {
594
+ this.tasks.set(update.runId, { ...task, ...update.data });
595
+ }
596
+ this.win?.webContents.send("state:update", update);
597
+ }
598
+ setupIPCHandlers() {
599
+ if (this.handlersRegistered) return;
600
+ this.handlersRegistered = true;
601
+ const fetchJson = async (url) => {
602
+ try {
603
+ const res = await globalThis.fetch(url, { signal: AbortSignal.timeout(5e3) });
604
+ if (!res.ok) return null;
605
+ return await res.json().catch(() => null);
606
+ } catch {
607
+ return null;
608
+ }
609
+ };
610
+ ipcMain.handle("state:getTasks", async () => {
611
+ const json = await fetchJson(`${API_URL}/api/v1/tasks`);
612
+ return json?.data || [];
613
+ });
614
+ ipcMain.handle("state:getTask", async (_e, runId) => {
615
+ const json = await fetchJson(`${API_URL}/api/v1/tasks/${runId}`);
616
+ return json?.data ?? null;
617
+ });
618
+ ipcMain.handle("state:getEvents", async (_e, runId, since) => {
619
+ let url = `${API_URL}/api/v1/tasks/${runId}/events`;
620
+ if (since) url += `?since=${since}`;
621
+ const json = await fetchJson(url);
622
+ return json?.data || [];
623
+ });
624
+ }
625
+ stop() {
626
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
627
+ if (this.ws) this.ws.close();
628
+ this.ws = null;
629
+ this.win = null;
630
+ }
631
+ };
632
+ var stateBridge = new StateBridge();
633
+
634
+ // src/main/env-check.mts
635
+ import { promisify } from "node:util";
636
+ import { exec, spawnSync } from "node:child_process";
637
+ import { existsSync } from "node:fs";
638
+ import path4 from "node:path";
639
+ import os3 from "node:os";
640
+ var execAsync = promisify(exec);
641
+ function resolveNpxBin2() {
642
+ return process.platform === "win32" ? "npx.cmd" : "npx";
643
+ }
644
+ function resolveCamoVersionFromText(stdout, stderr) {
645
+ const merged = `${String(stdout || "")}
646
+ ${String(stderr || "")}`.trim();
647
+ if (!merged) return "unknown";
648
+ const lines = merged.split(/\r?\n/).map((x) => x.trim()).filter(Boolean);
649
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
650
+ if (!/version/i.test(lines[i])) continue;
651
+ const m = lines[i].match(/\d+\.\d+\.\d+(?:[-+][A-Za-z0-9._-]+)?/);
652
+ if (m?.[0]) return m[0];
653
+ }
654
+ return "unknown";
655
+ }
656
+ function runVersionCheck(command, args, explicitPath) {
657
+ try {
658
+ const ret = spawnSync(command, args, {
659
+ encoding: "utf8",
660
+ timeout: 8e3,
661
+ windowsHide: true
662
+ });
663
+ if (ret.status !== 0) {
664
+ return {
665
+ installed: false,
666
+ error: String(ret.stderr || ret.stdout || "").trim() || `exit ${ret.status}`
667
+ };
668
+ }
669
+ return {
670
+ installed: true,
671
+ path: explicitPath || command,
672
+ version: resolveCamoVersionFromText(String(ret.stdout || ""), String(ret.stderr || ""))
673
+ };
674
+ } catch (err) {
675
+ return { installed: false, error: String(err) };
676
+ }
677
+ }
678
+ async function checkCamoCli() {
679
+ const pathCheck = runVersionCheck(process.platform === "win32" ? "camo.cmd" : "camo", ["help"], "PATH:camo");
680
+ if (pathCheck.installed) return pathCheck;
681
+ const cwd = process.cwd();
682
+ const suffix = process.platform === "win32" ? "camo.cmd" : "camo";
683
+ const localCandidates = [
684
+ path4.resolve(cwd, "node_modules", ".bin", suffix),
685
+ path4.resolve(cwd, "..", "node_modules", ".bin", suffix),
686
+ path4.resolve(cwd, "..", "..", "node_modules", ".bin", suffix)
687
+ ];
688
+ for (const candidate of localCandidates) {
689
+ if (!existsSync(candidate)) continue;
690
+ const ret = runVersionCheck(candidate, ["help"], candidate);
691
+ if (ret.installed) return ret;
692
+ }
693
+ const npxCheck = runVersionCheck(
694
+ resolveNpxBin2(),
695
+ ["--yes", "--package=@web-auto/camo", "camo", "help"],
696
+ "npx:@web-auto/camo"
697
+ );
698
+ if (npxCheck.installed) return npxCheck;
699
+ return {
700
+ installed: false,
701
+ error: "camo not found in PATH/local bin, and npx @web-auto/camo failed"
702
+ };
703
+ }
704
+ async function checkServices() {
705
+ const [unifiedApi, browserService, searchGate] = await Promise.all([
706
+ fetch("http://127.0.0.1:7701/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false),
707
+ fetch("http://127.0.0.1:7704/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false),
708
+ fetch("http://127.0.0.1:7790/health", { signal: AbortSignal.timeout(3e3) }).then((r) => r.ok).catch(() => false)
709
+ ]);
710
+ return { unifiedApi, browserService, searchGate };
711
+ }
712
+ async function checkFirefox() {
713
+ try {
714
+ const pythonBin = process.platform === "win32" ? "python" : "python3";
715
+ const ret = spawnSync(pythonBin, ["-m", "camoufox", "path"], {
716
+ encoding: "utf8",
717
+ timeout: 8e3,
718
+ windowsHide: true
719
+ });
720
+ if (ret.status === 0) {
721
+ const lines = String(ret.stdout || "").split(/\r?\n/).map((x) => x.trim()).filter(Boolean);
722
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
723
+ const line = lines[i];
724
+ if (line && (line.startsWith("/") || /^[A-Z]:\\/.test(line))) return { installed: true, path: line };
725
+ }
726
+ return { installed: true };
727
+ }
728
+ } catch {
729
+ }
730
+ const platform = process.platform;
731
+ try {
732
+ if (platform === "win32") {
733
+ const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
734
+ const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
735
+ const localAppData = process.env.LOCALAPPDATA || path4.join(os3.homedir(), "AppData", "Local");
736
+ const possiblePaths = [
737
+ path4.join(programFiles, "Mozilla Firefox", "firefox.exe"),
738
+ path4.join(programFilesX86, "Mozilla Firefox", "firefox.exe"),
739
+ path4.join(localAppData, "Mozilla Firefox", "firefox.exe")
740
+ ];
741
+ for (const firefoxPath2 of possiblePaths) {
742
+ if (existsSync(firefoxPath2)) return { installed: true, path: firefoxPath2 };
743
+ }
744
+ return { installed: false };
745
+ }
746
+ const macBundle = "/Applications/Firefox.app/Contents/MacOS/firefox";
747
+ if (platform === "darwin" && existsSync(macBundle)) return { installed: true, path: macBundle };
748
+ const { stdout } = await execAsync("which firefox", { timeout: 3e3 });
749
+ const firefoxPath = String(stdout || "").trim();
750
+ return firefoxPath ? { installed: true, path: firefoxPath } : { installed: false };
751
+ } catch {
752
+ return { installed: false };
753
+ }
754
+ }
755
+ async function checkGeoIP() {
756
+ const geoIpPath = path4.join(os3.homedir(), ".webauto", "geoip", "GeoLite2-City.mmdb");
757
+ if (existsSync(geoIpPath)) {
758
+ return { installed: true, path: geoIpPath };
759
+ }
760
+ return { installed: false };
761
+ }
762
+ async function checkEnvironment() {
763
+ const [camo, services, firefox, geoip] = await Promise.all([
764
+ checkCamoCli(),
765
+ checkServices(),
766
+ checkFirefox(),
767
+ checkGeoIP()
768
+ ]);
769
+ const allReady = camo.installed && services.unifiedApi && services.browserService && firefox.installed && geoip.installed;
770
+ return { camo, services, firefox, geoip, allReady };
771
+ }
772
+
773
+ // src/main/index.mts
774
+ var { app, BrowserWindow: BrowserWindow2, ipcMain: ipcMain2, shell, clipboard } = electron;
775
+ var __dirname = path5.dirname(fileURLToPath2(import.meta.url));
776
+ var APP_ROOT = path5.resolve(__dirname, "../..");
777
+ var REPO_ROOT2 = path5.resolve(APP_ROOT, "../..");
778
+ var DESKTOP_HEARTBEAT_FILE = path5.join(
779
+ os4.homedir(),
780
+ ".webauto",
781
+ "run",
782
+ "desktop-console-heartbeat.json"
783
+ );
784
+ var profileStore = createProfileStore({ repoRoot: REPO_ROOT2 });
785
+ var XHS_SCRIPTS_ROOT = path5.join(REPO_ROOT2, "scripts", "xiaohongshu");
786
+ var XHS_FULL_COLLECT_RE = /collect-content\.mjs$/;
787
+ function configureElectronPaths() {
788
+ try {
789
+ const downloadRoot = resolveDefaultDownloadRoot();
790
+ const normalized = path5.normalize(downloadRoot);
791
+ const baseDir = path5.basename(normalized).toLowerCase() === "download" ? path5.dirname(normalized) : normalized;
792
+ const userDataRoot = path5.join(baseDir, "desktop-console");
793
+ const cacheRoot = path5.join(userDataRoot, "cache");
794
+ const gpuCacheRoot = path5.join(cacheRoot, "gpu");
795
+ try {
796
+ mkdirSync(cacheRoot, { recursive: true });
797
+ } catch {
798
+ }
799
+ try {
800
+ mkdirSync(gpuCacheRoot, { recursive: true });
801
+ } catch {
802
+ }
803
+ app.setPath("userData", userDataRoot);
804
+ app.setPath("cache", cacheRoot);
805
+ app.commandLine.appendSwitch("disk-cache-dir", cacheRoot);
806
+ app.commandLine.appendSwitch("gpu-cache-dir", gpuCacheRoot);
807
+ } catch (err) {
808
+ console.warn("[desktop-console] failed to configure cache paths", err);
809
+ }
810
+ }
811
+ function now() {
812
+ return Date.now();
813
+ }
814
+ var GroupQueue = class {
815
+ running = false;
816
+ queue = [];
817
+ enqueue(job) {
818
+ this.queue.push(job);
819
+ void this.pump();
820
+ }
821
+ async pump() {
822
+ if (this.running) return;
823
+ this.running = true;
824
+ try {
825
+ while (this.queue.length > 0) {
826
+ const job = this.queue.shift();
827
+ await job();
828
+ }
829
+ } finally {
830
+ this.running = false;
831
+ }
832
+ }
833
+ };
834
+ var groupQueues = /* @__PURE__ */ new Map();
835
+ var runs = /* @__PURE__ */ new Map();
836
+ var UI_HEARTBEAT_TIMEOUT_MS = resolveUiHeartbeatTimeoutMs(process.env);
837
+ var lastUiHeartbeatAt = Date.now();
838
+ var heartbeatWatchdog = null;
839
+ var heartbeatTimeoutHandled = false;
840
+ var coreServicesStopRequested = false;
841
+ var coreServiceHeartbeatTimer = null;
842
+ var coreServiceHeartbeatStopped = false;
843
+ async function writeCoreServiceHeartbeat(status) {
844
+ const filePath = String(process.env.WEBAUTO_HEARTBEAT_FILE || DESKTOP_HEARTBEAT_FILE).trim() || DESKTOP_HEARTBEAT_FILE;
845
+ const payload = {
846
+ pid: process.pid,
847
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
848
+ status,
849
+ source: "desktop-console"
850
+ };
851
+ try {
852
+ await fs3.mkdir(path5.dirname(filePath), { recursive: true });
853
+ await fs3.writeFile(filePath, JSON.stringify(payload), "utf8");
854
+ } catch {
855
+ }
856
+ }
857
+ function startCoreServiceHeartbeat() {
858
+ const filePath = String(process.env.WEBAUTO_HEARTBEAT_FILE || DESKTOP_HEARTBEAT_FILE).trim() || DESKTOP_HEARTBEAT_FILE;
859
+ process.env.WEBAUTO_HEARTBEAT_FILE = filePath;
860
+ if (!process.env.WEBAUTO_HEARTBEAT_INTERVAL_MS) process.env.WEBAUTO_HEARTBEAT_INTERVAL_MS = "5000";
861
+ if (!process.env.WEBAUTO_HEARTBEAT_STALE_MS) process.env.WEBAUTO_HEARTBEAT_STALE_MS = "45000";
862
+ coreServiceHeartbeatStopped = false;
863
+ void writeCoreServiceHeartbeat("running");
864
+ if (coreServiceHeartbeatTimer) clearInterval(coreServiceHeartbeatTimer);
865
+ coreServiceHeartbeatTimer = setInterval(() => {
866
+ if (coreServiceHeartbeatStopped) return;
867
+ void writeCoreServiceHeartbeat("running");
868
+ }, 5e3);
869
+ coreServiceHeartbeatTimer.unref();
870
+ }
871
+ function stopCoreServiceHeartbeat() {
872
+ if (coreServiceHeartbeatStopped) return;
873
+ coreServiceHeartbeatStopped = true;
874
+ if (coreServiceHeartbeatTimer) {
875
+ clearInterval(coreServiceHeartbeatTimer);
876
+ coreServiceHeartbeatTimer = null;
877
+ }
878
+ void writeCoreServiceHeartbeat("stopped");
879
+ }
880
+ var stateBridgeStarted = false;
881
+ function ensureStateBridge() {
882
+ if (stateBridgeStarted) return;
883
+ const w = getWin();
884
+ if (w) {
885
+ stateBridge.start(w);
886
+ stateBridgeStarted = true;
887
+ }
888
+ }
889
+ var win = null;
890
+ configureElectronPaths();
891
+ function getWin() {
892
+ if (!win || win.isDestroyed()) return null;
893
+ return win;
894
+ }
895
+ function isUiOperational() {
896
+ const w = getWin();
897
+ if (!w) return false;
898
+ const wc = w.webContents;
899
+ if (!wc || wc.isDestroyed()) return false;
900
+ if (typeof wc.isCrashed === "function" && wc.isCrashed()) return false;
901
+ return true;
902
+ }
903
+ function sendEvent(evt) {
904
+ if (!win || win.isDestroyed()) return;
905
+ win.webContents.send("cmd:event", evt);
906
+ }
907
+ function markUiHeartbeat(source = "renderer") {
908
+ lastUiHeartbeatAt = Date.now();
909
+ heartbeatTimeoutHandled = false;
910
+ return { ok: true, ts: new Date(lastUiHeartbeatAt).toISOString(), source };
911
+ }
912
+ function terminateRunProcess(runId, reason = "manual") {
913
+ const run = runs.get(runId);
914
+ if (!run) return false;
915
+ const child = run.child;
916
+ const pid = Number(child.pid || 0);
917
+ try {
918
+ if (process.platform === "win32") {
919
+ if (pid > 0) {
920
+ spawn2("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
921
+ }
922
+ } else {
923
+ if (pid > 0) {
924
+ spawn2("pkill", ["-TERM", "-P", String(pid)], { stdio: "ignore" }).on("error", () => {
925
+ });
926
+ }
927
+ }
928
+ } catch {
929
+ }
930
+ try {
931
+ child.kill("SIGTERM");
932
+ } catch {
933
+ }
934
+ sendEvent({ type: "stderr", runId, line: `[watchdog] kill requested (${reason})`, ts: now() });
935
+ return true;
936
+ }
937
+ async function stopCoreServicesBestEffort(reason) {
938
+ if (coreServicesStopRequested) return;
939
+ coreServicesStopRequested = true;
940
+ try {
941
+ await stopCoreDaemon();
942
+ } catch (err) {
943
+ console.warn(`[desktop-console] core-daemon stop failed (${reason})`, err);
944
+ }
945
+ }
946
+ function killAllRuns(reason = "ui_heartbeat_timeout") {
947
+ for (const runId of Array.from(runs.keys())) {
948
+ terminateRunProcess(runId, reason);
949
+ }
950
+ if (reason === "ui_heartbeat_timeout" || reason === "window_closed") {
951
+ void stopCoreServicesBestEffort(reason);
952
+ }
953
+ }
954
+ function ensureHeartbeatWatchdog() {
955
+ if (heartbeatWatchdog) return;
956
+ heartbeatWatchdog = setInterval(() => {
957
+ const staleMs = Date.now() - lastUiHeartbeatAt;
958
+ const decision = decideWatchdogAction({
959
+ staleMs,
960
+ timeoutMs: UI_HEARTBEAT_TIMEOUT_MS,
961
+ alreadyHandled: heartbeatTimeoutHandled,
962
+ runCount: runs.size,
963
+ uiOperational: isUiOperational()
964
+ });
965
+ heartbeatTimeoutHandled = decision.nextHandled;
966
+ if (decision.action === "none") {
967
+ if (decision.reason === "stale_ui_alive") {
968
+ console.warn(
969
+ `[desktop-heartbeat] stale ${staleMs}ms > ${UI_HEARTBEAT_TIMEOUT_MS}ms, UI still alive, skip kill (likely timer throttling)`
970
+ );
971
+ }
972
+ return;
973
+ }
974
+ if (decision.action === "kill_runs") {
975
+ console.warn(`[desktop-heartbeat] stale ${staleMs}ms > ${UI_HEARTBEAT_TIMEOUT_MS}ms, killing ${runs.size} run(s)`);
976
+ killAllRuns("ui_heartbeat_timeout");
977
+ return;
978
+ }
979
+ console.warn(`[desktop-heartbeat] stale ${staleMs}ms > ${UI_HEARTBEAT_TIMEOUT_MS}ms, stopping core services`);
980
+ void stopCoreServicesBestEffort("heartbeat_stop_only");
981
+ }, 5e3);
982
+ heartbeatWatchdog.unref();
983
+ }
984
+ function getQueue(groupKey) {
985
+ const key = groupKey || "default";
986
+ let q = groupQueues.get(key);
987
+ if (!q) {
988
+ q = new GroupQueue();
989
+ groupQueues.set(key, q);
990
+ }
991
+ return q;
992
+ }
993
+ function generateRunId() {
994
+ return `run_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`;
995
+ }
996
+ function createLineEmitter(runId, type) {
997
+ let pending = "";
998
+ const emit = (line) => {
999
+ const normalized = String(line || "").replace(/\r$/, "");
1000
+ if (!normalized) return;
1001
+ sendEvent({ type, runId, line: normalized, ts: now() });
1002
+ };
1003
+ return {
1004
+ push(chunk) {
1005
+ pending += chunk.toString("utf8");
1006
+ let idx = pending.indexOf("\n");
1007
+ while (idx >= 0) {
1008
+ const line = pending.slice(0, idx);
1009
+ pending = pending.slice(idx + 1);
1010
+ emit(line);
1011
+ idx = pending.indexOf("\n");
1012
+ }
1013
+ },
1014
+ flush() {
1015
+ if (!pending) return;
1016
+ emit(pending);
1017
+ pending = "";
1018
+ }
1019
+ };
1020
+ }
1021
+ function resolveNodeBin2() {
1022
+ const explicit = String(process.env.WEBAUTO_NODE_BIN || "").trim();
1023
+ if (explicit) return explicit;
1024
+ const npmNode = String(process.env.npm_node_execpath || "").trim();
1025
+ if (npmNode) return npmNode;
1026
+ return process.platform === "win32" ? "node.exe" : "node";
1027
+ }
1028
+ function resolveCwd(input) {
1029
+ const raw = String(input || "").trim();
1030
+ if (!raw) return REPO_ROOT2;
1031
+ return path5.isAbsolute(raw) ? raw : path5.resolve(REPO_ROOT2, raw);
1032
+ }
1033
+ var cachedStateMod = null;
1034
+ async function getStateModule() {
1035
+ if (cachedStateMod) return cachedStateMod;
1036
+ try {
1037
+ const p = path5.join(REPO_ROOT2, "dist", "modules", "state", "src", "xiaohongshu-collect-state.js");
1038
+ cachedStateMod = await import(pathToFileURL3(p).href);
1039
+ return cachedStateMod;
1040
+ } catch {
1041
+ cachedStateMod = null;
1042
+ return null;
1043
+ }
1044
+ }
1045
+ async function spawnCommand(spec) {
1046
+ const runId = generateRunId();
1047
+ const groupKey = spec.groupKey || "xiaohongshu";
1048
+ const q = getQueue(groupKey);
1049
+ const cwd = resolveCwd(spec.cwd);
1050
+ q.enqueue(
1051
+ () => new Promise((resolve) => {
1052
+ let finished = false;
1053
+ let exitCode = null;
1054
+ let exitSignal = null;
1055
+ const finalize = (code, signal) => {
1056
+ if (finished) return;
1057
+ finished = true;
1058
+ sendEvent({ type: "exit", runId, exitCode: code, signal, ts: now() });
1059
+ runs.delete(runId);
1060
+ resolve();
1061
+ };
1062
+ const child = spawn2(resolveNodeBin2(), spec.args, {
1063
+ cwd,
1064
+ env: {
1065
+ ...process.env,
1066
+ WEBAUTO_DAEMON: "1",
1067
+ WEBAUTO_UI_HEARTBEAT: "1",
1068
+ ...spec.env || {}
1069
+ },
1070
+ stdio: ["ignore", "pipe", "pipe"],
1071
+ windowsHide: true
1072
+ });
1073
+ runs.set(runId, { child, title: spec.title, startedAt: now() });
1074
+ sendEvent({ type: "started", runId, title: spec.title, pid: child.pid ?? -1, ts: now() });
1075
+ const stdoutLines = createLineEmitter(runId, "stdout");
1076
+ const stderrLines = createLineEmitter(runId, "stderr");
1077
+ child.stdout?.on("data", (chunk) => {
1078
+ stdoutLines.push(chunk);
1079
+ });
1080
+ child.stderr?.on("data", (chunk) => {
1081
+ stderrLines.push(chunk);
1082
+ });
1083
+ child.on("error", (err) => {
1084
+ sendEvent({ type: "stderr", runId, line: `[spawn-error] ${err?.message || String(err)}`, ts: now() });
1085
+ finalize(null, "error");
1086
+ });
1087
+ child.on("exit", (code, signal) => {
1088
+ exitCode = code;
1089
+ exitSignal = signal;
1090
+ });
1091
+ child.on("close", (code, signal) => {
1092
+ stdoutLines.flush();
1093
+ stderrLines.flush();
1094
+ finalize(exitCode ?? code ?? null, exitSignal ?? signal ?? null);
1095
+ });
1096
+ })
1097
+ );
1098
+ return { runId };
1099
+ }
1100
+ async function runJson(spec) {
1101
+ const timeoutMs = typeof spec.timeoutMs === "number" ? spec.timeoutMs : 2e4;
1102
+ const cwd = resolveCwd(spec.cwd);
1103
+ const child = spawn2(resolveNodeBin2(), spec.args, {
1104
+ cwd,
1105
+ env: { ...process.env, ...spec.env || {} },
1106
+ stdio: ["ignore", "pipe", "pipe"],
1107
+ windowsHide: true
1108
+ });
1109
+ const stdout = [];
1110
+ const stderr = [];
1111
+ child.stdout?.on("data", (c) => stdout.push(c));
1112
+ child.stderr?.on("data", (c) => stderr.push(c));
1113
+ const timer = setTimeout(() => {
1114
+ try {
1115
+ child.kill("SIGTERM");
1116
+ } catch {
1117
+ }
1118
+ }, timeoutMs);
1119
+ const { code } = await new Promise((resolve) => {
1120
+ child.on("exit", (c) => resolve({ code: c }));
1121
+ });
1122
+ clearTimeout(timer);
1123
+ const out = Buffer.concat(stdout).toString("utf8").trim();
1124
+ const err = Buffer.concat(stderr).toString("utf8").trim();
1125
+ if (code !== 0) {
1126
+ return { ok: false, code, stdout: out, stderr: err };
1127
+ }
1128
+ try {
1129
+ const json = JSON.parse(out);
1130
+ return { ok: true, code, json };
1131
+ } catch {
1132
+ return { ok: true, code, stdout: out, stderr: err };
1133
+ }
1134
+ }
1135
+ async function scanResults(input) {
1136
+ const downloadRoot = String(input.downloadRoot || resolveDefaultDownloadRoot());
1137
+ const root = path5.join(downloadRoot, "xiaohongshu");
1138
+ const result = { ok: true, root, entries: [] };
1139
+ try {
1140
+ const stateMod = await getStateModule();
1141
+ const envDirs = await fs3.readdir(root, { withFileTypes: true });
1142
+ for (const envEnt of envDirs) {
1143
+ if (!envEnt.isDirectory()) continue;
1144
+ const env = envEnt.name;
1145
+ const envPath = path5.join(root, env);
1146
+ const keywordDirs = await fs3.readdir(envPath, { withFileTypes: true });
1147
+ for (const kwEnt of keywordDirs) {
1148
+ if (!kwEnt.isDirectory()) continue;
1149
+ const keyword = kwEnt.name;
1150
+ const kwPath = path5.join(envPath, keyword);
1151
+ const stat = await fs3.stat(kwPath).catch(() => null);
1152
+ let stateSummary = null;
1153
+ if (stateMod?.loadXhsCollectState) {
1154
+ try {
1155
+ const state = await stateMod.loadXhsCollectState({ keyword, env, downloadRoot });
1156
+ stateSummary = {
1157
+ status: state?.status,
1158
+ links: state?.listCollection?.collectedUrls?.length || 0,
1159
+ target: state?.listCollection?.targetCount || 0,
1160
+ completed: state?.detailCollection?.completed || 0,
1161
+ failed: state?.detailCollection?.failed || 0,
1162
+ updatedAt: state?.lastUpdateTime || null
1163
+ };
1164
+ } catch {
1165
+ }
1166
+ }
1167
+ result.entries.push({ env, keyword, path: kwPath, mtimeMs: stat?.mtimeMs || 0, state: stateSummary });
1168
+ }
1169
+ }
1170
+ result.entries.sort((a, b) => (b.mtimeMs || 0) - (a.mtimeMs || 0));
1171
+ } catch (e) {
1172
+ result.ok = false;
1173
+ result.error = e?.message || String(e);
1174
+ }
1175
+ return result;
1176
+ }
1177
+ async function listXhsFullCollectScripts() {
1178
+ try {
1179
+ const entries = await fs3.readdir(XHS_SCRIPTS_ROOT, { withFileTypes: true });
1180
+ const scripts = entries.filter((ent) => ent.isFile() && XHS_FULL_COLLECT_RE.test(ent.name)).map((ent) => {
1181
+ const name = ent.name;
1182
+ return {
1183
+ id: `xhs:${name}`,
1184
+ label: `Full Collect (${name})`,
1185
+ path: path5.join(XHS_SCRIPTS_ROOT, name)
1186
+ };
1187
+ });
1188
+ return { ok: true, scripts };
1189
+ } catch (err) {
1190
+ return { ok: false, error: err?.message || String(err), scripts: [] };
1191
+ }
1192
+ }
1193
+ async function readTextPreview(input) {
1194
+ const filePath = String(input.path || "");
1195
+ const maxBytes = typeof input.maxBytes === "number" ? input.maxBytes : 8e4;
1196
+ const maxLines = typeof input.maxLines === "number" ? input.maxLines : 200;
1197
+ try {
1198
+ const raw = await fs3.readFile(filePath, "utf8");
1199
+ const clipped = raw.slice(0, maxBytes);
1200
+ const lines = clipped.split(/\r?\n/g).slice(0, maxLines);
1201
+ return { ok: true, path: filePath, text: lines.join("\n") };
1202
+ } catch (err) {
1203
+ if (err?.code === "ENOENT") return { ok: false, path: filePath, error: "not_found" };
1204
+ return { ok: false, path: filePath, error: err?.message || String(err) };
1205
+ }
1206
+ }
1207
+ async function readTextTail(input) {
1208
+ const filePath = String(input?.path || "");
1209
+ const requestedOffset = typeof input?.fromOffset === "number" ? Math.max(0, Math.floor(input.fromOffset)) : 0;
1210
+ const maxBytes = typeof input?.maxBytes === "number" ? Math.max(1024, Math.floor(input.maxBytes)) : 256e3;
1211
+ const st = await fs3.stat(filePath);
1212
+ const size = Number(st?.size || 0);
1213
+ const fromOffset = requestedOffset > size ? 0 : requestedOffset;
1214
+ const toRead = Math.max(0, Math.min(maxBytes, size - fromOffset));
1215
+ if (toRead <= 0) {
1216
+ return { ok: true, path: filePath, text: "", fromOffset, nextOffset: fromOffset, fileSize: size };
1217
+ }
1218
+ const fh = await fs3.open(filePath, "r");
1219
+ try {
1220
+ const buf = Buffer.allocUnsafe(toRead);
1221
+ const { bytesRead } = await fh.read(buf, 0, toRead, fromOffset);
1222
+ const text = buf.subarray(0, bytesRead).toString("utf8");
1223
+ return {
1224
+ ok: true,
1225
+ path: filePath,
1226
+ text,
1227
+ fromOffset,
1228
+ nextOffset: fromOffset + bytesRead,
1229
+ fileSize: size
1230
+ };
1231
+ } finally {
1232
+ await fh.close();
1233
+ }
1234
+ }
1235
+ async function readFileBase64(input) {
1236
+ const filePath = String(input.path || "");
1237
+ const maxBytes = typeof input.maxBytes === "number" ? input.maxBytes : 8e6;
1238
+ const buf = await fs3.readFile(filePath);
1239
+ if (buf.byteLength > maxBytes) {
1240
+ return { ok: false, error: `file too large: ${buf.byteLength}` };
1241
+ }
1242
+ return { ok: true, data: buf.toString("base64") };
1243
+ }
1244
+ async function listDir(input) {
1245
+ const root = String(input?.root || "");
1246
+ const recursive = Boolean(input?.recursive);
1247
+ const maxEntries = typeof input?.maxEntries === "number" ? input.maxEntries : 2e3;
1248
+ const entries = [];
1249
+ const stack = [root];
1250
+ while (stack.length > 0 && entries.length < maxEntries) {
1251
+ const dir = stack.pop();
1252
+ const items = await fs3.readdir(dir, { withFileTypes: true }).catch(() => []);
1253
+ for (const ent of items) {
1254
+ if (entries.length >= maxEntries) break;
1255
+ const full = path5.join(dir, ent.name);
1256
+ const st = await fs3.stat(full).catch(() => null);
1257
+ entries.push({
1258
+ path: full,
1259
+ rel: path5.relative(root, full),
1260
+ name: ent.name,
1261
+ isDir: ent.isDirectory(),
1262
+ size: st?.size || 0,
1263
+ mtimeMs: st?.mtimeMs || 0
1264
+ });
1265
+ if (recursive && ent.isDirectory()) stack.push(full);
1266
+ }
1267
+ }
1268
+ entries.sort((a, b) => {
1269
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
1270
+ return b.mtimeMs - a.mtimeMs;
1271
+ });
1272
+ return { ok: true, root, entries, truncated: entries.length >= maxEntries };
1273
+ }
1274
+ function createWindow() {
1275
+ win = new BrowserWindow2({
1276
+ title: "WebAuto Desktop v0.1.1",
1277
+ width: 1280,
1278
+ height: 900,
1279
+ minWidth: 920,
1280
+ minHeight: 800,
1281
+ webPreferences: {
1282
+ preload: path5.join(APP_ROOT, "dist", "main", "preload.mjs"),
1283
+ contextIsolation: true,
1284
+ nodeIntegration: false,
1285
+ sandbox: false,
1286
+ // Prevent renderer timer throttling when app loses focus; heartbeat must remain stable.
1287
+ backgroundThrottling: false
1288
+ }
1289
+ });
1290
+ const htmlPath = path5.join(APP_ROOT, "dist", "renderer", "index.html");
1291
+ void win.loadFile(htmlPath);
1292
+ ensureStateBridge();
1293
+ }
1294
+ app.on("window-all-closed", () => {
1295
+ killAllRuns("window_closed");
1296
+ app.quit();
1297
+ });
1298
+ app.on("before-quit", () => {
1299
+ killAllRuns("before_quit");
1300
+ stopCoreServiceHeartbeat();
1301
+ void stopCoreServicesBestEffort("before_quit");
1302
+ if (heartbeatWatchdog) {
1303
+ clearInterval(heartbeatWatchdog);
1304
+ heartbeatWatchdog = null;
1305
+ }
1306
+ });
1307
+ app.on("will-quit", () => {
1308
+ killAllRuns("will_quit");
1309
+ stopCoreServiceHeartbeat();
1310
+ void stopCoreServicesBestEffort("will_quit");
1311
+ stateBridge.stop();
1312
+ });
1313
+ app.whenReady().then(async () => {
1314
+ startCoreServiceHeartbeat();
1315
+ const started = await startCoreDaemon().catch(() => false);
1316
+ if (!started) {
1317
+ console.warn("[desktop-console] core services are not healthy at startup");
1318
+ }
1319
+ markUiHeartbeat("main_ready");
1320
+ ensureHeartbeatWatchdog();
1321
+ createWindow();
1322
+ });
1323
+ ipcMain2.on("preload:test", () => {
1324
+ console.log("[preload-test] window.api OK");
1325
+ setTimeout(() => app.quit(), 200);
1326
+ });
1327
+ ipcMain2.handle("settings:get", async () => readDesktopConsoleSettings({ appRoot: APP_ROOT, repoRoot: REPO_ROOT2 }));
1328
+ ipcMain2.handle("settings:set", async (_evt, next) => {
1329
+ const updated = await writeDesktopConsoleSettings({ appRoot: APP_ROOT, repoRoot: REPO_ROOT2 }, next || {});
1330
+ const w = getWin();
1331
+ if (w) w.webContents.send("settings:changed", updated);
1332
+ return updated;
1333
+ });
1334
+ ipcMain2.handle("ai:listModels", async (_evt, input) => {
1335
+ try {
1336
+ const baseUrl = String(input?.baseUrl || "").trim().replace(/\/+$/, "");
1337
+ const apiKey = String(input?.apiKey || "").trim();
1338
+ const apiPath = String(input?.path || "/v1/models").trim() || "/v1/models";
1339
+ if (!baseUrl) return { ok: false, models: [], rawCount: 0, error: "baseUrl is required" };
1340
+ const headers = { "Content-Type": "application/json" };
1341
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
1342
+ const res = await fetch(`${baseUrl}${apiPath}`, {
1343
+ method: "GET",
1344
+ headers,
1345
+ signal: AbortSignal.timeout(15e3)
1346
+ });
1347
+ const json = await res.json().catch(() => ({}));
1348
+ if (!res.ok) {
1349
+ return {
1350
+ ok: false,
1351
+ models: [],
1352
+ rawCount: 0,
1353
+ error: json?.error?.message || `HTTP ${res.status}`
1354
+ };
1355
+ }
1356
+ const data = Array.isArray(json?.data) ? json.data : [];
1357
+ const models = data.map((m) => String(m?.id || "")).filter(Boolean);
1358
+ return { ok: true, models, rawCount: data.length };
1359
+ } catch (e) {
1360
+ return { ok: false, models: [], rawCount: 0, error: e?.message || String(e) };
1361
+ }
1362
+ });
1363
+ ipcMain2.handle(
1364
+ "ai:testChatCompletion",
1365
+ async (_evt, input) => {
1366
+ const startedAt = Date.now();
1367
+ try {
1368
+ const baseUrl = String(input?.baseUrl || "").trim().replace(/\/+$/, "");
1369
+ const apiKey = String(input?.apiKey || "").trim();
1370
+ const model = String(input?.model || "").trim();
1371
+ const timeoutMs = Math.max(5e3, Number(input?.timeoutMs || 25e3));
1372
+ if (!baseUrl) return { ok: false, latencyMs: 0, model, error: "baseUrl is required" };
1373
+ if (!model) return { ok: false, latencyMs: 0, model, error: "model is required" };
1374
+ const headers = { "Content-Type": "application/json" };
1375
+ if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
1376
+ const res = await fetch(`${baseUrl}/v1/chat/completions`, {
1377
+ method: "POST",
1378
+ headers,
1379
+ body: JSON.stringify({
1380
+ model,
1381
+ messages: [{ role: "user", content: "ping" }],
1382
+ max_tokens: 8,
1383
+ temperature: 0
1384
+ }),
1385
+ signal: AbortSignal.timeout(timeoutMs)
1386
+ });
1387
+ const json = await res.json().catch(() => ({}));
1388
+ if (!res.ok) {
1389
+ return {
1390
+ ok: false,
1391
+ latencyMs: Date.now() - startedAt,
1392
+ model,
1393
+ error: json?.error?.message || `HTTP ${res.status}`
1394
+ };
1395
+ }
1396
+ return {
1397
+ ok: true,
1398
+ latencyMs: Date.now() - startedAt,
1399
+ model
1400
+ };
1401
+ } catch (e) {
1402
+ return {
1403
+ ok: false,
1404
+ latencyMs: Date.now() - startedAt,
1405
+ model: String(input?.model || ""),
1406
+ error: e?.message || String(e)
1407
+ };
1408
+ }
1409
+ }
1410
+ );
1411
+ ipcMain2.handle("desktop:heartbeat", async () => markUiHeartbeat());
1412
+ ipcMain2.handle("cmd:spawn", async (_evt, spec) => {
1413
+ markUiHeartbeat("cmd_spawn");
1414
+ const title = String(spec?.title || "command");
1415
+ const cwd = String(spec?.cwd || REPO_ROOT2);
1416
+ const args = Array.isArray(spec?.args) ? spec.args : [];
1417
+ return spawnCommand({ title, cwd, args, env: spec.env, groupKey: spec.groupKey });
1418
+ });
1419
+ ipcMain2.handle("cmd:kill", async (_evt, input) => {
1420
+ const runId = String(input?.runId || "");
1421
+ const r = runs.get(runId);
1422
+ if (!r) return { ok: false, error: "not found" };
1423
+ try {
1424
+ const ok = terminateRunProcess(runId, "manual_stop");
1425
+ return { ok };
1426
+ } catch (e) {
1427
+ return { ok: false, error: e?.message || String(e) };
1428
+ }
1429
+ });
1430
+ ipcMain2.handle("cmd:runJson", async (_evt, spec) => {
1431
+ const cwd = String(spec?.cwd || REPO_ROOT2);
1432
+ const args = Array.isArray(spec?.args) ? spec.args : [];
1433
+ return runJson({ ...spec, cwd, args });
1434
+ });
1435
+ ipcMain2.handle("results:scan", async (_evt, spec) => scanResults(spec || {}));
1436
+ ipcMain2.handle("fs:listDir", async (_evt, spec) => listDir(spec));
1437
+ ipcMain2.handle(
1438
+ "fs:readTextPreview",
1439
+ async (_evt, spec) => readTextPreview(spec)
1440
+ );
1441
+ ipcMain2.handle(
1442
+ "fs:readTextTail",
1443
+ async (_evt, spec) => readTextTail(spec)
1444
+ );
1445
+ ipcMain2.handle("fs:readFileBase64", async (_evt, spec) => readFileBase64(spec));
1446
+ ipcMain2.handle("profiles:list", async () => profileStore.listProfiles());
1447
+ ipcMain2.handle("profiles:scan", async () => profileStore.scanProfiles());
1448
+ ipcMain2.handle("scripts:xhsFullCollect", async () => listXhsFullCollectScripts());
1449
+ ipcMain2.handle("profile:create", async (_evt, input) => profileStore.profileCreate(input || {}));
1450
+ ipcMain2.handle(
1451
+ "profile:delete",
1452
+ async (_evt, input) => profileStore.profileDelete(input || {})
1453
+ );
1454
+ ipcMain2.handle("fingerprint:delete", async (_evt, input) => profileStore.fingerprintDelete(input || {}));
1455
+ ipcMain2.handle(
1456
+ "fingerprint:regenerate",
1457
+ async (_evt, input) => profileStore.fingerprintRegenerate(input || {})
1458
+ );
1459
+ ipcMain2.handle("os:openPath", async (_evt, input) => {
1460
+ const p = String(input?.path || "");
1461
+ const r = await shell.openPath(p);
1462
+ return { ok: !r, error: r || null };
1463
+ });
1464
+ ipcMain2.handle("env:checkCamo", async () => checkCamoCli());
1465
+ ipcMain2.handle("env:checkServices", async () => checkServices());
1466
+ ipcMain2.handle("env:checkFirefox", async () => checkFirefox());
1467
+ ipcMain2.handle("env:checkGeoIP", async () => checkGeoIP());
1468
+ ipcMain2.handle("env:checkAll", async () => checkEnvironment());
1469
+ ipcMain2.handle("env:repairCore", async () => {
1470
+ const ok = await startCoreDaemon().catch(() => false);
1471
+ const services = await checkServices().catch(() => ({ unifiedApi: false, browserService: false }));
1472
+ return { ok, services };
1473
+ });
1474
+ ipcMain2.handle("config:saveLast", async (_evt, config) => {
1475
+ await saveCrawlConfig({ appRoot: APP_ROOT, repoRoot: REPO_ROOT2 }, config);
1476
+ return { ok: true };
1477
+ });
1478
+ ipcMain2.handle("config:loadLast", async () => {
1479
+ const config = await loadCrawlConfig({ appRoot: APP_ROOT, repoRoot: REPO_ROOT2 });
1480
+ return config;
1481
+ });
1482
+ ipcMain2.handle("config:export", async (_evt, { filePath, config }) => {
1483
+ return await exportConfigToFile(filePath, config);
1484
+ });
1485
+ ipcMain2.handle("config:import", async (_evt, { filePath }) => {
1486
+ return await importConfigFromFile(filePath);
1487
+ });
1488
+ ipcMain2.handle("clipboard:writeText", async (_evt, input) => {
1489
+ try {
1490
+ clipboard.writeText(String(input?.text || ""));
1491
+ return { ok: true };
1492
+ } catch (err) {
1493
+ return { ok: false, error: err?.message || String(err) };
1494
+ }
1495
+ });
1496
+ async function unifiedAction(action, payload) {
1497
+ const base = String((await readDesktopConsoleSettings({ appRoot: APP_ROOT, repoRoot: REPO_ROOT2 }))?.unifiedApiUrl || "http://127.0.0.1:7701");
1498
+ const res = await fetch(`${base}/v1/controller/action`, {
1499
+ method: "POST",
1500
+ headers: { "Content-Type": "application/json" },
1501
+ body: JSON.stringify({ action, payload }),
1502
+ signal: AbortSignal.timeout ? AbortSignal.timeout(2e4) : void 0
1503
+ });
1504
+ const json = await res.json().catch(() => ({}));
1505
+ if (!res.ok || json?.success === false || json?.ok === false) throw new Error(json?.error || "unified action failed");
1506
+ return json;
1507
+ }
1508
+ ipcMain2.handle("runtime:listSessions", async () => {
1509
+ const data = await unifiedAction("session:list", {}).catch(() => null);
1510
+ const sessions = data?.data?.sessions || data?.sessions || [];
1511
+ if (!Array.isArray(sessions)) return [];
1512
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
1513
+ return sessions.map((s) => ({
1514
+ profileId: String(s?.profileId || s?.profile_id || s?.sessionId || s?.session_id || ""),
1515
+ sessionId: String(s?.sessionId || s?.session_id || s?.profileId || s?.profile_id || ""),
1516
+ currentUrl: String(s?.currentUrl || s?.current_url || ""),
1517
+ lastPhase: String(s?.lastPhase || s?.phase || "phase1"),
1518
+ lastActiveAt: String(s?.lastActiveAt || now2),
1519
+ status: "running"
1520
+ })).filter((s) => s.profileId);
1521
+ });
1522
+ ipcMain2.handle("runtime:focus", async (_evt, input) => {
1523
+ const profileId = String(input?.profileId || "").trim();
1524
+ if (!profileId) return { ok: false, error: "missing profileId" };
1525
+ const focusRes = await unifiedAction("browser:focus", { profile: profileId }).catch(() => ({ ok: false }));
1526
+ await unifiedAction("browser:execute", {
1527
+ profile: profileId,
1528
+ script: `(() => {
1529
+ try {
1530
+ const id = '__webauto_focus_ring__';
1531
+ let el = document.getElementById(id);
1532
+ if (!el) {
1533
+ el = document.createElement('div');
1534
+ el.id = id;
1535
+ el.style.position = 'fixed';
1536
+ el.style.left = '8px';
1537
+ el.style.top = '8px';
1538
+ el.style.right = '8px';
1539
+ el.style.bottom = '8px';
1540
+ el.style.border = '3px solid #2b67ff';
1541
+ el.style.borderRadius = '10px';
1542
+ el.style.zIndex = '2147483647';
1543
+ el.style.pointerEvents = 'none';
1544
+ document.body.appendChild(el);
1545
+ }
1546
+ el.style.display = 'block';
1547
+ setTimeout(() => { try { el.remove(); } catch {} }, 1500);
1548
+ return true;
1549
+ } catch {
1550
+ return false;
1551
+ }
1552
+ })()`
1553
+ }).catch(() => null);
1554
+ return focusRes;
1555
+ });
1556
+ ipcMain2.handle("runtime:kill", async (_evt, input) => {
1557
+ const profileId = String(input?.profileId || "").trim();
1558
+ if (!profileId) return { ok: false, error: "missing profileId" };
1559
+ return unifiedAction("session:delete", { profileId }).catch((err) => ({ ok: false, error: err?.message || String(err) }));
1560
+ });
1561
+ ipcMain2.handle("runtime:restartPhase1", async (_evt, input) => {
1562
+ const profileId = String(input?.profileId || "").trim();
1563
+ if (!profileId) return { ok: false, error: "missing profileId" };
1564
+ const args = [path5.join(REPO_ROOT2, "scripts", "xiaohongshu", "phase1-boot.mjs"), "--profile", profileId, "--headless", "false"];
1565
+ return spawnCommand({ title: `Phase1 restart ${profileId}`, cwd: REPO_ROOT2, args, groupKey: "phase1" });
1566
+ });
1567
+ ipcMain2.handle("runtime:setBrowserTitle", async (_evt, input) => {
1568
+ const profileId = String(input?.profileId || "").trim();
1569
+ const title = String(input?.title || "").trim();
1570
+ if (!profileId || !title) return { ok: false, error: "missing profileId/title" };
1571
+ return unifiedAction("browser:execute", {
1572
+ profile: profileId,
1573
+ script: `(() => { try { document.title = ${JSON.stringify(title)}; return true; } catch { return false; } })()`
1574
+ }).catch((err) => ({ ok: false, error: err?.message || String(err) }));
1575
+ });
1576
+ ipcMain2.handle("runtime:setHeaderBar", async (_evt, input) => {
1577
+ const profileId = String(input?.profileId || "").trim();
1578
+ const label = String(input?.label || "").trim();
1579
+ const color = String(input?.color || "").trim();
1580
+ if (!profileId || !label || !color) return { ok: false, error: "missing profileId/label/color" };
1581
+ const safeColor = /^#[0-9a-fA-F]{6}$/.test(color) ? color : "#2b67ff";
1582
+ return unifiedAction("browser:execute", {
1583
+ profile: profileId,
1584
+ script: `(() => {
1585
+ try {
1586
+ const id = '__webauto_header_bar__';
1587
+ let bar = document.getElementById(id);
1588
+ if (!bar) {
1589
+ bar = document.createElement('div');
1590
+ bar.id = id;
1591
+ bar.style.position = 'fixed';
1592
+ bar.style.left = '0';
1593
+ bar.style.top = '0';
1594
+ bar.style.right = '0';
1595
+ bar.style.height = '22px';
1596
+ bar.style.zIndex = '2147483647';
1597
+ bar.style.display = 'flex';
1598
+ bar.style.alignItems = 'center';
1599
+ bar.style.padding = '0 10px';
1600
+ bar.style.fontSize = '12px';
1601
+ bar.style.fontFamily = 'system-ui, sans-serif';
1602
+ bar.style.fontWeight = '600';
1603
+ bar.style.color = '#fff';
1604
+ bar.style.pointerEvents = 'none';
1605
+ document.body.appendChild(bar);
1606
+ const html = document.documentElement;
1607
+ if (html) html.style.scrollPaddingTop = '22px';
1608
+ }
1609
+ bar.style.background = ${JSON.stringify(safeColor)};
1610
+ bar.textContent = ${JSON.stringify(label)};
1611
+ return true;
1612
+ } catch {
1613
+ return false;
1614
+ }
1615
+ })()`
1616
+ }).catch((err) => ({ ok: false, error: err?.message || String(err) }));
1617
+ });
1618
+ //# sourceMappingURL=index.mjs.map