@web-auto/webauto 0.1.13 → 0.1.14

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.
@@ -25,6 +25,39 @@ import { buildOpenDetailScript, buildSubmitSearchScript } from './xhs/search.mjs
25
25
 
26
26
  const XHS_OPERATION_LOCKS = new Map();
27
27
 
28
+ function emitOperationProgress(context, payload = {}) {
29
+ const emit = context?.emitProgress;
30
+ if (typeof emit !== 'function') return;
31
+ emit(payload);
32
+ }
33
+
34
+ function emitActionTrace(context, actionTrace = [], extra = {}) {
35
+ if (!Array.isArray(actionTrace) || actionTrace.length === 0) return;
36
+ for (let i = 0; i < actionTrace.length; i += 1) {
37
+ const row = actionTrace[i];
38
+ if (!row || typeof row !== 'object') continue;
39
+ const kind = String(row.kind || row.action || '').trim().toLowerCase() || 'trace';
40
+ emitOperationProgress(context, {
41
+ kind,
42
+ step: i + 1,
43
+ ...extra,
44
+ ...row,
45
+ });
46
+ }
47
+ }
48
+
49
+ function replaceEvaluateResultData(rawData, payload) {
50
+ if (rawData && typeof rawData === 'object') {
51
+ if (Object.prototype.hasOwnProperty.call(rawData, 'result')) {
52
+ return { ...rawData, result: payload };
53
+ }
54
+ if (rawData.data && typeof rawData.data === 'object' && Object.prototype.hasOwnProperty.call(rawData.data, 'result')) {
55
+ return { ...rawData, data: { ...rawData.data, result: payload } };
56
+ }
57
+ }
58
+ return payload;
59
+ }
60
+
28
61
  function toLockKey(text, fallback = '') {
29
62
  const value = String(text || '').trim();
30
63
  return value || fallback;
@@ -104,19 +137,40 @@ async function saveSharedClaimDoc(filePath, doc) {
104
137
  await fsp.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
105
138
  }
106
139
 
107
- async function executeSubmitSearchOperation({ profileId, params = {} }) {
140
+ async function executeSubmitSearchOperation({
141
+ profileId,
142
+ params = {},
143
+ context = {},
144
+ }) {
108
145
  const script = buildSubmitSearchScript(params);
109
146
  const highlight = params.highlight !== false;
110
147
  const lockKey = resolveSearchLockKey(params);
111
- return withSerializedLock(lockKey ? `xhs_submit_search:${lockKey}` : '', () => evaluateWithScript({
112
- profileId,
113
- script,
114
- message: 'xhs_submit_search done',
115
- highlight,
116
- }));
148
+ return withSerializedLock(lockKey ? `xhs_submit_search:${lockKey}` : '', async () => {
149
+ const operationResult = await evaluateWithScript({
150
+ profileId,
151
+ script,
152
+ message: 'xhs_submit_search done',
153
+ highlight,
154
+ });
155
+ const payload = extractEvaluateResultData(operationResult.data) || {};
156
+ const actionTrace = Array.isArray(payload.actionTrace) ? payload.actionTrace : [];
157
+ if (actionTrace.length > 0) {
158
+ emitActionTrace(context, actionTrace, { stage: 'xhs_submit_search' });
159
+ delete payload.actionTrace;
160
+ return {
161
+ ...operationResult,
162
+ data: replaceEvaluateResultData(operationResult.data, payload),
163
+ };
164
+ }
165
+ return operationResult;
166
+ });
117
167
  }
118
168
 
119
- async function executeOpenDetailOperation({ profileId, params = {} }) {
169
+ async function executeOpenDetailOperation({
170
+ profileId,
171
+ params = {},
172
+ context = {},
173
+ }) {
120
174
  const highlight = params.highlight !== false;
121
175
  const claimPath = resolveSharedClaimPath(params);
122
176
  const lockKey = claimPath ? `xhs_open_detail:${claimPath}` : '';
@@ -156,8 +210,15 @@ async function executeOpenDetailOperation({ profileId, params = {} }) {
156
210
  highlight,
157
211
  });
158
212
  const payload = extractEvaluateResultData(operationResult.data) || {};
213
+ const actionTrace = Array.isArray(payload.actionTrace) ? payload.actionTrace : [];
214
+ if (actionTrace.length > 0) {
215
+ emitActionTrace(context, actionTrace, { stage: 'xhs_open_detail' });
216
+ delete payload.actionTrace;
217
+ }
159
218
  return {
160
- operationResult,
219
+ operationResult: actionTrace.length > 0
220
+ ? { ...operationResult, data: replaceEvaluateResultData(operationResult.data, payload) }
221
+ : operationResult,
161
222
  payload: payload && typeof payload === 'object' ? payload : {},
162
223
  };
163
224
  };
@@ -361,7 +422,11 @@ async function handleRaiseError({ params }) {
361
422
  return asErrorPayload('OPERATION_FAILED', code || 'AUTOSCRIPT_ABORT');
362
423
  }
363
424
 
364
- async function executeCommentsHarvestOperation({ profileId, params = {} }) {
425
+ async function executeCommentsHarvestOperation({
426
+ profileId,
427
+ params = {},
428
+ context = {},
429
+ }) {
365
430
  const script = buildCommentsHarvestScript(params);
366
431
  const highlight = params.highlight !== false;
367
432
  const operationResult = await evaluateWithScript({
@@ -372,6 +437,11 @@ async function executeCommentsHarvestOperation({ profileId, params = {} }) {
372
437
  });
373
438
 
374
439
  const payload = extractEvaluateResultData(operationResult.data) || {};
440
+ const actionTrace = Array.isArray(payload.actionTrace) ? payload.actionTrace : [];
441
+ if (actionTrace.length > 0) {
442
+ emitActionTrace(context, actionTrace, { stage: 'xhs_comments_harvest' });
443
+ delete payload.actionTrace;
444
+ }
375
445
  const shouldPersistComments = params.persistComments === true || params.persistCollectedComments === true;
376
446
  const includeComments = params.includeComments !== false;
377
447
  const comments = Array.isArray(payload.comments) ? payload.comments : [];
@@ -379,12 +449,12 @@ async function executeCommentsHarvestOperation({ profileId, params = {} }) {
379
449
  if (!shouldPersistComments || !includeComments || comments.length === 0) {
380
450
  return {
381
451
  ...operationResult,
382
- data: {
452
+ data: replaceEvaluateResultData(operationResult.data, {
383
453
  ...payload,
384
454
  commentsPath: null,
385
455
  commentsAdded: 0,
386
456
  commentsTotal: Number(payload.collected || comments.length || 0),
387
- },
457
+ }),
388
458
  };
389
459
  }
390
460
 
@@ -434,10 +504,16 @@ export function isXhsAutoscriptAction(action) {
434
504
  return normalized === 'raise_error' || normalized.startsWith('xhs_');
435
505
  }
436
506
 
437
- export async function executeXhsAutoscriptOperation({ profileId, action, params = {} }) {
507
+ export async function executeXhsAutoscriptOperation({
508
+ profileId,
509
+ action,
510
+ params = {},
511
+ operation = null,
512
+ context = {},
513
+ }) {
438
514
  const handler = XHS_ACTION_HANDLERS[action];
439
515
  if (!handler) {
440
516
  return asErrorPayload('UNSUPPORTED_OPERATION', `Unsupported xhs operation: ${action}`);
441
517
  }
442
- return handler({ profileId, params });
518
+ return handler({ profileId, params, operation, context });
443
519
  }
@@ -640,6 +640,20 @@ export class AutoscriptRunner {
640
640
  attempt,
641
641
  maxAttempts,
642
642
  runtime: this.runtimeContext,
643
+ emitProgress: (payload = {}) => {
644
+ const detail = payload && typeof payload === 'object'
645
+ ? { ...payload }
646
+ : { value: payload };
647
+ const kind = String(detail.kind || '').trim() || 'trace';
648
+ if (Object.prototype.hasOwnProperty.call(detail, 'kind')) delete detail.kind;
649
+ this.log('autoscript:operation_progress', {
650
+ operationId: operation.id,
651
+ action: operation.action,
652
+ attempt,
653
+ kind,
654
+ ...detail,
655
+ });
656
+ },
643
657
  };
644
658
 
645
659
  await this.applyPacingBeforeAttempt(operation, attempt);
@@ -1,10 +1,59 @@
1
1
  #!/usr/bin/env node
2
- import { execSync } from 'node:child_process';
2
+ import { execSync, spawnSync } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
+ import { createRequire } from 'node:module';
7
+ import { fileURLToPath } from 'node:url';
6
8
  import { BROWSER_SERVICE_URL, loadConfig, setRepoRoot } from './config.mjs';
7
9
 
10
+ const requireFromHere = createRequire(import.meta.url);
11
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ function resolveNodeBin() {
14
+ const explicit = String(process.env.WEBAUTO_NODE_BIN || '').trim();
15
+ if (explicit) return explicit;
16
+ const npmNode = String(process.env.npm_node_execpath || '').trim();
17
+ if (npmNode) return npmNode;
18
+ return process.execPath;
19
+ }
20
+
21
+ function resolveCamoCliEntry() {
22
+ try {
23
+ const resolved = requireFromHere.resolve('@web-auto/camo/bin/camo.mjs');
24
+ if (resolved && fs.existsSync(resolved)) return resolved;
25
+ } catch {
26
+ return null;
27
+ }
28
+ return null;
29
+ }
30
+
31
+ function runCamoCli(args = [], options = {}) {
32
+ const entry = resolveCamoCliEntry();
33
+ if (!entry) {
34
+ return {
35
+ ok: false,
36
+ code: null,
37
+ stdout: '',
38
+ stderr: '@web-auto/camo/bin/camo.mjs not found',
39
+ entry: null,
40
+ };
41
+ }
42
+ const ret = spawnSync(resolveNodeBin(), [entry, ...args], {
43
+ encoding: 'utf8',
44
+ windowsHide: true,
45
+ stdio: options.stdio || 'pipe',
46
+ env: { ...process.env, ...(options.env || {}) },
47
+ });
48
+ return {
49
+ ok: ret.status === 0,
50
+ code: ret.status,
51
+ stdout: String(ret.stdout || ''),
52
+ stderr: String(ret.stderr || ''),
53
+ entry,
54
+ };
55
+ }
56
+
8
57
  export async function callAPI(action, payload = {}) {
9
58
  const r = await fetch(`${BROWSER_SERVICE_URL}/command`, {
10
59
  method: 'POST',
@@ -320,20 +369,13 @@ function scanCommonRepoRoots() {
320
369
 
321
370
  export function findRepoRootCandidate() {
322
371
  const cfg = loadConfig();
372
+ const cwdRoot = walkUpForRepoRoot(process.cwd());
373
+ const moduleRoot = walkUpForRepoRoot(MODULE_DIR);
323
374
  const candidates = [
324
375
  process.env.WEBAUTO_REPO_ROOT,
325
- process.cwd(),
326
376
  cfg.repoRoot,
327
- path.join('/Volumes', 'extension', 'code', 'webauto'),
328
- path.join('/Volumes', 'extension', 'code', 'WebAuto'),
329
- path.join(os.homedir(), 'Documents', 'github', 'webauto'),
330
- path.join(os.homedir(), 'Documents', 'github', 'WebAuto'),
331
- path.join(os.homedir(), 'github', 'webauto'),
332
- path.join(os.homedir(), 'github', 'WebAuto'),
333
- path.join('C:', 'code', 'webauto'),
334
- path.join('C:', 'code', 'WebAuto'),
335
- path.join('C:', 'Users', os.userInfo().username, 'code', 'webauto'),
336
- path.join('C:', 'Users', os.userInfo().username, 'code', 'WebAuto'),
377
+ moduleRoot,
378
+ cwdRoot,
337
379
  ].filter(Boolean);
338
380
 
339
381
  for (const root of candidates) {
@@ -345,22 +387,6 @@ export function findRepoRootCandidate() {
345
387
  return resolved;
346
388
  }
347
389
 
348
- const walked = walkUpForRepoRoot(process.cwd());
349
- if (walked) {
350
- if (cfg.repoRoot !== walked) {
351
- setRepoRoot(walked);
352
- }
353
- return walked;
354
- }
355
-
356
- const scanned = scanCommonRepoRoots();
357
- if (scanned) {
358
- if (cfg.repoRoot !== scanned) {
359
- setRepoRoot(scanned);
360
- }
361
- return scanned;
362
- }
363
-
364
390
  return null;
365
391
  }
366
392
 
@@ -384,12 +410,7 @@ export function detectCamoufoxPath() {
384
410
 
385
411
  export function ensureCamoufox() {
386
412
  if (detectCamoufoxPath()) return;
387
- console.log('Camoufox is not found. Installing...');
388
- execSync('npx --yes --package=camoufox camoufox fetch', { stdio: 'inherit' });
389
- if (!detectCamoufoxPath()) {
390
- throw new Error('Camoufox install finished but executable was not detected');
391
- }
392
- console.log('Camoufox installed.');
413
+ throw new Error('Camoufox is not installed. Run: webauto xhs install --download-browser');
393
414
  }
394
415
 
395
416
  export async function ensureBrowserService() {
@@ -405,19 +426,20 @@ export async function ensureBrowserService() {
405
426
 
406
427
  if (provider === 'camo') {
407
428
  const repoRoot = findRepoRootCandidate();
408
- if (repoRoot) {
409
- try {
410
- execSync(`npx --yes @web-auto/camo config repo-root ${JSON.stringify(repoRoot)}`, { stdio: 'ignore' });
411
- } catch {
412
- // best-effort only; init will still try using current config
413
- }
429
+ if (!repoRoot) {
430
+ throw new Error('WEBAUTO_REPO_ROOT is not set and no valid repo root was found');
431
+ }
432
+ const configRet = runCamoCli(['config', 'repo-root', repoRoot], { stdio: 'pipe' });
433
+ if (!configRet.ok) {
434
+ throw new Error(
435
+ `camo config repo-root failed: ${configRet.stderr.trim() || configRet.stdout.trim() || `exit ${configRet.code ?? 'null'}`}`,
436
+ );
414
437
  }
415
438
 
416
- try {
417
- console.log('Starting browser backend via camo init...');
418
- execSync('npx --yes @web-auto/camo init', { stdio: 'inherit' });
419
- } catch (error) {
420
- throw new Error(`camo init failed: ${error?.message || String(error)}`);
439
+ console.log('Starting browser backend via camo init...');
440
+ const initRet = runCamoCli(['init'], { stdio: 'inherit' });
441
+ if (!initRet.ok) {
442
+ throw new Error(`camo init failed: ${initRet.stderr.trim() || initRet.stdout.trim() || `exit ${initRet.code ?? 'null'}`}`);
421
443
  }
422
444
 
423
445
  for (let i = 0; i < 20; i += 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@web-auto/webauto",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "webauto": "bin/webauto.mjs"
@@ -14,6 +14,7 @@
14
14
  "services/",
15
15
  "apps/desktop-console/dist/",
16
16
  "apps/desktop-console/default-settings.json",
17
+ "apps/desktop-console/package.json",
17
18
  "apps/desktop-console/entry/ui-console.mjs",
18
19
  "apps/desktop-console/entry/ui-cli.mjs",
19
20
  "apps/webauto/",
@@ -60,9 +61,9 @@
60
61
  "check:modules": "echo \"Module checks skipped\"",
61
62
  "check:ts": "tsc --noEmit -p tsconfig.services.json",
62
63
  "test:modules:unit": "npx tsx --test $(find modules -name \"*.test.ts\" -o -name \"*.test.mts\" 2>/dev/null | tr \"\\n\" \" \")",
63
- "test:desktop-console:unit": "tsx --test apps/desktop-console/src/main/profile-store.test.mts apps/desktop-console/src/main/index-streaming.test.mts apps/desktop-console/src/main/ui-cli-bridge.test.mts apps/desktop-console/src/main/task-gateway.test.mts apps/desktop-console/src/main/heartbeat-watchdog.test.mts apps/desktop-console/src/main/core-daemon-manager.test.mts apps/desktop-console/src/main/desktop-settings.test.mts apps/desktop-console/src/main/env-check.test.mts",
64
+ "test:desktop-console:unit": "tsx --test apps/desktop-console/src/main/profile-store.test.mts apps/desktop-console/src/main/index-streaming.test.mts apps/desktop-console/src/main/ui-cli-bridge.test.mts apps/desktop-console/src/main/task-gateway.test.mts apps/desktop-console/src/main/heartbeat-watchdog.test.mts apps/desktop-console/src/main/core-daemon-manager.test.mts apps/desktop-console/src/main/desktop-settings.test.mts apps/desktop-console/src/main/env-check.test.mts apps/desktop-console/src/main/state-bridge.test.mts",
64
65
  "test:desktop-console:renderer": "npm --prefix apps/desktop-console run test:renderer",
65
- "test:desktop-console:coverage": "c8 --reporter=text --reporter=lcov --all --src apps/desktop-console/src/main --extension .mts --extension .mjs --extension .ts --exclude \"**/*.test.*\" --check-coverage --lines 75 --functions 75 --branches 55 --statements 75 --include \"apps/desktop-console/src/main/profile-store.mts\" --include \"apps/desktop-console/src/main/ui-cli-bridge.mts\" --include \"apps/desktop-console/src/main/task-gateway.mts\" --include \"apps/desktop-console/src/main/heartbeat-watchdog.mts\" --include \"apps/desktop-console/src/main/core-daemon-manager.mts\" --include \"apps/desktop-console/src/main/desktop-settings.mts\" --include \"apps/desktop-console/src/main/env-check.mts\" tsx --test apps/desktop-console/src/main/profile-store.test.mts apps/desktop-console/src/main/index-streaming.test.mts apps/desktop-console/src/main/ui-cli-bridge.test.mts apps/desktop-console/src/main/task-gateway.test.mts apps/desktop-console/src/main/heartbeat-watchdog.test.mts apps/desktop-console/src/main/core-daemon-manager.test.mts apps/desktop-console/src/main/desktop-settings.test.mts apps/desktop-console/src/main/env-check.test.mts",
66
+ "test:desktop-console:coverage": "c8 --reporter=text --reporter=lcov --all --src apps/desktop-console/src/main --extension .mts --extension .mjs --extension .ts --exclude \"**/*.test.*\" --check-coverage --lines 75 --functions 75 --branches 55 --statements 75 --include \"apps/desktop-console/src/main/profile-store.mts\" --include \"apps/desktop-console/src/main/ui-cli-bridge.mts\" --include \"apps/desktop-console/src/main/task-gateway.mts\" --include \"apps/desktop-console/src/main/heartbeat-watchdog.mts\" --include \"apps/desktop-console/src/main/core-daemon-manager.mts\" --include \"apps/desktop-console/src/main/desktop-settings.mts\" --include \"apps/desktop-console/src/main/env-check.mts\" --include \"apps/desktop-console/src/main/state-bridge.mts\" tsx --test apps/desktop-console/src/main/profile-store.test.mts apps/desktop-console/src/main/index-streaming.test.mts apps/desktop-console/src/main/ui-cli-bridge.test.mts apps/desktop-console/src/main/task-gateway.test.mts apps/desktop-console/src/main/heartbeat-watchdog.test.mts apps/desktop-console/src/main/core-daemon-manager.test.mts apps/desktop-console/src/main/desktop-settings.test.mts apps/desktop-console/src/main/env-check.test.mts",
66
67
  "test:webauto:schedule:unit": "node --test tests/unit/webauto/schedule-store.test.mjs tests/unit/webauto/schedule-cli.test.mjs",
67
68
  "test:webauto:ui-cli:unit": "node --test tests/unit/webauto/ui-cli-command.test.mjs",
68
69
  "test:webauto:install:unit": "node --test tests/unit/webauto/xhs-install.test.mjs",