@web-auto/webauto 0.1.7 → 0.1.9

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 (32) hide show
  1. package/apps/desktop-console/dist/main/index.mjs +802 -90
  2. package/apps/desktop-console/dist/main/preload.mjs +3 -0
  3. package/apps/desktop-console/dist/renderer/index.html +9 -1
  4. package/apps/desktop-console/dist/renderer/index.js +784 -332
  5. package/apps/desktop-console/entry/ui-cli.mjs +23 -8
  6. package/apps/desktop-console/entry/ui-console.mjs +8 -3
  7. package/apps/webauto/entry/account.mjs +69 -8
  8. package/apps/webauto/entry/lib/account-detect.mjs +106 -25
  9. package/apps/webauto/entry/lib/account-store.mjs +121 -22
  10. package/apps/webauto/entry/lib/schedule-store.mjs +0 -12
  11. package/apps/webauto/entry/profilepool.mjs +45 -3
  12. package/apps/webauto/entry/schedule.mjs +44 -2
  13. package/apps/webauto/entry/weibo-unified.mjs +2 -2
  14. package/apps/webauto/entry/xhs-install.mjs +220 -51
  15. package/apps/webauto/entry/xhs-unified.mjs +33 -6
  16. package/bin/webauto.mjs +80 -4
  17. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
  18. package/dist/services/unified-api/server.js +5 -0
  19. package/dist/services/unified-api/task-state.js +2 -0
  20. package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +142 -14
  21. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +16 -1
  22. package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +104 -0
  23. package/modules/camo-runtime/src/autoscript/runtime.mjs +14 -4
  24. package/modules/camo-runtime/src/autoscript/schema.mjs +9 -0
  25. package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +9 -2
  26. package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +107 -1
  27. package/modules/camo-runtime/src/container/runtime-core/subscription.mjs +24 -2
  28. package/modules/camo-runtime/src/utils/browser-service.mjs +4 -0
  29. package/package.json +6 -3
  30. package/scripts/bump-version.mjs +120 -0
  31. package/services/unified-api/server.ts +4 -0
  32. package/services/unified-api/task-state.ts +5 -0
