@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.
- package/apps/desktop-console/default-settings.json +1 -0
- package/apps/desktop-console/dist/main/index.mjs +1618 -0
- package/apps/desktop-console/{src → dist}/main/preload.mjs +10 -0
- package/apps/desktop-console/dist/renderer/index.js +3063 -0
- package/apps/desktop-console/entry/ui-console.mjs +299 -0
- package/apps/webauto/entry/account.mjs +356 -0
- package/apps/webauto/entry/lib/account-detect.mjs +160 -0
- package/apps/webauto/entry/lib/account-store.mjs +587 -0
- package/apps/webauto/entry/lib/profilepool.mjs +1 -1
- package/apps/webauto/entry/xhs-install.mjs +27 -3
- package/apps/webauto/entry/xhs-status.mjs +152 -0
- package/apps/webauto/entry/xhs-unified.mjs +595 -17
- package/bin/webauto.mjs +247 -12
- package/dist/apps/webauto/server.js +66 -0
- package/dist/modules/camo-backend/src/index.js +575 -0
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +817 -0
- package/dist/modules/camo-backend/src/internal/ElementRegistry.js +61 -0
- package/dist/modules/camo-backend/src/internal/ProfileLock.js +85 -0
- package/dist/modules/camo-backend/src/internal/SessionManager.js +172 -0
- package/dist/modules/camo-backend/src/internal/container-matcher.js +852 -0
- package/dist/modules/camo-backend/src/internal/engine-manager.js +258 -0
- package/dist/modules/camo-backend/src/internal/fingerprint.js +203 -0
- package/dist/modules/camo-backend/src/internal/pageRuntime.js +29 -0
- package/dist/modules/camo-backend/src/internal/runtimeInjector.js +30 -0
- package/dist/modules/camo-backend/src/internal/state-bus.js +46 -0
- package/dist/modules/camo-backend/src/internal/storage-paths.js +36 -0
- package/dist/modules/camo-backend/src/internal/ws-server.js +1202 -0
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +423 -0
- package/dist/modules/camo-runtime/src/utils/config.mjs +77 -0
- package/dist/modules/container-registry/src/index.js +184 -0
- package/dist/modules/logging/src/index.js +92 -0
- package/dist/modules/operations/src/builtin.js +27 -0
- package/dist/modules/operations/src/container-binding.js +75 -0
- package/dist/modules/operations/src/executor.js +146 -0
- package/dist/modules/operations/src/operations/click.js +167 -0
- package/dist/modules/operations/src/operations/extract.js +204 -0
- package/dist/modules/operations/src/operations/find-child.js +17 -0
- package/dist/modules/operations/src/operations/highlight.js +138 -0
- package/dist/modules/operations/src/operations/key.js +61 -0
- package/dist/modules/operations/src/operations/navigate.js +148 -0
- package/dist/modules/operations/src/operations/scroll.js +126 -0
- package/dist/modules/operations/src/operations/type.js +190 -0
- package/dist/modules/operations/src/queue.js +100 -0
- package/dist/modules/operations/src/registry.js +11 -0
- package/dist/modules/operations/src/system/mouse.js +33 -0
- package/dist/modules/state/src/atomic-json.js +33 -0
- package/dist/modules/workflow/blocks/AnchorVerificationBlock.js +71 -0
- package/dist/modules/workflow/blocks/BehaviorRandomizer.js +26 -0
- package/dist/modules/workflow/blocks/CallWorkflowBlock.js +38 -0
- package/dist/modules/workflow/blocks/CloseDetailBlock.js +209 -0
- package/dist/modules/workflow/blocks/CollectBatch.js +137 -0
- package/dist/modules/workflow/blocks/CollectCommentsBlock.js +415 -0
- package/dist/modules/workflow/blocks/CollectSearchListBlock.js +599 -0
- package/dist/modules/workflow/blocks/CollectWeiboPosts.js +229 -0
- package/dist/modules/workflow/blocks/DetectPageStateBlock.js +259 -0
- package/dist/modules/workflow/blocks/EnsureLoginBlock.js +162 -0
- package/dist/modules/workflow/blocks/EnsureSession.js +426 -0
- package/dist/modules/workflow/blocks/ErrorClassifier.js +164 -0
- package/dist/modules/workflow/blocks/ErrorRecoveryBlock.js +319 -0
- package/dist/modules/workflow/blocks/ExpandCommentsBlock.js +1032 -0
- package/dist/modules/workflow/blocks/ExtractDetailBlock.js +310 -0
- package/dist/modules/workflow/blocks/ExtractPostFields.js +88 -0
- package/dist/modules/workflow/blocks/GenerateSmartReplyBlock.js +68 -0
- package/dist/modules/workflow/blocks/GoToSearchBlock.js +497 -0
- package/dist/modules/workflow/blocks/GracefulFallbackBlock.js +104 -0
- package/dist/modules/workflow/blocks/HighlightBlock.js +66 -0
- package/dist/modules/workflow/blocks/InitAutoScroll.js +65 -0
- package/dist/modules/workflow/blocks/LoadContainerDefinition.js +50 -0
- package/dist/modules/workflow/blocks/LoadContainerIndex.js +43 -0
- package/dist/modules/workflow/blocks/LocateAndGuardBlock.js +176 -0
- package/dist/modules/workflow/blocks/LoginRecoveryBlock.js +242 -0
- package/dist/modules/workflow/blocks/MatchContainers.js +64 -0
- package/dist/modules/workflow/blocks/MonitoringBlock.js +190 -0
- package/dist/modules/workflow/blocks/OpenDetailBlock.js +1240 -0
- package/dist/modules/workflow/blocks/OrganizeXhsNotesBlock.js +117 -0
- package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +270 -0
- package/dist/modules/workflow/blocks/PickSinglePost.js +69 -0
- package/dist/modules/workflow/blocks/ProgressTracker.js +125 -0
- package/dist/modules/workflow/blocks/RecordFixtureBlock.js +44 -0
- package/dist/modules/workflow/blocks/RenderMarkdown.js +48 -0
- package/dist/modules/workflow/blocks/SaveFile.js +54 -0
- package/dist/modules/workflow/blocks/ScrollNextBatch.js +72 -0
- package/dist/modules/workflow/blocks/SessionHealthBlock.js +73 -0
- package/dist/modules/workflow/blocks/StartBrowserService.js +45 -0
- package/dist/modules/workflow/blocks/ValidateContainerDefinition.js +67 -0
- package/dist/modules/workflow/blocks/ValidateExtract.js +35 -0
- package/dist/modules/workflow/blocks/WaitSearchPermitBlock.js +162 -0
- package/dist/modules/workflow/blocks/WaitStable.js +74 -0
- package/dist/modules/workflow/blocks/WarmupCommentsBlock.js +120 -0
- package/dist/modules/workflow/blocks/WorkflowExecutor.js +156 -0
- package/dist/modules/workflow/blocks/XiaohongshuCollectFromLinksBlock.js +1004 -0
- package/dist/modules/workflow/blocks/XiaohongshuCollectLinksBlock.js +1049 -0
- package/dist/modules/workflow/blocks/XiaohongshuFullCollectBlock.js +782 -0
- package/dist/modules/workflow/blocks/helpers/anchorVerify.js +198 -0
- package/dist/modules/workflow/blocks/helpers/asyncWorkQueue.js +53 -0
- package/dist/modules/workflow/blocks/helpers/commentScroller.js +334 -0
- package/dist/modules/workflow/blocks/helpers/commentSectionLocator.js +126 -0
- package/dist/modules/workflow/blocks/helpers/containerAnchors.js +301 -0
- package/dist/modules/workflow/blocks/helpers/debugArtifacts.js +6 -0
- package/dist/modules/workflow/blocks/helpers/downloadPaths.js +29 -0
- package/dist/modules/workflow/blocks/helpers/expandCommentsController.js +53 -0
- package/dist/modules/workflow/blocks/helpers/expandCommentsExtractor.js +129 -0
- package/dist/modules/workflow/blocks/helpers/macosVisionOcrPlugin.js +116 -0
- package/dist/modules/workflow/blocks/helpers/mergeXhsMarkdown.js +109 -0
- package/dist/modules/workflow/blocks/helpers/openDetailController.js +56 -0
- package/dist/modules/workflow/blocks/helpers/openDetailTypes.js +7 -0
- package/dist/modules/workflow/blocks/helpers/openDetailViewport.js +474 -0
- package/dist/modules/workflow/blocks/helpers/openDetailWaiter.js +104 -0
- package/dist/modules/workflow/blocks/helpers/operationLogger.js +195 -0
- package/dist/modules/workflow/blocks/helpers/persistedNotes.js +107 -0
- package/dist/modules/workflow/blocks/helpers/replyExpander.js +260 -0
- package/dist/modules/workflow/blocks/helpers/scrollIntoView.js +138 -0
- package/dist/modules/workflow/blocks/helpers/searchExecutor.js +328 -0
- package/dist/modules/workflow/blocks/helpers/searchGate.js +46 -0
- package/dist/modules/workflow/blocks/helpers/searchPageState.js +164 -0
- package/dist/modules/workflow/blocks/helpers/searchResultWaiter.js +64 -0
- package/dist/modules/workflow/blocks/helpers/simpleAnchor.js +134 -0
- package/dist/modules/workflow/blocks/helpers/smartReply.js +40 -0
- package/dist/modules/workflow/blocks/helpers/systemInput.js +635 -0
- package/dist/modules/workflow/blocks/helpers/targetCountMode.js +9 -0
- package/dist/modules/workflow/blocks/helpers/xhsCliArgs.js +80 -0
- package/dist/modules/workflow/blocks/helpers/xhsCommentDom.js +805 -0
- package/dist/modules/workflow/blocks/helpers/xhsNoteOrganizer.js +140 -0
- package/dist/modules/workflow/blocks/restore/RestorePhaseBlock.js +204 -0
- package/dist/modules/workflow/config/workflowRegistry.js +32 -0
- package/dist/modules/workflow/definitions/batch-collect-workflow.js +63 -0
- package/dist/modules/workflow/definitions/scroll-extract-workflow.js +74 -0
- package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow-v2.js +81 -0
- package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow.js +57 -0
- package/dist/modules/workflow/definitions/xiaohongshu-full-collect-workflow-v3.js +68 -0
- package/dist/modules/workflow/definitions/xiaohongshu-note-collect.js +49 -0
- package/dist/modules/workflow/definitions/xiaohongshu-phase1-workflow-v3.js +30 -0
- package/dist/modules/workflow/definitions/xiaohongshu-phase2-links-workflow-v3.js +40 -0
- package/dist/modules/workflow/definitions/xiaohongshu-phase3-collect-workflow-v1.js +54 -0
- package/dist/modules/workflow/definitions/xiaohongshu-phase34-from-links-workflow-v3.js +25 -0
- package/dist/modules/workflow/src/WeiboEventDrivenWorkflowRunner.js +308 -0
- package/dist/modules/workflow/src/context.js +70 -0
- package/dist/modules/workflow/src/index.js +5 -0
- package/dist/modules/workflow/src/orchestrator.js +230 -0
- package/dist/modules/workflow/src/runner.js +55 -0
- package/dist/modules/workflow/src/runtime.js +70 -0
- package/dist/modules/workflow/workflows/WeiboFeedExtractionWorkflow.js +359 -0
- package/dist/modules/workflow/workflows/XiaohongshuLoginWorkflow.js +110 -0
- package/dist/modules/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
- package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
- package/dist/modules/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +42 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
- package/dist/modules/xiaohongshu/app/src/index.js +9 -0
- package/dist/modules/xiaohongshu/app/src/utils/checkpoints.js +222 -0
- package/dist/modules/xiaohongshu/app/src/utils/controllerAction.js +43 -0
- package/dist/services/controller/src/controller.js +1476 -0
- package/dist/services/controller/src/index.js +2 -0
- package/dist/services/controller/src/payload-normalizer.js +129 -0
- package/dist/services/shared/heartbeat.js +120 -0
- package/dist/services/shared/lib/errorHandler.js +2 -0
- package/dist/services/shared/serviceProcessLogger.js +139 -0
- package/dist/services/unified-api/RemoteBrowserSession.js +176 -0
- package/dist/services/unified-api/RemoteSessionManager.js +148 -0
- package/dist/services/unified-api/container-operations-handler.js +115 -0
- package/dist/services/unified-api/server.js +652 -0
- package/dist/services/unified-api/state-registry.js +274 -0
- package/dist/services/unified-api/task-persistence.js +66 -0
- package/dist/services/unified-api/task-state.js +130 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +12 -5
- package/modules/xiaohongshu/app/pnpm-lock.yaml +24 -0
- package/package.json +37 -9
- package/.beads/README.md +0 -81
- package/.beads/config.yaml +0 -67
- package/.beads/interactions.jsonl +0 -0
- package/.beads/issues.jsonl +0 -180
- package/.beads/metadata.json +0 -4
- package/.claude/settings.local.json +0 -10
- package/.github/workflows/ci.yml +0 -55
- package/AGENTS.md +0 -253
- package/apps/desktop-console/README.md +0 -27
- package/apps/desktop-console/package-lock.json +0 -897
- package/apps/desktop-console/package.json +0 -20
- package/apps/desktop-console/scripts/build-and-install.mjs +0 -19
- package/apps/desktop-console/scripts/build.mjs +0 -45
- package/apps/desktop-console/scripts/test-preload.mjs +0 -13
- package/apps/desktop-console/src/main/config.mts +0 -26
- package/apps/desktop-console/src/main/core-daemon-manager.mts +0 -131
- package/apps/desktop-console/src/main/desktop-settings.mts +0 -267
- package/apps/desktop-console/src/main/heartbeat-watchdog.mts +0 -50
- package/apps/desktop-console/src/main/heartbeat-watchdog.test.mts +0 -68
- package/apps/desktop-console/src/main/index-streaming.test.mts +0 -20
- package/apps/desktop-console/src/main/index.mts +0 -980
- package/apps/desktop-console/src/main/profile-store.mts +0 -239
- package/apps/desktop-console/src/main/profile-store.test.mts +0 -54
- package/apps/desktop-console/src/main/state-bridge.mts +0 -114
- package/apps/desktop-console/src/main/task-state-types.ts +0 -32
- package/apps/desktop-console/src/renderer/hooks/use-task-state.mts +0 -120
- package/apps/desktop-console/src/renderer/index.mts +0 -133
- package/apps/desktop-console/src/renderer/index.test.mts +0 -34
- package/apps/desktop-console/src/renderer/path-helpers.mts +0 -46
- package/apps/desktop-console/src/renderer/path-helpers.test.mts +0 -14
- package/apps/desktop-console/src/renderer/tabs/debug.mts +0 -48
- package/apps/desktop-console/src/renderer/tabs/debug.test.mts +0 -22
- package/apps/desktop-console/src/renderer/tabs/logs.mts +0 -421
- package/apps/desktop-console/src/renderer/tabs/logs.test.mts +0 -27
- package/apps/desktop-console/src/renderer/tabs/preflight.mts +0 -486
- package/apps/desktop-console/src/renderer/tabs/preflight.test.mts +0 -33
- package/apps/desktop-console/src/renderer/tabs/profile-pool.mts +0 -213
- package/apps/desktop-console/src/renderer/tabs/results.mts +0 -171
- package/apps/desktop-console/src/renderer/tabs/run.test.mts +0 -63
- package/apps/desktop-console/src/renderer/tabs/runtime.mts +0 -151
- package/apps/desktop-console/src/renderer/tabs/settings.mts +0 -146
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/account-flow.mts +0 -486
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/guide-browser-check.mts +0 -56
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/helpers.mts +0 -262
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/layout-block.mts +0 -430
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/live-stats.mts +0 -847
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/run-flow.mts +0 -443
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu-state.mts +0 -425
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu.mts +0 -497
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu.test.mts +0 -291
- package/apps/desktop-console/src/renderer/ui-components.mts +0 -31
- package/docs/README_camoufox_chinese.md +0 -141
- package/docs/USAGE_V3.md +0 -163
- package/docs/arch/OCR_MACOS_PLUGIN.md +0 -39
- package/docs/arch/PORTS.md +0 -40
- package/docs/arch/REGRESSION_CHECKLIST.md +0 -121
- package/docs/arch/SEARCH_GATE.md +0 -224
- package/docs/arch/VIEWPORT_SAFETY.md +0 -182
- package/docs/arch/XIAOHONGSHU_OFFLINE_MOCK_DESIGN.md +0 -267
- package/docs/xiaohongshu-container-driven-summary.md +0 -221
- package/docs/xiaohongshu-full-collect-runbook.md +0 -134
- package/docs/xiaohongshu-next-steps.md +0 -228
- package/docs/xiaohongshu-quickstart.md +0 -73
- package/docs/xiaohongshu-workflow-summary.md +0 -227
- package/modules/container-registry/tests/container-registry.test.ts +0 -16
- package/modules/logging/tests/logging.test.ts +0 -38
- package/modules/operations/tests/operations.test.ts +0 -22
- package/modules/operations/tests/viewport-filter.test.ts +0 -161
- package/modules/operations/tests/visible-only.test.ts +0 -250
- package/modules/session-manager/tests/session-manager.test.ts +0 -23
- package/modules/state/src/atomic-json.test.ts +0 -30
- package/modules/state/src/paths.test.ts +0 -59
- package/modules/state/src/xiaohongshu-collect-state.test.ts +0 -259
- package/modules/workflow/blocks/AnchorVerificationBlock.d.ts.map +0 -1
- package/modules/workflow/blocks/AnchorVerificationBlock.js.map +0 -1
- package/modules/workflow/blocks/DetectPageStateBlock.d.ts.map +0 -1
- package/modules/workflow/blocks/DetectPageStateBlock.js.map +0 -1
- package/modules/workflow/blocks/ErrorRecoveryBlock.d.ts.map +0 -1
- package/modules/workflow/blocks/ErrorRecoveryBlock.js.map +0 -1
- package/modules/workflow/blocks/WaitSearchPermitBlock.d.ts.map +0 -1
- package/modules/workflow/blocks/WaitSearchPermitBlock.js.map +0 -1
- package/modules/workflow/blocks/helpers/containerAnchors.d.ts.map +0 -1
- package/modules/workflow/blocks/helpers/containerAnchors.js.map +0 -1
- package/modules/workflow/blocks/helpers/downloadPaths.test.ts +0 -62
- package/modules/workflow/blocks/helpers/mergeXhsMarkdown.test.ts +0 -121
- package/modules/workflow/blocks/helpers/operationLogger.d.ts.map +0 -1
- package/modules/workflow/blocks/helpers/operationLogger.js.map +0 -1
- package/modules/workflow/blocks/helpers/persistedNotes.test.ts +0 -268
- package/modules/workflow/blocks/helpers/searchPageState.d.ts.map +0 -1
- package/modules/workflow/blocks/helpers/searchPageState.js.map +0 -1
- package/modules/workflow/blocks/helpers/targetCountMode.test.ts +0 -29
- package/modules/workflow/blocks/helpers/xhsCliArgs.test.ts +0 -75
- package/modules/workflow/tests/smartReply.test.ts +0 -32
- package/modules/xiaohongshu/app/src/blocks/Phase3Interact.matcher.test.ts +0 -33
- package/modules/xiaohongshu/app/src/utils/__tests__/checkpoints.test.ts +0 -141
- package/modules/xiaohongshu/app/tests/commentMatchDsl.test.ts +0 -50
- package/modules/xiaohongshu/app/tests/commentMatcher.test.ts +0 -46
- package/modules/xiaohongshu/app/tests/sharding.test.ts +0 -31
- package/package-scripts.json +0 -8
- package/runtime/infra/utils/README.md +0 -13
- package/runtime/infra/utils/scripts/README.md +0 -0
- package/runtime/infra/utils/scripts/development/eval-in-session.mjs +0 -40
- package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +0 -35
- package/runtime/infra/utils/scripts/service/kill-port.mjs +0 -24
- package/runtime/infra/utils/scripts/service/start-api.mjs +0 -39
- package/runtime/infra/utils/scripts/service/start-browser-service.mjs +0 -106
- package/runtime/infra/utils/scripts/service/stop-api.mjs +0 -18
- package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +0 -104
- package/runtime/infra/utils/scripts/test-services.mjs +0 -94
- package/services/shared/heartbeat.test.ts +0 -102
- package/services/unified-api/__tests__/task-state.test.ts +0 -95
- package/sitecustomize.py +0 -19
- package/tests/README.md +0 -194
- package/tests/e2e/workflows/weibo-feed-extraction.test.ts +0 -171
- package/tests/fixtures/data/container-definitions.json +0 -67
- package/tests/fixtures/pages/simple-page.html +0 -69
- package/tests/integration/01-test-container-match.mjs +0 -188
- package/tests/integration/02-test-dom-branch.mjs +0 -161
- package/tests/integration/03-test-container-operation-system.mjs +0 -91
- package/tests/integration/05-test-container-lifecycle-events.mjs +0 -224
- package/tests/integration/05-test-container-lifecycle-with-events.mjs +0 -250
- package/tests/integration/06-test-container-dom-tree-drawing.mjs +0 -256
- package/tests/integration/07-test-weibo-container-lifecycle.mjs +0 -355
- package/tests/integration/08-test-weibo-feed-workflow.test.mjs +0 -164
- package/tests/integration/10-test-visual-analyzer.mjs +0 -312
- package/tests/integration/11-test-visual-loop.mjs +0 -284
- package/tests/integration/12-test-simple-visual-loop.mjs +0 -242
- package/tests/integration/13-test-visual-robust.mjs +0 -185
- package/tests/integration/14-test-visual-highlight-loop.mjs +0 -271
- package/tests/integration/inspect-page.mjs +0 -50
- package/tests/integration/run-all-tests.mjs +0 -95
- package/tests/patch_verification/CODEX_PATCH_TEST.md +0 -103
- package/tests/patch_verification/PHASE2_ANALYSIS.md +0 -179
- package/tests/patch_verification/PHASE2_OPTIMIZATION_REPORT.md +0 -55
- package/tests/patch_verification/PHASE2_TO_PHASE4_SUMMARY.md +0 -126
- package/tests/patch_verification/QUICK_TEST_SEQUENCE.md +0 -262
- package/tests/patch_verification/README.md +0 -143
- package/tests/patch_verification/RUN_TESTS.md +0 -60
- package/tests/patch_verification/TEST_EXECUTION.md +0 -99
- package/tests/patch_verification/TEST_PLAN.md +0 -328
- package/tests/patch_verification/TEST_RESULTS.md +0 -34
- package/tests/patch_verification/TOOL_TEST_PLAN.md +0 -48
- package/tests/patch_verification/run-tool-test.mjs +0 -121
- package/tests/patch_verification/temp_test_files/test01.txt +0 -1
- package/tests/patch_verification/temp_test_files/test02.txt +0 -3
- package/tests/patch_verification/temp_test_files/test02_gnu.txt +0 -3
- package/tests/patch_verification/temp_test_files/test03.txt +0 -1
- package/tests/patch_verification/temp_test_files/test03_multiline.txt +0 -5
- package/tests/patch_verification/temp_test_files/test04_function.ts +0 -5
- package/tests/patch_verification/temp_test_files/test05_import.ts +0 -4
- package/tests/patch_verification/temp_test_files/test06_special_chars.txt +0 -4
- package/tests/patch_verification/temp_test_files/test07_indentation.ts +0 -5
- package/tests/patch_verification/temp_test_files/test08_mismatch.txt +0 -1
- package/tests/patch_verification/temp_test_files/test_add_02.txt +0 -3
- package/tests/patch_verification/temp_test_files/test_simple.txt +0 -1
- package/tests/runner/TestReporter.mjs +0 -57
- package/tests/runner/TestRunner.mjs +0 -244
- package/tests/unit/commands/profile.test.mjs +0 -10
- package/tests/unit/container/change-notifier.test.mjs +0 -181
- package/tests/unit/lifecycle/session-registry.test.mjs +0 -135
- package/tests/unit/operations/registry.test.ts +0 -73
- package/tests/unit/utils/browser-service.test.mjs +0 -153
- package/tests/unit/utils/config.test.mjs +0 -166
- package/tests/unit/utils/fingerprint.test.mjs +0 -166
- package/tsconfig.json +0 -31
- package/tsconfig.services.json +0 -26
- /package/apps/desktop-console/{src → dist}/renderer/index.html +0 -0
- /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
|