@web-auto/webauto 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/apps/desktop-console/default-settings.json +2 -2
  2. package/apps/desktop-console/dist/main/index.mjs +983 -128
  3. package/apps/desktop-console/dist/main/preload.mjs +7 -0
  4. package/apps/desktop-console/dist/renderer/index.html +622 -50
  5. package/apps/desktop-console/dist/renderer/index.js +2423 -469
  6. package/apps/desktop-console/dist/renderer/run.mts +6 -5
  7. package/apps/desktop-console/entry/ui-cli.mjs +672 -0
  8. package/apps/desktop-console/entry/ui-console.mjs +416 -29
  9. package/apps/webauto/entry/account.mjs +89 -53
  10. package/apps/webauto/entry/browser-status.mjs +7 -10
  11. package/apps/webauto/entry/lib/account-detect.mjs +254 -28
  12. package/apps/webauto/entry/lib/account-store.mjs +219 -30
  13. package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
  14. package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
  15. package/apps/webauto/entry/lib/profilepool.mjs +14 -5
  16. package/apps/webauto/entry/lib/quota-status.mjs +23 -0
  17. package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
  18. package/apps/webauto/entry/profilepool.mjs +106 -17
  19. package/apps/webauto/entry/schedule.mjs +612 -0
  20. package/apps/webauto/entry/weibo-unified.mjs +134 -0
  21. package/apps/webauto/entry/xhs-install.mjs +256 -31
  22. package/apps/webauto/entry/xhs-status.mjs +5 -2
  23. package/apps/webauto/entry/xhs-unified.mjs +631 -98
  24. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
  25. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
  26. package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
  27. package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
  28. package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
  29. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
  30. package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
  31. package/bin/camoufox-cli.mjs +61 -0
  32. package/bin/webauto.mjs +301 -54
  33. package/dist/modules/camo-backend/src/index.js +49 -1
  34. package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
  35. package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
  36. package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
  37. package/dist/modules/collection-manager/bloom-filter.js +91 -0
  38. package/dist/modules/collection-manager/date-utils.js +275 -0
  39. package/dist/modules/collection-manager/index.js +258 -0
  40. package/dist/modules/collection-manager/storage.js +195 -0
  41. package/dist/modules/collection-manager/types.js +47 -0
  42. package/dist/modules/logging/src/index.js +1 -1
  43. package/dist/modules/process-registry/index.js +230 -0
  44. package/dist/modules/rate-limiter/index.js +242 -0
  45. package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
  46. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
  47. package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
  48. package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
  49. package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
  50. package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
  51. package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
  52. package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
  53. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
  54. package/dist/modules/workflow/config/workflowRegistry.js +2 -0
  55. package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
  56. package/dist/modules/workflow/src/runner.js +6 -0
  57. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
  58. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
  59. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
  60. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
  61. package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
  62. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
  63. package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
  64. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
  65. package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
  66. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
  67. package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  68. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
  69. package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  70. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
  71. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
  72. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
  73. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
  74. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
  75. package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
  76. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
  77. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  78. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
  79. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  80. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
  81. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  82. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
  83. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  84. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
  85. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  86. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
  87. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  88. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
  89. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  90. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
  91. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  92. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
  93. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  94. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
  95. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  96. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
  97. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  98. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
  99. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  100. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
  101. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  102. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
  103. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  104. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
  105. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  106. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
  107. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  108. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
  109. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  110. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
  111. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  112. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
  113. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  114. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
  115. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  116. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
  117. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  118. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
  119. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  120. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
  121. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
  122. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
  123. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  124. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
  125. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
  126. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
  127. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  128. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
  129. package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  130. package/dist/services/shared/serviceProcessLogger.js +1 -1
  131. package/dist/services/unified-api/server.js +105 -11
  132. package/modules/camo-backend/src/index.ts +46 -1
  133. package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
  134. package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
  135. package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
  136. package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
  137. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
  138. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
  139. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
  140. package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
  141. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
  142. package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
  143. package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
  144. package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
  145. package/modules/collection-manager/bloom-filter.ts +112 -0
  146. package/modules/collection-manager/date-utils.ts +316 -0
  147. package/modules/collection-manager/index.ts +309 -0
  148. package/modules/collection-manager/package.json +10 -0
  149. package/modules/collection-manager/storage.ts +174 -0
  150. package/modules/collection-manager/types.ts +156 -0
  151. package/modules/logging/src/index.ts +1 -1
  152. package/modules/process-registry/index.ts +284 -0
  153. package/modules/rate-limiter/index.ts +322 -0
  154. package/modules/state/src/paths.ts +9 -1
  155. package/modules/task-scheduler/index.ts +293 -0
  156. package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
  157. package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
  158. package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
  159. package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
  160. package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
  161. package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
  162. package/modules/workflow/config/workflowRegistry.ts +2 -0
  163. package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
  164. package/modules/workflow/src/runner.ts +6 -0
  165. package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
  166. package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
  167. package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
  168. package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
  169. package/package.json +13 -4
  170. package/scripts/postinstall-resources.mjs +62 -0
  171. package/scripts/test/run-coverage.mjs +76 -0
  172. package/scripts/weibo/search.ts +49 -0
  173. package/services/shared/serviceProcessLogger.ts +1 -1
  174. package/services/unified-api/server.ts +98 -12
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Workflow Block: DetectPageStateBlock
3
+ *
4
+ * 基于 URL + 容器匹配检测当前页面阶段:
5
+ * - xiaohongshu: login / detail / search / home / unknown
6
+ * - weibo: login / detail / search / home / unknown
7
+ *
8
+ * 用于在进入各 Phase 前做“入口锚点”判定。
9
+ */
10
+ import { logControllerActionError, logControllerActionResult, logControllerActionStart, } from './helpers/operationLogger.js';
11
+ const PLATFORM_CONTAINERS = {
12
+ xiaohongshu: [
13
+ { id: 'xiaohongshu_login.login_guard', name: '登录页', urlPattern: /\/login/ },
14
+ { id: 'xiaohongshu_detail.modal_shell', name: '详情页', urlPattern: /\/explore\// },
15
+ {
16
+ id: 'xiaohongshu_search.search_result_list',
17
+ name: '搜索结果页',
18
+ urlPattern: /\/search_result/,
19
+ },
20
+ { id: 'xiaohongshu_home', name: '主页/推荐流', urlPattern: /\/explore/ },
21
+ ],
22
+ weibo: [
23
+ { id: 'weibo_login.login_guard', name: '登录页', urlPattern: /\/signin/ },
24
+ { id: 'weibo_detail.modal_shell', name: '详情页', urlPattern: /\/\d+\// },
25
+ { id: 'weibo_search.feed_list', name: '搜索结果页', urlPattern: /\/search/ },
26
+ { id: 'weibo_home.feed_list', name: '主页', urlPattern: /^https:\/\/weibo\.com\/?$/ },
27
+ ],
28
+ };
29
+ function detectPlatformFromUrl(url) {
30
+ if (url.includes('xiaohongshu.com'))
31
+ return 'xiaohongshu';
32
+ if (url.includes('weibo.com'))
33
+ return 'weibo';
34
+ return 'unknown';
35
+ }
36
+ function detectPageByUrl(url, platformContainers) {
37
+ for (const def of platformContainers) {
38
+ if (def.urlPattern && def.urlPattern.test(url)) {
39
+ return def;
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ function detectPageByContainer(matchIds, platformContainers, currentUrl) {
45
+ const containerIds = new Set(matchIds);
46
+ for (const def of platformContainers) {
47
+ if (containerIds.has(def.id)) {
48
+ // 对于小红书搜索结果页,额外检查 URL 是否包含 search_result,避免误把首页当成搜索页
49
+ if (def.id === 'xiaohongshu_search.search_result_list' && !currentUrl.includes('search_result')) {
50
+ continue;
51
+ }
52
+ return def;
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ function mapContainerIdToStage(platform, containerId) {
58
+ if (!containerId)
59
+ return 'unknown';
60
+ if (platform === 'xiaohongshu') {
61
+ if (containerId === 'xiaohongshu_login.login_guard')
62
+ return 'login';
63
+ if (containerId === 'xiaohongshu_detail.modal_shell')
64
+ return 'detail';
65
+ if (containerId === 'xiaohongshu_search.search_result_list')
66
+ return 'search';
67
+ if (containerId === 'xiaohongshu_home')
68
+ return 'home';
69
+ return 'unknown';
70
+ }
71
+ if (platform === 'weibo') {
72
+ if (containerId === 'weibo_login.login_guard')
73
+ return 'login';
74
+ if (containerId === 'weibo_detail.modal_shell')
75
+ return 'detail';
76
+ if (containerId === 'weibo_search.feed_list')
77
+ return 'search';
78
+ if (containerId === 'weibo_home.feed_list')
79
+ return 'home';
80
+ return 'unknown';
81
+ }
82
+ return 'unknown';
83
+ }
84
+ function detectRiskControlByUrl(url, platform) {
85
+ const u = url.toLowerCase();
86
+ if (platform === 'xiaohongshu') {
87
+ return (u.includes('/website-login/captcha') ||
88
+ u.includes('/website-login/verify') ||
89
+ u.includes('/website-login/security') ||
90
+ u.includes('verifyuuid=') ||
91
+ u.includes('verifytype=') ||
92
+ u.includes('verifybiz='));
93
+ }
94
+ if (platform === 'weibo') {
95
+ return u.includes('/signup/verify') || u.includes('/security/') || u.includes('/sina/verify');
96
+ }
97
+ return false;
98
+ }
99
+ function detectLoginByUrl(url, platform) {
100
+ if (platform === 'xiaohongshu')
101
+ return /\/login/.test(url);
102
+ if (platform === 'weibo')
103
+ return /\/signin/.test(url);
104
+ return false;
105
+ }
106
+ export async function execute(input) {
107
+ const { sessionId, platform: platformHint = 'auto', serviceUrl = 'http://127.0.0.1:7701', } = input;
108
+ const controllerUrl = `${serviceUrl}/v1/controller/action`;
109
+ async function controllerAction(action, payload = {}) {
110
+ const opId = logControllerActionStart(action, payload, { source: 'DetectPageStateBlock' });
111
+ try {
112
+ const res = await fetch(controllerUrl, {
113
+ method: 'POST',
114
+ headers: { 'Content-Type': 'application/json' },
115
+ body: JSON.stringify({ action, payload }),
116
+ });
117
+ if (!res.ok) {
118
+ throw new Error(`HTTP ${res.status}: ${await res.text()}`);
119
+ }
120
+ const data = await res.json();
121
+ const result = data.data || data;
122
+ logControllerActionResult(opId, action, result, { source: 'DetectPageStateBlock' });
123
+ return result;
124
+ }
125
+ catch (error) {
126
+ logControllerActionError(opId, action, error, payload, { source: 'DetectPageStateBlock' });
127
+ throw error;
128
+ }
129
+ }
130
+ async function getCurrentUrl() {
131
+ const data = await controllerAction('browser:execute', {
132
+ profile: sessionId,
133
+ script: 'window.location.href',
134
+ });
135
+ return data?.result || data?.data?.result || '';
136
+ }
137
+ async function getDomSummary() {
138
+ try {
139
+ const data = await controllerAction('browser:execute', {
140
+ profile: sessionId,
141
+ script: `(() => {
142
+ const hasDetailMask = !!document.querySelector('.note-detail-mask');
143
+ const hasSearchInput = !!document.querySelector('input[type="search"], #search-input');
144
+ return {
145
+ hasDetailMask,
146
+ hasSearchInput,
147
+ readyState: document.readyState,
148
+ title: document.title
149
+ };
150
+ })()`
151
+ });
152
+ const result = data?.result || data?.data?.result || null;
153
+ if (result && typeof result === 'object')
154
+ return result;
155
+ return {};
156
+ }
157
+ catch {
158
+ return {};
159
+ }
160
+ }
161
+ async function matchContainers() {
162
+ // Controller's containers:match requires an explicit URL.
163
+ // Use the current page URL as the match target; this keeps matching aligned with the active document.
164
+ const currentUrl = await getCurrentUrl().catch(() => '');
165
+ const data = await controllerAction('containers:match', {
166
+ profile: sessionId,
167
+ url: currentUrl,
168
+ });
169
+ const rootId = data?.container?.id || data?.data?.container?.id || null;
170
+ const matches = data?.snapshot?.matches || data?.data?.snapshot?.matches || {};
171
+ const matchIds = Object.entries(matches)
172
+ .filter(([, info]) => {
173
+ const mc = info?.match_count ?? info?.matchCount ?? 0;
174
+ return mc > 0;
175
+ })
176
+ .map(([id]) => id);
177
+ return { rootId, matchIds };
178
+ }
179
+ // 1) 始终优先尝试通过 URL 判定平台和大致页面
180
+ const url = await getCurrentUrl().catch(() => '');
181
+ let platform;
182
+ if (platformHint === 'auto') {
183
+ platform = url ? detectPlatformFromUrl(url) : 'unknown';
184
+ }
185
+ else {
186
+ platform = platformHint;
187
+ }
188
+ if (platform === 'unknown') {
189
+ return {
190
+ success: false,
191
+ sessionId,
192
+ platform,
193
+ url,
194
+ stage: 'unknown',
195
+ error: url ? 'Unknown platform from URL' : 'URL unavailable',
196
+ };
197
+ }
198
+ // Hard-stop detection (URL-based) to avoid triggering risk-control via repeated container matching.
199
+ if (url && detectRiskControlByUrl(url, platform)) {
200
+ return {
201
+ success: true,
202
+ sessionId,
203
+ platform,
204
+ url,
205
+ stage: 'login',
206
+ pageName: 'risk_control',
207
+ rootId: null,
208
+ matchIds: [],
209
+ dom: await getDomSummary(),
210
+ };
211
+ }
212
+ if (url && detectLoginByUrl(url, platform)) {
213
+ return {
214
+ success: true,
215
+ sessionId,
216
+ platform,
217
+ url,
218
+ stage: 'login',
219
+ pageName: 'login_page',
220
+ rootId: null,
221
+ matchIds: [],
222
+ dom: await getDomSummary(),
223
+ };
224
+ }
225
+ const platformContainers = PLATFORM_CONTAINERS[platform] || [];
226
+ const urlDetection = url ? detectPageByUrl(url, platformContainers) : null;
227
+ // 2) 再尝试做一次容器匹配;如果失败则回退到 URL-only 的判定,而不是直接失败
228
+ let rootId = null;
229
+ let matchIds = [];
230
+ let matchError;
231
+ const dom = await getDomSummary();
232
+ try {
233
+ const matched = await matchContainers();
234
+ rootId = matched.rootId;
235
+ matchIds = matched.matchIds;
236
+ }
237
+ catch (error) {
238
+ matchError = error?.message || String(error);
239
+ }
240
+ const containerDetection = matchIds.length > 0
241
+ ? detectPageByContainer([rootId, ...matchIds].filter(Boolean), platformContainers, url)
242
+ : null;
243
+ // 以容器检测为主,URL 为辅;若容器匹配失败则退回 URL-only
244
+ const effectiveDetection = containerDetection || urlDetection || null;
245
+ const stage = mapContainerIdToStage(platform, effectiveDetection?.id);
246
+ return {
247
+ success: true,
248
+ sessionId,
249
+ platform,
250
+ url,
251
+ stage,
252
+ pageName: effectiveDetection?.name,
253
+ rootId,
254
+ matchIds,
255
+ dom,
256
+ error: matchError,
257
+ };
258
+ }
259
+ //# sourceMappingURL=DetectPageStateBlock.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Workflow Block: ErrorRecoveryBlock
3
+ *
4
+ * 职责:
5
+ * - 提供阶段错误后的恢复策略
6
+ * - 统一恢复到安全起点(搜索页或主页)
7
+ * - 验证恢复是否成功(锚点回环)
8
+ * - 支持ESC恢复模式(用于Phase3/4详情页恢复)
9
+ * - 支持多种恢复路径(home/search/detail)
10
+ */
11
+ export interface ErrorRecoveryInput {
12
+ sessionId: string;
13
+ fromStage: 'search' | 'detail' | 'home';
14
+ targetStage: 'search' | 'home';
15
+ serviceUrl?: string;
16
+ maxRetries?: number;
17
+ recoveryMode?: 'esc' | 'navigate';
18
+ }
19
+ export interface ErrorRecoveryOutput {
20
+ success: boolean;
21
+ recovered: boolean;
22
+ finalStage: 'search' | 'home' | 'unknown';
23
+ currentUrl?: string;
24
+ error?: string;
25
+ method?: string;
26
+ }
27
+ export declare function execute(input: ErrorRecoveryInput): Promise<ErrorRecoveryOutput>;
28
+ //# sourceMappingURL=ErrorRecoveryBlock.d.ts.map
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Workflow Block: ErrorRecoveryBlock
3
+ *
4
+ * 职责:
5
+ * - 提供阶段错误后的恢复策略
6
+ * - 统一恢复到安全起点(搜索页或主页)
7
+ * - 验证恢复是否成功(锚点回环)
8
+ * - 支持ESC恢复模式(用于Phase3/4详情页恢复)
9
+ * - 支持多种恢复路径(home/search/detail)
10
+ */
11
+ import { logControllerActionError, logControllerActionResult, logControllerActionStart, } from './helpers/operationLogger.js';
12
+ export async function execute(input) {
13
+ const { sessionId, fromStage, targetStage, serviceUrl = 'http://127.0.0.1:7701', maxRetries = 2, recoveryMode = 'navigate' } = input;
14
+ // 开发阶段策略:只允许系统级 ESC 恢复;禁止 navigate/JS click/dispatchEvent 等兜底
15
+ if (recoveryMode !== 'esc') {
16
+ return {
17
+ success: false,
18
+ recovered: false,
19
+ finalStage: 'unknown',
20
+ error: 'ErrorRecoveryBlock: only esc recovery is allowed (navigate disabled)',
21
+ };
22
+ }
23
+ const controllerUrl = `${serviceUrl}/v1/controller/action`;
24
+ async function controllerAction(action, payload = {}) {
25
+ const opId = logControllerActionStart(action, payload, { source: 'ErrorRecoveryBlock' });
26
+ try {
27
+ const res = await fetch(controllerUrl, {
28
+ method: 'POST',
29
+ headers: { 'Content-Type': 'application/json' },
30
+ body: JSON.stringify({ action, payload }),
31
+ signal: AbortSignal.timeout ? AbortSignal.timeout(10000) : undefined
32
+ });
33
+ if (!res.ok) {
34
+ throw new Error(`HTTP ${res.status}: ${await res.text()}`);
35
+ }
36
+ const data = await res.json();
37
+ const result = data.data || data;
38
+ logControllerActionResult(opId, action, result, { source: 'ErrorRecoveryBlock' });
39
+ return result;
40
+ }
41
+ catch (error) {
42
+ logControllerActionError(opId, action, error, payload, { source: 'ErrorRecoveryBlock' });
43
+ throw error;
44
+ }
45
+ }
46
+ async function getCurrentUrl() {
47
+ const data = await controllerAction('browser:execute', {
48
+ profile: sessionId,
49
+ script: 'location.href'
50
+ });
51
+ return data?.result || data?.data?.result || '';
52
+ }
53
+ async function verifyStage(stage) {
54
+ try {
55
+ const { verifyAnchorByContainerId } = await import('./helpers/containerAnchors.js');
56
+ const targetContainerId = stage === 'search'
57
+ ? 'xiaohongshu_search.search_result_list'
58
+ : 'xiaohongshu_home';
59
+ const anchor = await verifyAnchorByContainerId(targetContainerId, sessionId, serviceUrl, '2px solid #4caf50', 1200);
60
+ if (!anchor.found || !anchor.rect) {
61
+ return false;
62
+ }
63
+ const rectOk = anchor.rect.width > 0 && anchor.rect.height > 0;
64
+ if (!rectOk) {
65
+ return false;
66
+ }
67
+ // 额外约束:确认“可见的”详情 overlay 已完全消失,避免在仍有详情 overlay 时误判为 search/home
68
+ try {
69
+ const domState = await controllerAction('browser:execute', {
70
+ profile: sessionId,
71
+ script: `(() => {
72
+ const selectors = [
73
+ '.note-detail-mask',
74
+ '.note-detail-page',
75
+ '.note-detail-dialog',
76
+ '.note-detail',
77
+ '.detail-container',
78
+ '.media-container'
79
+ ];
80
+ const isVisible = (el) => {
81
+ if (!el) return false;
82
+ const style = window.getComputedStyle(el);
83
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
84
+ const r = el.getBoundingClientRect();
85
+ if (!r.width || !r.height) return false;
86
+ if (r.bottom <= 0 || r.top >= window.innerHeight) return false;
87
+ return true;
88
+ };
89
+ let visibleOverlay = null;
90
+ for (const sel of selectors) {
91
+ const el = document.querySelector(sel);
92
+ if (el && isVisible(el)) {
93
+ visibleOverlay = el;
94
+ break;
95
+ }
96
+ }
97
+ return { hasDetailOverlayVisible: !!visibleOverlay };
98
+ })()`,
99
+ });
100
+ const payload = domState.result || domState.data?.result || domState;
101
+ if (payload?.hasDetailOverlayVisible) {
102
+ console.warn('[ErrorRecovery] verifyStage: visible detail overlay still present, stage not stable');
103
+ return false;
104
+ }
105
+ }
106
+ catch (e) {
107
+ console.warn('[ErrorRecovery] verifyStage DOM check error:', e.message);
108
+ }
109
+ return true;
110
+ }
111
+ catch (error) {
112
+ console.warn('[ErrorRecovery] verifyStage 锚点验证异常:', error.message);
113
+ return false;
114
+ }
115
+ }
116
+ async function recoverWithEsc() {
117
+ console.log('[ErrorRecovery] 使用ESC模式恢复...');
118
+ // 0. 入口锚点:先确认当前确实处于“详情态”(存在“可见的” detail overlay),避免在错误页面上盲动。
119
+ let hasDetailOverlay = false;
120
+ try {
121
+ const domState = await controllerAction('browser:execute', {
122
+ profile: sessionId,
123
+ script: `(() => {
124
+ const selectors = [
125
+ '.note-detail-mask',
126
+ '.note-detail-page',
127
+ '.note-detail-dialog',
128
+ '.note-detail',
129
+ '.detail-container',
130
+ '.media-container'
131
+ ];
132
+ const isVisible = (el) => {
133
+ if (!el) return false;
134
+ const style = window.getComputedStyle(el);
135
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
136
+ const r = el.getBoundingClientRect();
137
+ if (!r.width || !r.height) return false;
138
+ if (r.bottom <= 0 || r.top >= window.innerHeight) return false;
139
+ return true;
140
+ };
141
+ let visibleOverlay = null;
142
+ for (const sel of selectors) {
143
+ const el = document.querySelector(sel);
144
+ if (el && isVisible(el)) {
145
+ visibleOverlay = el;
146
+ break;
147
+ }
148
+ }
149
+ return { hasDetailOverlayVisible: !!visibleOverlay };
150
+ })()`,
151
+ });
152
+ const payload = domState.result || domState.data?.result || domState;
153
+ hasDetailOverlay = Boolean(payload?.hasDetailOverlayVisible);
154
+ }
155
+ catch (e) {
156
+ console.warn(`[ErrorRecovery] detail-overlay DOM probe failed before ESC recovery: ${e.message}`);
157
+ }
158
+ if (!hasDetailOverlay) {
159
+ console.warn('[ErrorRecovery] detail overlay not detected before ESC recovery, continue ESC path based on stage hint');
160
+ // 这里不再直接返回失败:
161
+ // - 来到 ErrorRecoveryBlock 且 fromStage=detail 时,调用方已经认为当前处于详情态
162
+ // - DOM 预探测只是辅助信号,可能因为样式/结构变化导致误判
163
+ // 因此即便未检测到 overlay,也继续尝试容器关闭与 ESC 按键,由后续 verifyStage 严格确认结果
164
+ }
165
+ // 0.1 次级锚点(非强制):尝试命中详情 modal 容器,用于高亮确认;失败不再视为致命,仅记录日志。
166
+ try {
167
+ const { verifyAnchorByContainerId } = await import('./helpers/containerAnchors.js');
168
+ const detailAnchor = await verifyAnchorByContainerId('xiaohongshu_detail.modal_shell', sessionId, serviceUrl, '2px solid #ff4444', 1200);
169
+ if (!detailAnchor.found || !detailAnchor.rect) {
170
+ console.warn('[ErrorRecovery] detail modal anchor not found, continue ESC recovery based on DOM overlay only');
171
+ }
172
+ }
173
+ catch (e) {
174
+ console.warn(`[ErrorRecovery] verify detail anchor failed before ESC recovery: ${e.message}`);
175
+ // 容器锚点只是次级信号,这里不再直接失败,后续依赖 DOM + search_result_list 锚点确认恢复效果
176
+ }
177
+ // 1) 优先通过容器运行时 + 系统点击关闭详情 modal(点击右上角关闭按钮)
178
+ try {
179
+ const clickResult = await controllerAction('container:operation', {
180
+ containerId: 'xiaohongshu_detail.modal_shell',
181
+ operationId: 'click',
182
+ config: {
183
+ selector: '.note-detail-mask .close-box, .note-detail-mask .close-circle',
184
+ useSystemMouse: true,
185
+ retries: 1,
186
+ },
187
+ sessionId
188
+ });
189
+ console.log('[ErrorRecovery] container-close click result:', JSON.stringify(clickResult));
190
+ await new Promise((resolve) => setTimeout(resolve, 1500));
191
+ // 1.1 关闭后通过搜索结果列表锚点 + DOM overlay 检查判断是否已回到安全阶段
192
+ try {
193
+ const { verifyAnchorByContainerId } = await import('./helpers/containerAnchors.js');
194
+ const anchor = await verifyAnchorByContainerId('xiaohongshu_search.search_result_list', sessionId, serviceUrl, '2px solid #4caf50', 1000);
195
+ const anchorOk = anchor.found && anchor.rect && anchor.rect.width > 0 && anchor.rect.height > 0;
196
+ let overlayGone = true;
197
+ try {
198
+ const domState = await controllerAction('browser:execute', {
199
+ profile: sessionId,
200
+ script: `(() => {
201
+ const selectors = [
202
+ '.note-detail-mask',
203
+ '.note-detail-page',
204
+ '.note-detail-dialog',
205
+ '.note-detail',
206
+ '.detail-container',
207
+ '.media-container'
208
+ ];
209
+ const isVisible = (el) => {
210
+ if (!el) return false;
211
+ const style = window.getComputedStyle(el);
212
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
213
+ const r = el.getBoundingClientRect();
214
+ if (!r.width || !r.height) return false;
215
+ if (r.bottom <= 0 || r.top >= window.innerHeight) return false;
216
+ return true;
217
+ };
218
+ let visibleOverlay = null;
219
+ for (const sel of selectors) {
220
+ const el = document.querySelector(sel);
221
+ if (el && isVisible(el)) {
222
+ visibleOverlay = el;
223
+ break;
224
+ }
225
+ }
226
+ return { hasDetailOverlayVisible: !!visibleOverlay };
227
+ })()`,
228
+ });
229
+ const payload = domState.result || domState.data?.result || domState;
230
+ overlayGone = !Boolean(payload?.hasDetailOverlayVisible);
231
+ }
232
+ catch (e) {
233
+ console.warn(`[ErrorRecovery] overlay DOM check after container-close failed: ${e.message}`);
234
+ }
235
+ if (anchorOk && overlayGone) {
236
+ console.log('[ErrorRecovery] ✅ detail closed via container operation (anchor matched, overlay gone)');
237
+ return { success: true, method: 'container-close' };
238
+ }
239
+ if (anchorOk && !overlayGone) {
240
+ console.warn('[ErrorRecovery] search_result_list visible but detail overlay still present after container-close, will fallback to ESC key');
241
+ }
242
+ }
243
+ catch (err) {
244
+ console.warn(`[ErrorRecovery] verify search_result_list after close failed: ${err?.message || err}`);
245
+ }
246
+ }
247
+ catch (err) {
248
+ console.warn(`[ErrorRecovery] container close failed, fallback to ESC key: ${err?.message || String(err)}`);
249
+ }
250
+ // 2) 兜底:再次发送 ESC 键关闭详情页
251
+ try {
252
+ await controllerAction('keyboard:press', { profileId: sessionId, key: 'Escape' });
253
+ await new Promise((resolve) => setTimeout(resolve, 1500));
254
+ const { verifyAnchorByContainerId } = await import('./helpers/containerAnchors.js');
255
+ const anchor = await verifyAnchorByContainerId('xiaohongshu_search.search_result_list', sessionId, serviceUrl, '2px solid #4caf50', 1000);
256
+ const ok = Boolean(anchor.found && anchor.rect && anchor.rect.width > 0 && anchor.rect.height > 0);
257
+ return { success: ok, method: 'esc-key-double' };
258
+ }
259
+ catch (err) {
260
+ console.warn(`[ErrorRecovery] second ESC fallback failed: ${err?.message || err}`);
261
+ return { success: false, method: 'esc-key-double-error' };
262
+ }
263
+ }
264
+ console.log(`[ErrorRecovery] 从 ${fromStage} 恢复到 ${targetStage}...`);
265
+ try {
266
+ const currentUrl = await getCurrentUrl();
267
+ const atSearch = await verifyStage('search');
268
+ const atHome = await verifyStage('home');
269
+ if ((targetStage === 'search' && atSearch) || (targetStage === 'home' && atHome)) {
270
+ console.log(`[ErrorRecovery] ✅ 已在目标阶段 ${targetStage}(anchor verified)`);
271
+ return {
272
+ success: true,
273
+ recovered: false,
274
+ finalStage: targetStage,
275
+ currentUrl,
276
+ method: 'already-at-target',
277
+ };
278
+ }
279
+ if (fromStage === 'detail' && targetStage === 'search') {
280
+ const escResult = await recoverWithEsc();
281
+ if (escResult.success) {
282
+ const verified = await verifyStage('search');
283
+ if (verified) {
284
+ return {
285
+ success: true,
286
+ recovered: true,
287
+ finalStage: 'search',
288
+ currentUrl: await getCurrentUrl(),
289
+ method: escResult.method,
290
+ };
291
+ }
292
+ }
293
+ return {
294
+ success: false,
295
+ recovered: false,
296
+ finalStage: 'unknown',
297
+ currentUrl: await getCurrentUrl(),
298
+ error: 'esc_recovery_failed',
299
+ method: 'esc_recovery_failed',
300
+ };
301
+ }
302
+ return {
303
+ success: false,
304
+ recovered: false,
305
+ finalStage: 'unknown',
306
+ currentUrl,
307
+ error: `unsupported_recovery_path: from=${fromStage} target=${targetStage} (esc only)`,
308
+ };
309
+ }
310
+ catch (err) {
311
+ return {
312
+ success: false,
313
+ recovered: false,
314
+ finalStage: 'unknown',
315
+ error: `恢复异常: ${err.message}`
316
+ };
317
+ }
318
+ }
319
+ //# sourceMappingURL=ErrorRecoveryBlock.js.map
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Workflow Block: WaitSearchPermitBlock
3
+ *
4
+ * 职责:
5
+ * 1. 向 SearchGate 申请搜索许可
6
+ * 2. 如果未获许可,自动等待并重试
7
+ * 3. 只有拿到许可后才成功返回
8
+ */
9
+ export interface WaitSearchPermitInput {
10
+ sessionId: string;
11
+ keyword?: string;
12
+ gateUrl?: string;
13
+ serviceUrl?: string;
14
+ maxWaitMs?: number;
15
+ skipIfAlreadyOnSearchResult?: boolean;
16
+ dev?: boolean;
17
+ devTag?: string;
18
+ }
19
+ export interface WaitSearchPermitOutput {
20
+ success: boolean;
21
+ granted: boolean;
22
+ waitedMs: number;
23
+ skipped?: boolean;
24
+ reason?: string | null;
25
+ retryAfterMs?: number | null;
26
+ deny?: {
27
+ code: string;
28
+ message: string;
29
+ retryAfterMs: number | null;
30
+ details: any;
31
+ suggestedActions: string[];
32
+ } | null;
33
+ error?: string;
34
+ }
35
+ export declare function execute(input: WaitSearchPermitInput): Promise<WaitSearchPermitOutput>;
36
+ //# sourceMappingURL=WaitSearchPermitBlock.d.ts.map