@@ -409,6 +409,8 @@ async function runFullCover(endpoint) {
409
409
  await runProbe('scheduler', '#scheduler-name');
410
410
  await runProbe('scheduler', '#scheduler-enabled');
411
411
  await runProbe('scheduler', '#scheduler-type');
412
+ await runProbe('scheduler', '#scheduler-periodic-type-wrap');
413
+ await runProbe('scheduler', '#scheduler-periodic-type');
412
414
  await runProbe('scheduler', '#scheduler-interval-wrap');
413
415
  await runProbe('scheduler', '#scheduler-runat-wrap');
414
416
  await runProbe('scheduler', '#scheduler-interval');
@@ -424,21 +426,34 @@ async function runFullCover(endpoint) {
424
426
  await runProbe('scheduler', '#scheduler-dryrun');
425
427
  await runProbe('scheduler', '#scheduler-like-keywords');
426
428
  await runProbe('scheduler', '#scheduler-save-btn');
429
+ await runProbe('scheduler', '#scheduler-run-now-btn');
427
430
  await runProbe('scheduler', '#scheduler-reset-btn');
428
- await select('#scheduler-type', 'once');
429
- await wait('#scheduler-runat-wrap', 8000, 'visible');
431
+ await select('#scheduler-type', 'immediate');
432
+ await wait('#scheduler-periodic-type-wrap', 8000, 'hidden');
433
+ await wait('#scheduler-runat-wrap', 8000, 'hidden');
430
434
  await wait('#scheduler-interval-wrap', 8000, 'hidden');
431
- await select('#scheduler-type', 'daily');
435
+ await select('#scheduler-type', 'periodic');
436
+ await wait('#scheduler-periodic-type-wrap', 8000, 'visible');
437
+ await wait('#scheduler-interval-wrap', 8000, 'visible');
438
+ await wait('#scheduler-runat-wrap', 8000, 'hidden');
439
+ await select('#scheduler-periodic-type', 'daily');
432
440
  await wait('#scheduler-runat-wrap', 8000, 'visible');
433
- await select('#scheduler-type', 'weekly');
441
+ await wait('#scheduler-interval-wrap', 8000, 'hidden');
442
+ await select('#scheduler-periodic-type', 'weekly');
434
443
  await wait('#scheduler-runat-wrap', 8000, 'visible');
435
- await select('#scheduler-type', 'interval');
436
- await wait('#scheduler-interval-wrap', 8000, 'visible');
444
+ await wait('#scheduler-interval-wrap', 8000, 'hidden');
445
+ await select('#scheduler-periodic-type', 'interval');
437
446
  await wait('#scheduler-runat-wrap', 8000, 'hidden');
447
+ await wait('#scheduler-interval-wrap', 8000, 'visible');
448
+ await select('#scheduler-type', 'scheduled');
449
+ await wait('#scheduler-periodic-type-wrap', 8000, 'hidden');
450
+ await wait('#scheduler-runat-wrap', 8000, 'visible');
451
+ await wait('#scheduler-interval-wrap', 8000, 'hidden');
438
452
  await input('#scheduler-name', taskName);
439
- await select('#scheduler-type', 'interval');
453
+ await select('#scheduler-type', 'periodic');
454
+ await select('#scheduler-periodic-type', 'interval');
440
455
  await input('#scheduler-interval', '20');
441
- await input('#scheduler-profile', 'xiaohongshu-batch-0');
456
+ await input('#scheduler-profile', '');
442
457
  await input('#scheduler-keyword', keywordSeed);
443
458
  await input('#scheduler-max-notes', '20');
444
459
  await select('#scheduler-env', 'debug');
@@ -10,11 +10,15 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
10
  const APP_ROOT = path.resolve(__dirname, '..');
11
11
  const DIST_MAIN = path.join(APP_ROOT, 'dist', 'main', 'index.mjs');
12
12
 
13
- const args = minimist(process.argv.slice(2), {
14
- boolean: ['build', 'install', 'check', 'help', 'headless', 'no-daemon', 'dry-run', 'no-dry-run', 'parallel', 'do-likes'],
13
+ const rawArgv = process.argv.slice(2);
14
+ const args = minimist(rawArgv, {
15
+ boolean: ['build', 'install', 'check', 'help', 'headless', 'no-daemon', 'foreground', 'dry-run', 'no-dry-run', 'parallel', 'do-likes'],
15
16
  string: ['profile', 'profiles', 'keyword', 'target', 'scenario', 'output', 'concurrency', 'like-keywords', 'max-likes'],
16
17
  alias: { h: 'help', p: 'profile', k: 'keyword', t: 'target', o: 'output' }
17
18
  });
19
+ // minimist treats `--no-foo` as negation of `foo`, so `--no-daemon` must be
20
+ // detected from raw argv to keep backward-compatible CLI behavior.
21
+ const noDaemon = rawArgv.includes('--no-daemon') || rawArgv.includes('--foreground') || args.foreground === true;
18
22
 
19
23
  function printHelp() {
20
24
  console.log(`webauto ui console
@@ -35,6 +39,7 @@ Options:
35
39
  --build Auto-build if missing
36
40
  --install Auto-install if missing deps
37
41
  --no-daemon Run in foreground mode
42
+ --foreground Alias of --no-daemon
38
43
  --scenario Test scenario name
39
44
  --profile Test profile ID
40
45
  --profiles Test profile IDs (comma-separated)
@@ -677,7 +682,7 @@ async function main() {
677
682
  return;
678
683
  }
679
684
 
680
- await startConsole(args['no-daemon']);
685
+ await startConsole(noDaemon);
681
686
  }
682
687
 
683
688
  main().catch((err) => {
@@ -46,6 +46,12 @@ function normalizeAlias(input) {
46
46
  return value || null;
47
47
  }
48
48
 
49
+ function normalizePlatform(input, fallback = 'xiaohongshu') {
50
+ const raw = String(input || fallback).trim().toLowerCase();
51
+ if (!raw || raw === 'xhs') return 'xiaohongshu';
52
+ return raw;
53
+ }
54
+
49
55
  async function publishAccountEvent(type, payload) {
50
56
  try {
51
57
  await publishBusEvent({
@@ -60,9 +66,42 @@ async function publishAccountEvent(type, payload) {
60
66
 
61
67
  async function detectAliasFromActivePage(profileId, selector) {
62
68
  const { callAPI } = await import('../../../modules/camo-runtime/src/utils/browser-service.mjs');
63
- const script = `(() => {
69
+ const script = `(async () => {
70
+ const normalize = (value) => String(value || '').replace(/\\s+/g, ' ').trim();
71
+ const isVisible = (node) => {
72
+ if (!(node instanceof HTMLElement)) return false;
73
+ const style = window.getComputedStyle(node);
74
+ if (!style) return false;
75
+ if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') === 0) return false;
76
+ const rect = node.getBoundingClientRect();
77
+ return rect.width > 0 && rect.height > 0;
78
+ };
79
+ const isSelfTabText = (value) => {
80
+ const text = normalize(value);
81
+ return text === '我' || text === '我的' || text === '个人主页' || text === '我的主页';
82
+ };
83
+ const selfTabCandidates = Array.from(document.querySelectorAll('a, button, [role="tab"], [role="link"], [class*="tab"]'))
84
+ .map((node) => ({
85
+ node,
86
+ text: normalize(node.textContent || ''),
87
+ title: normalize(node.getAttribute?.('title') || ''),
88
+ aria: normalize(node.getAttribute?.('aria-label') || ''),
89
+ }))
90
+ .filter((item) => isSelfTabText(item.text) || isSelfTabText(item.title) || isSelfTabText(item.aria));
91
+ const selfTarget = selfTabCandidates.find((item) => isVisible(item.node)) || selfTabCandidates[0] || null;
92
+ if (selfTarget?.node) {
93
+ try {
94
+ selfTarget.node.click();
95
+ await new Promise((resolve) => setTimeout(resolve, 900));
96
+ } catch {
97
+ // ignore self-tab click failure
98
+ }
99
+ }
100
+
64
101
  const requested = ${JSON.stringify(String(selector || '').trim())};
65
102
  const defaultSelectors = [
103
+ '[data-testid*="nickname"]',
104
+ '[class*="profile"] [class*="name"]',
66
105
  '[class*="user"] [class*="name"]',
67
106
  '[class*="nickname"]',
68
107
  '[class*="account"] [class*="name"]',
@@ -76,14 +115,19 @@ async function detectAliasFromActivePage(profileId, selector) {
76
115
  for (const sel of selectors) {
77
116
  const nodes = Array.from(document.querySelectorAll(sel)).slice(0, 6);
78
117
  for (const node of nodes) {
79
- const text = clean(node.textContent || '');
118
+ const text = clean(node.textContent || node.getAttribute?.('title') || '');
80
119
  if (!text) continue;
81
120
  candidates.push({ text, selector: sel });
82
121
  }
83
122
  }
123
+ const userInfo = document.querySelector('[class*="user"] [class*="nickname"], [class*="profile"] [class*="nickname"]');
124
+ if (userInfo) {
125
+ const text = clean(userInfo.textContent || '');
126
+ if (text) candidates.push({ text, selector: 'profile.nickname' });
127
+ }
84
128
  const title = clean(document.title || '');
85
129
  if (title) candidates.push({ text: title, selector: 'document.title' });
86
- const bad = ['小红书', '登录', '注册', '搜索'];
130
+ const bad = ['小红书', '登录', '注册', '搜索', '我', '消息', '通知'];
87
131
  const picked = candidates.find((item) => {
88
132
  if (!item?.text) return false;
89
133
  if (item.text.length < 2) return false;
@@ -114,7 +158,7 @@ function printHelp() {
114
158
 
115
159
  Usage:
116
160
  webauto account --help
117
- webauto account list [--json]
161
+ webauto account list [--platform <name>] [--json]
118
162
  webauto account list --records [--json]
119
163
  webauto account add [--platform <name>] [--alias <alias>] [--name <name>] [--username <username>] [--profile <id>] [--fingerprint <id>] [--status pending|active|disabled|archived] [--json]
120
164
  webauto account get <id|alias> [--json]
@@ -143,8 +187,9 @@ Examples:
143
187
  `);
144
188
  }
145
189
 
146
- async function cmdList(jsonMode) {
147
- const result = listAccountProfiles();
190
+ async function cmdList(jsonMode, platformArg = '') {
191
+ const platform = normalizeAlias(platformArg) ? normalizePlatform(platformArg) : '';
192
+ const result = listAccountProfiles(platform ? { platform } : {});
148
193
  output({ ok: true, ...result }, jsonMode);
149
194
  }
150
195
 
@@ -229,8 +274,20 @@ async function cmdLogin(idOrAlias, argv, jsonMode) {
229
274
  const account = getAccount(idOrAlias);
230
275
  await ensureProfile(account.profileId);
231
276
  const url = String(argv.url || inferLoginUrl(account.platform)).trim();
277
+ // Default idle timeout: 30 minutes, configurable via env or CLI.
278
+ // Keep validation semantics aligned with camo parseDurationMs.
232
279
  const idleTimeout = String(argv['idle-timeout'] || process.env.WEBAUTO_LOGIN_IDLE_TIMEOUT || '30m').trim() || '30m';
233
280
 
281
+ const idleTimeoutLower = idleTimeout.toLowerCase();
282
+ const idleTimeoutOk = /^(?:\d+(?:\.\d+)?(?:ms|s|m|h)?|0|off|none|disable|disabled)$/.test(idleTimeoutLower);
283
+ if (!idleTimeoutOk) {
284
+ output({
285
+ ok: false,
286
+ error: 'Invalid idle-timeout format. Use forms like 30m, 1800s, 5000ms, 1h, 0, off.',
287
+ }, jsonMode);
288
+ process.exit(1);
289
+ }
290
+
234
291
  const pendingProfile = await syncXhsAccountByProfile(account.profileId, { pendingWhileLogin: true }).catch((error) => ({
235
292
  profileId: account.profileId,
236
293
  valid: false,
@@ -340,9 +397,13 @@ async function cmdSyncAlias(idOrAlias, argv, jsonMode) {
340
397
  async function cmdSync(target, argv, jsonMode) {
341
398
  const pendingWhileLogin = parseBoolean(argv['pending-while-login'], false);
342
399
  const resolveAlias = parseBoolean(argv['resolve-alias'], false);
400
+ const platform = normalizePlatform(argv.platform || 'xiaohongshu');
343
401
  const value = String(target || '').trim().toLowerCase();
344
402
  if (!value || value === 'all') {
345
- const rows = listAccountProfiles().profiles;
403
+ if (platform !== 'xiaohongshu') {
404
+ throw new Error(`account sync currently supports platform=xiaohongshu only, got: ${platform}`);
405
+ }
406
+ const rows = listAccountProfiles({ platform: 'xiaohongshu' }).profiles;
346
407
  const profileIds = rows.map((item) => item.profileId);
347
408
  const synced = await syncXhsAccountsByProfiles(profileIds, { pendingWhileLogin, resolveAlias });
348
409
  output({ ok: true, count: synced.length, profiles: synced }, jsonMode);
@@ -373,7 +434,7 @@ async function main() {
373
434
  return;
374
435
  }
375
436
 
376
- if (cmd === 'list') return argv.records ? cmdListRecords(jsonMode) : cmdList(jsonMode);
437
+ if (cmd === 'list') return argv.records ? cmdListRecords(jsonMode) : cmdList(jsonMode, argv.platform);
377
438
  if (cmd === 'add') return cmdAdd(argv, jsonMode);
378
439
  if (cmd === 'get') return cmdGet(arg1, jsonMode);
379
440
  if (cmd === 'update') return cmdUpdate(arg1, argv, jsonMode);
@@ -14,7 +14,29 @@ function sleep(ms) {
14
14
 
15
15
  function buildDetectScript() {
16
16
  return `(() => {
17
- const guard = Boolean(document.querySelector('.login-container, .login-dialog, #login-container'));
17
+ const isVisible = (node) => {
18
+ if (!(node instanceof HTMLElement)) return false;
19
+ const style = window.getComputedStyle(node);
20
+ if (!style) return false;
21
+ if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') === 0) return false;
22
+ const rect = node.getBoundingClientRect();
23
+ return rect.width > 0 && rect.height > 0;
24
+ };
25
+ const loginGuardSelectors = [
26
+ '.login-container',
27
+ '.login-dialog',
28
+ '#login-container',
29
+ ];
30
+ const loginGuardNodes = loginGuardSelectors
31
+ .flatMap((selector) => Array.from(document.querySelectorAll(selector)));
32
+ const visibleLoginGuardNodes = loginGuardNodes.filter((node) => isVisible(node));
33
+ const loginGuardText = visibleLoginGuardNodes
34
+ .slice(0, 6)
35
+ .map((node) => String(node.textContent || '').replace(/\\s+/g, ' ').trim())
36
+ .join(' ');
37
+ const hasLoginText = /登录|扫码|验证码|手机号|请先登录|注册|sign\\s*in/i.test(loginGuardText);
38
+ const loginUrl = /\\/login|signin|passport|account\\/login/i.test(String(location.href || ''));
39
+ const hasGuardSignal = (visibleLoginGuardNodes.length > 0 && hasLoginText) || loginUrl;
18
40
  const candidates = [];
19
41
  const normalizeAlias = (value) => {
20
42
  const text = String(value || '').replace(/\\s+/g, ' ').trim();
@@ -100,14 +122,6 @@ function buildDetectScript() {
100
122
  }
101
123
  }
102
124
 
103
- const searchHistoryKey = Object.keys(localStorage || {}).find((key) => String(key || '').startsWith('xhs-pc-search-history-'));
104
- if (searchHistoryKey) {
105
- const matched = String(searchHistoryKey).match(/^xhs-pc-search-history-(.+)$/);
106
- if (matched && matched[1]) {
107
- pushCandidate(matched[1], null, 'localStorage.search_history');
108
- }
109
- }
110
-
111
125
  const selfNavEntry = Array.from(document.querySelectorAll('a[href*="/user/profile/"]'))
112
126
  .find((node) => {
113
127
  const labels = readLabelCandidates(node);
@@ -132,8 +146,6 @@ function buildDetectScript() {
132
146
  const labels = readLabelCandidates(anchor);
133
147
  if (labels.some(isSelfLabel)) {
134
148
  pushCandidate(matched[1], alias, 'anchor.self');
135
- } else {
136
- pushCandidate(matched[1], alias, 'anchor');
137
149
  }
138
150
  }
139
151
 
@@ -146,15 +158,11 @@ function buildDetectScript() {
146
158
  }
147
159
  }
148
160
 
149
- const strongCandidates = candidates.filter((item) => item.source !== 'localStorage.search_history');
150
- const best = strongCandidates
161
+ const best = candidates
151
162
  .find((item) => item.source === 'initial_state.user_info')
152
- || strongCandidates.find((item) => item.source === 'nav.self')
153
- || strongCandidates.find((item) => item.source === 'anchor.self')
154
- || strongCandidates.find((item) => item.source === 'anchor' && item.alias)
155
- || strongCandidates.find((item) => item.source === 'anchor')
156
- || strongCandidates.find((item) => item.id && item.id.length >= 6)
157
- || candidates.find((item) => item.source === 'localStorage.search_history')
163
+ || candidates.find((item) => item.source === 'nav.self')
164
+ || candidates.find((item) => item.source === 'anchor.self')
165
+ || candidates.find((item) => item.id && item.id.length >= 6)
158
166
  || candidates[0]
159
167
  || null;
160
168
  let alias = best ? best.alias : null;
@@ -169,9 +177,10 @@ function buildDetectScript() {
169
177
  const picked = findAliasFromDom();
170
178
  if (picked) alias = picked;
171
179
  }
180
+ const hasAccountSignal = Boolean(best && best.id);
172
181
  return {
173
182
  url: location.href,
174
- hasLoginGuard: guard,
183
+ hasLoginGuard: hasGuardSignal,
175
184
  accountId: best ? best.id : null,
176
185
  alias: alias || null,
177
186
  source: best ? best.source : null,
@@ -241,8 +250,77 @@ function buildAliasResolveScript() {
241
250
  })()`;
242
251
  }
243
252
 
253
+ function buildGotoSelfTabScript() {
254
+ return `(() => {
255
+ const normalize = (value) => String(value || '').replace(/\\s+/g, ' ').trim();
256
+ const isSelfLabel = (value) => {
257
+ const text = normalize(value);
258
+ if (!text) return false;
259
+ return text === '我' || text === '我的' || text === '个人主页' || text === '我的主页';
260
+ };
261
+ const isVisible = (node) => {
262
+ if (!(node instanceof HTMLElement)) return false;
263
+ const style = window.getComputedStyle(node);
264
+ if (!style) return false;
265
+ if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') === 0) return false;
266
+ const rect = node.getBoundingClientRect();
267
+ return rect.width > 0 && rect.height > 0;
268
+ };
269
+ const candidates = Array.from(document.querySelectorAll('a, button, [role="tab"], [role="link"], [class*="tab"]'))
270
+ .map((node) => {
271
+ const text = normalize(node.textContent || '');
272
+ const title = normalize(node.getAttribute?.('title') || '');
273
+ const aria = normalize(node.getAttribute?.('aria-label') || '');
274
+ return {
275
+ node,
276
+ text,
277
+ title,
278
+ aria,
279
+ };
280
+ })
281
+ .filter((item) => isSelfLabel(item.text) || isSelfLabel(item.title) || isSelfLabel(item.aria));
282
+ const target = candidates.find((item) => isVisible(item.node)) || candidates[0] || null;
283
+ if (!target?.node) {
284
+ return { clicked: false, reason: 'self_tab_not_found' };
285
+ }
286
+ try {
287
+ target.node.click();
288
+ return {
289
+ clicked: true,
290
+ reason: 'ok',
291
+ label: target.text || target.title || target.aria || null,
292
+ };
293
+ } catch (error) {
294
+ return { clicked: false, reason: String(error?.message || error || 'click_failed') };
295
+ }
296
+ })()`;
297
+ }
298
+
299
+ async function resolveAliasFromSelfTab(profileId) {
300
+ if (!profileId) return null;
301
+ try {
302
+ await callAPI('evaluate', { profileId, script: buildGotoSelfTabScript() });
303
+ await sleep(900);
304
+ const payload = await callAPI('evaluate', { profileId, script: buildAliasResolveScript() });
305
+ const result = payload?.result || payload?.data || payload || {};
306
+ const alias = normalizeText(result.alias);
307
+ if (!alias) return null;
308
+ return {
309
+ alias,
310
+ source: normalizeText(result.source) ? `self_tab:${normalizeText(result.source)}` : 'self_tab',
311
+ candidates: Array.isArray(result.candidates) ? result.candidates : [],
312
+ };
313
+ } catch {
314
+ return null;
315
+ }
316
+ }
317
+
244
318
  async function resolveAliasFromProfilePage(profileId, accountId) {
245
319
  if (!profileId || !accountId) return null;
320
+ const fromSelfTab = await resolveAliasFromSelfTab(profileId);
321
+ if (fromSelfTab?.alias) {
322
+ return fromSelfTab;
323
+ }
246
324
  let originalUrl = null;
247
325
  try {
248
326
  const urlPayload = await callAPI('evaluate', { profileId, script: 'window.location.href' });
@@ -296,15 +374,15 @@ export async function detectXhsAccountIdentity(profileId, options = {}) {
296
374
  };
297
375
 
298
376
  let detected = await runDetect();
299
- const shouldRetry = !detected.hasLoginGuard && (
377
+ const shouldRetry = (
300
378
  !detected.accountId
301
- || detected.source === 'localStorage.search_history'
302
379
  || !detected.alias
380
+ || detected.hasLoginGuard
303
381
  );
304
382
  if (shouldRetry) {
305
383
  await sleep(1200);
306
384
  const retry = await runDetect();
307
- if (retry.accountId && (!detected.accountId || detected.source === 'localStorage.search_history')) {
385
+ if (retry.accountId && !detected.accountId) {
308
386
  detected.accountId = retry.accountId;
309
387
  detected.source = retry.source || detected.source;
310
388
  detected.candidates = retry.candidates;
@@ -314,6 +392,9 @@ export async function detectXhsAccountIdentity(profileId, options = {}) {
314
392
  if (!detected.source) detected.source = retry.source || detected.source;
315
393
  }
316
394
  if (retry.url && !detected.url) detected.url = retry.url;
395
+ if (detected.hasLoginGuard && !retry.hasLoginGuard) {
396
+ detected.hasLoginGuard = false;
397
+ }
317
398
  }
318
399
  if (options?.resolveAlias === true && detected.accountId && !detected.alias) {
319
400
  const resolved = await resolveAliasFromProfilePage(detected.profileId, detected.accountId);
@@ -330,7 +411,7 @@ export async function syncXhsAccountByProfile(profileId, options = {}) {
330
411
  if (!normalizedProfileId) throw new Error('profileId is required');
331
412
  const pendingWhileLogin = options?.pendingWhileLogin === true;
332
413
  try {
333
- const existing = listAccountProfiles().profiles.find(
414
+ const existing = listAccountProfiles({ platform: 'xiaohongshu' }).profiles.find(
334
415
  (item) => String(item?.profileId || '').trim() === normalizedProfileId,
335
416
  );
336
417
  const shouldResolveAlias = options?.resolveAlias === true
@@ -362,7 +443,7 @@ export async function syncXhsAccountByProfile(profileId, options = {}) {
362
443
  const msg = String(error?.message || error || '');
363
444
  if (msg.toLowerCase().includes('operation is insecure')) {
364
445
  try {
365
- const existing = listAccountProfiles().profiles.find((item) => String(item?.profileId || '').trim() === normalizedProfileId);
446
+ const existing = listAccountProfiles({ platform: 'xiaohongshu' }).profiles.find((item) => String(item?.profileId || '').trim() === normalizedProfileId);
366
447
  if (existing && existing.valid) return existing;
367
448
  } catch {
368
449
  // ignore fallback lookup