weacpx 0.2.0 → 0.2.1
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 +87 -9
- package/dist/bridge/bridge-main.js +56 -7
- package/dist/cli.js +1433 -366
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -45,8 +45,378 @@ var __export = (target, all) => {
|
|
|
45
45
|
});
|
|
46
46
|
};
|
|
47
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
48
|
+
var __promiseAll = (args) => Promise.all(args);
|
|
48
49
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
50
|
|
|
51
|
+
// src/daemon/daemon-status.ts
|
|
52
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
53
|
+
import { dirname } from "node:path";
|
|
54
|
+
|
|
55
|
+
class DaemonStatusStore {
|
|
56
|
+
path;
|
|
57
|
+
constructor(path) {
|
|
58
|
+
this.path = path;
|
|
59
|
+
}
|
|
60
|
+
async load() {
|
|
61
|
+
try {
|
|
62
|
+
const content = await readFile(this.path, "utf8");
|
|
63
|
+
if (content.trim() === "") {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return JSON.parse(content);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error.code === "ENOENT") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async save(status) {
|
|
75
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
76
|
+
await writeFile(this.path, JSON.stringify(status, null, 2));
|
|
77
|
+
}
|
|
78
|
+
async clear() {
|
|
79
|
+
await rm(this.path, { force: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
var init_daemon_status = () => {};
|
|
83
|
+
|
|
84
|
+
// src/daemon/daemon-controller.ts
|
|
85
|
+
import { mkdir as mkdir2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
86
|
+
import { dirname as dirname2 } from "node:path";
|
|
87
|
+
|
|
88
|
+
class DaemonController {
|
|
89
|
+
paths;
|
|
90
|
+
deps;
|
|
91
|
+
statusStore;
|
|
92
|
+
startupPollIntervalMs;
|
|
93
|
+
startupTimeoutMs;
|
|
94
|
+
onStartupPoll;
|
|
95
|
+
shutdownPollIntervalMs;
|
|
96
|
+
shutdownTimeoutMs;
|
|
97
|
+
onShutdownPoll;
|
|
98
|
+
constructor(paths, deps) {
|
|
99
|
+
this.paths = paths;
|
|
100
|
+
this.deps = deps;
|
|
101
|
+
this.statusStore = new DaemonStatusStore(paths.statusFile);
|
|
102
|
+
this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
|
|
103
|
+
this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
|
|
104
|
+
this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
|
|
105
|
+
this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
|
|
106
|
+
this.onStartupPoll = deps.onStartupPoll ?? (async () => {
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, this.startupPollIntervalMs));
|
|
108
|
+
});
|
|
109
|
+
this.onShutdownPoll = deps.onShutdownPoll ?? (async () => {
|
|
110
|
+
await new Promise((resolve) => setTimeout(resolve, this.shutdownPollIntervalMs));
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async getStatus() {
|
|
114
|
+
const pid = await this.loadPid();
|
|
115
|
+
const status = await this.statusStore.load();
|
|
116
|
+
if (!pid) {
|
|
117
|
+
return { state: "stopped" };
|
|
118
|
+
}
|
|
119
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
120
|
+
await this.clearRuntimeFiles();
|
|
121
|
+
return { state: "stopped", stale: true };
|
|
122
|
+
}
|
|
123
|
+
if (!status) {
|
|
124
|
+
return { state: "indeterminate", pid, reason: "missing-status" };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
state: "running",
|
|
128
|
+
pid,
|
|
129
|
+
status
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async start() {
|
|
133
|
+
const current = await this.getStatus();
|
|
134
|
+
if (current.state === "running") {
|
|
135
|
+
return { state: "already-running", pid: current.pid };
|
|
136
|
+
}
|
|
137
|
+
if (current.state === "indeterminate") {
|
|
138
|
+
throw new Error(`weacpx daemon process is already running (pid ${current.pid}) but status metadata is missing`);
|
|
139
|
+
}
|
|
140
|
+
await this.statusStore.clear();
|
|
141
|
+
const pid = await this.deps.spawnDetached();
|
|
142
|
+
await this.writePid(pid);
|
|
143
|
+
await this.waitForStartupMetadata(pid);
|
|
144
|
+
return { state: "started", pid };
|
|
145
|
+
}
|
|
146
|
+
async stop() {
|
|
147
|
+
const pid = await this.loadPid();
|
|
148
|
+
if (!pid) {
|
|
149
|
+
return { state: "stopped", detail: "not-running" };
|
|
150
|
+
}
|
|
151
|
+
if (this.deps.isProcessRunning(pid)) {
|
|
152
|
+
await this.deps.terminateProcess(pid);
|
|
153
|
+
await this.waitForShutdown(pid);
|
|
154
|
+
}
|
|
155
|
+
await this.clearRuntimeFiles();
|
|
156
|
+
return { state: "stopped", detail: "stopped" };
|
|
157
|
+
}
|
|
158
|
+
async loadPid() {
|
|
159
|
+
try {
|
|
160
|
+
const content = await readFile2(this.paths.pidFile, "utf8");
|
|
161
|
+
const pid = Number(content.trim());
|
|
162
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error.code === "ENOENT") {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async writePid(pid) {
|
|
171
|
+
await mkdir2(dirname2(this.paths.pidFile), { recursive: true });
|
|
172
|
+
await writeFile2(this.paths.pidFile, `${pid}
|
|
173
|
+
`);
|
|
174
|
+
}
|
|
175
|
+
async clearRuntimeFiles() {
|
|
176
|
+
await rm2(this.paths.pidFile, { force: true });
|
|
177
|
+
await this.statusStore.clear();
|
|
178
|
+
}
|
|
179
|
+
async waitForStartupMetadata(pid) {
|
|
180
|
+
const deadline = Date.now() + this.startupTimeoutMs;
|
|
181
|
+
while (Date.now() < deadline) {
|
|
182
|
+
const status = await this.statusStore.load();
|
|
183
|
+
if (status?.pid === pid) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
187
|
+
await this.clearRuntimeFiles();
|
|
188
|
+
throw new Error(`weacpx daemon exited before reporting ready state (pid ${pid})`);
|
|
189
|
+
}
|
|
190
|
+
await this.onStartupPoll();
|
|
191
|
+
}
|
|
192
|
+
throw new Error(`weacpx daemon did not report ready state within ${this.startupTimeoutMs}ms (pid ${pid})`);
|
|
193
|
+
}
|
|
194
|
+
async waitForShutdown(pid) {
|
|
195
|
+
const deadline = Date.now() + this.shutdownTimeoutMs;
|
|
196
|
+
while (Date.now() < deadline) {
|
|
197
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
await this.onShutdownPoll();
|
|
201
|
+
}
|
|
202
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
throw new Error(`weacpx daemon did not exit within ${this.shutdownTimeoutMs}ms (pid ${pid})`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
var init_daemon_controller = __esm(() => {
|
|
209
|
+
init_daemon_status();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// src/daemon/create-daemon-controller.ts
|
|
213
|
+
import { mkdir as mkdir3, open } from "node:fs/promises";
|
|
214
|
+
import { spawn } from "node:child_process";
|
|
215
|
+
function createDaemonController(paths, options) {
|
|
216
|
+
return new DaemonController(paths, {
|
|
217
|
+
isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning,
|
|
218
|
+
spawnDetached: async () => {
|
|
219
|
+
await mkdir3(paths.runtimeDir, { recursive: true });
|
|
220
|
+
const stdoutHandle = await open(paths.stdoutLog, "a");
|
|
221
|
+
const stderrHandle = await open(paths.stderrLog, "a");
|
|
222
|
+
try {
|
|
223
|
+
return await (options.spawnProcess ?? defaultSpawnProcess)(buildSpawnRequest(paths, options, stdoutHandle.fd, stderrHandle.fd));
|
|
224
|
+
} finally {
|
|
225
|
+
await stdoutHandle.close();
|
|
226
|
+
await stderrHandle.close();
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
terminateProcess: options.terminateProcess ?? defaultTerminateProcess
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function defaultIsProcessRunning(pid) {
|
|
233
|
+
try {
|
|
234
|
+
process.kill(pid, 0);
|
|
235
|
+
return true;
|
|
236
|
+
} catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function buildSpawnRequest(paths, options, stdoutFd, stderrFd) {
|
|
241
|
+
const platform = options.platform ?? process.platform;
|
|
242
|
+
if (platform === "win32") {
|
|
243
|
+
return {
|
|
244
|
+
mode: "windows-hidden",
|
|
245
|
+
command: "powershell.exe",
|
|
246
|
+
args: [
|
|
247
|
+
"-NoProfile",
|
|
248
|
+
"-NonInteractive",
|
|
249
|
+
"-EncodedCommand",
|
|
250
|
+
buildWindowsLauncherScript()
|
|
251
|
+
],
|
|
252
|
+
options: {
|
|
253
|
+
cwd: options.cwd,
|
|
254
|
+
env: {
|
|
255
|
+
...options.env,
|
|
256
|
+
WEACPX_DAEMON_COMMAND: options.processExecPath,
|
|
257
|
+
WEACPX_DAEMON_ARG0: options.cliEntryPath,
|
|
258
|
+
WEACPX_DAEMON_ARG1: "run",
|
|
259
|
+
WEACPX_DAEMON_CWD: options.cwd,
|
|
260
|
+
WEACPX_DAEMON_STDOUT: paths.stdoutLog,
|
|
261
|
+
WEACPX_DAEMON_STDERR: paths.stderrLog
|
|
262
|
+
},
|
|
263
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
264
|
+
windowsHide: true
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
mode: "direct",
|
|
270
|
+
command: options.processExecPath,
|
|
271
|
+
args: [options.cliEntryPath, "run"],
|
|
272
|
+
options: {
|
|
273
|
+
cwd: options.cwd,
|
|
274
|
+
detached: true,
|
|
275
|
+
env: options.env,
|
|
276
|
+
stdio: ["ignore", stdoutFd, stderrFd]
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function buildWindowsLauncherScript() {
|
|
281
|
+
const script = [
|
|
282
|
+
"$process = Start-Process -FilePath $env:WEACPX_DAEMON_COMMAND `",
|
|
283
|
+
" -ArgumentList @($env:WEACPX_DAEMON_ARG0, $env:WEACPX_DAEMON_ARG1) `",
|
|
284
|
+
" -WorkingDirectory $env:WEACPX_DAEMON_CWD `",
|
|
285
|
+
" -RedirectStandardOutput $env:WEACPX_DAEMON_STDOUT `",
|
|
286
|
+
" -RedirectStandardError $env:WEACPX_DAEMON_STDERR `",
|
|
287
|
+
" -WindowStyle Hidden `",
|
|
288
|
+
" -PassThru",
|
|
289
|
+
"[Console]::Out.WriteLine($process.Id)"
|
|
290
|
+
].join(`
|
|
291
|
+
`);
|
|
292
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
293
|
+
}
|
|
294
|
+
async function defaultSpawnProcess(request) {
|
|
295
|
+
if (request.mode === "windows-hidden") {
|
|
296
|
+
return await spawnWindowsHiddenProcess(request);
|
|
297
|
+
}
|
|
298
|
+
const child = spawn(request.command, request.args, request.options);
|
|
299
|
+
child.unref();
|
|
300
|
+
return child.pid ?? 0;
|
|
301
|
+
}
|
|
302
|
+
async function spawnWindowsHiddenProcess(request) {
|
|
303
|
+
return await new Promise((resolve, reject) => {
|
|
304
|
+
const child = spawn(request.command, request.args, request.options);
|
|
305
|
+
let stdout = "";
|
|
306
|
+
let settled = false;
|
|
307
|
+
child.stdout?.setEncoding("utf8");
|
|
308
|
+
child.stdout?.on("data", (chunk) => {
|
|
309
|
+
stdout += chunk;
|
|
310
|
+
if (settled) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const pid = Number.parseInt(stdout.trim(), 10);
|
|
314
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
settled = true;
|
|
318
|
+
child.stdout?.destroy();
|
|
319
|
+
child.unref();
|
|
320
|
+
resolve(pid);
|
|
321
|
+
});
|
|
322
|
+
child.on("error", (error) => {
|
|
323
|
+
if (settled) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
settled = true;
|
|
327
|
+
reject(error);
|
|
328
|
+
});
|
|
329
|
+
child.on("close", (code) => {
|
|
330
|
+
if (settled) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (code !== 0) {
|
|
334
|
+
settled = true;
|
|
335
|
+
reject(new Error(`Failed to launch hidden Windows daemon process (exit ${code ?? 1})`));
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const pid = Number.parseInt(stdout.trim(), 10);
|
|
339
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
340
|
+
settled = true;
|
|
341
|
+
reject(new Error("Failed to read daemon pid from hidden Windows launcher"));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
settled = true;
|
|
345
|
+
resolve(pid);
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
async function defaultTerminateProcess(pid) {
|
|
350
|
+
await terminateProcessTree(pid);
|
|
351
|
+
}
|
|
352
|
+
async function terminateProcessTree(pid, platform = process.platform, runCommand = defaultRunProcessCommand, killProcess = (targetPid, signal) => {
|
|
353
|
+
process.kill(targetPid, signal);
|
|
354
|
+
}, isProcessRunning = defaultIsProcessRunning) {
|
|
355
|
+
if (platform === "win32") {
|
|
356
|
+
try {
|
|
357
|
+
await runCommand("taskkill", ["/PID", String(pid), "/T", "/F"]);
|
|
358
|
+
} catch {}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const targetPid = pid > 0 ? -pid : pid;
|
|
362
|
+
try {
|
|
363
|
+
killProcess(targetPid, "SIGTERM");
|
|
364
|
+
} catch {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const deadline = Date.now() + 5000;
|
|
368
|
+
while (Date.now() < deadline) {
|
|
369
|
+
if (!isProcessRunning(targetPid)) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
killProcess(targetPid, "SIGKILL");
|
|
376
|
+
} catch {}
|
|
377
|
+
}
|
|
378
|
+
async function defaultRunProcessCommand(command, args) {
|
|
379
|
+
return await new Promise((resolve, reject) => {
|
|
380
|
+
const child = spawn(command, args, { stdio: "ignore" });
|
|
381
|
+
child.on("error", reject);
|
|
382
|
+
child.on("close", (code) => resolve(code ?? 1));
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
var init_create_daemon_controller = __esm(() => {
|
|
386
|
+
init_daemon_controller();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// src/daemon/daemon-files.ts
|
|
390
|
+
import { join } from "node:path";
|
|
391
|
+
function resolveDaemonPaths(options) {
|
|
392
|
+
const runtimeDir = options.runtimeDir ?? join(options.home, ".weacpx", "runtime");
|
|
393
|
+
return {
|
|
394
|
+
runtimeDir,
|
|
395
|
+
pidFile: join(runtimeDir, "daemon.pid"),
|
|
396
|
+
statusFile: join(runtimeDir, "status.json"),
|
|
397
|
+
stdoutLog: join(runtimeDir, "stdout.log"),
|
|
398
|
+
stderrLog: join(runtimeDir, "stderr.log"),
|
|
399
|
+
appLog: join(runtimeDir, "app.log")
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
var init_daemon_files = () => {};
|
|
403
|
+
|
|
404
|
+
// src/version.ts
|
|
405
|
+
import fs from "node:fs";
|
|
406
|
+
import path from "node:path";
|
|
407
|
+
import { fileURLToPath } from "node:url";
|
|
408
|
+
function readVersion() {
|
|
409
|
+
try {
|
|
410
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
411
|
+
const pkgPath = path.resolve(dir, "..", "..", "package.json");
|
|
412
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
413
|
+
return pkg.version ?? "unknown";
|
|
414
|
+
} catch {
|
|
415
|
+
return "unknown";
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
var init_version = () => {};
|
|
419
|
+
|
|
50
420
|
// src/weixin/monitor/consumer-lock.ts
|
|
51
421
|
import { mkdir as mkdir5, open as open2, readFile as readFile3, rm as rm4 } from "node:fs/promises";
|
|
52
422
|
import { dirname as dirname4, join as join2 } from "node:path";
|
|
@@ -128,9 +498,9 @@ function createWeixinConsumerLock(options = {}) {
|
|
|
128
498
|
}
|
|
129
499
|
};
|
|
130
500
|
}
|
|
131
|
-
async function loadLockMetadata(
|
|
501
|
+
async function loadLockMetadata(path2) {
|
|
132
502
|
try {
|
|
133
|
-
const raw = await readFile3(
|
|
503
|
+
const raw = await readFile3(path2, "utf8");
|
|
134
504
|
const parsed = JSON.parse(raw);
|
|
135
505
|
if (!parsed || typeof parsed.pid !== "number" || !parsed.mode || !parsed.configPath || !parsed.statePath) {
|
|
136
506
|
return null;
|
|
@@ -1189,15 +1559,15 @@ var require_main = __commonJS((exports, module) => {
|
|
|
1189
1559
|
|
|
1190
1560
|
// src/weixin/storage/state-dir.ts
|
|
1191
1561
|
import os from "node:os";
|
|
1192
|
-
import
|
|
1562
|
+
import path2 from "node:path";
|
|
1193
1563
|
function resolveStateDir() {
|
|
1194
|
-
return process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim() ||
|
|
1564
|
+
return process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim() || path2.join(os.homedir(), ".openclaw");
|
|
1195
1565
|
}
|
|
1196
1566
|
var init_state_dir = () => {};
|
|
1197
1567
|
|
|
1198
1568
|
// src/weixin/auth/accounts.ts
|
|
1199
|
-
import
|
|
1200
|
-
import
|
|
1569
|
+
import fs2 from "node:fs";
|
|
1570
|
+
import path3 from "node:path";
|
|
1201
1571
|
function normalizeAccountId(raw) {
|
|
1202
1572
|
return raw.trim().toLowerCase().replace(/[@.]/g, "-");
|
|
1203
1573
|
}
|
|
@@ -1211,17 +1581,17 @@ function deriveRawAccountId(normalizedId) {
|
|
|
1211
1581
|
return;
|
|
1212
1582
|
}
|
|
1213
1583
|
function resolveWeixinStateDir() {
|
|
1214
|
-
return
|
|
1584
|
+
return path3.join(resolveStateDir(), "openclaw-weixin");
|
|
1215
1585
|
}
|
|
1216
1586
|
function resolveAccountIndexPath() {
|
|
1217
|
-
return
|
|
1587
|
+
return path3.join(resolveWeixinStateDir(), "accounts.json");
|
|
1218
1588
|
}
|
|
1219
1589
|
function listIndexedWeixinAccountIds() {
|
|
1220
1590
|
const filePath = resolveAccountIndexPath();
|
|
1221
1591
|
try {
|
|
1222
|
-
if (!
|
|
1592
|
+
if (!fs2.existsSync(filePath))
|
|
1223
1593
|
return [];
|
|
1224
|
-
const raw =
|
|
1594
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
1225
1595
|
const parsed = JSON.parse(raw);
|
|
1226
1596
|
if (!Array.isArray(parsed))
|
|
1227
1597
|
return [];
|
|
@@ -1232,21 +1602,21 @@ function listIndexedWeixinAccountIds() {
|
|
|
1232
1602
|
}
|
|
1233
1603
|
function registerWeixinAccountId(accountId) {
|
|
1234
1604
|
const dir = resolveWeixinStateDir();
|
|
1235
|
-
|
|
1236
|
-
|
|
1605
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1606
|
+
fs2.writeFileSync(resolveAccountIndexPath(), JSON.stringify([accountId], null, 2), "utf-8");
|
|
1237
1607
|
}
|
|
1238
1608
|
function resolveAccountsDir() {
|
|
1239
|
-
return
|
|
1609
|
+
return path3.join(resolveWeixinStateDir(), "accounts");
|
|
1240
1610
|
}
|
|
1241
1611
|
function resolveAccountPath(accountId) {
|
|
1242
|
-
return
|
|
1612
|
+
return path3.join(resolveAccountsDir(), `${accountId}.json`);
|
|
1243
1613
|
}
|
|
1244
1614
|
function loadLegacyToken() {
|
|
1245
|
-
const legacyPath =
|
|
1615
|
+
const legacyPath = path3.join(resolveStateDir(), "credentials", "openclaw-weixin", "credentials.json");
|
|
1246
1616
|
try {
|
|
1247
|
-
if (!
|
|
1617
|
+
if (!fs2.existsSync(legacyPath))
|
|
1248
1618
|
return;
|
|
1249
|
-
const raw =
|
|
1619
|
+
const raw = fs2.readFileSync(legacyPath, "utf-8");
|
|
1250
1620
|
const parsed = JSON.parse(raw);
|
|
1251
1621
|
return typeof parsed.token === "string" ? parsed.token : undefined;
|
|
1252
1622
|
} catch {
|
|
@@ -1255,8 +1625,8 @@ function loadLegacyToken() {
|
|
|
1255
1625
|
}
|
|
1256
1626
|
function readAccountFile(filePath) {
|
|
1257
1627
|
try {
|
|
1258
|
-
if (
|
|
1259
|
-
return JSON.parse(
|
|
1628
|
+
if (fs2.existsSync(filePath)) {
|
|
1629
|
+
return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
1260
1630
|
}
|
|
1261
1631
|
} catch {}
|
|
1262
1632
|
return null;
|
|
@@ -1278,7 +1648,7 @@ function loadWeixinAccount(accountId) {
|
|
|
1278
1648
|
}
|
|
1279
1649
|
function saveWeixinAccount(accountId, update) {
|
|
1280
1650
|
const dir = resolveAccountsDir();
|
|
1281
|
-
|
|
1651
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1282
1652
|
const existing = loadWeixinAccount(accountId) ?? {};
|
|
1283
1653
|
const token = update.token?.trim() || existing.token;
|
|
1284
1654
|
const baseUrl = update.baseUrl?.trim() || existing.baseUrl;
|
|
@@ -1289,14 +1659,14 @@ function saveWeixinAccount(accountId, update) {
|
|
|
1289
1659
|
...userId ? { userId } : {}
|
|
1290
1660
|
};
|
|
1291
1661
|
const filePath = resolveAccountPath(accountId);
|
|
1292
|
-
|
|
1662
|
+
fs2.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
1293
1663
|
try {
|
|
1294
|
-
|
|
1664
|
+
fs2.chmodSync(filePath, 384);
|
|
1295
1665
|
} catch {}
|
|
1296
1666
|
}
|
|
1297
1667
|
function clearWeixinAccount(accountId) {
|
|
1298
1668
|
try {
|
|
1299
|
-
|
|
1669
|
+
fs2.unlinkSync(resolveAccountPath(accountId));
|
|
1300
1670
|
} catch {}
|
|
1301
1671
|
}
|
|
1302
1672
|
function clearAllWeixinAccounts() {
|
|
@@ -1305,21 +1675,21 @@ function clearAllWeixinAccounts() {
|
|
|
1305
1675
|
clearWeixinAccount(id);
|
|
1306
1676
|
}
|
|
1307
1677
|
try {
|
|
1308
|
-
|
|
1678
|
+
fs2.writeFileSync(resolveAccountIndexPath(), "[]", "utf-8");
|
|
1309
1679
|
} catch {}
|
|
1310
1680
|
}
|
|
1311
1681
|
function resolveConfigPath() {
|
|
1312
1682
|
const envPath = process.env.OPENCLAW_CONFIG?.trim();
|
|
1313
1683
|
if (envPath)
|
|
1314
1684
|
return envPath;
|
|
1315
|
-
return
|
|
1685
|
+
return path3.join(resolveStateDir(), "openclaw.json");
|
|
1316
1686
|
}
|
|
1317
1687
|
function loadConfigRouteTag(accountId) {
|
|
1318
1688
|
try {
|
|
1319
1689
|
const configPath = resolveConfigPath();
|
|
1320
|
-
if (!
|
|
1690
|
+
if (!fs2.existsSync(configPath))
|
|
1321
1691
|
return;
|
|
1322
|
-
const raw =
|
|
1692
|
+
const raw = fs2.readFileSync(configPath, "utf-8");
|
|
1323
1693
|
const cfg = JSON.parse(raw);
|
|
1324
1694
|
const channels = cfg.channels;
|
|
1325
1695
|
const section = channels?.["openclaw-weixin"];
|
|
@@ -1367,9 +1737,9 @@ var init_accounts = __esm(() => {
|
|
|
1367
1737
|
});
|
|
1368
1738
|
|
|
1369
1739
|
// src/weixin/util/logger.ts
|
|
1370
|
-
import
|
|
1740
|
+
import fs3 from "node:fs";
|
|
1371
1741
|
import os2 from "node:os";
|
|
1372
|
-
import
|
|
1742
|
+
import path4 from "node:path";
|
|
1373
1743
|
function resolveMinLevel() {
|
|
1374
1744
|
const env = process.env.OPENCLAW_LOG_LEVEL?.toUpperCase();
|
|
1375
1745
|
if (env && env in LEVEL_IDS)
|
|
@@ -1388,7 +1758,7 @@ function localDateKey(now) {
|
|
|
1388
1758
|
}
|
|
1389
1759
|
function resolveMainLogPath() {
|
|
1390
1760
|
const dateKey = localDateKey(new Date);
|
|
1391
|
-
return
|
|
1761
|
+
return path4.join(MAIN_LOG_DIR, `openclaw-${dateKey}.log`);
|
|
1392
1762
|
}
|
|
1393
1763
|
function buildLoggerName(accountId) {
|
|
1394
1764
|
return accountId ? `${SUBSYSTEM}/${accountId}` : SUBSYSTEM;
|
|
@@ -1417,10 +1787,10 @@ function writeLog(level, message, accountId) {
|
|
|
1417
1787
|
});
|
|
1418
1788
|
try {
|
|
1419
1789
|
if (!logDirEnsured) {
|
|
1420
|
-
|
|
1790
|
+
fs3.mkdirSync(MAIN_LOG_DIR, { recursive: true });
|
|
1421
1791
|
logDirEnsured = true;
|
|
1422
1792
|
}
|
|
1423
|
-
|
|
1793
|
+
fs3.appendFileSync(resolveMainLogPath(), `${entry}
|
|
1424
1794
|
`, "utf-8");
|
|
1425
1795
|
} catch {}
|
|
1426
1796
|
}
|
|
@@ -1449,7 +1819,7 @@ function createLogger(accountId) {
|
|
|
1449
1819
|
}
|
|
1450
1820
|
var MAIN_LOG_DIR, SUBSYSTEM = "gateway/channels/openclaw-weixin", RUNTIME = "node", RUNTIME_VERSION, HOSTNAME, PARENT_NAMES, LEVEL_IDS, DEFAULT_LOG_LEVEL = "INFO", minLevelId, logDirEnsured = false, logger;
|
|
1451
1821
|
var init_logger = __esm(() => {
|
|
1452
|
-
MAIN_LOG_DIR =
|
|
1822
|
+
MAIN_LOG_DIR = path4.join("/tmp", "openclaw");
|
|
1453
1823
|
RUNTIME_VERSION = process.versions.node;
|
|
1454
1824
|
HOSTNAME = os2.hostname() || "unknown";
|
|
1455
1825
|
PARENT_NAMES = ["openclaw"];
|
|
@@ -1500,19 +1870,6 @@ var DEFAULT_BODY_MAX_LEN = 200, DEFAULT_TOKEN_PREFIX_LEN = 6;
|
|
|
1500
1870
|
|
|
1501
1871
|
// src/weixin/api/api.ts
|
|
1502
1872
|
import crypto from "node:crypto";
|
|
1503
|
-
import fs3 from "node:fs";
|
|
1504
|
-
import path4 from "node:path";
|
|
1505
|
-
import { fileURLToPath } from "node:url";
|
|
1506
|
-
function readChannelVersion() {
|
|
1507
|
-
try {
|
|
1508
|
-
const dir = path4.dirname(fileURLToPath(import.meta.url));
|
|
1509
|
-
const pkgPath = path4.resolve(dir, "..", "..", "package.json");
|
|
1510
|
-
const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
1511
|
-
return pkg.version ?? "unknown";
|
|
1512
|
-
} catch {
|
|
1513
|
-
return "unknown";
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
1873
|
function buildBaseInfo() {
|
|
1517
1874
|
return { channel_version: CHANNEL_VERSION };
|
|
1518
1875
|
}
|
|
@@ -1688,9 +2045,10 @@ async function sendTyping(params) {
|
|
|
1688
2045
|
}
|
|
1689
2046
|
var CHANNEL_VERSION, DEFAULT_LONG_POLL_TIMEOUT_MS = 35000, DEFAULT_API_TIMEOUT_MS = 15000, DEFAULT_CONFIG_TIMEOUT_MS = 1e4;
|
|
1690
2047
|
var init_api = __esm(() => {
|
|
2048
|
+
init_version();
|
|
1691
2049
|
init_accounts();
|
|
1692
2050
|
init_logger();
|
|
1693
|
-
CHANNEL_VERSION =
|
|
2051
|
+
CHANNEL_VERSION = readVersion();
|
|
1694
2052
|
});
|
|
1695
2053
|
|
|
1696
2054
|
// src/weixin/auth/login-qr.ts
|
|
@@ -2637,7 +2995,7 @@ function markdownToPlainText(text) {
|
|
|
2637
2995
|
result = result.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
|
|
2638
2996
|
result = result.replace(/^\|[\s:|-]+\|$/gm, "");
|
|
2639
2997
|
result = result.replace(/^\|(.+)\|$/gm, (_, inner) => inner.split("|").map((cell) => cell.trim()).join(" "));
|
|
2640
|
-
result = result.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(
|
|
2998
|
+
result = result.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`(.+?)`/g, "$1");
|
|
2641
2999
|
return result;
|
|
2642
3000
|
}
|
|
2643
3001
|
function buildTextMessageReq(params) {
|
|
@@ -3253,6 +3611,9 @@ async function monitorWeixinProvider(opts) {
|
|
|
3253
3611
|
log(`[weixin] no previous sync buf, starting fresh`);
|
|
3254
3612
|
}
|
|
3255
3613
|
const configManager = new WeixinConfigManager({ baseUrl, token }, log);
|
|
3614
|
+
const seenMessageIds = new Set;
|
|
3615
|
+
const messageIdOrder = [];
|
|
3616
|
+
const DEDUP_WINDOW = 100;
|
|
3256
3617
|
let nextTimeoutMs = longPollTimeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS2;
|
|
3257
3618
|
let consecutiveFailures = 0;
|
|
3258
3619
|
while (!abortSignal?.aborted) {
|
|
@@ -3296,6 +3657,18 @@ async function monitorWeixinProvider(opts) {
|
|
|
3296
3657
|
}
|
|
3297
3658
|
const list = resp.msgs ?? [];
|
|
3298
3659
|
for (const full of list) {
|
|
3660
|
+
const msgId = full.message_id;
|
|
3661
|
+
if (msgId != null) {
|
|
3662
|
+
if (seenMessageIds.has(msgId)) {
|
|
3663
|
+
aLog.info(`duplicate message skipped: message_id=${msgId}`);
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
seenMessageIds.add(msgId);
|
|
3667
|
+
messageIdOrder.push(msgId);
|
|
3668
|
+
if (messageIdOrder.length > DEDUP_WINDOW) {
|
|
3669
|
+
seenMessageIds.delete(messageIdOrder.shift());
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3299
3672
|
aLog.info(`inbound: from=${full.from_user_id} types=${full.item_list?.map((i) => i.type).join(",") ?? "none"}`);
|
|
3300
3673
|
const fromUserId = full.from_user_id ?? "";
|
|
3301
3674
|
const cachedConfig = await configManager.getForUser(fromUserId, full.context_token);
|
|
@@ -4485,23 +4858,23 @@ async function handleCancel(context, chatKey) {
|
|
|
4485
4858
|
async function handleSessionReset(context, chatKey) {
|
|
4486
4859
|
return await context.lifecycle.resetCurrentSession(chatKey);
|
|
4487
4860
|
}
|
|
4861
|
+
async function promptWithSession(context, session, text, reply) {
|
|
4862
|
+
const effectiveReplyMode = session.replyMode ?? context.config?.wechat.replyMode ?? "stream";
|
|
4863
|
+
const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
|
|
4864
|
+
const result = await context.interaction.promptTransportSession(session, text, transportReply);
|
|
4865
|
+
return { text: transportReply ? undefined : result.text };
|
|
4866
|
+
}
|
|
4488
4867
|
async function handlePrompt(context, chatKey, text, reply) {
|
|
4489
4868
|
const session = await context.sessions.getCurrentSession(chatKey);
|
|
4490
4869
|
if (!session) {
|
|
4491
4870
|
return { text: NO_CURRENT_SESSION_TEXT };
|
|
4492
4871
|
}
|
|
4493
4872
|
try {
|
|
4494
|
-
|
|
4495
|
-
const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
|
|
4496
|
-
const result = await context.interaction.promptTransportSession(session, text, transportReply);
|
|
4497
|
-
return { text: result.text };
|
|
4873
|
+
return await promptWithSession(context, session, text, reply);
|
|
4498
4874
|
} catch (error) {
|
|
4499
4875
|
const recovered = await context.recovery.tryRecoverMissingSession(session, error);
|
|
4500
4876
|
if (recovered) {
|
|
4501
|
-
|
|
4502
|
-
const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
|
|
4503
|
-
const result = await context.interaction.promptTransportSession(recovered, text, transportReply);
|
|
4504
|
-
return { text: result.text };
|
|
4877
|
+
return await promptWithSession(context, recovered, text, reply);
|
|
4505
4878
|
}
|
|
4506
4879
|
return context.recovery.renderTransportError(session, error);
|
|
4507
4880
|
}
|
|
@@ -4892,7 +5265,7 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent, target
|
|
|
4892
5265
|
`)
|
|
4893
5266
|
};
|
|
4894
5267
|
}
|
|
4895
|
-
const session = ops.resolveSession(alias, agent, workspace.name,
|
|
5268
|
+
const session = ops.resolveSession(alias, agent, workspace.name, alias);
|
|
4896
5269
|
try {
|
|
4897
5270
|
await ops.ensureTransportSession(session);
|
|
4898
5271
|
const exists = await ops.checkTransportSession(session);
|
|
@@ -5635,11 +6008,18 @@ var init_ensure_config = __esm(() => {
|
|
|
5635
6008
|
|
|
5636
6009
|
// src/config/resolve-acpx-command.ts
|
|
5637
6010
|
import { readFileSync } from "node:fs";
|
|
5638
|
-
import { posix, win32 } from "node:path";
|
|
5639
6011
|
import { createRequire as createRequire2 } from "node:module";
|
|
6012
|
+
import { posix, win32 } from "node:path";
|
|
5640
6013
|
function resolveAcpxCommand(options = {}) {
|
|
6014
|
+
return resolveAcpxCommandMetadata(options).command;
|
|
6015
|
+
}
|
|
6016
|
+
function resolveAcpxCommandMetadata(options = {}) {
|
|
5641
6017
|
if (options.configuredCommand) {
|
|
5642
|
-
return
|
|
6018
|
+
return {
|
|
6019
|
+
command: options.configuredCommand,
|
|
6020
|
+
source: "config",
|
|
6021
|
+
explanation: "transport.command is set, so the configured command wins."
|
|
6022
|
+
};
|
|
5643
6023
|
}
|
|
5644
6024
|
const platform = options.platform ?? process.platform;
|
|
5645
6025
|
const resolvePackageJson = options.resolvePackageJson ?? ((id) => require2.resolve(id));
|
|
@@ -5651,10 +6031,18 @@ function resolveAcpxCommand(options = {}) {
|
|
|
5651
6031
|
const packageDir = pathApi.dirname(packageJsonPath);
|
|
5652
6032
|
const binPath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && typeof pkg.bin.acpx === "string" ? pkg.bin.acpx : null;
|
|
5653
6033
|
if (binPath) {
|
|
5654
|
-
return
|
|
6034
|
+
return {
|
|
6035
|
+
command: pathApi.resolve(packageDir, binPath),
|
|
6036
|
+
source: "bundled",
|
|
6037
|
+
explanation: "transport.command is unset, so the bundled acpx dependency is used."
|
|
6038
|
+
};
|
|
5655
6039
|
}
|
|
5656
6040
|
} catch {}
|
|
5657
|
-
return
|
|
6041
|
+
return {
|
|
6042
|
+
command: "acpx",
|
|
6043
|
+
source: "PATH",
|
|
6044
|
+
explanation: "transport.command is unset and no bundled acpx was found, so PATH is the fallback."
|
|
6045
|
+
};
|
|
5658
6046
|
}
|
|
5659
6047
|
var require2;
|
|
5660
6048
|
var init_resolve_acpx_command = __esm(() => {
|
|
@@ -6634,6 +7022,7 @@ var init_acpx_cli_transport = __esm(() => {
|
|
|
6634
7022
|
var exports_main = {};
|
|
6635
7023
|
__export(exports_main, {
|
|
6636
7024
|
resolveRuntimePaths: () => resolveRuntimePaths,
|
|
7025
|
+
resolveBridgeEntryPath: () => resolveBridgeEntryPath,
|
|
6637
7026
|
main: () => main2,
|
|
6638
7027
|
buildApp: () => buildApp
|
|
6639
7028
|
});
|
|
@@ -6737,362 +7126,974 @@ var init_main = __esm(async () => {
|
|
|
6737
7126
|
if (false) {}
|
|
6738
7127
|
});
|
|
6739
7128
|
|
|
6740
|
-
// src/
|
|
6741
|
-
import {
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
7129
|
+
// src/doctor/checks/acpx-check.ts
|
|
7130
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
7131
|
+
async function checkAcpx(options = {}) {
|
|
7132
|
+
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
7133
|
+
try {
|
|
7134
|
+
const config = await (options.loadConfig ?? loadConfig)(runtimePaths.configPath);
|
|
7135
|
+
const metadata = (options.resolveAcpxCommandMetadata ?? resolveAcpxCommandMetadata)({
|
|
7136
|
+
configuredCommand: config.transport.command
|
|
7137
|
+
});
|
|
7138
|
+
const version = await (options.runVersion ?? defaultRunVersion)(metadata.command);
|
|
7139
|
+
return {
|
|
7140
|
+
id: "acpx",
|
|
7141
|
+
label: "acpx",
|
|
7142
|
+
severity: "pass",
|
|
7143
|
+
summary: `resolved ${metadata.command} (${version})`,
|
|
7144
|
+
details: buildDetails(metadata, version, options.verbose),
|
|
7145
|
+
metadata: {
|
|
7146
|
+
command: metadata.command,
|
|
7147
|
+
source: metadata.source,
|
|
7148
|
+
version
|
|
7149
|
+
}
|
|
7150
|
+
};
|
|
7151
|
+
} catch (error) {
|
|
7152
|
+
const message = formatError(error);
|
|
7153
|
+
const details = [`config path: ${runtimePaths.configPath}`, `error: ${message}`];
|
|
7154
|
+
return {
|
|
7155
|
+
id: "acpx",
|
|
7156
|
+
label: "acpx",
|
|
7157
|
+
severity: "fail",
|
|
7158
|
+
summary: "acpx version check failed",
|
|
7159
|
+
details
|
|
7160
|
+
};
|
|
7161
|
+
}
|
|
7162
|
+
}
|
|
7163
|
+
function buildDetails(metadata, version, verbose) {
|
|
7164
|
+
const details = [
|
|
7165
|
+
`command: ${metadata.command}`,
|
|
7166
|
+
`source: ${metadata.source}`,
|
|
7167
|
+
`version: ${version}`
|
|
7168
|
+
];
|
|
7169
|
+
if (verbose) {
|
|
7170
|
+
details.push(`resolution: ${metadata.explanation}`);
|
|
7171
|
+
}
|
|
7172
|
+
return details;
|
|
7173
|
+
}
|
|
7174
|
+
async function defaultRunVersion(command) {
|
|
7175
|
+
const spawnSpec = resolveSpawnCommand(command, ["--version"]);
|
|
7176
|
+
return await new Promise((resolve2, reject) => {
|
|
7177
|
+
const child = spawn4(spawnSpec.command, spawnSpec.args, {
|
|
7178
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
7179
|
+
});
|
|
7180
|
+
let stdout = "";
|
|
7181
|
+
let stderr = "";
|
|
7182
|
+
child.stdout.on("data", (chunk) => {
|
|
7183
|
+
stdout += String(chunk);
|
|
7184
|
+
});
|
|
7185
|
+
child.stderr.on("data", (chunk) => {
|
|
7186
|
+
stderr += String(chunk);
|
|
7187
|
+
});
|
|
7188
|
+
child.on("error", reject);
|
|
7189
|
+
child.on("close", (code) => {
|
|
7190
|
+
if (code === 0) {
|
|
7191
|
+
const version = stdout.trim() || stderr.trim();
|
|
7192
|
+
if (version.length > 0) {
|
|
7193
|
+
resolve2(version);
|
|
7194
|
+
return;
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
7197
|
+
reject(new Error(stderr.trim() || stdout.trim() || `acpx --version exited with code ${code ?? 1}`));
|
|
7198
|
+
});
|
|
7199
|
+
});
|
|
7200
|
+
}
|
|
7201
|
+
function formatError(error) {
|
|
7202
|
+
return error instanceof Error ? error.message : String(error);
|
|
7203
|
+
}
|
|
7204
|
+
var init_acpx_check = __esm(async () => {
|
|
7205
|
+
init_load_config();
|
|
7206
|
+
init_resolve_acpx_command();
|
|
7207
|
+
init_spawn_command();
|
|
7208
|
+
await init_main();
|
|
7209
|
+
});
|
|
6752
7210
|
|
|
6753
|
-
// src/
|
|
6754
|
-
|
|
6755
|
-
|
|
7211
|
+
// src/doctor/checks/bridge-check.ts
|
|
7212
|
+
async function checkBridge(options = {}) {
|
|
7213
|
+
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
7214
|
+
try {
|
|
7215
|
+
const config = await (options.loadConfig ?? loadConfig)(runtimePaths.configPath);
|
|
7216
|
+
if (config.transport.type === "acpx-cli") {
|
|
7217
|
+
return {
|
|
7218
|
+
id: "bridge",
|
|
7219
|
+
label: "Bridge",
|
|
7220
|
+
severity: "skip",
|
|
7221
|
+
summary: "bridge check skipped for acpx-cli transport"
|
|
7222
|
+
};
|
|
7223
|
+
}
|
|
7224
|
+
const metadata = (options.resolveAcpxCommandMetadata ?? resolveAcpxCommandMetadata)({
|
|
7225
|
+
configuredCommand: config.transport.command
|
|
7226
|
+
});
|
|
7227
|
+
const client = await (options.spawnAcpxBridgeClient ?? spawnAcpxBridgeClient)({
|
|
7228
|
+
acpxCommand: metadata.command,
|
|
7229
|
+
bridgeEntryPath: (options.resolveBridgeEntryPath ?? resolveBridgeEntryPath)(),
|
|
7230
|
+
cwd: options.cwd ?? process.cwd(),
|
|
7231
|
+
permissionMode: config.transport.permissionMode,
|
|
7232
|
+
nonInteractivePermissions: config.transport.nonInteractivePermissions
|
|
7233
|
+
});
|
|
7234
|
+
try {
|
|
7235
|
+
return {
|
|
7236
|
+
id: "bridge",
|
|
7237
|
+
label: "Bridge",
|
|
7238
|
+
severity: "pass",
|
|
7239
|
+
summary: "bridge responded to ping",
|
|
7240
|
+
details: buildDetails2(metadata, options.verbose),
|
|
7241
|
+
metadata: {
|
|
7242
|
+
acpxCommand: metadata.command,
|
|
7243
|
+
source: metadata.source
|
|
7244
|
+
}
|
|
7245
|
+
};
|
|
7246
|
+
} finally {
|
|
7247
|
+
await client.dispose();
|
|
7248
|
+
}
|
|
7249
|
+
} catch (error) {
|
|
7250
|
+
return {
|
|
7251
|
+
id: "bridge",
|
|
7252
|
+
label: "Bridge",
|
|
7253
|
+
severity: "fail",
|
|
7254
|
+
summary: "bridge startup failed",
|
|
7255
|
+
details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError2(error)}`]
|
|
7256
|
+
};
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
function buildDetails2(metadata, verbose) {
|
|
7260
|
+
const details = [`acpx command: ${metadata.command}`, `source: ${metadata.source}`];
|
|
7261
|
+
if (verbose) {
|
|
7262
|
+
details.push(`resolution: ${metadata.explanation}`);
|
|
7263
|
+
}
|
|
7264
|
+
return details;
|
|
7265
|
+
}
|
|
7266
|
+
function formatError2(error) {
|
|
7267
|
+
return error instanceof Error ? error.message : String(error);
|
|
7268
|
+
}
|
|
7269
|
+
var init_bridge_check = __esm(async () => {
|
|
7270
|
+
init_load_config();
|
|
7271
|
+
init_resolve_acpx_command();
|
|
7272
|
+
init_acpx_bridge_client();
|
|
7273
|
+
await init_main();
|
|
7274
|
+
});
|
|
6756
7275
|
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
7276
|
+
// src/doctor/checks/config-check.ts
|
|
7277
|
+
async function checkConfig(options = {}) {
|
|
7278
|
+
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
7279
|
+
const configPath = runtimePaths.configPath;
|
|
7280
|
+
try {
|
|
7281
|
+
const config = await (options.loadConfig ?? loadConfig)(configPath);
|
|
7282
|
+
return {
|
|
7283
|
+
id: "config",
|
|
7284
|
+
label: "Config",
|
|
7285
|
+
severity: "pass",
|
|
7286
|
+
summary: "configuration loaded",
|
|
7287
|
+
details: [`config path: ${configPath}`],
|
|
7288
|
+
metadata: {
|
|
7289
|
+
configPath,
|
|
7290
|
+
config
|
|
7291
|
+
}
|
|
7292
|
+
};
|
|
7293
|
+
} catch (error) {
|
|
7294
|
+
return {
|
|
7295
|
+
id: "config",
|
|
7296
|
+
label: "Config",
|
|
7297
|
+
severity: "fail",
|
|
7298
|
+
summary: "configuration is invalid",
|
|
7299
|
+
details: [`config path: ${configPath}`, `error: ${formatError3(error)}`]
|
|
7300
|
+
};
|
|
6761
7301
|
}
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
7302
|
+
}
|
|
7303
|
+
function formatError3(error) {
|
|
7304
|
+
if (error instanceof Error) {
|
|
7305
|
+
return error.message;
|
|
7306
|
+
}
|
|
7307
|
+
return String(error);
|
|
7308
|
+
}
|
|
7309
|
+
var init_config_check = __esm(async () => {
|
|
7310
|
+
init_load_config();
|
|
7311
|
+
await init_main();
|
|
7312
|
+
});
|
|
7313
|
+
|
|
7314
|
+
// src/doctor/checks/daemon-check.ts
|
|
7315
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7316
|
+
import { homedir as homedir6 } from "node:os";
|
|
7317
|
+
async function checkDaemon(options = {}) {
|
|
7318
|
+
const home = options.home ?? process.env.HOME ?? homedir6();
|
|
7319
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({ home });
|
|
7320
|
+
const controller = createDaemonController(paths, {
|
|
7321
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
7322
|
+
cliEntryPath: options.cliEntryPath ?? resolveCliEntryPath(),
|
|
7323
|
+
cwd: options.cwd ?? process.cwd(),
|
|
7324
|
+
env: options.env ?? process.env,
|
|
7325
|
+
isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning3
|
|
7326
|
+
});
|
|
7327
|
+
try {
|
|
7328
|
+
const status = await controller.getStatus();
|
|
7329
|
+
switch (status.state) {
|
|
7330
|
+
case "running":
|
|
7331
|
+
return {
|
|
7332
|
+
id: "daemon",
|
|
7333
|
+
label: "Daemon",
|
|
7334
|
+
severity: "pass",
|
|
7335
|
+
summary: "daemon is running",
|
|
7336
|
+
details: [`pid: ${status.pid}`],
|
|
7337
|
+
metadata: {
|
|
7338
|
+
paths,
|
|
7339
|
+
status
|
|
7340
|
+
}
|
|
7341
|
+
};
|
|
7342
|
+
case "stopped":
|
|
7343
|
+
return {
|
|
7344
|
+
id: "daemon",
|
|
7345
|
+
label: "Daemon",
|
|
7346
|
+
severity: "warn",
|
|
7347
|
+
summary: status.stale ? "daemon was stopped and stale runtime files were cleared" : "daemon is not running",
|
|
7348
|
+
details: status.stale ? ["stale runtime files were cleared"] : undefined,
|
|
7349
|
+
suggestions: ["run: weacpx start"],
|
|
7350
|
+
metadata: {
|
|
7351
|
+
paths,
|
|
7352
|
+
status
|
|
7353
|
+
}
|
|
7354
|
+
};
|
|
7355
|
+
case "indeterminate":
|
|
7356
|
+
return {
|
|
7357
|
+
id: "daemon",
|
|
7358
|
+
label: "Daemon",
|
|
7359
|
+
severity: "fail",
|
|
7360
|
+
summary: "daemon status is indeterminate",
|
|
7361
|
+
details: [`pid: ${status.pid}`, `reason: ${status.reason}`],
|
|
7362
|
+
metadata: {
|
|
7363
|
+
paths,
|
|
7364
|
+
status
|
|
7365
|
+
}
|
|
7366
|
+
};
|
|
7367
|
+
}
|
|
7368
|
+
return {
|
|
7369
|
+
id: "daemon",
|
|
7370
|
+
label: "Daemon",
|
|
7371
|
+
severity: "fail",
|
|
7372
|
+
summary: "daemon status lookup returned an unknown state",
|
|
7373
|
+
details: [`state: ${status.state ?? "unknown"}`],
|
|
7374
|
+
metadata: {
|
|
7375
|
+
paths
|
|
6767
7376
|
}
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
7377
|
+
};
|
|
7378
|
+
} catch (error) {
|
|
7379
|
+
return {
|
|
7380
|
+
id: "daemon",
|
|
7381
|
+
label: "Daemon",
|
|
7382
|
+
severity: "fail",
|
|
7383
|
+
summary: "daemon status could not be read",
|
|
7384
|
+
details: [
|
|
7385
|
+
`runtime dir: ${paths.runtimeDir}`,
|
|
7386
|
+
`pid file: ${paths.pidFile}`,
|
|
7387
|
+
`status file: ${paths.statusFile}`,
|
|
7388
|
+
`error: ${formatError4(error)}`
|
|
7389
|
+
],
|
|
7390
|
+
metadata: {
|
|
7391
|
+
paths
|
|
6772
7392
|
}
|
|
6773
|
-
|
|
6774
|
-
}
|
|
6775
|
-
}
|
|
6776
|
-
async save(status) {
|
|
6777
|
-
await mkdir(dirname(this.path), { recursive: true });
|
|
6778
|
-
await writeFile(this.path, JSON.stringify(status, null, 2));
|
|
7393
|
+
};
|
|
6779
7394
|
}
|
|
6780
|
-
|
|
6781
|
-
|
|
7395
|
+
}
|
|
7396
|
+
function defaultIsProcessRunning3(pid) {
|
|
7397
|
+
try {
|
|
7398
|
+
process.kill(pid, 0);
|
|
7399
|
+
return true;
|
|
7400
|
+
} catch {
|
|
7401
|
+
return false;
|
|
6782
7402
|
}
|
|
6783
7403
|
}
|
|
7404
|
+
function resolveCliEntryPath() {
|
|
7405
|
+
return process.argv[1] ?? fileURLToPath4(import.meta.url);
|
|
7406
|
+
}
|
|
7407
|
+
function formatError4(error) {
|
|
7408
|
+
return error instanceof Error ? error.message : String(error);
|
|
7409
|
+
}
|
|
7410
|
+
var init_daemon_check = __esm(() => {
|
|
7411
|
+
init_create_daemon_controller();
|
|
7412
|
+
init_daemon_files();
|
|
7413
|
+
});
|
|
6784
7414
|
|
|
6785
|
-
// src/
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
7415
|
+
// src/doctor/checks/runtime-check.ts
|
|
7416
|
+
import { constants } from "node:fs";
|
|
7417
|
+
import { access as access3, stat as stat2 } from "node:fs/promises";
|
|
7418
|
+
import { dirname as dirname10 } from "node:path";
|
|
7419
|
+
import { homedir as homedir7 } from "node:os";
|
|
7420
|
+
async function checkRuntime(options = {}) {
|
|
7421
|
+
const home = options.home ?? process.env.HOME ?? homedir7();
|
|
7422
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({ home });
|
|
7423
|
+
const probe = options.probe ?? createRuntimeFsProbe();
|
|
7424
|
+
const platform = options.platform ?? process.platform;
|
|
7425
|
+
const checks = [
|
|
7426
|
+
await checkDirectoryCreatable("runtimeDir", paths.runtimeDir, probe, platform),
|
|
7427
|
+
await checkFileCreatable("pidFile", paths.pidFile, probe, platform),
|
|
7428
|
+
await checkFileCreatable("statusFile", paths.statusFile, probe, platform),
|
|
7429
|
+
await checkFileCreatable("stdoutLog", paths.stdoutLog, probe, platform),
|
|
7430
|
+
await checkFileCreatable("stderrLog", paths.stderrLog, probe, platform),
|
|
7431
|
+
await checkFileCreatable("appLog", paths.appLog, probe, platform)
|
|
7432
|
+
];
|
|
7433
|
+
const failure = checks.find((check) => !check.ok);
|
|
7434
|
+
if (failure) {
|
|
7435
|
+
return {
|
|
7436
|
+
id: "runtime",
|
|
7437
|
+
label: "Runtime",
|
|
7438
|
+
severity: "fail",
|
|
7439
|
+
summary: "daemon runtime paths are not usable",
|
|
7440
|
+
details: checks.map((check) => check.detail)
|
|
7441
|
+
};
|
|
6810
7442
|
}
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
return { state: "stopped", stale: true };
|
|
7443
|
+
return {
|
|
7444
|
+
id: "runtime",
|
|
7445
|
+
label: "Runtime",
|
|
7446
|
+
severity: "pass",
|
|
7447
|
+
summary: "daemon runtime paths are usable",
|
|
7448
|
+
details: checks.map((check) => check.detail),
|
|
7449
|
+
metadata: {
|
|
7450
|
+
paths
|
|
6820
7451
|
}
|
|
6821
|
-
|
|
6822
|
-
|
|
7452
|
+
};
|
|
7453
|
+
}
|
|
7454
|
+
function createRuntimeFsProbe() {
|
|
7455
|
+
return {
|
|
7456
|
+
stat: async (path11) => await stat2(path11),
|
|
7457
|
+
access: async (path11, mode) => await access3(path11, mode)
|
|
7458
|
+
};
|
|
7459
|
+
}
|
|
7460
|
+
async function checkDirectoryCreatable(label, path11, probe, platform) {
|
|
7461
|
+
try {
|
|
7462
|
+
const stats = await probe.stat(path11);
|
|
7463
|
+
if (!stats.isDirectory()) {
|
|
7464
|
+
return {
|
|
7465
|
+
ok: false,
|
|
7466
|
+
detail: `${label}: ${path11} (exists but is not a directory)`
|
|
7467
|
+
};
|
|
6823
7468
|
}
|
|
7469
|
+
await probe.access(path11, directoryAccessMode(platform));
|
|
6824
7470
|
return {
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
status
|
|
7471
|
+
ok: true,
|
|
7472
|
+
detail: `${label}: ${path11} (writable)`
|
|
6828
7473
|
};
|
|
6829
|
-
}
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
7474
|
+
} catch (error) {
|
|
7475
|
+
if (!isMissingPathError(error)) {
|
|
7476
|
+
return {
|
|
7477
|
+
ok: false,
|
|
7478
|
+
detail: `${label}: ${path11} (unusable: ${formatError5(error)})`
|
|
7479
|
+
};
|
|
6834
7480
|
}
|
|
6835
|
-
|
|
6836
|
-
|
|
7481
|
+
const parentCheck = await checkCreatableAncestorDirectory(path11, probe, platform);
|
|
7482
|
+
if (!parentCheck.ok) {
|
|
7483
|
+
return {
|
|
7484
|
+
ok: false,
|
|
7485
|
+
detail: `${label}: ${path11} (parent not writable: ${parentCheck.blockingPath})`
|
|
7486
|
+
};
|
|
6837
7487
|
}
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
return { state: "started", pid };
|
|
7488
|
+
return {
|
|
7489
|
+
ok: true,
|
|
7490
|
+
detail: `${label}: ${path11} (creatable via ${parentCheck.creatableFrom})`
|
|
7491
|
+
};
|
|
6843
7492
|
}
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
7493
|
+
}
|
|
7494
|
+
async function checkFileCreatable(label, path11, probe, platform) {
|
|
7495
|
+
try {
|
|
7496
|
+
const stats = await probe.stat(path11);
|
|
7497
|
+
if (stats.isDirectory()) {
|
|
7498
|
+
return {
|
|
7499
|
+
ok: false,
|
|
7500
|
+
detail: `${label}: ${path11} (exists but is a directory)`
|
|
7501
|
+
};
|
|
6848
7502
|
}
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
7503
|
+
await probe.access(path11, constants.W_OK);
|
|
7504
|
+
return {
|
|
7505
|
+
ok: true,
|
|
7506
|
+
detail: `${label}: ${path11} (writable)`
|
|
7507
|
+
};
|
|
7508
|
+
} catch (error) {
|
|
7509
|
+
if (!isMissingPathError(error)) {
|
|
7510
|
+
return {
|
|
7511
|
+
ok: false,
|
|
7512
|
+
detail: `${label}: ${path11} (unusable: ${formatError5(error)})`
|
|
7513
|
+
};
|
|
6852
7514
|
}
|
|
6853
|
-
await
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
const pid = Number(content.trim());
|
|
6860
|
-
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
6861
|
-
} catch (error) {
|
|
6862
|
-
if (error.code === "ENOENT") {
|
|
6863
|
-
return null;
|
|
6864
|
-
}
|
|
6865
|
-
throw error;
|
|
7515
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname10(path11), probe, platform);
|
|
7516
|
+
if (!parentCheck.ok) {
|
|
7517
|
+
return {
|
|
7518
|
+
ok: false,
|
|
7519
|
+
detail: `${label}: ${path11} (parent not writable: ${parentCheck.blockingPath})`
|
|
7520
|
+
};
|
|
6866
7521
|
}
|
|
7522
|
+
return {
|
|
7523
|
+
ok: true,
|
|
7524
|
+
detail: `${label}: ${path11} (creatable via ${parentCheck.creatableFrom})`
|
|
7525
|
+
};
|
|
6867
7526
|
}
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
const deadline = Date.now() + this.startupTimeoutMs;
|
|
6879
|
-
while (Date.now() < deadline) {
|
|
6880
|
-
const status = await this.statusStore.load();
|
|
6881
|
-
if (status?.pid === pid) {
|
|
6882
|
-
return;
|
|
6883
|
-
}
|
|
6884
|
-
if (!this.deps.isProcessRunning(pid)) {
|
|
6885
|
-
await this.clearRuntimeFiles();
|
|
6886
|
-
throw new Error(`weacpx daemon exited before reporting ready state (pid ${pid})`);
|
|
6887
|
-
}
|
|
6888
|
-
await this.onStartupPoll();
|
|
7527
|
+
}
|
|
7528
|
+
async function checkCreatableAncestorDirectory(path11, probe, platform) {
|
|
7529
|
+
try {
|
|
7530
|
+
const stats = await probe.stat(path11);
|
|
7531
|
+
if (!stats.isDirectory()) {
|
|
7532
|
+
return {
|
|
7533
|
+
ok: false,
|
|
7534
|
+
creatableFrom: path11,
|
|
7535
|
+
blockingPath: path11
|
|
7536
|
+
};
|
|
6889
7537
|
}
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
7538
|
+
await probe.access(path11, directoryAccessMode(platform));
|
|
7539
|
+
return {
|
|
7540
|
+
ok: true,
|
|
7541
|
+
creatableFrom: path11
|
|
7542
|
+
};
|
|
7543
|
+
} catch (error) {
|
|
7544
|
+
if (!isMissingPathError(error)) {
|
|
7545
|
+
return {
|
|
7546
|
+
ok: false,
|
|
7547
|
+
creatableFrom: path11,
|
|
7548
|
+
blockingPath: path11
|
|
7549
|
+
};
|
|
6899
7550
|
}
|
|
6900
|
-
|
|
6901
|
-
|
|
7551
|
+
const parent = dirname10(path11);
|
|
7552
|
+
if (parent === path11) {
|
|
7553
|
+
return {
|
|
7554
|
+
ok: false,
|
|
7555
|
+
creatableFrom: path11,
|
|
7556
|
+
blockingPath: path11
|
|
7557
|
+
};
|
|
6902
7558
|
}
|
|
6903
|
-
|
|
7559
|
+
const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);
|
|
7560
|
+
if (!parentCheck.ok) {
|
|
7561
|
+
return parentCheck;
|
|
7562
|
+
}
|
|
7563
|
+
return {
|
|
7564
|
+
ok: true,
|
|
7565
|
+
creatableFrom: parentCheck.creatableFrom
|
|
7566
|
+
};
|
|
6904
7567
|
}
|
|
6905
7568
|
}
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
function createDaemonController(paths, options) {
|
|
6909
|
-
return new DaemonController(paths, {
|
|
6910
|
-
isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning,
|
|
6911
|
-
spawnDetached: async () => {
|
|
6912
|
-
await mkdir3(paths.runtimeDir, { recursive: true });
|
|
6913
|
-
const stdoutHandle = await open(paths.stdoutLog, "a");
|
|
6914
|
-
const stderrHandle = await open(paths.stderrLog, "a");
|
|
6915
|
-
try {
|
|
6916
|
-
return await (options.spawnProcess ?? defaultSpawnProcess)(buildSpawnRequest(paths, options, stdoutHandle.fd, stderrHandle.fd));
|
|
6917
|
-
} finally {
|
|
6918
|
-
await stdoutHandle.close();
|
|
6919
|
-
await stderrHandle.close();
|
|
6920
|
-
}
|
|
6921
|
-
},
|
|
6922
|
-
terminateProcess: options.terminateProcess ?? defaultTerminateProcess
|
|
6923
|
-
});
|
|
7569
|
+
function directoryAccessMode(platform) {
|
|
7570
|
+
return platform === "win32" ? constants.W_OK : DIRECTORY_USABLE;
|
|
6924
7571
|
}
|
|
6925
|
-
function
|
|
7572
|
+
function isMissingPathError(error) {
|
|
7573
|
+
return isErrnoError(error) && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
7574
|
+
}
|
|
7575
|
+
function isErrnoError(error) {
|
|
7576
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
7577
|
+
}
|
|
7578
|
+
function formatError5(error) {
|
|
7579
|
+
if (error instanceof Error) {
|
|
7580
|
+
return error.message;
|
|
7581
|
+
}
|
|
7582
|
+
return String(error);
|
|
7583
|
+
}
|
|
7584
|
+
var DIRECTORY_USABLE;
|
|
7585
|
+
var init_runtime_check = __esm(() => {
|
|
7586
|
+
init_daemon_files();
|
|
7587
|
+
DIRECTORY_USABLE = constants.W_OK | constants.X_OK;
|
|
7588
|
+
});
|
|
7589
|
+
|
|
7590
|
+
// src/doctor/checks/smoke-check.ts
|
|
7591
|
+
async function checkSmoke(options = {}, deps = {}) {
|
|
7592
|
+
const resolvedOptions = { ...options, ...deps };
|
|
7593
|
+
const runtimePaths = (resolvedOptions.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
6926
7594
|
try {
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
7595
|
+
const config = await (resolvedOptions.loadConfig ?? loadConfig)(runtimePaths.configPath);
|
|
7596
|
+
const agentSelection = selectAgent(config, resolvedOptions.agent);
|
|
7597
|
+
if (agentSelection.error) {
|
|
7598
|
+
return agentSelection.error;
|
|
7599
|
+
}
|
|
7600
|
+
const workspaceSelection = selectWorkspace(config, resolvedOptions.workspace);
|
|
7601
|
+
if (workspaceSelection.error) {
|
|
7602
|
+
return workspaceSelection.error;
|
|
7603
|
+
}
|
|
7604
|
+
const missingDefaults = [agentSelection.missingDefault, workspaceSelection.missingDefault].filter((value) => typeof value === "string");
|
|
7605
|
+
if (missingDefaults.length > 0) {
|
|
7606
|
+
return {
|
|
7607
|
+
id: "smoke",
|
|
7608
|
+
label: "Smoke",
|
|
7609
|
+
severity: "skip",
|
|
7610
|
+
summary: `smoke prerequisites missing: ${missingDefaults.join(", ")}`,
|
|
7611
|
+
details: [
|
|
7612
|
+
`config path: ${runtimePaths.configPath}`,
|
|
7613
|
+
...selectionDetails(agentSelection, workspaceSelection)
|
|
7614
|
+
],
|
|
7615
|
+
suggestions: ["configure at least one agent and one workspace before running --smoke"]
|
|
7616
|
+
};
|
|
7617
|
+
}
|
|
7618
|
+
const agent = agentSelection.value;
|
|
7619
|
+
const workspace = workspaceSelection.value;
|
|
7620
|
+
if (!agent || !workspace) {
|
|
7621
|
+
return {
|
|
7622
|
+
id: "smoke",
|
|
7623
|
+
label: "Smoke",
|
|
7624
|
+
severity: "skip",
|
|
7625
|
+
summary: "smoke prerequisites missing: agent, workspace",
|
|
7626
|
+
details: [
|
|
7627
|
+
`config path: ${runtimePaths.configPath}`,
|
|
7628
|
+
...selectionDetails(agentSelection, workspaceSelection)
|
|
7629
|
+
]
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
const metadata = (resolvedOptions.resolveAcpxCommandMetadata ?? resolveAcpxCommandMetadata)({
|
|
7633
|
+
configuredCommand: config.transport.command
|
|
7634
|
+
});
|
|
7635
|
+
const transport = await (resolvedOptions.createTransport ?? defaultCreateTransport)({
|
|
7636
|
+
config,
|
|
7637
|
+
metadata,
|
|
7638
|
+
resolveBridgeEntryPath: resolvedOptions.resolveBridgeEntryPath,
|
|
7639
|
+
spawnAcpxBridgeClient: resolvedOptions.spawnAcpxBridgeClient
|
|
7640
|
+
});
|
|
7641
|
+
const session = buildSession({
|
|
7642
|
+
config,
|
|
7643
|
+
agent,
|
|
7644
|
+
workspace,
|
|
7645
|
+
now: resolvedOptions.now
|
|
7646
|
+
});
|
|
7647
|
+
try {
|
|
7648
|
+
await transport.ensureSession(session);
|
|
7649
|
+
const reply = await transport.prompt(session, SMOKE_PROMPT);
|
|
7650
|
+
const replyText = reply.text.trim();
|
|
7651
|
+
if (replyText.length === 0) {
|
|
7652
|
+
return {
|
|
7653
|
+
id: "smoke",
|
|
7654
|
+
label: "Smoke",
|
|
7655
|
+
severity: "fail",
|
|
7656
|
+
summary: "smoke prompt returned empty text",
|
|
7657
|
+
details: buildDetails3({
|
|
7658
|
+
runtimePaths,
|
|
7659
|
+
metadata,
|
|
7660
|
+
session,
|
|
7661
|
+
agentReason: agentSelection.reason,
|
|
7662
|
+
workspaceReason: workspaceSelection.reason,
|
|
7663
|
+
replyText,
|
|
7664
|
+
verbose: resolvedOptions.verbose
|
|
7665
|
+
})
|
|
7666
|
+
};
|
|
7667
|
+
}
|
|
7668
|
+
return {
|
|
7669
|
+
id: "smoke",
|
|
7670
|
+
label: "Smoke",
|
|
7671
|
+
severity: replyText === "ok" ? "pass" : "warn",
|
|
7672
|
+
summary: replyText === "ok" ? "smoke prompt succeeded and reply received" : "smoke prompt succeeded with non-ideal reply",
|
|
7673
|
+
details: buildDetails3({
|
|
7674
|
+
runtimePaths,
|
|
7675
|
+
metadata,
|
|
7676
|
+
session,
|
|
7677
|
+
agentReason: agentSelection.reason,
|
|
7678
|
+
workspaceReason: workspaceSelection.reason,
|
|
7679
|
+
replyText,
|
|
7680
|
+
verbose: resolvedOptions.verbose
|
|
7681
|
+
}),
|
|
7682
|
+
metadata: {
|
|
7683
|
+
agent: session.agent,
|
|
7684
|
+
workspace: session.workspace,
|
|
7685
|
+
transportSession: session.transportSession,
|
|
7686
|
+
replyText
|
|
7687
|
+
}
|
|
7688
|
+
};
|
|
7689
|
+
} finally {
|
|
7690
|
+
await transport.dispose?.();
|
|
7691
|
+
}
|
|
7692
|
+
} catch (error) {
|
|
7693
|
+
return {
|
|
7694
|
+
id: "smoke",
|
|
7695
|
+
label: "Smoke",
|
|
7696
|
+
severity: "fail",
|
|
7697
|
+
summary: "smoke transport probe failed",
|
|
7698
|
+
details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError6(error)}`]
|
|
7699
|
+
};
|
|
6931
7700
|
}
|
|
6932
7701
|
}
|
|
6933
|
-
function
|
|
6934
|
-
|
|
6935
|
-
|
|
7702
|
+
function selectAgent(config, explicitAgent) {
|
|
7703
|
+
if (explicitAgent) {
|
|
7704
|
+
if (!(explicitAgent in config.agents)) {
|
|
7705
|
+
return {
|
|
7706
|
+
reason: "explicit --agent",
|
|
7707
|
+
error: createSelectionFailure(`smoke agent not found: ${explicitAgent}`)
|
|
7708
|
+
};
|
|
7709
|
+
}
|
|
6936
7710
|
return {
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
cwd: options.cwd,
|
|
6947
|
-
env: {
|
|
6948
|
-
...options.env,
|
|
6949
|
-
WEACPX_DAEMON_COMMAND: options.processExecPath,
|
|
6950
|
-
WEACPX_DAEMON_ARG0: options.cliEntryPath,
|
|
6951
|
-
WEACPX_DAEMON_ARG1: "run",
|
|
6952
|
-
WEACPX_DAEMON_CWD: options.cwd,
|
|
6953
|
-
WEACPX_DAEMON_STDOUT: paths.stdoutLog,
|
|
6954
|
-
WEACPX_DAEMON_STDERR: paths.stderrLog
|
|
6955
|
-
},
|
|
6956
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
6957
|
-
windowsHide: true
|
|
6958
|
-
}
|
|
7711
|
+
value: explicitAgent,
|
|
7712
|
+
reason: "explicit --agent"
|
|
7713
|
+
};
|
|
7714
|
+
}
|
|
7715
|
+
const firstAgent = Object.keys(config.agents)[0];
|
|
7716
|
+
if (!firstAgent) {
|
|
7717
|
+
return {
|
|
7718
|
+
missingDefault: "agent",
|
|
7719
|
+
reason: "no configured agent available"
|
|
6959
7720
|
};
|
|
6960
7721
|
}
|
|
6961
7722
|
return {
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
7723
|
+
value: firstAgent,
|
|
7724
|
+
reason: "default first configured agent"
|
|
7725
|
+
};
|
|
7726
|
+
}
|
|
7727
|
+
function selectWorkspace(config, explicitWorkspace) {
|
|
7728
|
+
if (explicitWorkspace) {
|
|
7729
|
+
if (!(explicitWorkspace in config.workspaces)) {
|
|
7730
|
+
return {
|
|
7731
|
+
reason: "explicit --workspace",
|
|
7732
|
+
error: createSelectionFailure(`smoke workspace not found: ${explicitWorkspace}`)
|
|
7733
|
+
};
|
|
6970
7734
|
}
|
|
7735
|
+
return {
|
|
7736
|
+
value: explicitWorkspace,
|
|
7737
|
+
reason: "explicit --workspace"
|
|
7738
|
+
};
|
|
7739
|
+
}
|
|
7740
|
+
const firstWorkspace = Object.keys(config.workspaces)[0];
|
|
7741
|
+
if (!firstWorkspace) {
|
|
7742
|
+
return {
|
|
7743
|
+
missingDefault: "workspace",
|
|
7744
|
+
reason: "no configured workspace available"
|
|
7745
|
+
};
|
|
7746
|
+
}
|
|
7747
|
+
return {
|
|
7748
|
+
value: firstWorkspace,
|
|
7749
|
+
reason: "default first configured workspace"
|
|
6971
7750
|
};
|
|
6972
7751
|
}
|
|
6973
|
-
function
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
" -WindowStyle Hidden `",
|
|
6981
|
-
" -PassThru",
|
|
6982
|
-
"[Console]::Out.WriteLine($process.Id)"
|
|
6983
|
-
].join(`
|
|
6984
|
-
`);
|
|
6985
|
-
return Buffer.from(script, "utf16le").toString("base64");
|
|
7752
|
+
function createSelectionFailure(summary) {
|
|
7753
|
+
return {
|
|
7754
|
+
id: "smoke",
|
|
7755
|
+
label: "Smoke",
|
|
7756
|
+
severity: "fail",
|
|
7757
|
+
summary
|
|
7758
|
+
};
|
|
6986
7759
|
}
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
7760
|
+
function selectionDetails(agentSelection, workspaceSelection) {
|
|
7761
|
+
const details = [];
|
|
7762
|
+
if (agentSelection.value) {
|
|
7763
|
+
details.push(`agent: ${agentSelection.value} (${agentSelection.reason})`);
|
|
7764
|
+
} else {
|
|
7765
|
+
details.push(`agent: unavailable (${agentSelection.reason})`);
|
|
6990
7766
|
}
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
7767
|
+
if (workspaceSelection.value) {
|
|
7768
|
+
details.push(`workspace: ${workspaceSelection.value} (${workspaceSelection.reason})`);
|
|
7769
|
+
} else {
|
|
7770
|
+
details.push(`workspace: unavailable (${workspaceSelection.reason})`);
|
|
7771
|
+
}
|
|
7772
|
+
return details;
|
|
6994
7773
|
}
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
child.on("close", (code) => {
|
|
7023
|
-
if (settled) {
|
|
7024
|
-
return;
|
|
7025
|
-
}
|
|
7026
|
-
if (code !== 0) {
|
|
7027
|
-
settled = true;
|
|
7028
|
-
reject(new Error(`Failed to launch hidden Windows daemon process (exit ${code ?? 1})`));
|
|
7029
|
-
return;
|
|
7030
|
-
}
|
|
7031
|
-
const pid = Number.parseInt(stdout.trim(), 10);
|
|
7032
|
-
if (!Number.isFinite(pid) || pid <= 0) {
|
|
7033
|
-
settled = true;
|
|
7034
|
-
reject(new Error("Failed to read daemon pid from hidden Windows launcher"));
|
|
7035
|
-
return;
|
|
7036
|
-
}
|
|
7037
|
-
settled = true;
|
|
7038
|
-
resolve(pid);
|
|
7774
|
+
function buildSession(options) {
|
|
7775
|
+
const timestamp = (options.now ?? (() => new Date))().getTime();
|
|
7776
|
+
const agentConfig = options.config.agents[options.agent];
|
|
7777
|
+
const workspaceConfig = options.config.workspaces[options.workspace];
|
|
7778
|
+
if (!agentConfig) {
|
|
7779
|
+
throw new Error(`smoke agent not found: ${options.agent}`);
|
|
7780
|
+
}
|
|
7781
|
+
if (!workspaceConfig) {
|
|
7782
|
+
throw new Error(`smoke workspace not found: ${options.workspace}`);
|
|
7783
|
+
}
|
|
7784
|
+
return {
|
|
7785
|
+
alias: "weacpx-doctor",
|
|
7786
|
+
agent: options.agent,
|
|
7787
|
+
...agentConfig.command ? { agentCommand: agentConfig.command } : {},
|
|
7788
|
+
workspace: options.workspace,
|
|
7789
|
+
transportSession: `weacpx-doctor-${timestamp}`,
|
|
7790
|
+
replyMode: options.config.wechat.replyMode,
|
|
7791
|
+
cwd: workspaceConfig.cwd
|
|
7792
|
+
};
|
|
7793
|
+
}
|
|
7794
|
+
async function defaultCreateTransport(options) {
|
|
7795
|
+
if (options.config.transport.type === "acpx-bridge") {
|
|
7796
|
+
const client = await (options.spawnAcpxBridgeClient ?? spawnAcpxBridgeClient)({
|
|
7797
|
+
acpxCommand: options.metadata.command,
|
|
7798
|
+
bridgeEntryPath: (options.resolveBridgeEntryPath ?? resolveBridgeEntryPath)(),
|
|
7799
|
+
permissionMode: options.config.transport.permissionMode,
|
|
7800
|
+
nonInteractivePermissions: options.config.transport.nonInteractivePermissions
|
|
7039
7801
|
});
|
|
7802
|
+
return new AcpxBridgeTransport(client);
|
|
7803
|
+
}
|
|
7804
|
+
return new AcpxCliTransport({
|
|
7805
|
+
...options.config.transport,
|
|
7806
|
+
command: options.metadata.command
|
|
7040
7807
|
});
|
|
7041
7808
|
}
|
|
7042
|
-
|
|
7043
|
-
|
|
7809
|
+
function buildDetails3(options) {
|
|
7810
|
+
const details = [
|
|
7811
|
+
`config path: ${options.runtimePaths.configPath}`,
|
|
7812
|
+
`agent: ${options.session.agent} (${options.agentReason})`,
|
|
7813
|
+
`workspace: ${options.session.workspace} (${options.workspaceReason})`,
|
|
7814
|
+
`transport session: ${options.session.transportSession}`,
|
|
7815
|
+
`reply: ${JSON.stringify(options.replyText)}`
|
|
7816
|
+
];
|
|
7817
|
+
if (options.verbose) {
|
|
7818
|
+
details.push(`transport type: ${options.session.agentCommand ? "agent-command" : "driver-default"}`);
|
|
7819
|
+
details.push(`acpx command: ${options.metadata.command}`);
|
|
7820
|
+
details.push(`acpx source: ${options.metadata.source}`);
|
|
7821
|
+
details.push(`smoke prompt: ${JSON.stringify(SMOKE_PROMPT)}`);
|
|
7822
|
+
}
|
|
7823
|
+
return details;
|
|
7044
7824
|
}
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
}
|
|
7048
|
-
|
|
7825
|
+
function formatError6(error) {
|
|
7826
|
+
return error instanceof Error ? error.message : String(error);
|
|
7827
|
+
}
|
|
7828
|
+
var SMOKE_PROMPT = "Reply with exactly: ok";
|
|
7829
|
+
var init_smoke_check = __esm(async () => {
|
|
7830
|
+
init_load_config();
|
|
7831
|
+
init_resolve_acpx_command();
|
|
7832
|
+
init_acpx_bridge_client();
|
|
7833
|
+
init_acpx_cli_transport();
|
|
7834
|
+
await init_main();
|
|
7835
|
+
});
|
|
7836
|
+
|
|
7837
|
+
// src/doctor/checks/wechat-check.ts
|
|
7838
|
+
async function checkWechat(options = {}) {
|
|
7839
|
+
const ids = listWeixinAccountIds();
|
|
7840
|
+
const accounts = ids.map((accountId) => {
|
|
7049
7841
|
try {
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7842
|
+
return {
|
|
7843
|
+
accountId,
|
|
7844
|
+
account: resolveWeixinAccount(accountId)
|
|
7845
|
+
};
|
|
7846
|
+
} catch (error) {
|
|
7847
|
+
return {
|
|
7848
|
+
accountId,
|
|
7849
|
+
error: formatError7(error)
|
|
7850
|
+
};
|
|
7851
|
+
}
|
|
7852
|
+
});
|
|
7853
|
+
const configuredAccount = accounts.find((entry) => ("account" in entry) && entry.account.configured);
|
|
7854
|
+
const loggedIn = Boolean(configuredAccount);
|
|
7855
|
+
if (!loggedIn) {
|
|
7856
|
+
return {
|
|
7857
|
+
id: "wechat",
|
|
7858
|
+
label: "WeChat",
|
|
7859
|
+
severity: "warn",
|
|
7860
|
+
summary: "wechat is not logged in",
|
|
7861
|
+
details: buildVerboseDetails(false, options.verbose, accounts),
|
|
7862
|
+
suggestions: ["weacpx login"]
|
|
7863
|
+
};
|
|
7053
7864
|
}
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7865
|
+
return {
|
|
7866
|
+
id: "wechat",
|
|
7867
|
+
label: "WeChat",
|
|
7868
|
+
severity: "pass",
|
|
7869
|
+
summary: "wechat is logged in",
|
|
7870
|
+
details: buildVerboseDetails(true, options.verbose, accounts)
|
|
7871
|
+
};
|
|
7872
|
+
}
|
|
7873
|
+
function buildVerboseDetails(loggedIn, verbose, accounts) {
|
|
7874
|
+
if (!verbose) {
|
|
7058
7875
|
return;
|
|
7059
7876
|
}
|
|
7060
|
-
const
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7877
|
+
const details = [];
|
|
7878
|
+
details.push(`loggedIn: ${loggedIn}`);
|
|
7879
|
+
details.push(`accountIds: ${accounts.length > 0 ? accounts.map((entry) => entry.accountId).join(", ") : "(none)"}`);
|
|
7880
|
+
for (const entry of accounts) {
|
|
7881
|
+
if ("account" in entry) {
|
|
7882
|
+
details.push(`account[${entry.account.accountId}].configured: ${entry.account.configured}`);
|
|
7883
|
+
details.push(`account[${entry.account.accountId}].baseUrl: ${entry.account.baseUrl}`);
|
|
7884
|
+
continue;
|
|
7064
7885
|
}
|
|
7065
|
-
|
|
7886
|
+
details.push(`account[${entry.accountId}].resolveError: ${entry.error ?? "unknown"}`);
|
|
7066
7887
|
}
|
|
7067
|
-
|
|
7068
|
-
killProcess(targetPid, "SIGKILL");
|
|
7069
|
-
} catch {}
|
|
7888
|
+
return details;
|
|
7070
7889
|
}
|
|
7071
|
-
|
|
7072
|
-
return
|
|
7073
|
-
const child = spawn(command, args, { stdio: "ignore" });
|
|
7074
|
-
child.on("error", reject);
|
|
7075
|
-
child.on("close", (code) => resolve(code ?? 1));
|
|
7076
|
-
});
|
|
7890
|
+
function formatError7(error) {
|
|
7891
|
+
return error instanceof Error ? error.message : String(error);
|
|
7077
7892
|
}
|
|
7893
|
+
var init_wechat_check = __esm(() => {
|
|
7894
|
+
init_weixin();
|
|
7895
|
+
});
|
|
7078
7896
|
|
|
7079
|
-
// src/
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7897
|
+
// src/doctor/render-doctor.ts
|
|
7898
|
+
function renderDoctor(report, options = {}) {
|
|
7899
|
+
return options.verbose ? renderVerboseDoctor(report) : renderDefaultDoctor(report);
|
|
7900
|
+
}
|
|
7901
|
+
function renderDefaultDoctor(report) {
|
|
7902
|
+
const lines = [];
|
|
7903
|
+
for (const check of report.checks) {
|
|
7904
|
+
lines.push(renderCheckLine(check));
|
|
7905
|
+
}
|
|
7906
|
+
lines.push(renderSummaryLine(report.checks));
|
|
7907
|
+
const suggestions = collectSuggestions(report.checks);
|
|
7908
|
+
if (suggestions.length > 0) {
|
|
7909
|
+
lines.push("Next steps:");
|
|
7910
|
+
for (const suggestion of suggestions) {
|
|
7911
|
+
lines.push(`- ${suggestion}`);
|
|
7912
|
+
}
|
|
7913
|
+
}
|
|
7914
|
+
return lines;
|
|
7915
|
+
}
|
|
7916
|
+
function renderVerboseDoctor(report) {
|
|
7917
|
+
const lines = [];
|
|
7918
|
+
for (const check of report.checks) {
|
|
7919
|
+
lines.push(renderCheckLine(check));
|
|
7920
|
+
for (const detail of check.details ?? []) {
|
|
7921
|
+
lines.push(` detail: ${detail}`);
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
lines.push(renderSummaryLine(report.checks));
|
|
7925
|
+
const suggestions = collectSuggestions(report.checks);
|
|
7926
|
+
if (suggestions.length > 0) {
|
|
7927
|
+
lines.push("Next steps:");
|
|
7928
|
+
for (const suggestion of suggestions) {
|
|
7929
|
+
lines.push(`- ${suggestion}`);
|
|
7930
|
+
}
|
|
7931
|
+
}
|
|
7932
|
+
return lines;
|
|
7933
|
+
}
|
|
7934
|
+
function renderCheckLine(check) {
|
|
7935
|
+
return `${SEVERITY_LABELS[check.severity]} ${check.label}: ${check.summary}`;
|
|
7936
|
+
}
|
|
7937
|
+
function renderSummaryLine(checks) {
|
|
7938
|
+
const counts = summarizeChecks(checks);
|
|
7939
|
+
return `Summary: PASS ${counts.pass}, WARN ${counts.warn}, FAIL ${counts.fail}, SKIP ${counts.skip}`;
|
|
7940
|
+
}
|
|
7941
|
+
function summarizeChecks(checks) {
|
|
7942
|
+
return checks.reduce((counts, check) => {
|
|
7943
|
+
counts[check.severity] += 1;
|
|
7944
|
+
return counts;
|
|
7945
|
+
}, { pass: 0, warn: 0, fail: 0, skip: 0 });
|
|
7946
|
+
}
|
|
7947
|
+
function collectSuggestions(checks) {
|
|
7948
|
+
const seen = new Set;
|
|
7949
|
+
const suggestions = [];
|
|
7950
|
+
for (const check of checks) {
|
|
7951
|
+
for (const suggestion of check.suggestions ?? []) {
|
|
7952
|
+
if (seen.has(suggestion)) {
|
|
7953
|
+
continue;
|
|
7954
|
+
}
|
|
7955
|
+
seen.add(suggestion);
|
|
7956
|
+
suggestions.push(suggestion);
|
|
7957
|
+
}
|
|
7958
|
+
}
|
|
7959
|
+
return suggestions;
|
|
7960
|
+
}
|
|
7961
|
+
var SEVERITY_LABELS;
|
|
7962
|
+
var init_render_doctor = __esm(() => {
|
|
7963
|
+
SEVERITY_LABELS = {
|
|
7964
|
+
pass: "PASS",
|
|
7965
|
+
warn: "WARN",
|
|
7966
|
+
fail: "FAIL",
|
|
7967
|
+
skip: "SKIP"
|
|
7968
|
+
};
|
|
7969
|
+
});
|
|
7970
|
+
|
|
7971
|
+
// src/doctor/doctor.ts
|
|
7972
|
+
import { homedir as homedir8 } from "node:os";
|
|
7973
|
+
import { join as join6 } from "node:path";
|
|
7974
|
+
async function runDoctor(options = {}, deps = {}) {
|
|
7975
|
+
const home = deps.home ?? process.env.HOME ?? homedir8();
|
|
7976
|
+
const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
|
|
7977
|
+
const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
|
|
7978
|
+
const checks = [];
|
|
7979
|
+
checks.push(await (deps.checkConfig ?? checkConfig)({
|
|
7980
|
+
loadConfig: sharedLoadConfig,
|
|
7981
|
+
resolveRuntimePaths: () => runtimePaths
|
|
7982
|
+
}));
|
|
7983
|
+
checks.push(await (deps.checkRuntime ?? checkRuntime)({
|
|
7984
|
+
home
|
|
7985
|
+
}));
|
|
7986
|
+
checks.push(await (deps.checkDaemon ?? checkDaemon)({
|
|
7987
|
+
home
|
|
7988
|
+
}));
|
|
7989
|
+
checks.push(await (deps.checkWechat ?? checkWechat)({
|
|
7990
|
+
verbose: options.verbose
|
|
7991
|
+
}));
|
|
7992
|
+
checks.push(await (deps.checkAcpx ?? checkAcpx)({
|
|
7993
|
+
verbose: options.verbose,
|
|
7994
|
+
loadConfig: sharedLoadConfig,
|
|
7995
|
+
resolveRuntimePaths: () => runtimePaths
|
|
7996
|
+
}));
|
|
7997
|
+
checks.push(await (deps.checkBridge ?? checkBridge)({
|
|
7998
|
+
verbose: options.verbose,
|
|
7999
|
+
loadConfig: sharedLoadConfig,
|
|
8000
|
+
resolveRuntimePaths: () => runtimePaths
|
|
8001
|
+
}));
|
|
8002
|
+
checks.push(options.smoke === true ? await (deps.checkSmoke ?? ((runOptions) => defaultCheckSmoke(runOptions, {
|
|
8003
|
+
resolveRuntimePaths: () => runtimePaths,
|
|
8004
|
+
loadConfig: sharedLoadConfig
|
|
8005
|
+
})))(options) : createSmokeSkipResult("smoke probe not requested"));
|
|
8006
|
+
const report = { checks };
|
|
8007
|
+
const output = (deps.renderDoctor ?? renderDoctor)(report, options);
|
|
7083
8008
|
return {
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
8009
|
+
report,
|
|
8010
|
+
output,
|
|
8011
|
+
exitCode: checks.some((check) => check.severity === "fail") ? 1 : 0
|
|
8012
|
+
};
|
|
8013
|
+
}
|
|
8014
|
+
function resolveDoctorRuntimePaths(home, resolver) {
|
|
8015
|
+
if (resolver) {
|
|
8016
|
+
return resolver();
|
|
8017
|
+
}
|
|
8018
|
+
if (depsUseExplicitRuntimeOverrides()) {
|
|
8019
|
+
return resolveRuntimePaths();
|
|
8020
|
+
}
|
|
8021
|
+
return {
|
|
8022
|
+
configPath: join6(home, ".weacpx", "config.json"),
|
|
8023
|
+
statePath: join6(home, ".weacpx", "state.json")
|
|
8024
|
+
};
|
|
8025
|
+
}
|
|
8026
|
+
function depsUseExplicitRuntimeOverrides() {
|
|
8027
|
+
return Boolean(process.env.WEACPX_CONFIG || process.env.WEACPX_STATE);
|
|
8028
|
+
}
|
|
8029
|
+
function createSharedLoadConfig(runtimePaths, loader) {
|
|
8030
|
+
let pending;
|
|
8031
|
+
return async (configPath) => {
|
|
8032
|
+
if (configPath !== runtimePaths.configPath) {
|
|
8033
|
+
return await loader(configPath);
|
|
8034
|
+
}
|
|
8035
|
+
pending ??= loader(configPath);
|
|
8036
|
+
return await pending;
|
|
8037
|
+
};
|
|
8038
|
+
}
|
|
8039
|
+
async function defaultCheckSmoke(options, deps) {
|
|
8040
|
+
return await checkSmoke(options, {
|
|
8041
|
+
resolveRuntimePaths: deps.resolveRuntimePaths,
|
|
8042
|
+
loadConfig: deps.loadConfig
|
|
8043
|
+
});
|
|
8044
|
+
}
|
|
8045
|
+
function createSmokeSkipResult(summary) {
|
|
8046
|
+
return {
|
|
8047
|
+
id: "smoke",
|
|
8048
|
+
label: "Smoke",
|
|
8049
|
+
severity: "skip",
|
|
8050
|
+
summary
|
|
7090
8051
|
};
|
|
7091
8052
|
}
|
|
8053
|
+
var init_doctor = __esm(async () => {
|
|
8054
|
+
init_load_config();
|
|
8055
|
+
init_daemon_check();
|
|
8056
|
+
init_runtime_check();
|
|
8057
|
+
init_wechat_check();
|
|
8058
|
+
init_render_doctor();
|
|
8059
|
+
await __promiseAll([
|
|
8060
|
+
init_main(),
|
|
8061
|
+
init_acpx_check(),
|
|
8062
|
+
init_bridge_check(),
|
|
8063
|
+
init_config_check(),
|
|
8064
|
+
init_smoke_check()
|
|
8065
|
+
]);
|
|
8066
|
+
});
|
|
8067
|
+
|
|
8068
|
+
// src/doctor/index.ts
|
|
8069
|
+
var exports_doctor = {};
|
|
8070
|
+
__export(exports_doctor, {
|
|
8071
|
+
main: () => main3
|
|
8072
|
+
});
|
|
8073
|
+
async function main3(options, deps = {}) {
|
|
8074
|
+
const result = await (deps.runDoctor ?? runDoctor)(options);
|
|
8075
|
+
const print = deps.print ?? ((line) => console.log(line));
|
|
8076
|
+
for (const line of result.output) {
|
|
8077
|
+
print(line);
|
|
8078
|
+
}
|
|
8079
|
+
return result.exitCode;
|
|
8080
|
+
}
|
|
8081
|
+
var init_doctor2 = __esm(async () => {
|
|
8082
|
+
await init_doctor();
|
|
8083
|
+
});
|
|
8084
|
+
|
|
8085
|
+
// src/cli.ts
|
|
8086
|
+
init_create_daemon_controller();
|
|
8087
|
+
init_daemon_files();
|
|
8088
|
+
import { homedir as homedir9 } from "node:os";
|
|
8089
|
+
import { sep } from "node:path";
|
|
8090
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7092
8091
|
|
|
7093
8092
|
// src/daemon/daemon-runtime.ts
|
|
8093
|
+
init_daemon_status();
|
|
7094
8094
|
import { mkdir as mkdir4, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
|
|
7095
8095
|
import { dirname as dirname3 } from "node:path";
|
|
8096
|
+
|
|
7096
8097
|
class DaemonRuntime {
|
|
7097
8098
|
paths;
|
|
7098
8099
|
options;
|
|
@@ -7140,6 +8141,7 @@ class DaemonRuntime {
|
|
|
7140
8141
|
}
|
|
7141
8142
|
|
|
7142
8143
|
// src/cli.ts
|
|
8144
|
+
init_version();
|
|
7143
8145
|
init_consumer_lock();
|
|
7144
8146
|
var HELP_LINES = [
|
|
7145
8147
|
"用法:",
|
|
@@ -7148,13 +8150,26 @@ var HELP_LINES = [
|
|
|
7148
8150
|
"weacpx run - 前台运行",
|
|
7149
8151
|
"weacpx start - 后台启动",
|
|
7150
8152
|
"weacpx status - 查看状态",
|
|
7151
|
-
"weacpx stop - 停止服务"
|
|
8153
|
+
"weacpx stop - 停止服务",
|
|
8154
|
+
"weacpx doctor - 运行诊断",
|
|
8155
|
+
"weacpx version - 查看版本"
|
|
7152
8156
|
];
|
|
7153
8157
|
async function runCli(args, deps = {}) {
|
|
7154
8158
|
const command = args[0];
|
|
7155
8159
|
const print = deps.print ?? ((line) => console.log(line));
|
|
7156
|
-
const controller = deps.controller ?? createDefaultController();
|
|
7157
8160
|
switch (command) {
|
|
8161
|
+
case "version":
|
|
8162
|
+
case "--version":
|
|
8163
|
+
case "-v":
|
|
8164
|
+
print(readVersion());
|
|
8165
|
+
return 0;
|
|
8166
|
+
case "--help":
|
|
8167
|
+
case "-h": {
|
|
8168
|
+
for (const line of HELP_LINES) {
|
|
8169
|
+
print(line);
|
|
8170
|
+
}
|
|
8171
|
+
return 0;
|
|
8172
|
+
}
|
|
7158
8173
|
case "login":
|
|
7159
8174
|
await (deps.login ?? defaultLogin)();
|
|
7160
8175
|
return 0;
|
|
@@ -7164,7 +8179,18 @@ async function runCli(args, deps = {}) {
|
|
|
7164
8179
|
case "run":
|
|
7165
8180
|
await (deps.run ?? defaultRun)();
|
|
7166
8181
|
return 0;
|
|
8182
|
+
case "doctor": {
|
|
8183
|
+
const parsed = parseDoctorArgs(args.slice(1));
|
|
8184
|
+
if (!parsed.ok) {
|
|
8185
|
+
for (const line of HELP_LINES) {
|
|
8186
|
+
print(line);
|
|
8187
|
+
}
|
|
8188
|
+
return 1;
|
|
8189
|
+
}
|
|
8190
|
+
return await (deps.doctor ?? defaultDoctor)(parsed.options);
|
|
8191
|
+
}
|
|
7167
8192
|
case "start": {
|
|
8193
|
+
const controller = deps.controller ?? createDefaultController();
|
|
7168
8194
|
const result = await controller.start();
|
|
7169
8195
|
if (result.state === "already-running") {
|
|
7170
8196
|
print("weacpx 已在后台运行");
|
|
@@ -7176,6 +8202,7 @@ async function runCli(args, deps = {}) {
|
|
|
7176
8202
|
return 0;
|
|
7177
8203
|
}
|
|
7178
8204
|
case "status": {
|
|
8205
|
+
const controller = deps.controller ?? createDefaultController();
|
|
7179
8206
|
const status = await controller.getStatus();
|
|
7180
8207
|
if (status.state === "indeterminate") {
|
|
7181
8208
|
print("weacpx 进程仍在运行,但状态元数据缺失");
|
|
@@ -7198,6 +8225,7 @@ async function runCli(args, deps = {}) {
|
|
|
7198
8225
|
return 0;
|
|
7199
8226
|
}
|
|
7200
8227
|
case "stop": {
|
|
8228
|
+
const controller = deps.controller ?? createDefaultController();
|
|
7201
8229
|
const result = await controller.stop();
|
|
7202
8230
|
if (result.detail === "not-running") {
|
|
7203
8231
|
print("weacpx 未运行");
|
|
@@ -7214,8 +8242,8 @@ async function runCli(args, deps = {}) {
|
|
|
7214
8242
|
}
|
|
7215
8243
|
}
|
|
7216
8244
|
async function defaultLogin() {
|
|
7217
|
-
const { main:
|
|
7218
|
-
await
|
|
8245
|
+
const { main: main4 } = await init_login().then(() => exports_login);
|
|
8246
|
+
await main4();
|
|
7219
8247
|
}
|
|
7220
8248
|
async function defaultLogout() {
|
|
7221
8249
|
const { logout: logout2 } = await Promise.resolve().then(() => (init_weixin_sdk(), exports_weixin_sdk));
|
|
@@ -7232,7 +8260,7 @@ async function defaultRun() {
|
|
|
7232
8260
|
const daemonRuntime = new DaemonRuntime(daemonPaths, { pid: process.pid });
|
|
7233
8261
|
await runConsole2(runtimePaths, {
|
|
7234
8262
|
buildApp: (paths) => buildApp2(paths, {
|
|
7235
|
-
defaultLoggingLevel:
|
|
8263
|
+
defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep}src${sep}`) ? "debug" : "info"
|
|
7236
8264
|
}),
|
|
7237
8265
|
loadWeixinSdk: loadWeixinSdk2,
|
|
7238
8266
|
daemonRuntime,
|
|
@@ -7244,27 +8272,66 @@ async function defaultRun() {
|
|
|
7244
8272
|
})
|
|
7245
8273
|
});
|
|
7246
8274
|
}
|
|
8275
|
+
async function defaultDoctor(options) {
|
|
8276
|
+
const { main: main4 } = await init_doctor2().then(() => exports_doctor);
|
|
8277
|
+
return await main4(options);
|
|
8278
|
+
}
|
|
7247
8279
|
function createDefaultController() {
|
|
7248
8280
|
const daemonPaths = resolveDaemonPaths({ home: requireHome() });
|
|
7249
8281
|
return createDaemonController(daemonPaths, {
|
|
7250
8282
|
processExecPath: process.execPath,
|
|
7251
|
-
cliEntryPath:
|
|
8283
|
+
cliEntryPath: resolveCliEntryPath2(),
|
|
7252
8284
|
cwd: process.cwd(),
|
|
7253
8285
|
env: process.env
|
|
7254
8286
|
});
|
|
7255
8287
|
}
|
|
7256
8288
|
function requireHome() {
|
|
7257
|
-
const home = process.env.HOME ??
|
|
8289
|
+
const home = process.env.HOME ?? homedir9();
|
|
7258
8290
|
if (!home) {
|
|
7259
8291
|
throw new Error("Unable to resolve the current user home directory");
|
|
7260
8292
|
}
|
|
7261
8293
|
return home;
|
|
7262
8294
|
}
|
|
7263
|
-
function
|
|
8295
|
+
function resolveCliEntryPath2() {
|
|
7264
8296
|
if (process.argv[1]) {
|
|
7265
8297
|
return process.argv[1];
|
|
7266
8298
|
}
|
|
7267
|
-
return
|
|
8299
|
+
return fileURLToPath5(import.meta.url);
|
|
8300
|
+
}
|
|
8301
|
+
function parseDoctorArgs(args) {
|
|
8302
|
+
const options = {};
|
|
8303
|
+
for (let index = 0;index < args.length; index++) {
|
|
8304
|
+
const arg = args[index];
|
|
8305
|
+
switch (arg) {
|
|
8306
|
+
case "--verbose":
|
|
8307
|
+
options.verbose = true;
|
|
8308
|
+
break;
|
|
8309
|
+
case "--smoke":
|
|
8310
|
+
options.smoke = true;
|
|
8311
|
+
break;
|
|
8312
|
+
case "--agent": {
|
|
8313
|
+
const value = args[index + 1];
|
|
8314
|
+
if (!value || value.startsWith("--")) {
|
|
8315
|
+
return { ok: false };
|
|
8316
|
+
}
|
|
8317
|
+
options.agent = value;
|
|
8318
|
+
index++;
|
|
8319
|
+
break;
|
|
8320
|
+
}
|
|
8321
|
+
case "--workspace": {
|
|
8322
|
+
const value = args[index + 1];
|
|
8323
|
+
if (!value || value.startsWith("--")) {
|
|
8324
|
+
return { ok: false };
|
|
8325
|
+
}
|
|
8326
|
+
options.workspace = value;
|
|
8327
|
+
index++;
|
|
8328
|
+
break;
|
|
8329
|
+
}
|
|
8330
|
+
default:
|
|
8331
|
+
return { ok: false };
|
|
8332
|
+
}
|
|
8333
|
+
}
|
|
8334
|
+
return { ok: true, options };
|
|
7268
8335
|
}
|
|
7269
8336
|
if (__require.main == __require.module) {
|
|
7270
8337
|
process.exitCode = await runCli(process.argv.slice(2));
|