@web-auto/webauto 0.1.17 → 0.1.19
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/README.md +122 -53
- package/apps/desktop-console/dist/main/index.mjs +229 -14
- package/apps/desktop-console/dist/renderer/index.js +237 -8
- package/apps/desktop-console/entry/ui-cli.mjs +290 -21
- package/apps/desktop-console/entry/ui-console.mjs +46 -15
- package/apps/webauto/entry/account.mjs +126 -27
- package/apps/webauto/entry/lib/account-detect.mjs +399 -9
- package/apps/webauto/entry/lib/account-store.mjs +201 -109
- package/apps/webauto/entry/lib/iflow-reply.mjs +194 -0
- package/apps/webauto/entry/lib/profile-policy.mjs +48 -0
- package/apps/webauto/entry/lib/profilepool.mjs +12 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +29 -2
- package/apps/webauto/entry/lib/session-init.mjs +227 -0
- package/apps/webauto/entry/lib/upgrade-check.mjs +269 -0
- package/apps/webauto/entry/lib/xhs-unified-blocks.mjs +160 -0
- package/apps/webauto/entry/lib/xhs-unified-output-blocks.mjs +83 -0
- package/apps/webauto/entry/lib/xhs-unified-plan-blocks.mjs +55 -0
- package/apps/webauto/entry/lib/xhs-unified-profile-blocks.mjs +542 -0
- package/apps/webauto/entry/lib/xhs-unified-runtime-blocks.mjs +436 -0
- package/apps/webauto/entry/profilepool.mjs +56 -9
- package/apps/webauto/entry/smart-reply-cli.mjs +267 -0
- package/apps/webauto/entry/weibo-unified.mjs +84 -11
- package/apps/webauto/entry/xhs-orchestrate.mjs +43 -1
- package/apps/webauto/entry/xhs-unified.mjs +92 -997
- package/bin/webauto.mjs +22 -4
- package/dist/modules/camo-backend/src/index.js +33 -0
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +232 -49
- package/dist/modules/camo-backend/src/internal/engine-manager.js +14 -13
- package/dist/modules/camo-backend/src/internal/ws-server.js +16 -19
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +38 -6
- package/dist/modules/workflow/blocks/EnsureSession.js +0 -8
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +78 -6
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +266 -192
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +2 -0
- package/dist/modules/workflow/src/runner.js +2 -0
- package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +150 -37
- package/dist/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.js +491 -0
- package/modules/camo-backend/src/index.ts +31 -0
- package/modules/camo-backend/src/internal/BrowserSession.ts +224 -53
- package/modules/camo-backend/src/internal/engine-manager.ts +14 -15
- package/modules/camo-backend/src/internal/ws-server.ts +17 -17
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/common.mjs +12 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/persistence.mjs +57 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +2475 -243
- package/modules/camo-runtime/src/autoscript/runtime.mjs +35 -30
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +80 -443
- package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +39 -6
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +206 -39
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +0 -79
- package/modules/camo-runtime/src/container/runtime-core/operations/viewport.mjs +46 -0
- package/modules/camo-runtime/src/utils/browser-service.mjs +41 -6
- package/modules/camo-runtime/src/utils/js-policy.mjs +28 -0
- package/modules/workflow/blocks/EnsureSession.ts +0 -4
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +81 -6
- package/modules/workflow/blocks/WeiboCollectSearchLinksBlock.ts +316 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +2 -0
- package/modules/workflow/src/runner.ts +2 -0
- package/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.ts +198 -53
- package/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.ts +706 -0
- package/package.json +2 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +0 -498
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/detail.mjs +0 -181
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +0 -691
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +0 -388
- package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +0 -135
package/README.md
CHANGED
|
@@ -1,137 +1,206 @@
|
|
|
1
1
|
# @web-auto/webauto
|
|
2
2
|
|
|
3
|
-
Windows 优先的 WebAuto CLI + Desktop UI
|
|
3
|
+
Windows 优先的 WebAuto CLI + Desktop UI 使用说明(面向直接安装使用)。
|
|
4
4
|
|
|
5
|
-
## 1.
|
|
5
|
+
## 1. 安装
|
|
6
6
|
|
|
7
7
|
要求:
|
|
8
8
|
- Node.js 18+(建议 20+)
|
|
9
9
|
- npm 可用
|
|
10
10
|
|
|
11
|
-
全局安装:
|
|
12
|
-
|
|
13
11
|
```bat
|
|
14
12
|
npm install -g @web-auto/webauto
|
|
13
|
+
webauto --help
|
|
15
14
|
```
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
## 2. 首次启动(推荐路径)
|
|
17
|
+
|
|
18
|
+
直接启动 UI:
|
|
18
19
|
|
|
19
20
|
```bat
|
|
20
|
-
webauto
|
|
21
|
+
webauto ui console
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
也可以用 UI CLI 自动拉起(适合脚本/远程):
|
|
25
|
+
|
|
26
|
+
```bat
|
|
27
|
+
webauto ui cli start --json
|
|
28
|
+
webauto ui cli status --json
|
|
21
29
|
```
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
说明:
|
|
32
|
+
- `webauto` 默认会自动设置运行根目录,不需要手工设置 `WEBAUTO_REPO_ROOT`。
|
|
33
|
+
- 首次运行会按需准备运行依赖(Electron/服务进程)。
|
|
34
|
+
|
|
35
|
+
## 3. Windows 默认目录规则
|
|
36
|
+
|
|
37
|
+
未设置环境变量时:
|
|
38
|
+
- 如果存在 `D:` 盘:默认使用 `D:\webauto`
|
|
39
|
+
- 否则:默认使用 `%USERPROFILE%\.webauto`
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
可选覆盖(仅在你需要自定义目录时):
|
|
42
|
+
- `WEBAUTO_HOME`(推荐)
|
|
43
|
+
- `WEBAUTO_ROOT` / `WEBAUTO_PORTABLE_ROOT`(兼容旧变量)
|
|
44
|
+
|
|
45
|
+
PowerShell 示例:
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
$env:WEBAUTO_HOME = 'E:\my-webauto'
|
|
49
|
+
webauto ui console
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
CMD 示例:
|
|
26
53
|
|
|
27
54
|
```bat
|
|
55
|
+
set WEBAUTO_HOME=E:\my-webauto
|
|
28
56
|
webauto ui console
|
|
29
57
|
```
|
|
30
58
|
|
|
31
|
-
|
|
59
|
+
## 4. UI 常用流程(推荐人机流程)
|
|
60
|
+
|
|
61
|
+
1. 启动 UI:`webauto ui console`
|
|
62
|
+
2. 在任务页填写关键词、目标数、账号等参数
|
|
63
|
+
3. 点“保存并执行”或“执行”
|
|
64
|
+
4. 用 UI CLI 查询当前状态(可选)
|
|
32
65
|
|
|
33
66
|
```bat
|
|
34
|
-
webauto ui cli start --json
|
|
35
67
|
webauto ui cli status --json
|
|
68
|
+
webauto xhs status --json
|
|
36
69
|
```
|
|
37
70
|
|
|
38
71
|
说明:
|
|
39
|
-
-
|
|
40
|
-
-
|
|
72
|
+
- `ui cli status`:轻量健康/状态查询(适合轮询)
|
|
73
|
+
- `ui cli snapshot`:完整 UI 快照(字段更全,开销更大)
|
|
41
74
|
|
|
42
|
-
##
|
|
75
|
+
## 5. UI CLI 操作示例(模拟真实 UI 操作)
|
|
43
76
|
|
|
44
77
|
```bat
|
|
45
|
-
|
|
78
|
+
:: 启动并确认 UI
|
|
79
|
+
webauto ui cli start --json
|
|
80
|
+
webauto ui cli status --json
|
|
46
81
|
|
|
47
|
-
::
|
|
82
|
+
:: 切到任务 tab
|
|
48
83
|
webauto ui cli tab --tab tasks --json
|
|
49
84
|
|
|
50
|
-
::
|
|
85
|
+
:: 输入参数
|
|
51
86
|
webauto ui cli input --selector "#task-keyword" --value "deepseek" --json
|
|
52
87
|
webauto ui cli input --selector "#task-target" --value "20" --json
|
|
53
88
|
|
|
54
|
-
::
|
|
55
|
-
webauto ui cli click --selector "#task-run-
|
|
89
|
+
:: 触发执行(按你的页面按钮 selector)
|
|
90
|
+
webauto ui cli click --selector "#task-run-btn" --json
|
|
91
|
+
|
|
92
|
+
:: 等待 runId 出现
|
|
93
|
+
webauto ui cli wait --selector "#run-id-text" --state exists --timeout 20000 --json
|
|
56
94
|
|
|
57
|
-
::
|
|
95
|
+
:: 取轻量状态 / 完整快照
|
|
58
96
|
webauto ui cli status --json
|
|
59
97
|
webauto ui cli snapshot --json
|
|
98
|
+
```
|
|
60
99
|
|
|
61
|
-
|
|
62
|
-
|
|
100
|
+
## 6. 账号与任务命令(CLI)
|
|
101
|
+
|
|
102
|
+
```bat
|
|
103
|
+
:: 账号
|
|
104
|
+
webauto account list
|
|
105
|
+
webauto account login xhs-0001 --url https://www.xiaohongshu.com
|
|
106
|
+
webauto account sync-alias xhs-0001
|
|
107
|
+
|
|
108
|
+
:: 调度任务
|
|
109
|
+
webauto schedule list
|
|
110
|
+
webauto schedule run <taskId>
|
|
63
111
|
```
|
|
64
112
|
|
|
65
|
-
|
|
113
|
+
## 7. XHS 运行前初始化
|
|
114
|
+
|
|
115
|
+
建议先检查/准备依赖:
|
|
66
116
|
|
|
67
117
|
```bat
|
|
68
|
-
webauto xhs
|
|
69
|
-
webauto xhs
|
|
118
|
+
webauto xhs install --check --json
|
|
119
|
+
webauto xhs install --download-browser --json
|
|
120
|
+
webauto xhs install --download-geoip --json
|
|
121
|
+
webauto xhs install --ensure-backend --json
|
|
70
122
|
```
|
|
71
123
|
|
|
72
|
-
##
|
|
124
|
+
## 8. XHS 任务执行与状态
|
|
73
125
|
|
|
74
|
-
|
|
75
|
-
- 有 `D:` 盘:`D:\webauto`
|
|
76
|
-
- 无 `D:` 盘:`%USERPROFILE%\.webauto`
|
|
126
|
+
完整采集(搜索 + 评论 + 点赞):
|
|
77
127
|
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
|
|
128
|
+
```bat
|
|
129
|
+
webauto xhs unified --profile xiaohongshu-batch-1 --keyword "deepseek" --max-notes 200 --do-comments true --persist-comments true --do-likes true --like-keywords "太强了,真不错" --match-mode any --match-min-hits 1 --max-likes 10 --env debug --tab-count 4
|
|
130
|
+
```
|
|
81
131
|
|
|
82
|
-
|
|
132
|
+
仅查状态:
|
|
83
133
|
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
webauto
|
|
134
|
+
```bat
|
|
135
|
+
webauto xhs status --json
|
|
136
|
+
webauto xhs status --run-id <runId> --json
|
|
87
137
|
```
|
|
88
138
|
|
|
89
|
-
|
|
139
|
+
## 9. 流控 Gate(按平台隔离)
|
|
140
|
+
|
|
141
|
+
默认会使用平台 gate 参数控制节奏(含随机区间),你可以在线修改并立即生效。
|
|
90
142
|
|
|
91
143
|
```bat
|
|
92
|
-
|
|
93
|
-
webauto
|
|
144
|
+
webauto xhs gate get --platform xiaohongshu --json
|
|
145
|
+
webauto xhs gate set --platform xiaohongshu --patch-json "{\"noteInterval\":{\"minMs\":2600,\"maxMs\":5200}}" --json
|
|
146
|
+
webauto xhs gate reset --platform xiaohongshu --json
|
|
94
147
|
```
|
|
95
148
|
|
|
96
|
-
|
|
149
|
+
## 10. 输出与日志
|
|
150
|
+
|
|
151
|
+
常见目录(以默认目录为例):
|
|
152
|
+
- 数据根:`D:\webauto` 或 `%USERPROFILE%\.webauto`
|
|
153
|
+
- 采集输出:`<WEBAUTO_HOME>\download\xiaohongshu\<env>\<keyword>\`
|
|
154
|
+
- 运行日志:`<WEBAUTO_HOME>\logs\`
|
|
97
155
|
|
|
98
|
-
|
|
156
|
+
典型文件:
|
|
157
|
+
- `comments.jsonl`
|
|
158
|
+
- `like-evidence\<noteId>\summary-*.json`
|
|
159
|
+
- `run.log` / `run-events.jsonl`
|
|
99
160
|
|
|
100
|
-
|
|
161
|
+
## 11. 常见问题排查
|
|
101
162
|
|
|
102
|
-
|
|
163
|
+
### 11.1 UI 启动报错 `Lock file can not be created`
|
|
164
|
+
|
|
165
|
+
通常是残留进程占用:
|
|
103
166
|
|
|
104
167
|
```bat
|
|
105
168
|
taskkill /F /IM electron.exe /T
|
|
106
169
|
taskkill /F /IM node.exe /T
|
|
107
|
-
webauto ui console
|
|
170
|
+
webauto ui console --no-daemon
|
|
108
171
|
```
|
|
109
172
|
|
|
110
|
-
###
|
|
173
|
+
### 11.2 `ui cli fetch failed`
|
|
111
174
|
|
|
112
|
-
先确认 UI
|
|
175
|
+
先确认 UI 已启动,再查状态:
|
|
113
176
|
|
|
114
177
|
```bat
|
|
115
178
|
webauto ui cli start --json
|
|
116
179
|
webauto ui cli status --json
|
|
117
180
|
```
|
|
118
181
|
|
|
119
|
-
|
|
182
|
+
如果仍失败,前台模式查看实时日志:
|
|
120
183
|
|
|
121
184
|
```bat
|
|
122
185
|
webauto ui console --no-daemon
|
|
123
186
|
```
|
|
124
187
|
|
|
125
|
-
|
|
188
|
+
### 11.3 旧账号/Profile 看不到
|
|
189
|
+
|
|
190
|
+
先确认当前数据根目录是否与历史目录一致。
|
|
191
|
+
- 若历史数据在其它目录,可临时设置 `WEBAUTO_HOME` 指向旧目录再启动。
|
|
192
|
+
- 或把旧目录下的 `profiles/`、`cookies/` 等迁移到当前数据根后再启动。
|
|
193
|
+
|
|
194
|
+
## 12. 升级与版本确认
|
|
126
195
|
|
|
127
196
|
```bat
|
|
128
|
-
|
|
129
|
-
webauto
|
|
130
|
-
webauto
|
|
197
|
+
npm install -g @web-auto/webauto@latest
|
|
198
|
+
webauto version
|
|
199
|
+
webauto version --json
|
|
131
200
|
```
|
|
132
201
|
|
|
133
|
-
##
|
|
202
|
+
## 13. 开发者文档
|
|
134
203
|
|
|
135
|
-
|
|
204
|
+
如果你在仓库模式下开发,请看:
|
|
136
205
|
- `apps/desktop-console/README.md`
|
|
137
|
-
|
|
206
|
+
- `AGENTS.md`
|
|
@@ -919,9 +919,10 @@ import path5 from "node:path";
|
|
|
919
919
|
import { promises as fs3 } from "node:fs";
|
|
920
920
|
var DEFAULT_HOST = "127.0.0.1";
|
|
921
921
|
var DEFAULT_PORT = 7716;
|
|
922
|
-
var DEFAULT_SNAPSHOT_TIMEOUT_MS =
|
|
923
|
-
var DEFAULT_ACTION_TIMEOUT_MS =
|
|
922
|
+
var DEFAULT_SNAPSHOT_TIMEOUT_MS = 35e3;
|
|
923
|
+
var DEFAULT_ACTION_TIMEOUT_MS = 3e4;
|
|
924
924
|
var DEFAULT_WAIT_PROBE_TIMEOUT_MS = 3e3;
|
|
925
|
+
var UI_CLI_ACTION_LOG_FILE = path5.join(resolveWebautoRoot2(), "logs", "ui-cli-actions.jsonl");
|
|
925
926
|
function normalizePathForPlatform(raw, platform = process.platform) {
|
|
926
927
|
const input = String(raw || "").trim();
|
|
927
928
|
const isWinPath = platform === "win32" || /^[A-Za-z]:[\\/]/.test(input);
|
|
@@ -997,6 +998,45 @@ function toActionError(input, error, extra = {}) {
|
|
|
997
998
|
};
|
|
998
999
|
return payload;
|
|
999
1000
|
}
|
|
1001
|
+
function clipText(value, maxLen = 220) {
|
|
1002
|
+
const text = String(value ?? "");
|
|
1003
|
+
if (text.length <= maxLen) return text;
|
|
1004
|
+
return `${text.slice(0, Math.max(0, maxLen - 3))}...`;
|
|
1005
|
+
}
|
|
1006
|
+
function summarizeAction(input) {
|
|
1007
|
+
const rawClient = input?._client;
|
|
1008
|
+
const client = rawClient && typeof rawClient === "object" ? {
|
|
1009
|
+
client: clipText(rawClient.client ?? "", 80) || null,
|
|
1010
|
+
cmd: clipText(rawClient.cmd ?? "", 80) || null,
|
|
1011
|
+
pid: Number.isFinite(Number(rawClient.pid)) ? Math.floor(Number(rawClient.pid)) : null,
|
|
1012
|
+
ppid: Number.isFinite(Number(rawClient.ppid)) ? Math.floor(Number(rawClient.ppid)) : null
|
|
1013
|
+
} : null;
|
|
1014
|
+
return {
|
|
1015
|
+
action: String(input?.action || "").trim() || null,
|
|
1016
|
+
selector: String(input?.selector || "").trim() || null,
|
|
1017
|
+
tabId: String(input?.tabId || "").trim() || null,
|
|
1018
|
+
tabLabel: String(input?.tabLabel || "").trim() || null,
|
|
1019
|
+
state: String(input?.state || "").trim() || null,
|
|
1020
|
+
key: String(input?.key || "").trim() || null,
|
|
1021
|
+
text: clipText(input?.text ?? "", 160) || null,
|
|
1022
|
+
value: clipText(input?.value ?? "", 160) || null,
|
|
1023
|
+
timeoutMs: readInt(input?.timeoutMs, 0) || null,
|
|
1024
|
+
intervalMs: readInt(input?.intervalMs, 0) || null,
|
|
1025
|
+
nth: Number.isFinite(Number(input?.nth)) ? Math.floor(Number(input?.nth)) : null,
|
|
1026
|
+
exact: input?.exact === true ? true : null,
|
|
1027
|
+
detailed: input?.detailed === true ? true : null,
|
|
1028
|
+
client
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
async function appendActionLog(entry) {
|
|
1032
|
+
const payload = { ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry };
|
|
1033
|
+
try {
|
|
1034
|
+
await fs3.mkdir(path5.dirname(UI_CLI_ACTION_LOG_FILE), { recursive: true });
|
|
1035
|
+
await fs3.appendFile(UI_CLI_ACTION_LOG_FILE, `${JSON.stringify(payload)}
|
|
1036
|
+
`, "utf8");
|
|
1037
|
+
} catch {
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1000
1040
|
async function withTimeout(promise, timeoutMs, label) {
|
|
1001
1041
|
const ms = readInt(timeoutMs, 0);
|
|
1002
1042
|
if (ms <= 0) return promise;
|
|
@@ -1382,7 +1422,26 @@ var UiCliBridge = class {
|
|
|
1382
1422
|
}
|
|
1383
1423
|
if (method === "POST" && url.pathname === "/action") {
|
|
1384
1424
|
const body = await parseBody(req);
|
|
1425
|
+
const actionId = `act-${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 8)}`;
|
|
1426
|
+
const startedAt = Date.now();
|
|
1427
|
+
await appendActionLog({
|
|
1428
|
+
event: "action.request",
|
|
1429
|
+
actionId,
|
|
1430
|
+
method,
|
|
1431
|
+
path: url.pathname,
|
|
1432
|
+
remoteAddress: req.socket?.remoteAddress || null,
|
|
1433
|
+
remotePort: Number.isFinite(Number(req.socket?.remotePort)) ? Number(req.socket?.remotePort) : null,
|
|
1434
|
+
userAgent: clipText(req.headers?.["user-agent"] || "", 160) || null,
|
|
1435
|
+
payload: summarizeAction(body || {})
|
|
1436
|
+
});
|
|
1385
1437
|
const result = await this.handleAction(body || {});
|
|
1438
|
+
await appendActionLog({
|
|
1439
|
+
event: "action.response",
|
|
1440
|
+
actionId,
|
|
1441
|
+
elapsedMs: Date.now() - startedAt,
|
|
1442
|
+
ok: result?.ok === true,
|
|
1443
|
+
error: result?.ok === true ? null : String(result?.error || "action_failed")
|
|
1444
|
+
});
|
|
1386
1445
|
return sendJson(res, result.ok ? 200 : 400, result);
|
|
1387
1446
|
}
|
|
1388
1447
|
return sendJson(res, 404, { ok: false, error: "not_found" });
|
|
@@ -1434,6 +1493,9 @@ var UiCliBridge = class {
|
|
|
1434
1493
|
async handleAction(input) {
|
|
1435
1494
|
const action = String(input?.action || "").trim();
|
|
1436
1495
|
if (!action) return toActionError(input, "missing_action");
|
|
1496
|
+
if (action === "restart") {
|
|
1497
|
+
return this.handleRestart(input);
|
|
1498
|
+
}
|
|
1437
1499
|
if (action === "wait") {
|
|
1438
1500
|
return this.waitForSelector(input);
|
|
1439
1501
|
}
|
|
@@ -1451,6 +1513,22 @@ var UiCliBridge = class {
|
|
|
1451
1513
|
return toActionError(input, err?.message || String(err), { details: err?.stack || null });
|
|
1452
1514
|
}
|
|
1453
1515
|
}
|
|
1516
|
+
async handleRestart(input) {
|
|
1517
|
+
const onRestart = this.options.onRestart;
|
|
1518
|
+
if (typeof onRestart !== "function") {
|
|
1519
|
+
return toActionError(input, "restart_not_supported");
|
|
1520
|
+
}
|
|
1521
|
+
const reason = String(input?.reason || input?.value || "").trim() || "ui_cli";
|
|
1522
|
+
try {
|
|
1523
|
+
const out = await Promise.resolve(onRestart({ reason, source: "ui_cli_bridge" }));
|
|
1524
|
+
if (out && typeof out === "object") {
|
|
1525
|
+
return { ok: true, restarting: true, reason, ...out };
|
|
1526
|
+
}
|
|
1527
|
+
return { ok: true, restarting: true, reason };
|
|
1528
|
+
} catch (err) {
|
|
1529
|
+
return toActionError(input, err?.message || String(err), { details: err?.stack || null });
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1454
1532
|
async waitForSelector(input) {
|
|
1455
1533
|
const selector = String(input.selector || "").trim();
|
|
1456
1534
|
if (!selector) return toActionError(input, "missing_selector");
|
|
@@ -1696,6 +1774,25 @@ async function scheduleInvoke(options, input) {
|
|
|
1696
1774
|
if (action === "run") {
|
|
1697
1775
|
const taskId = asText(input?.taskId);
|
|
1698
1776
|
if (!taskId) return { ok: false, error: "missing taskId" };
|
|
1777
|
+
if (input?.background === true) {
|
|
1778
|
+
const script = path6.join(options.repoRoot, "apps", "webauto", "entry", "schedule.mjs");
|
|
1779
|
+
const ret = await options.spawnCommand({
|
|
1780
|
+
title: `schedule run ${taskId}`,
|
|
1781
|
+
cwd: options.repoRoot,
|
|
1782
|
+
args: [script, "run", taskId, "--json"],
|
|
1783
|
+
groupKey: `schedule-run:${taskId}`
|
|
1784
|
+
});
|
|
1785
|
+
const runId = asText(ret?.runId);
|
|
1786
|
+
return {
|
|
1787
|
+
ok: true,
|
|
1788
|
+
json: {
|
|
1789
|
+
ok: true,
|
|
1790
|
+
background: true,
|
|
1791
|
+
runId: runId || null,
|
|
1792
|
+
runResult: runId ? { runId } : {}
|
|
1793
|
+
}
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1699
1796
|
return runScheduleJson(options, ["run", taskId], timeoutMs);
|
|
1700
1797
|
}
|
|
1701
1798
|
if (action === "delete") {
|
|
@@ -1770,6 +1867,8 @@ async function runEphemeralTask(options, input) {
|
|
|
1770
1867
|
String(asBool(argv["do-comments"], true)),
|
|
1771
1868
|
"--fetch-body",
|
|
1772
1869
|
String(asBool(argv["fetch-body"], true)),
|
|
1870
|
+
"--service-reset",
|
|
1871
|
+
String(asBool(argv["service-reset"], false)),
|
|
1773
1872
|
"--do-likes",
|
|
1774
1873
|
String(asBool(argv["do-likes"], false)),
|
|
1775
1874
|
"--like-keywords",
|
|
@@ -1815,8 +1914,15 @@ function trackBrowserProcess(pid) {
|
|
|
1815
1914
|
spawnedBrowserProcesses.add(pid);
|
|
1816
1915
|
}
|
|
1817
1916
|
}
|
|
1917
|
+
function safeConsole(method, ...args) {
|
|
1918
|
+
try {
|
|
1919
|
+
const fn = console[method];
|
|
1920
|
+
if (typeof fn === "function") fn(...args);
|
|
1921
|
+
} catch {
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1818
1924
|
function cleanupAllBrowserProcesses(reason = "ui_close") {
|
|
1819
|
-
|
|
1925
|
+
safeConsole("log", `[process-cleanup] Cleaning up ${spawnedBrowserProcesses.size} browser process(s) (${reason})`);
|
|
1820
1926
|
for (const pid of spawnedBrowserProcesses) {
|
|
1821
1927
|
try {
|
|
1822
1928
|
if (process.platform === "win32") {
|
|
@@ -1825,11 +1931,11 @@ function cleanupAllBrowserProcesses(reason = "ui_close") {
|
|
|
1825
1931
|
process.kill(pid, "SIGTERM");
|
|
1826
1932
|
}
|
|
1827
1933
|
} catch (err) {
|
|
1828
|
-
|
|
1934
|
+
safeConsole("warn", `[process-cleanup] Failed to kill PID ${pid}:`, err);
|
|
1829
1935
|
}
|
|
1830
1936
|
}
|
|
1831
1937
|
spawnedBrowserProcesses.clear();
|
|
1832
|
-
|
|
1938
|
+
safeConsole("log", "[process-cleanup] Cleanup complete");
|
|
1833
1939
|
}
|
|
1834
1940
|
var __dirname = path7.dirname(fileURLToPath2(import.meta.url));
|
|
1835
1941
|
var APP_ROOT = path7.resolve(__dirname, "../..");
|
|
@@ -1856,6 +1962,12 @@ var DESKTOP_HEARTBEAT_FILE = path7.join(
|
|
|
1856
1962
|
"run",
|
|
1857
1963
|
"desktop-console-heartbeat.json"
|
|
1858
1964
|
);
|
|
1965
|
+
var DESKTOP_LIFECYCLE_LOG_FILE = path7.join(
|
|
1966
|
+
os5.homedir(),
|
|
1967
|
+
".webauto",
|
|
1968
|
+
"logs",
|
|
1969
|
+
"desktop-lifecycle.jsonl"
|
|
1970
|
+
);
|
|
1859
1971
|
var profileStore = createProfileStore({ repoRoot: REPO_ROOT2 });
|
|
1860
1972
|
var XHS_SCRIPTS_ROOT = path7.join(REPO_ROOT2, "scripts", "xiaohongshu");
|
|
1861
1973
|
var XHS_FULL_COLLECT_RE = /collect-content\.mjs$/;
|
|
@@ -1897,6 +2009,20 @@ function configureElectronPaths() {
|
|
|
1897
2009
|
function now() {
|
|
1898
2010
|
return Date.now();
|
|
1899
2011
|
}
|
|
2012
|
+
async function appendDesktopLifecycle(event, extra = {}) {
|
|
2013
|
+
const payload = {
|
|
2014
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2015
|
+
event: String(event || "unknown").trim() || "unknown",
|
|
2016
|
+
pid: process.pid,
|
|
2017
|
+
...extra
|
|
2018
|
+
};
|
|
2019
|
+
try {
|
|
2020
|
+
await fs4.mkdir(path7.dirname(DESKTOP_LIFECYCLE_LOG_FILE), { recursive: true });
|
|
2021
|
+
await fs4.appendFile(DESKTOP_LIFECYCLE_LOG_FILE, `${JSON.stringify(payload)}
|
|
2022
|
+
`, "utf8");
|
|
2023
|
+
} catch {
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
1900
2026
|
var GroupQueue = class {
|
|
1901
2027
|
running = false;
|
|
1902
2028
|
queue = [];
|
|
@@ -1955,6 +2081,7 @@ var heartbeatTimeoutHandled = false;
|
|
|
1955
2081
|
var coreServicesStopRequested = false;
|
|
1956
2082
|
var coreServiceHeartbeatTimer = null;
|
|
1957
2083
|
var coreServiceHeartbeatStopped = false;
|
|
2084
|
+
var restartRequested = false;
|
|
1958
2085
|
var RUN_LOG_DIR = path7.join(os5.homedir(), ".webauto", "logs");
|
|
1959
2086
|
function appendRunLog(runId, line) {
|
|
1960
2087
|
const rid = String(runId || "").trim();
|
|
@@ -2012,16 +2139,55 @@ function ensureStateBridge() {
|
|
|
2012
2139
|
}
|
|
2013
2140
|
}
|
|
2014
2141
|
var win = null;
|
|
2015
|
-
var uiCliBridge = new UiCliBridge({
|
|
2142
|
+
var uiCliBridge = new UiCliBridge({
|
|
2143
|
+
getWindow: getWin,
|
|
2144
|
+
onRestart: ({ reason }) => requestAppRestart(reason)
|
|
2145
|
+
});
|
|
2016
2146
|
configureElectronPaths();
|
|
2017
2147
|
var singleInstanceLock = app.requestSingleInstanceLock();
|
|
2018
2148
|
if (!singleInstanceLock) {
|
|
2149
|
+
void appendDesktopLifecycle("single_instance_lock_failed");
|
|
2019
2150
|
app.quit();
|
|
2020
2151
|
}
|
|
2021
2152
|
function getWin() {
|
|
2022
2153
|
if (!win || win.isDestroyed()) return null;
|
|
2023
2154
|
return win;
|
|
2024
2155
|
}
|
|
2156
|
+
function requestAppRestart(reason = "ui_cli") {
|
|
2157
|
+
const normalizedReason = String(reason || "").trim() || "ui_cli";
|
|
2158
|
+
if (restartRequested) {
|
|
2159
|
+
return { accepted: false, reason: normalizedReason };
|
|
2160
|
+
}
|
|
2161
|
+
restartRequested = true;
|
|
2162
|
+
void appendDesktopLifecycle("restart_requested", { reason: normalizedReason });
|
|
2163
|
+
console.warn(`[desktop-console] restart requested: ${normalizedReason}`);
|
|
2164
|
+
const w = getWin();
|
|
2165
|
+
if (w) {
|
|
2166
|
+
try {
|
|
2167
|
+
w.webContents.send("app:restart-requested", {
|
|
2168
|
+
reason: normalizedReason,
|
|
2169
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
2170
|
+
});
|
|
2171
|
+
} catch {
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
setTimeout(() => {
|
|
2175
|
+
void Promise.race([
|
|
2176
|
+
ensureAppExitCleanup(`restart:${normalizedReason}`, { stopStateBridge: true }),
|
|
2177
|
+
sleep2(1500)
|
|
2178
|
+
]).catch(() => null).finally(() => {
|
|
2179
|
+
try {
|
|
2180
|
+
app.relaunch();
|
|
2181
|
+
} catch (err) {
|
|
2182
|
+
restartRequested = false;
|
|
2183
|
+
console.error("[desktop-console] relaunch failed", err);
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
app.exit(0);
|
|
2187
|
+
});
|
|
2188
|
+
}, 25);
|
|
2189
|
+
return { accepted: true, reason: normalizedReason };
|
|
2190
|
+
}
|
|
2025
2191
|
if (singleInstanceLock) {
|
|
2026
2192
|
app.on("second-instance", () => {
|
|
2027
2193
|
const w = getWin();
|
|
@@ -2469,7 +2635,8 @@ async function spawnCommand(spec) {
|
|
|
2469
2635
|
return { runId };
|
|
2470
2636
|
}
|
|
2471
2637
|
async function runJson(spec) {
|
|
2472
|
-
const
|
|
2638
|
+
const timeoutRaw = Number(spec.timeoutMs);
|
|
2639
|
+
const timeoutMs = Number.isFinite(timeoutRaw) ? Math.floor(timeoutRaw) : 2e4;
|
|
2473
2640
|
const cwd = resolveCwd(spec.cwd);
|
|
2474
2641
|
const child = spawn2(resolveNodeBin2(), spec.args, {
|
|
2475
2642
|
cwd,
|
|
@@ -2481,16 +2648,19 @@ async function runJson(spec) {
|
|
|
2481
2648
|
const stderr = [];
|
|
2482
2649
|
child.stdout?.on("data", (c) => stdout.push(c));
|
|
2483
2650
|
child.stderr?.on("data", (c) => stderr.push(c));
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2651
|
+
let timer = null;
|
|
2652
|
+
if (timeoutMs > 0) {
|
|
2653
|
+
timer = setTimeout(() => {
|
|
2654
|
+
try {
|
|
2655
|
+
child.kill("SIGTERM");
|
|
2656
|
+
} catch {
|
|
2657
|
+
}
|
|
2658
|
+
}, timeoutMs);
|
|
2659
|
+
}
|
|
2490
2660
|
const { code } = await new Promise((resolve) => {
|
|
2491
2661
|
child.on("exit", (c) => resolve({ code: c }));
|
|
2492
2662
|
});
|
|
2493
|
-
clearTimeout(timer);
|
|
2663
|
+
if (timer) clearTimeout(timer);
|
|
2494
2664
|
const out = Buffer.concat(stdout).toString("utf8").trim();
|
|
2495
2665
|
const err = Buffer.concat(stderr).toString("utf8").trim();
|
|
2496
2666
|
if (code !== 0) {
|
|
@@ -2756,20 +2926,58 @@ function createWindow() {
|
|
|
2756
2926
|
}
|
|
2757
2927
|
});
|
|
2758
2928
|
const htmlPath = path7.join(APP_ROOT, "dist", "renderer", "index.html");
|
|
2929
|
+
void appendDesktopLifecycle("window_created", {
|
|
2930
|
+
width: win.getBounds().width,
|
|
2931
|
+
height: win.getBounds().height,
|
|
2932
|
+
title: VERSION_INFO.windowTitle
|
|
2933
|
+
});
|
|
2934
|
+
win.on("close", () => {
|
|
2935
|
+
void appendDesktopLifecycle("window_close");
|
|
2936
|
+
});
|
|
2937
|
+
win.on("closed", () => {
|
|
2938
|
+
void appendDesktopLifecycle("window_closed");
|
|
2939
|
+
});
|
|
2940
|
+
win.on("unresponsive", () => {
|
|
2941
|
+
void appendDesktopLifecycle("window_unresponsive");
|
|
2942
|
+
});
|
|
2943
|
+
win.webContents.on("render-process-gone", (_event, details) => {
|
|
2944
|
+
void appendDesktopLifecycle("render_process_gone", {
|
|
2945
|
+
reason: String(details?.reason || "").trim() || null,
|
|
2946
|
+
exitCode: Number.isFinite(Number(details?.exitCode)) ? Number(details?.exitCode) : null
|
|
2947
|
+
});
|
|
2948
|
+
});
|
|
2949
|
+
win.webContents.on("did-fail-load", (_event, code, desc, validatedURL, isMainFrame) => {
|
|
2950
|
+
void appendDesktopLifecycle("did_fail_load", {
|
|
2951
|
+
code,
|
|
2952
|
+
desc: String(desc || ""),
|
|
2953
|
+
url: String(validatedURL || ""),
|
|
2954
|
+
isMainFrame: Boolean(isMainFrame)
|
|
2955
|
+
});
|
|
2956
|
+
});
|
|
2759
2957
|
void win.loadFile(htmlPath);
|
|
2760
2958
|
ensureStateBridge();
|
|
2761
2959
|
}
|
|
2762
2960
|
app.on("window-all-closed", () => {
|
|
2961
|
+
void appendDesktopLifecycle("window_all_closed");
|
|
2763
2962
|
void ensureAppExitCleanup("window_closed");
|
|
2764
2963
|
app.quit();
|
|
2765
2964
|
});
|
|
2766
2965
|
app.on("before-quit", () => {
|
|
2966
|
+
void appendDesktopLifecycle("before_quit");
|
|
2767
2967
|
void ensureAppExitCleanup("before_quit");
|
|
2768
2968
|
});
|
|
2769
2969
|
app.on("will-quit", () => {
|
|
2970
|
+
void appendDesktopLifecycle("will_quit");
|
|
2770
2971
|
void ensureAppExitCleanup("will_quit", { stopStateBridge: true });
|
|
2771
2972
|
});
|
|
2973
|
+
app.on("quit", (_evt, exitCode) => {
|
|
2974
|
+
void appendDesktopLifecycle("quit", { exitCode });
|
|
2975
|
+
});
|
|
2976
|
+
process.on("exit", (code) => {
|
|
2977
|
+
void appendDesktopLifecycle("process_exit", { code });
|
|
2978
|
+
});
|
|
2772
2979
|
app.whenReady().then(async () => {
|
|
2980
|
+
void appendDesktopLifecycle("app_ready");
|
|
2773
2981
|
startCoreServiceHeartbeat();
|
|
2774
2982
|
const started = await startCoreDaemon().catch((err) => {
|
|
2775
2983
|
console.error("[desktop-console] core services startup failed", err);
|
|
@@ -2786,12 +2994,19 @@ app.whenReady().then(async () => {
|
|
|
2786
2994
|
createWindow();
|
|
2787
2995
|
try {
|
|
2788
2996
|
await uiCliBridge.start();
|
|
2997
|
+
void appendDesktopLifecycle("ui_cli_bridge_started");
|
|
2789
2998
|
} catch (err) {
|
|
2999
|
+
void appendDesktopLifecycle("ui_cli_bridge_start_failed", {
|
|
3000
|
+
error: err?.message || String(err)
|
|
3001
|
+
});
|
|
2790
3002
|
console.error("[desktop-console] ui-cli bridge start failed", err);
|
|
2791
3003
|
await ensureAppExitCleanup("ui_cli_bridge_start_failed", { stopStateBridge: true }).catch(() => null);
|
|
2792
3004
|
app.exit(1);
|
|
2793
3005
|
}
|
|
2794
3006
|
}).catch(async (err) => {
|
|
3007
|
+
void appendDesktopLifecycle("app_startup_exception", {
|
|
3008
|
+
error: err?.message || String(err)
|
|
3009
|
+
});
|
|
2795
3010
|
console.error("[desktop-console] fatal startup error", err);
|
|
2796
3011
|
await ensureAppExitCleanup("startup_exception", { stopStateBridge: true }).catch(() => null);
|
|
2797
3012
|
app.exit(1);
|