weacpx 0.2.0 → 0.2.2
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 +173 -20
- package/dist/cli.js +1487 -367
- 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
|
|
@@ -2065,6 +2423,47 @@ var init_session_guard = __esm(() => {
|
|
|
2065
2423
|
pauseUntilMap = new Map;
|
|
2066
2424
|
});
|
|
2067
2425
|
|
|
2426
|
+
// src/weixin/messaging/conversation-executor.ts
|
|
2427
|
+
function createConversationExecutor() {
|
|
2428
|
+
const states = new Map;
|
|
2429
|
+
const getState = (conversationId) => {
|
|
2430
|
+
const existing = states.get(conversationId);
|
|
2431
|
+
if (existing)
|
|
2432
|
+
return existing;
|
|
2433
|
+
const created = { activeControls: 0 };
|
|
2434
|
+
states.set(conversationId, created);
|
|
2435
|
+
return created;
|
|
2436
|
+
};
|
|
2437
|
+
const cleanupState = (conversationId, state) => {
|
|
2438
|
+
if (!state.normalTail && state.activeControls === 0) {
|
|
2439
|
+
states.delete(conversationId);
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
return {
|
|
2443
|
+
run(conversationId, lane, task) {
|
|
2444
|
+
const state = getState(conversationId);
|
|
2445
|
+
if (lane === "control") {
|
|
2446
|
+
state.activeControls += 1;
|
|
2447
|
+
return Promise.resolve().then(task).finally(() => {
|
|
2448
|
+
state.activeControls -= 1;
|
|
2449
|
+
cleanupState(conversationId, state);
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
const previous = state.normalTail ?? Promise.resolve();
|
|
2453
|
+
const next = previous.catch(() => {
|
|
2454
|
+
return;
|
|
2455
|
+
}).then(task);
|
|
2456
|
+
state.normalTail = next;
|
|
2457
|
+
return next.finally(() => {
|
|
2458
|
+
if (state.normalTail === next) {
|
|
2459
|
+
state.normalTail = undefined;
|
|
2460
|
+
}
|
|
2461
|
+
cleanupState(conversationId, state);
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2068
2467
|
// src/weixin/api/types.ts
|
|
2069
2468
|
var UploadMediaType, MessageType, MessageItemType, MessageState, TypingStatus;
|
|
2070
2469
|
var init_types = __esm(() => {
|
|
@@ -2637,7 +3036,7 @@ function markdownToPlainText(text) {
|
|
|
2637
3036
|
result = result.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
|
|
2638
3037
|
result = result.replace(/^\|[\s:|-]+\|$/gm, "");
|
|
2639
3038
|
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(
|
|
3039
|
+
result = result.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/__(.+?)__/g, "$1").replace(/~~(.+?)~~/g, "$1").replace(/`(.+?)`/g, "$1");
|
|
2641
3040
|
return result;
|
|
2642
3041
|
}
|
|
2643
3042
|
function buildTextMessageReq(params) {
|
|
@@ -3002,6 +3401,10 @@ function extractTextBody(itemList) {
|
|
|
3002
3401
|
}
|
|
3003
3402
|
return "";
|
|
3004
3403
|
}
|
|
3404
|
+
function getWeixinMessageTurnLane(full) {
|
|
3405
|
+
const textBody = extractTextBody(full.item_list).trim().toLowerCase();
|
|
3406
|
+
return textBody === "/cancel" || textBody === "/stop" ? "control" : "normal";
|
|
3407
|
+
}
|
|
3005
3408
|
function findMediaItem(itemList) {
|
|
3006
3409
|
if (!itemList?.length)
|
|
3007
3410
|
return;
|
|
@@ -3253,6 +3656,10 @@ async function monitorWeixinProvider(opts) {
|
|
|
3253
3656
|
log(`[weixin] no previous sync buf, starting fresh`);
|
|
3254
3657
|
}
|
|
3255
3658
|
const configManager = new WeixinConfigManager({ baseUrl, token }, log);
|
|
3659
|
+
const conversationExecutor = createConversationExecutor();
|
|
3660
|
+
const seenMessageIds = new Set;
|
|
3661
|
+
const messageIdOrder = [];
|
|
3662
|
+
const DEDUP_WINDOW = 100;
|
|
3256
3663
|
let nextTimeoutMs = longPollTimeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS2;
|
|
3257
3664
|
let consecutiveFailures = 0;
|
|
3258
3665
|
while (!abortSignal?.aborted) {
|
|
@@ -3296,10 +3703,22 @@ async function monitorWeixinProvider(opts) {
|
|
|
3296
3703
|
}
|
|
3297
3704
|
const list = resp.msgs ?? [];
|
|
3298
3705
|
for (const full of list) {
|
|
3706
|
+
const msgId = full.message_id;
|
|
3707
|
+
if (msgId != null) {
|
|
3708
|
+
if (seenMessageIds.has(msgId)) {
|
|
3709
|
+
aLog.info(`duplicate message skipped: message_id=${msgId}`);
|
|
3710
|
+
continue;
|
|
3711
|
+
}
|
|
3712
|
+
seenMessageIds.add(msgId);
|
|
3713
|
+
messageIdOrder.push(msgId);
|
|
3714
|
+
if (messageIdOrder.length > DEDUP_WINDOW) {
|
|
3715
|
+
seenMessageIds.delete(messageIdOrder.shift());
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3299
3718
|
aLog.info(`inbound: from=${full.from_user_id} types=${full.item_list?.map((i) => i.type).join(",") ?? "none"}`);
|
|
3300
3719
|
const fromUserId = full.from_user_id ?? "";
|
|
3301
3720
|
const cachedConfig = await configManager.getForUser(fromUserId, full.context_token);
|
|
3302
|
-
|
|
3721
|
+
conversationExecutor.run(full.from_user_id ?? "", getWeixinMessageTurnLane(full), () => handleWeixinMessageTurn(full, {
|
|
3303
3722
|
accountId,
|
|
3304
3723
|
agent,
|
|
3305
3724
|
baseUrl,
|
|
@@ -3308,6 +3727,8 @@ async function monitorWeixinProvider(opts) {
|
|
|
3308
3727
|
typingTicket: cachedConfig.typingTicket,
|
|
3309
3728
|
log,
|
|
3310
3729
|
errLog
|
|
3730
|
+
})).catch((err) => {
|
|
3731
|
+
errLog(`[weixin] message turn failed: ${String(err)}`);
|
|
3311
3732
|
});
|
|
3312
3733
|
}
|
|
3313
3734
|
} catch (err) {
|
|
@@ -4479,29 +4900,34 @@ async function handleCancel(context, chatKey) {
|
|
|
4479
4900
|
const result = await context.interaction.cancelTransportSession(session);
|
|
4480
4901
|
return { text: result.message || "cancelled" };
|
|
4481
4902
|
} catch (error) {
|
|
4903
|
+
const recovered = await context.recovery.tryRecoverMissingSession(session, error);
|
|
4904
|
+
if (recovered) {
|
|
4905
|
+
const result = await context.interaction.cancelTransportSession(recovered);
|
|
4906
|
+
return { text: result.message || "cancelled" };
|
|
4907
|
+
}
|
|
4482
4908
|
return context.recovery.renderTransportError(session, error);
|
|
4483
4909
|
}
|
|
4484
4910
|
}
|
|
4485
4911
|
async function handleSessionReset(context, chatKey) {
|
|
4486
4912
|
return await context.lifecycle.resetCurrentSession(chatKey);
|
|
4487
4913
|
}
|
|
4914
|
+
async function promptWithSession(context, session, text, reply) {
|
|
4915
|
+
const effectiveReplyMode = session.replyMode ?? context.config?.wechat.replyMode ?? "stream";
|
|
4916
|
+
const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
|
|
4917
|
+
const result = await context.interaction.promptTransportSession(session, text, transportReply);
|
|
4918
|
+
return { text: transportReply ? undefined : result.text };
|
|
4919
|
+
}
|
|
4488
4920
|
async function handlePrompt(context, chatKey, text, reply) {
|
|
4489
4921
|
const session = await context.sessions.getCurrentSession(chatKey);
|
|
4490
4922
|
if (!session) {
|
|
4491
4923
|
return { text: NO_CURRENT_SESSION_TEXT };
|
|
4492
4924
|
}
|
|
4493
4925
|
try {
|
|
4494
|
-
|
|
4495
|
-
const transportReply = effectiveReplyMode === "stream" ? reply : undefined;
|
|
4496
|
-
const result = await context.interaction.promptTransportSession(session, text, transportReply);
|
|
4497
|
-
return { text: result.text };
|
|
4926
|
+
return await promptWithSession(context, session, text, reply);
|
|
4498
4927
|
} catch (error) {
|
|
4499
4928
|
const recovered = await context.recovery.tryRecoverMissingSession(session, error);
|
|
4500
4929
|
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 };
|
|
4930
|
+
return await promptWithSession(context, recovered, text, reply);
|
|
4505
4931
|
}
|
|
4506
4932
|
return context.recovery.renderTransportError(session, error);
|
|
4507
4933
|
}
|
|
@@ -4892,7 +5318,7 @@ async function handleSessionShortcutCommand(context, ops, chatKey, agent, target
|
|
|
4892
5318
|
`)
|
|
4893
5319
|
};
|
|
4894
5320
|
}
|
|
4895
|
-
const session = ops.resolveSession(alias, agent, workspace.name,
|
|
5321
|
+
const session = ops.resolveSession(alias, agent, workspace.name, alias);
|
|
4896
5322
|
try {
|
|
4897
5323
|
await ops.ensureTransportSession(session);
|
|
4898
5324
|
const exists = await ops.checkTransportSession(session);
|
|
@@ -5635,11 +6061,18 @@ var init_ensure_config = __esm(() => {
|
|
|
5635
6061
|
|
|
5636
6062
|
// src/config/resolve-acpx-command.ts
|
|
5637
6063
|
import { readFileSync } from "node:fs";
|
|
5638
|
-
import { posix, win32 } from "node:path";
|
|
5639
6064
|
import { createRequire as createRequire2 } from "node:module";
|
|
6065
|
+
import { posix, win32 } from "node:path";
|
|
5640
6066
|
function resolveAcpxCommand(options = {}) {
|
|
6067
|
+
return resolveAcpxCommandMetadata(options).command;
|
|
6068
|
+
}
|
|
6069
|
+
function resolveAcpxCommandMetadata(options = {}) {
|
|
5641
6070
|
if (options.configuredCommand) {
|
|
5642
|
-
return
|
|
6071
|
+
return {
|
|
6072
|
+
command: options.configuredCommand,
|
|
6073
|
+
source: "config",
|
|
6074
|
+
explanation: "transport.command is set, so the configured command wins."
|
|
6075
|
+
};
|
|
5643
6076
|
}
|
|
5644
6077
|
const platform = options.platform ?? process.platform;
|
|
5645
6078
|
const resolvePackageJson = options.resolvePackageJson ?? ((id) => require2.resolve(id));
|
|
@@ -5651,10 +6084,18 @@ function resolveAcpxCommand(options = {}) {
|
|
|
5651
6084
|
const packageDir = pathApi.dirname(packageJsonPath);
|
|
5652
6085
|
const binPath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin && typeof pkg.bin.acpx === "string" ? pkg.bin.acpx : null;
|
|
5653
6086
|
if (binPath) {
|
|
5654
|
-
return
|
|
6087
|
+
return {
|
|
6088
|
+
command: pathApi.resolve(packageDir, binPath),
|
|
6089
|
+
source: "bundled",
|
|
6090
|
+
explanation: "transport.command is unset, so the bundled acpx dependency is used."
|
|
6091
|
+
};
|
|
5655
6092
|
}
|
|
5656
6093
|
} catch {}
|
|
5657
|
-
return
|
|
6094
|
+
return {
|
|
6095
|
+
command: "acpx",
|
|
6096
|
+
source: "PATH",
|
|
6097
|
+
explanation: "transport.command is unset and no bundled acpx was found, so PATH is the fallback."
|
|
6098
|
+
};
|
|
5658
6099
|
}
|
|
5659
6100
|
var require2;
|
|
5660
6101
|
var init_resolve_acpx_command = __esm(() => {
|
|
@@ -6634,6 +7075,7 @@ var init_acpx_cli_transport = __esm(() => {
|
|
|
6634
7075
|
var exports_main = {};
|
|
6635
7076
|
__export(exports_main, {
|
|
6636
7077
|
resolveRuntimePaths: () => resolveRuntimePaths,
|
|
7078
|
+
resolveBridgeEntryPath: () => resolveBridgeEntryPath,
|
|
6637
7079
|
main: () => main2,
|
|
6638
7080
|
buildApp: () => buildApp
|
|
6639
7081
|
});
|
|
@@ -6737,362 +7179,974 @@ var init_main = __esm(async () => {
|
|
|
6737
7179
|
if (false) {}
|
|
6738
7180
|
});
|
|
6739
7181
|
|
|
6740
|
-
// src/
|
|
6741
|
-
import {
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
7182
|
+
// src/doctor/checks/acpx-check.ts
|
|
7183
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
7184
|
+
async function checkAcpx(options = {}) {
|
|
7185
|
+
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
7186
|
+
try {
|
|
7187
|
+
const config = await (options.loadConfig ?? loadConfig)(runtimePaths.configPath);
|
|
7188
|
+
const metadata = (options.resolveAcpxCommandMetadata ?? resolveAcpxCommandMetadata)({
|
|
7189
|
+
configuredCommand: config.transport.command
|
|
7190
|
+
});
|
|
7191
|
+
const version = await (options.runVersion ?? defaultRunVersion)(metadata.command);
|
|
7192
|
+
return {
|
|
7193
|
+
id: "acpx",
|
|
7194
|
+
label: "acpx",
|
|
7195
|
+
severity: "pass",
|
|
7196
|
+
summary: `resolved ${metadata.command} (${version})`,
|
|
7197
|
+
details: buildDetails(metadata, version, options.verbose),
|
|
7198
|
+
metadata: {
|
|
7199
|
+
command: metadata.command,
|
|
7200
|
+
source: metadata.source,
|
|
7201
|
+
version
|
|
7202
|
+
}
|
|
7203
|
+
};
|
|
7204
|
+
} catch (error) {
|
|
7205
|
+
const message = formatError(error);
|
|
7206
|
+
const details = [`config path: ${runtimePaths.configPath}`, `error: ${message}`];
|
|
7207
|
+
return {
|
|
7208
|
+
id: "acpx",
|
|
7209
|
+
label: "acpx",
|
|
7210
|
+
severity: "fail",
|
|
7211
|
+
summary: "acpx version check failed",
|
|
7212
|
+
details
|
|
7213
|
+
};
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
7216
|
+
function buildDetails(metadata, version, verbose) {
|
|
7217
|
+
const details = [
|
|
7218
|
+
`command: ${metadata.command}`,
|
|
7219
|
+
`source: ${metadata.source}`,
|
|
7220
|
+
`version: ${version}`
|
|
7221
|
+
];
|
|
7222
|
+
if (verbose) {
|
|
7223
|
+
details.push(`resolution: ${metadata.explanation}`);
|
|
7224
|
+
}
|
|
7225
|
+
return details;
|
|
7226
|
+
}
|
|
7227
|
+
async function defaultRunVersion(command) {
|
|
7228
|
+
const spawnSpec = resolveSpawnCommand(command, ["--version"]);
|
|
7229
|
+
return await new Promise((resolve2, reject) => {
|
|
7230
|
+
const child = spawn4(spawnSpec.command, spawnSpec.args, {
|
|
7231
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
7232
|
+
});
|
|
7233
|
+
let stdout = "";
|
|
7234
|
+
let stderr = "";
|
|
7235
|
+
child.stdout.on("data", (chunk) => {
|
|
7236
|
+
stdout += String(chunk);
|
|
7237
|
+
});
|
|
7238
|
+
child.stderr.on("data", (chunk) => {
|
|
7239
|
+
stderr += String(chunk);
|
|
7240
|
+
});
|
|
7241
|
+
child.on("error", reject);
|
|
7242
|
+
child.on("close", (code) => {
|
|
7243
|
+
if (code === 0) {
|
|
7244
|
+
const version = stdout.trim() || stderr.trim();
|
|
7245
|
+
if (version.length > 0) {
|
|
7246
|
+
resolve2(version);
|
|
7247
|
+
return;
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7250
|
+
reject(new Error(stderr.trim() || stdout.trim() || `acpx --version exited with code ${code ?? 1}`));
|
|
7251
|
+
});
|
|
7252
|
+
});
|
|
7253
|
+
}
|
|
7254
|
+
function formatError(error) {
|
|
7255
|
+
return error instanceof Error ? error.message : String(error);
|
|
7256
|
+
}
|
|
7257
|
+
var init_acpx_check = __esm(async () => {
|
|
7258
|
+
init_load_config();
|
|
7259
|
+
init_resolve_acpx_command();
|
|
7260
|
+
init_spawn_command();
|
|
7261
|
+
await init_main();
|
|
7262
|
+
});
|
|
6752
7263
|
|
|
6753
|
-
// src/
|
|
6754
|
-
|
|
6755
|
-
|
|
7264
|
+
// src/doctor/checks/bridge-check.ts
|
|
7265
|
+
async function checkBridge(options = {}) {
|
|
7266
|
+
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
7267
|
+
try {
|
|
7268
|
+
const config = await (options.loadConfig ?? loadConfig)(runtimePaths.configPath);
|
|
7269
|
+
if (config.transport.type === "acpx-cli") {
|
|
7270
|
+
return {
|
|
7271
|
+
id: "bridge",
|
|
7272
|
+
label: "Bridge",
|
|
7273
|
+
severity: "skip",
|
|
7274
|
+
summary: "bridge check skipped for acpx-cli transport"
|
|
7275
|
+
};
|
|
7276
|
+
}
|
|
7277
|
+
const metadata = (options.resolveAcpxCommandMetadata ?? resolveAcpxCommandMetadata)({
|
|
7278
|
+
configuredCommand: config.transport.command
|
|
7279
|
+
});
|
|
7280
|
+
const client = await (options.spawnAcpxBridgeClient ?? spawnAcpxBridgeClient)({
|
|
7281
|
+
acpxCommand: metadata.command,
|
|
7282
|
+
bridgeEntryPath: (options.resolveBridgeEntryPath ?? resolveBridgeEntryPath)(),
|
|
7283
|
+
cwd: options.cwd ?? process.cwd(),
|
|
7284
|
+
permissionMode: config.transport.permissionMode,
|
|
7285
|
+
nonInteractivePermissions: config.transport.nonInteractivePermissions
|
|
7286
|
+
});
|
|
7287
|
+
try {
|
|
7288
|
+
return {
|
|
7289
|
+
id: "bridge",
|
|
7290
|
+
label: "Bridge",
|
|
7291
|
+
severity: "pass",
|
|
7292
|
+
summary: "bridge responded to ping",
|
|
7293
|
+
details: buildDetails2(metadata, options.verbose),
|
|
7294
|
+
metadata: {
|
|
7295
|
+
acpxCommand: metadata.command,
|
|
7296
|
+
source: metadata.source
|
|
7297
|
+
}
|
|
7298
|
+
};
|
|
7299
|
+
} finally {
|
|
7300
|
+
await client.dispose();
|
|
7301
|
+
}
|
|
7302
|
+
} catch (error) {
|
|
7303
|
+
return {
|
|
7304
|
+
id: "bridge",
|
|
7305
|
+
label: "Bridge",
|
|
7306
|
+
severity: "fail",
|
|
7307
|
+
summary: "bridge startup failed",
|
|
7308
|
+
details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError2(error)}`]
|
|
7309
|
+
};
|
|
7310
|
+
}
|
|
7311
|
+
}
|
|
7312
|
+
function buildDetails2(metadata, verbose) {
|
|
7313
|
+
const details = [`acpx command: ${metadata.command}`, `source: ${metadata.source}`];
|
|
7314
|
+
if (verbose) {
|
|
7315
|
+
details.push(`resolution: ${metadata.explanation}`);
|
|
7316
|
+
}
|
|
7317
|
+
return details;
|
|
7318
|
+
}
|
|
7319
|
+
function formatError2(error) {
|
|
7320
|
+
return error instanceof Error ? error.message : String(error);
|
|
7321
|
+
}
|
|
7322
|
+
var init_bridge_check = __esm(async () => {
|
|
7323
|
+
init_load_config();
|
|
7324
|
+
init_resolve_acpx_command();
|
|
7325
|
+
init_acpx_bridge_client();
|
|
7326
|
+
await init_main();
|
|
7327
|
+
});
|
|
6756
7328
|
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
7329
|
+
// src/doctor/checks/config-check.ts
|
|
7330
|
+
async function checkConfig(options = {}) {
|
|
7331
|
+
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
7332
|
+
const configPath = runtimePaths.configPath;
|
|
7333
|
+
try {
|
|
7334
|
+
const config = await (options.loadConfig ?? loadConfig)(configPath);
|
|
7335
|
+
return {
|
|
7336
|
+
id: "config",
|
|
7337
|
+
label: "Config",
|
|
7338
|
+
severity: "pass",
|
|
7339
|
+
summary: "configuration loaded",
|
|
7340
|
+
details: [`config path: ${configPath}`],
|
|
7341
|
+
metadata: {
|
|
7342
|
+
configPath,
|
|
7343
|
+
config
|
|
7344
|
+
}
|
|
7345
|
+
};
|
|
7346
|
+
} catch (error) {
|
|
7347
|
+
return {
|
|
7348
|
+
id: "config",
|
|
7349
|
+
label: "Config",
|
|
7350
|
+
severity: "fail",
|
|
7351
|
+
summary: "configuration is invalid",
|
|
7352
|
+
details: [`config path: ${configPath}`, `error: ${formatError3(error)}`]
|
|
7353
|
+
};
|
|
6761
7354
|
}
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
7355
|
+
}
|
|
7356
|
+
function formatError3(error) {
|
|
7357
|
+
if (error instanceof Error) {
|
|
7358
|
+
return error.message;
|
|
7359
|
+
}
|
|
7360
|
+
return String(error);
|
|
7361
|
+
}
|
|
7362
|
+
var init_config_check = __esm(async () => {
|
|
7363
|
+
init_load_config();
|
|
7364
|
+
await init_main();
|
|
7365
|
+
});
|
|
7366
|
+
|
|
7367
|
+
// src/doctor/checks/daemon-check.ts
|
|
7368
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7369
|
+
import { homedir as homedir6 } from "node:os";
|
|
7370
|
+
async function checkDaemon(options = {}) {
|
|
7371
|
+
const home = options.home ?? process.env.HOME ?? homedir6();
|
|
7372
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({ home });
|
|
7373
|
+
const controller = createDaemonController(paths, {
|
|
7374
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
7375
|
+
cliEntryPath: options.cliEntryPath ?? resolveCliEntryPath(),
|
|
7376
|
+
cwd: options.cwd ?? process.cwd(),
|
|
7377
|
+
env: options.env ?? process.env,
|
|
7378
|
+
isProcessRunning: options.isProcessRunning ?? defaultIsProcessRunning3
|
|
7379
|
+
});
|
|
7380
|
+
try {
|
|
7381
|
+
const status = await controller.getStatus();
|
|
7382
|
+
switch (status.state) {
|
|
7383
|
+
case "running":
|
|
7384
|
+
return {
|
|
7385
|
+
id: "daemon",
|
|
7386
|
+
label: "Daemon",
|
|
7387
|
+
severity: "pass",
|
|
7388
|
+
summary: "daemon is running",
|
|
7389
|
+
details: [`pid: ${status.pid}`],
|
|
7390
|
+
metadata: {
|
|
7391
|
+
paths,
|
|
7392
|
+
status
|
|
7393
|
+
}
|
|
7394
|
+
};
|
|
7395
|
+
case "stopped":
|
|
7396
|
+
return {
|
|
7397
|
+
id: "daemon",
|
|
7398
|
+
label: "Daemon",
|
|
7399
|
+
severity: "warn",
|
|
7400
|
+
summary: status.stale ? "daemon was stopped and stale runtime files were cleared" : "daemon is not running",
|
|
7401
|
+
details: status.stale ? ["stale runtime files were cleared"] : undefined,
|
|
7402
|
+
suggestions: ["run: weacpx start"],
|
|
7403
|
+
metadata: {
|
|
7404
|
+
paths,
|
|
7405
|
+
status
|
|
7406
|
+
}
|
|
7407
|
+
};
|
|
7408
|
+
case "indeterminate":
|
|
7409
|
+
return {
|
|
7410
|
+
id: "daemon",
|
|
7411
|
+
label: "Daemon",
|
|
7412
|
+
severity: "fail",
|
|
7413
|
+
summary: "daemon status is indeterminate",
|
|
7414
|
+
details: [`pid: ${status.pid}`, `reason: ${status.reason}`],
|
|
7415
|
+
metadata: {
|
|
7416
|
+
paths,
|
|
7417
|
+
status
|
|
7418
|
+
}
|
|
7419
|
+
};
|
|
7420
|
+
}
|
|
7421
|
+
return {
|
|
7422
|
+
id: "daemon",
|
|
7423
|
+
label: "Daemon",
|
|
7424
|
+
severity: "fail",
|
|
7425
|
+
summary: "daemon status lookup returned an unknown state",
|
|
7426
|
+
details: [`state: ${status.state ?? "unknown"}`],
|
|
7427
|
+
metadata: {
|
|
7428
|
+
paths
|
|
6767
7429
|
}
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
7430
|
+
};
|
|
7431
|
+
} catch (error) {
|
|
7432
|
+
return {
|
|
7433
|
+
id: "daemon",
|
|
7434
|
+
label: "Daemon",
|
|
7435
|
+
severity: "fail",
|
|
7436
|
+
summary: "daemon status could not be read",
|
|
7437
|
+
details: [
|
|
7438
|
+
`runtime dir: ${paths.runtimeDir}`,
|
|
7439
|
+
`pid file: ${paths.pidFile}`,
|
|
7440
|
+
`status file: ${paths.statusFile}`,
|
|
7441
|
+
`error: ${formatError4(error)}`
|
|
7442
|
+
],
|
|
7443
|
+
metadata: {
|
|
7444
|
+
paths
|
|
6772
7445
|
}
|
|
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));
|
|
7446
|
+
};
|
|
6779
7447
|
}
|
|
6780
|
-
|
|
6781
|
-
|
|
7448
|
+
}
|
|
7449
|
+
function defaultIsProcessRunning3(pid) {
|
|
7450
|
+
try {
|
|
7451
|
+
process.kill(pid, 0);
|
|
7452
|
+
return true;
|
|
7453
|
+
} catch {
|
|
7454
|
+
return false;
|
|
6782
7455
|
}
|
|
6783
7456
|
}
|
|
7457
|
+
function resolveCliEntryPath() {
|
|
7458
|
+
return process.argv[1] ?? fileURLToPath4(import.meta.url);
|
|
7459
|
+
}
|
|
7460
|
+
function formatError4(error) {
|
|
7461
|
+
return error instanceof Error ? error.message : String(error);
|
|
7462
|
+
}
|
|
7463
|
+
var init_daemon_check = __esm(() => {
|
|
7464
|
+
init_create_daemon_controller();
|
|
7465
|
+
init_daemon_files();
|
|
7466
|
+
});
|
|
6784
7467
|
|
|
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
|
-
|
|
7468
|
+
// src/doctor/checks/runtime-check.ts
|
|
7469
|
+
import { constants } from "node:fs";
|
|
7470
|
+
import { access as access3, stat as stat2 } from "node:fs/promises";
|
|
7471
|
+
import { dirname as dirname10 } from "node:path";
|
|
7472
|
+
import { homedir as homedir7 } from "node:os";
|
|
7473
|
+
async function checkRuntime(options = {}) {
|
|
7474
|
+
const home = options.home ?? process.env.HOME ?? homedir7();
|
|
7475
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({ home });
|
|
7476
|
+
const probe = options.probe ?? createRuntimeFsProbe();
|
|
7477
|
+
const platform = options.platform ?? process.platform;
|
|
7478
|
+
const checks = [
|
|
7479
|
+
await checkDirectoryCreatable("runtimeDir", paths.runtimeDir, probe, platform),
|
|
7480
|
+
await checkFileCreatable("pidFile", paths.pidFile, probe, platform),
|
|
7481
|
+
await checkFileCreatable("statusFile", paths.statusFile, probe, platform),
|
|
7482
|
+
await checkFileCreatable("stdoutLog", paths.stdoutLog, probe, platform),
|
|
7483
|
+
await checkFileCreatable("stderrLog", paths.stderrLog, probe, platform),
|
|
7484
|
+
await checkFileCreatable("appLog", paths.appLog, probe, platform)
|
|
7485
|
+
];
|
|
7486
|
+
const failure = checks.find((check) => !check.ok);
|
|
7487
|
+
if (failure) {
|
|
7488
|
+
return {
|
|
7489
|
+
id: "runtime",
|
|
7490
|
+
label: "Runtime",
|
|
7491
|
+
severity: "fail",
|
|
7492
|
+
summary: "daemon runtime paths are not usable",
|
|
7493
|
+
details: checks.map((check) => check.detail)
|
|
7494
|
+
};
|
|
6810
7495
|
}
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
return { state: "stopped", stale: true };
|
|
7496
|
+
return {
|
|
7497
|
+
id: "runtime",
|
|
7498
|
+
label: "Runtime",
|
|
7499
|
+
severity: "pass",
|
|
7500
|
+
summary: "daemon runtime paths are usable",
|
|
7501
|
+
details: checks.map((check) => check.detail),
|
|
7502
|
+
metadata: {
|
|
7503
|
+
paths
|
|
6820
7504
|
}
|
|
6821
|
-
|
|
6822
|
-
|
|
7505
|
+
};
|
|
7506
|
+
}
|
|
7507
|
+
function createRuntimeFsProbe() {
|
|
7508
|
+
return {
|
|
7509
|
+
stat: async (path11) => await stat2(path11),
|
|
7510
|
+
access: async (path11, mode) => await access3(path11, mode)
|
|
7511
|
+
};
|
|
7512
|
+
}
|
|
7513
|
+
async function checkDirectoryCreatable(label, path11, probe, platform) {
|
|
7514
|
+
try {
|
|
7515
|
+
const stats = await probe.stat(path11);
|
|
7516
|
+
if (!stats.isDirectory()) {
|
|
7517
|
+
return {
|
|
7518
|
+
ok: false,
|
|
7519
|
+
detail: `${label}: ${path11} (exists but is not a directory)`
|
|
7520
|
+
};
|
|
6823
7521
|
}
|
|
7522
|
+
await probe.access(path11, directoryAccessMode(platform));
|
|
6824
7523
|
return {
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
status
|
|
7524
|
+
ok: true,
|
|
7525
|
+
detail: `${label}: ${path11} (writable)`
|
|
6828
7526
|
};
|
|
6829
|
-
}
|
|
6830
|
-
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
7527
|
+
} catch (error) {
|
|
7528
|
+
if (!isMissingPathError(error)) {
|
|
7529
|
+
return {
|
|
7530
|
+
ok: false,
|
|
7531
|
+
detail: `${label}: ${path11} (unusable: ${formatError5(error)})`
|
|
7532
|
+
};
|
|
6834
7533
|
}
|
|
6835
|
-
|
|
6836
|
-
|
|
7534
|
+
const parentCheck = await checkCreatableAncestorDirectory(path11, probe, platform);
|
|
7535
|
+
if (!parentCheck.ok) {
|
|
7536
|
+
return {
|
|
7537
|
+
ok: false,
|
|
7538
|
+
detail: `${label}: ${path11} (parent not writable: ${parentCheck.blockingPath})`
|
|
7539
|
+
};
|
|
6837
7540
|
}
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
return { state: "started", pid };
|
|
7541
|
+
return {
|
|
7542
|
+
ok: true,
|
|
7543
|
+
detail: `${label}: ${path11} (creatable via ${parentCheck.creatableFrom})`
|
|
7544
|
+
};
|
|
6843
7545
|
}
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
7546
|
+
}
|
|
7547
|
+
async function checkFileCreatable(label, path11, probe, platform) {
|
|
7548
|
+
try {
|
|
7549
|
+
const stats = await probe.stat(path11);
|
|
7550
|
+
if (stats.isDirectory()) {
|
|
7551
|
+
return {
|
|
7552
|
+
ok: false,
|
|
7553
|
+
detail: `${label}: ${path11} (exists but is a directory)`
|
|
7554
|
+
};
|
|
6848
7555
|
}
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
7556
|
+
await probe.access(path11, constants.W_OK);
|
|
7557
|
+
return {
|
|
7558
|
+
ok: true,
|
|
7559
|
+
detail: `${label}: ${path11} (writable)`
|
|
7560
|
+
};
|
|
7561
|
+
} catch (error) {
|
|
7562
|
+
if (!isMissingPathError(error)) {
|
|
7563
|
+
return {
|
|
7564
|
+
ok: false,
|
|
7565
|
+
detail: `${label}: ${path11} (unusable: ${formatError5(error)})`
|
|
7566
|
+
};
|
|
6852
7567
|
}
|
|
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;
|
|
7568
|
+
const parentCheck = await checkCreatableAncestorDirectory(dirname10(path11), probe, platform);
|
|
7569
|
+
if (!parentCheck.ok) {
|
|
7570
|
+
return {
|
|
7571
|
+
ok: false,
|
|
7572
|
+
detail: `${label}: ${path11} (parent not writable: ${parentCheck.blockingPath})`
|
|
7573
|
+
};
|
|
6866
7574
|
}
|
|
7575
|
+
return {
|
|
7576
|
+
ok: true,
|
|
7577
|
+
detail: `${label}: ${path11} (creatable via ${parentCheck.creatableFrom})`
|
|
7578
|
+
};
|
|
6867
7579
|
}
|
|
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();
|
|
7580
|
+
}
|
|
7581
|
+
async function checkCreatableAncestorDirectory(path11, probe, platform) {
|
|
7582
|
+
try {
|
|
7583
|
+
const stats = await probe.stat(path11);
|
|
7584
|
+
if (!stats.isDirectory()) {
|
|
7585
|
+
return {
|
|
7586
|
+
ok: false,
|
|
7587
|
+
creatableFrom: path11,
|
|
7588
|
+
blockingPath: path11
|
|
7589
|
+
};
|
|
6889
7590
|
}
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
7591
|
+
await probe.access(path11, directoryAccessMode(platform));
|
|
7592
|
+
return {
|
|
7593
|
+
ok: true,
|
|
7594
|
+
creatableFrom: path11
|
|
7595
|
+
};
|
|
7596
|
+
} catch (error) {
|
|
7597
|
+
if (!isMissingPathError(error)) {
|
|
7598
|
+
return {
|
|
7599
|
+
ok: false,
|
|
7600
|
+
creatableFrom: path11,
|
|
7601
|
+
blockingPath: path11
|
|
7602
|
+
};
|
|
6899
7603
|
}
|
|
6900
|
-
|
|
6901
|
-
|
|
7604
|
+
const parent = dirname10(path11);
|
|
7605
|
+
if (parent === path11) {
|
|
7606
|
+
return {
|
|
7607
|
+
ok: false,
|
|
7608
|
+
creatableFrom: path11,
|
|
7609
|
+
blockingPath: path11
|
|
7610
|
+
};
|
|
6902
7611
|
}
|
|
6903
|
-
|
|
7612
|
+
const parentCheck = await checkCreatableAncestorDirectory(parent, probe, platform);
|
|
7613
|
+
if (!parentCheck.ok) {
|
|
7614
|
+
return parentCheck;
|
|
7615
|
+
}
|
|
7616
|
+
return {
|
|
7617
|
+
ok: true,
|
|
7618
|
+
creatableFrom: parentCheck.creatableFrom
|
|
7619
|
+
};
|
|
6904
7620
|
}
|
|
6905
7621
|
}
|
|
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
|
-
});
|
|
7622
|
+
function directoryAccessMode(platform) {
|
|
7623
|
+
return platform === "win32" ? constants.W_OK : DIRECTORY_USABLE;
|
|
6924
7624
|
}
|
|
6925
|
-
function
|
|
7625
|
+
function isMissingPathError(error) {
|
|
7626
|
+
return isErrnoError(error) && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
7627
|
+
}
|
|
7628
|
+
function isErrnoError(error) {
|
|
7629
|
+
return typeof error === "object" && error !== null && "code" in error;
|
|
7630
|
+
}
|
|
7631
|
+
function formatError5(error) {
|
|
7632
|
+
if (error instanceof Error) {
|
|
7633
|
+
return error.message;
|
|
7634
|
+
}
|
|
7635
|
+
return String(error);
|
|
7636
|
+
}
|
|
7637
|
+
var DIRECTORY_USABLE;
|
|
7638
|
+
var init_runtime_check = __esm(() => {
|
|
7639
|
+
init_daemon_files();
|
|
7640
|
+
DIRECTORY_USABLE = constants.W_OK | constants.X_OK;
|
|
7641
|
+
});
|
|
7642
|
+
|
|
7643
|
+
// src/doctor/checks/smoke-check.ts
|
|
7644
|
+
async function checkSmoke(options = {}, deps = {}) {
|
|
7645
|
+
const resolvedOptions = { ...options, ...deps };
|
|
7646
|
+
const runtimePaths = (resolvedOptions.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
6926
7647
|
try {
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
7648
|
+
const config = await (resolvedOptions.loadConfig ?? loadConfig)(runtimePaths.configPath);
|
|
7649
|
+
const agentSelection = selectAgent(config, resolvedOptions.agent);
|
|
7650
|
+
if (agentSelection.error) {
|
|
7651
|
+
return agentSelection.error;
|
|
7652
|
+
}
|
|
7653
|
+
const workspaceSelection = selectWorkspace(config, resolvedOptions.workspace);
|
|
7654
|
+
if (workspaceSelection.error) {
|
|
7655
|
+
return workspaceSelection.error;
|
|
7656
|
+
}
|
|
7657
|
+
const missingDefaults = [agentSelection.missingDefault, workspaceSelection.missingDefault].filter((value) => typeof value === "string");
|
|
7658
|
+
if (missingDefaults.length > 0) {
|
|
7659
|
+
return {
|
|
7660
|
+
id: "smoke",
|
|
7661
|
+
label: "Smoke",
|
|
7662
|
+
severity: "skip",
|
|
7663
|
+
summary: `smoke prerequisites missing: ${missingDefaults.join(", ")}`,
|
|
7664
|
+
details: [
|
|
7665
|
+
`config path: ${runtimePaths.configPath}`,
|
|
7666
|
+
...selectionDetails(agentSelection, workspaceSelection)
|
|
7667
|
+
],
|
|
7668
|
+
suggestions: ["configure at least one agent and one workspace before running --smoke"]
|
|
7669
|
+
};
|
|
7670
|
+
}
|
|
7671
|
+
const agent = agentSelection.value;
|
|
7672
|
+
const workspace = workspaceSelection.value;
|
|
7673
|
+
if (!agent || !workspace) {
|
|
7674
|
+
return {
|
|
7675
|
+
id: "smoke",
|
|
7676
|
+
label: "Smoke",
|
|
7677
|
+
severity: "skip",
|
|
7678
|
+
summary: "smoke prerequisites missing: agent, workspace",
|
|
7679
|
+
details: [
|
|
7680
|
+
`config path: ${runtimePaths.configPath}`,
|
|
7681
|
+
...selectionDetails(agentSelection, workspaceSelection)
|
|
7682
|
+
]
|
|
7683
|
+
};
|
|
7684
|
+
}
|
|
7685
|
+
const metadata = (resolvedOptions.resolveAcpxCommandMetadata ?? resolveAcpxCommandMetadata)({
|
|
7686
|
+
configuredCommand: config.transport.command
|
|
7687
|
+
});
|
|
7688
|
+
const transport = await (resolvedOptions.createTransport ?? defaultCreateTransport)({
|
|
7689
|
+
config,
|
|
7690
|
+
metadata,
|
|
7691
|
+
resolveBridgeEntryPath: resolvedOptions.resolveBridgeEntryPath,
|
|
7692
|
+
spawnAcpxBridgeClient: resolvedOptions.spawnAcpxBridgeClient
|
|
7693
|
+
});
|
|
7694
|
+
const session = buildSession({
|
|
7695
|
+
config,
|
|
7696
|
+
agent,
|
|
7697
|
+
workspace,
|
|
7698
|
+
now: resolvedOptions.now
|
|
7699
|
+
});
|
|
7700
|
+
try {
|
|
7701
|
+
await transport.ensureSession(session);
|
|
7702
|
+
const reply = await transport.prompt(session, SMOKE_PROMPT);
|
|
7703
|
+
const replyText = reply.text.trim();
|
|
7704
|
+
if (replyText.length === 0) {
|
|
7705
|
+
return {
|
|
7706
|
+
id: "smoke",
|
|
7707
|
+
label: "Smoke",
|
|
7708
|
+
severity: "fail",
|
|
7709
|
+
summary: "smoke prompt returned empty text",
|
|
7710
|
+
details: buildDetails3({
|
|
7711
|
+
runtimePaths,
|
|
7712
|
+
metadata,
|
|
7713
|
+
session,
|
|
7714
|
+
agentReason: agentSelection.reason,
|
|
7715
|
+
workspaceReason: workspaceSelection.reason,
|
|
7716
|
+
replyText,
|
|
7717
|
+
verbose: resolvedOptions.verbose
|
|
7718
|
+
})
|
|
7719
|
+
};
|
|
7720
|
+
}
|
|
7721
|
+
return {
|
|
7722
|
+
id: "smoke",
|
|
7723
|
+
label: "Smoke",
|
|
7724
|
+
severity: replyText === "ok" ? "pass" : "warn",
|
|
7725
|
+
summary: replyText === "ok" ? "smoke prompt succeeded and reply received" : "smoke prompt succeeded with non-ideal reply",
|
|
7726
|
+
details: buildDetails3({
|
|
7727
|
+
runtimePaths,
|
|
7728
|
+
metadata,
|
|
7729
|
+
session,
|
|
7730
|
+
agentReason: agentSelection.reason,
|
|
7731
|
+
workspaceReason: workspaceSelection.reason,
|
|
7732
|
+
replyText,
|
|
7733
|
+
verbose: resolvedOptions.verbose
|
|
7734
|
+
}),
|
|
7735
|
+
metadata: {
|
|
7736
|
+
agent: session.agent,
|
|
7737
|
+
workspace: session.workspace,
|
|
7738
|
+
transportSession: session.transportSession,
|
|
7739
|
+
replyText
|
|
7740
|
+
}
|
|
7741
|
+
};
|
|
7742
|
+
} finally {
|
|
7743
|
+
await transport.dispose?.();
|
|
7744
|
+
}
|
|
7745
|
+
} catch (error) {
|
|
7746
|
+
return {
|
|
7747
|
+
id: "smoke",
|
|
7748
|
+
label: "Smoke",
|
|
7749
|
+
severity: "fail",
|
|
7750
|
+
summary: "smoke transport probe failed",
|
|
7751
|
+
details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError6(error)}`]
|
|
7752
|
+
};
|
|
6931
7753
|
}
|
|
6932
7754
|
}
|
|
6933
|
-
function
|
|
6934
|
-
|
|
6935
|
-
|
|
7755
|
+
function selectAgent(config, explicitAgent) {
|
|
7756
|
+
if (explicitAgent) {
|
|
7757
|
+
if (!(explicitAgent in config.agents)) {
|
|
7758
|
+
return {
|
|
7759
|
+
reason: "explicit --agent",
|
|
7760
|
+
error: createSelectionFailure(`smoke agent not found: ${explicitAgent}`)
|
|
7761
|
+
};
|
|
7762
|
+
}
|
|
6936
7763
|
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
|
-
}
|
|
7764
|
+
value: explicitAgent,
|
|
7765
|
+
reason: "explicit --agent"
|
|
7766
|
+
};
|
|
7767
|
+
}
|
|
7768
|
+
const firstAgent = Object.keys(config.agents)[0];
|
|
7769
|
+
if (!firstAgent) {
|
|
7770
|
+
return {
|
|
7771
|
+
missingDefault: "agent",
|
|
7772
|
+
reason: "no configured agent available"
|
|
6959
7773
|
};
|
|
6960
7774
|
}
|
|
6961
7775
|
return {
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
7776
|
+
value: firstAgent,
|
|
7777
|
+
reason: "default first configured agent"
|
|
7778
|
+
};
|
|
7779
|
+
}
|
|
7780
|
+
function selectWorkspace(config, explicitWorkspace) {
|
|
7781
|
+
if (explicitWorkspace) {
|
|
7782
|
+
if (!(explicitWorkspace in config.workspaces)) {
|
|
7783
|
+
return {
|
|
7784
|
+
reason: "explicit --workspace",
|
|
7785
|
+
error: createSelectionFailure(`smoke workspace not found: ${explicitWorkspace}`)
|
|
7786
|
+
};
|
|
6970
7787
|
}
|
|
7788
|
+
return {
|
|
7789
|
+
value: explicitWorkspace,
|
|
7790
|
+
reason: "explicit --workspace"
|
|
7791
|
+
};
|
|
7792
|
+
}
|
|
7793
|
+
const firstWorkspace = Object.keys(config.workspaces)[0];
|
|
7794
|
+
if (!firstWorkspace) {
|
|
7795
|
+
return {
|
|
7796
|
+
missingDefault: "workspace",
|
|
7797
|
+
reason: "no configured workspace available"
|
|
7798
|
+
};
|
|
7799
|
+
}
|
|
7800
|
+
return {
|
|
7801
|
+
value: firstWorkspace,
|
|
7802
|
+
reason: "default first configured workspace"
|
|
6971
7803
|
};
|
|
6972
7804
|
}
|
|
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");
|
|
7805
|
+
function createSelectionFailure(summary) {
|
|
7806
|
+
return {
|
|
7807
|
+
id: "smoke",
|
|
7808
|
+
label: "Smoke",
|
|
7809
|
+
severity: "fail",
|
|
7810
|
+
summary
|
|
7811
|
+
};
|
|
6986
7812
|
}
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
7813
|
+
function selectionDetails(agentSelection, workspaceSelection) {
|
|
7814
|
+
const details = [];
|
|
7815
|
+
if (agentSelection.value) {
|
|
7816
|
+
details.push(`agent: ${agentSelection.value} (${agentSelection.reason})`);
|
|
7817
|
+
} else {
|
|
7818
|
+
details.push(`agent: unavailable (${agentSelection.reason})`);
|
|
6990
7819
|
}
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
7820
|
+
if (workspaceSelection.value) {
|
|
7821
|
+
details.push(`workspace: ${workspaceSelection.value} (${workspaceSelection.reason})`);
|
|
7822
|
+
} else {
|
|
7823
|
+
details.push(`workspace: unavailable (${workspaceSelection.reason})`);
|
|
7824
|
+
}
|
|
7825
|
+
return details;
|
|
6994
7826
|
}
|
|
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);
|
|
7827
|
+
function buildSession(options) {
|
|
7828
|
+
const timestamp = (options.now ?? (() => new Date))().getTime();
|
|
7829
|
+
const agentConfig = options.config.agents[options.agent];
|
|
7830
|
+
const workspaceConfig = options.config.workspaces[options.workspace];
|
|
7831
|
+
if (!agentConfig) {
|
|
7832
|
+
throw new Error(`smoke agent not found: ${options.agent}`);
|
|
7833
|
+
}
|
|
7834
|
+
if (!workspaceConfig) {
|
|
7835
|
+
throw new Error(`smoke workspace not found: ${options.workspace}`);
|
|
7836
|
+
}
|
|
7837
|
+
return {
|
|
7838
|
+
alias: "weacpx-doctor",
|
|
7839
|
+
agent: options.agent,
|
|
7840
|
+
...agentConfig.command ? { agentCommand: agentConfig.command } : {},
|
|
7841
|
+
workspace: options.workspace,
|
|
7842
|
+
transportSession: `weacpx-doctor-${timestamp}`,
|
|
7843
|
+
replyMode: options.config.wechat.replyMode,
|
|
7844
|
+
cwd: workspaceConfig.cwd
|
|
7845
|
+
};
|
|
7846
|
+
}
|
|
7847
|
+
async function defaultCreateTransport(options) {
|
|
7848
|
+
if (options.config.transport.type === "acpx-bridge") {
|
|
7849
|
+
const client = await (options.spawnAcpxBridgeClient ?? spawnAcpxBridgeClient)({
|
|
7850
|
+
acpxCommand: options.metadata.command,
|
|
7851
|
+
bridgeEntryPath: (options.resolveBridgeEntryPath ?? resolveBridgeEntryPath)(),
|
|
7852
|
+
permissionMode: options.config.transport.permissionMode,
|
|
7853
|
+
nonInteractivePermissions: options.config.transport.nonInteractivePermissions
|
|
7039
7854
|
});
|
|
7855
|
+
return new AcpxBridgeTransport(client);
|
|
7856
|
+
}
|
|
7857
|
+
return new AcpxCliTransport({
|
|
7858
|
+
...options.config.transport,
|
|
7859
|
+
command: options.metadata.command
|
|
7040
7860
|
});
|
|
7041
7861
|
}
|
|
7042
|
-
|
|
7043
|
-
|
|
7862
|
+
function buildDetails3(options) {
|
|
7863
|
+
const details = [
|
|
7864
|
+
`config path: ${options.runtimePaths.configPath}`,
|
|
7865
|
+
`agent: ${options.session.agent} (${options.agentReason})`,
|
|
7866
|
+
`workspace: ${options.session.workspace} (${options.workspaceReason})`,
|
|
7867
|
+
`transport session: ${options.session.transportSession}`,
|
|
7868
|
+
`reply: ${JSON.stringify(options.replyText)}`
|
|
7869
|
+
];
|
|
7870
|
+
if (options.verbose) {
|
|
7871
|
+
details.push(`transport type: ${options.session.agentCommand ? "agent-command" : "driver-default"}`);
|
|
7872
|
+
details.push(`acpx command: ${options.metadata.command}`);
|
|
7873
|
+
details.push(`acpx source: ${options.metadata.source}`);
|
|
7874
|
+
details.push(`smoke prompt: ${JSON.stringify(SMOKE_PROMPT)}`);
|
|
7875
|
+
}
|
|
7876
|
+
return details;
|
|
7044
7877
|
}
|
|
7045
|
-
|
|
7046
|
-
|
|
7047
|
-
}
|
|
7048
|
-
|
|
7878
|
+
function formatError6(error) {
|
|
7879
|
+
return error instanceof Error ? error.message : String(error);
|
|
7880
|
+
}
|
|
7881
|
+
var SMOKE_PROMPT = "Reply with exactly: ok";
|
|
7882
|
+
var init_smoke_check = __esm(async () => {
|
|
7883
|
+
init_load_config();
|
|
7884
|
+
init_resolve_acpx_command();
|
|
7885
|
+
init_acpx_bridge_client();
|
|
7886
|
+
init_acpx_cli_transport();
|
|
7887
|
+
await init_main();
|
|
7888
|
+
});
|
|
7889
|
+
|
|
7890
|
+
// src/doctor/checks/wechat-check.ts
|
|
7891
|
+
async function checkWechat(options = {}) {
|
|
7892
|
+
const ids = listWeixinAccountIds();
|
|
7893
|
+
const accounts = ids.map((accountId) => {
|
|
7049
7894
|
try {
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7895
|
+
return {
|
|
7896
|
+
accountId,
|
|
7897
|
+
account: resolveWeixinAccount(accountId)
|
|
7898
|
+
};
|
|
7899
|
+
} catch (error) {
|
|
7900
|
+
return {
|
|
7901
|
+
accountId,
|
|
7902
|
+
error: formatError7(error)
|
|
7903
|
+
};
|
|
7904
|
+
}
|
|
7905
|
+
});
|
|
7906
|
+
const configuredAccount = accounts.find((entry) => ("account" in entry) && entry.account.configured);
|
|
7907
|
+
const loggedIn = Boolean(configuredAccount);
|
|
7908
|
+
if (!loggedIn) {
|
|
7909
|
+
return {
|
|
7910
|
+
id: "wechat",
|
|
7911
|
+
label: "WeChat",
|
|
7912
|
+
severity: "warn",
|
|
7913
|
+
summary: "wechat is not logged in",
|
|
7914
|
+
details: buildVerboseDetails(false, options.verbose, accounts),
|
|
7915
|
+
suggestions: ["weacpx login"]
|
|
7916
|
+
};
|
|
7053
7917
|
}
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7918
|
+
return {
|
|
7919
|
+
id: "wechat",
|
|
7920
|
+
label: "WeChat",
|
|
7921
|
+
severity: "pass",
|
|
7922
|
+
summary: "wechat is logged in",
|
|
7923
|
+
details: buildVerboseDetails(true, options.verbose, accounts)
|
|
7924
|
+
};
|
|
7925
|
+
}
|
|
7926
|
+
function buildVerboseDetails(loggedIn, verbose, accounts) {
|
|
7927
|
+
if (!verbose) {
|
|
7058
7928
|
return;
|
|
7059
7929
|
}
|
|
7060
|
-
const
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7930
|
+
const details = [];
|
|
7931
|
+
details.push(`loggedIn: ${loggedIn}`);
|
|
7932
|
+
details.push(`accountIds: ${accounts.length > 0 ? accounts.map((entry) => entry.accountId).join(", ") : "(none)"}`);
|
|
7933
|
+
for (const entry of accounts) {
|
|
7934
|
+
if ("account" in entry) {
|
|
7935
|
+
details.push(`account[${entry.account.accountId}].configured: ${entry.account.configured}`);
|
|
7936
|
+
details.push(`account[${entry.account.accountId}].baseUrl: ${entry.account.baseUrl}`);
|
|
7937
|
+
continue;
|
|
7064
7938
|
}
|
|
7065
|
-
|
|
7939
|
+
details.push(`account[${entry.accountId}].resolveError: ${entry.error ?? "unknown"}`);
|
|
7066
7940
|
}
|
|
7067
|
-
|
|
7068
|
-
killProcess(targetPid, "SIGKILL");
|
|
7069
|
-
} catch {}
|
|
7941
|
+
return details;
|
|
7070
7942
|
}
|
|
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
|
-
});
|
|
7943
|
+
function formatError7(error) {
|
|
7944
|
+
return error instanceof Error ? error.message : String(error);
|
|
7077
7945
|
}
|
|
7946
|
+
var init_wechat_check = __esm(() => {
|
|
7947
|
+
init_weixin();
|
|
7948
|
+
});
|
|
7078
7949
|
|
|
7079
|
-
// src/
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7950
|
+
// src/doctor/render-doctor.ts
|
|
7951
|
+
function renderDoctor(report, options = {}) {
|
|
7952
|
+
return options.verbose ? renderVerboseDoctor(report) : renderDefaultDoctor(report);
|
|
7953
|
+
}
|
|
7954
|
+
function renderDefaultDoctor(report) {
|
|
7955
|
+
const lines = [];
|
|
7956
|
+
for (const check of report.checks) {
|
|
7957
|
+
lines.push(renderCheckLine(check));
|
|
7958
|
+
}
|
|
7959
|
+
lines.push(renderSummaryLine(report.checks));
|
|
7960
|
+
const suggestions = collectSuggestions(report.checks);
|
|
7961
|
+
if (suggestions.length > 0) {
|
|
7962
|
+
lines.push("Next steps:");
|
|
7963
|
+
for (const suggestion of suggestions) {
|
|
7964
|
+
lines.push(`- ${suggestion}`);
|
|
7965
|
+
}
|
|
7966
|
+
}
|
|
7967
|
+
return lines;
|
|
7968
|
+
}
|
|
7969
|
+
function renderVerboseDoctor(report) {
|
|
7970
|
+
const lines = [];
|
|
7971
|
+
for (const check of report.checks) {
|
|
7972
|
+
lines.push(renderCheckLine(check));
|
|
7973
|
+
for (const detail of check.details ?? []) {
|
|
7974
|
+
lines.push(` detail: ${detail}`);
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
lines.push(renderSummaryLine(report.checks));
|
|
7978
|
+
const suggestions = collectSuggestions(report.checks);
|
|
7979
|
+
if (suggestions.length > 0) {
|
|
7980
|
+
lines.push("Next steps:");
|
|
7981
|
+
for (const suggestion of suggestions) {
|
|
7982
|
+
lines.push(`- ${suggestion}`);
|
|
7983
|
+
}
|
|
7984
|
+
}
|
|
7985
|
+
return lines;
|
|
7986
|
+
}
|
|
7987
|
+
function renderCheckLine(check) {
|
|
7988
|
+
return `${SEVERITY_LABELS[check.severity]} ${check.label}: ${check.summary}`;
|
|
7989
|
+
}
|
|
7990
|
+
function renderSummaryLine(checks) {
|
|
7991
|
+
const counts = summarizeChecks(checks);
|
|
7992
|
+
return `Summary: PASS ${counts.pass}, WARN ${counts.warn}, FAIL ${counts.fail}, SKIP ${counts.skip}`;
|
|
7993
|
+
}
|
|
7994
|
+
function summarizeChecks(checks) {
|
|
7995
|
+
return checks.reduce((counts, check) => {
|
|
7996
|
+
counts[check.severity] += 1;
|
|
7997
|
+
return counts;
|
|
7998
|
+
}, { pass: 0, warn: 0, fail: 0, skip: 0 });
|
|
7999
|
+
}
|
|
8000
|
+
function collectSuggestions(checks) {
|
|
8001
|
+
const seen = new Set;
|
|
8002
|
+
const suggestions = [];
|
|
8003
|
+
for (const check of checks) {
|
|
8004
|
+
for (const suggestion of check.suggestions ?? []) {
|
|
8005
|
+
if (seen.has(suggestion)) {
|
|
8006
|
+
continue;
|
|
8007
|
+
}
|
|
8008
|
+
seen.add(suggestion);
|
|
8009
|
+
suggestions.push(suggestion);
|
|
8010
|
+
}
|
|
8011
|
+
}
|
|
8012
|
+
return suggestions;
|
|
8013
|
+
}
|
|
8014
|
+
var SEVERITY_LABELS;
|
|
8015
|
+
var init_render_doctor = __esm(() => {
|
|
8016
|
+
SEVERITY_LABELS = {
|
|
8017
|
+
pass: "PASS",
|
|
8018
|
+
warn: "WARN",
|
|
8019
|
+
fail: "FAIL",
|
|
8020
|
+
skip: "SKIP"
|
|
8021
|
+
};
|
|
8022
|
+
});
|
|
8023
|
+
|
|
8024
|
+
// src/doctor/doctor.ts
|
|
8025
|
+
import { homedir as homedir8 } from "node:os";
|
|
8026
|
+
import { join as join6 } from "node:path";
|
|
8027
|
+
async function runDoctor(options = {}, deps = {}) {
|
|
8028
|
+
const home = deps.home ?? process.env.HOME ?? homedir8();
|
|
8029
|
+
const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
|
|
8030
|
+
const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
|
|
8031
|
+
const checks = [];
|
|
8032
|
+
checks.push(await (deps.checkConfig ?? checkConfig)({
|
|
8033
|
+
loadConfig: sharedLoadConfig,
|
|
8034
|
+
resolveRuntimePaths: () => runtimePaths
|
|
8035
|
+
}));
|
|
8036
|
+
checks.push(await (deps.checkRuntime ?? checkRuntime)({
|
|
8037
|
+
home
|
|
8038
|
+
}));
|
|
8039
|
+
checks.push(await (deps.checkDaemon ?? checkDaemon)({
|
|
8040
|
+
home
|
|
8041
|
+
}));
|
|
8042
|
+
checks.push(await (deps.checkWechat ?? checkWechat)({
|
|
8043
|
+
verbose: options.verbose
|
|
8044
|
+
}));
|
|
8045
|
+
checks.push(await (deps.checkAcpx ?? checkAcpx)({
|
|
8046
|
+
verbose: options.verbose,
|
|
8047
|
+
loadConfig: sharedLoadConfig,
|
|
8048
|
+
resolveRuntimePaths: () => runtimePaths
|
|
8049
|
+
}));
|
|
8050
|
+
checks.push(await (deps.checkBridge ?? checkBridge)({
|
|
8051
|
+
verbose: options.verbose,
|
|
8052
|
+
loadConfig: sharedLoadConfig,
|
|
8053
|
+
resolveRuntimePaths: () => runtimePaths
|
|
8054
|
+
}));
|
|
8055
|
+
checks.push(options.smoke === true ? await (deps.checkSmoke ?? ((runOptions) => defaultCheckSmoke(runOptions, {
|
|
8056
|
+
resolveRuntimePaths: () => runtimePaths,
|
|
8057
|
+
loadConfig: sharedLoadConfig
|
|
8058
|
+
})))(options) : createSmokeSkipResult("smoke probe not requested"));
|
|
8059
|
+
const report = { checks };
|
|
8060
|
+
const output = (deps.renderDoctor ?? renderDoctor)(report, options);
|
|
7083
8061
|
return {
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
8062
|
+
report,
|
|
8063
|
+
output,
|
|
8064
|
+
exitCode: checks.some((check) => check.severity === "fail") ? 1 : 0
|
|
8065
|
+
};
|
|
8066
|
+
}
|
|
8067
|
+
function resolveDoctorRuntimePaths(home, resolver) {
|
|
8068
|
+
if (resolver) {
|
|
8069
|
+
return resolver();
|
|
8070
|
+
}
|
|
8071
|
+
if (depsUseExplicitRuntimeOverrides()) {
|
|
8072
|
+
return resolveRuntimePaths();
|
|
8073
|
+
}
|
|
8074
|
+
return {
|
|
8075
|
+
configPath: join6(home, ".weacpx", "config.json"),
|
|
8076
|
+
statePath: join6(home, ".weacpx", "state.json")
|
|
8077
|
+
};
|
|
8078
|
+
}
|
|
8079
|
+
function depsUseExplicitRuntimeOverrides() {
|
|
8080
|
+
return Boolean(process.env.WEACPX_CONFIG || process.env.WEACPX_STATE);
|
|
8081
|
+
}
|
|
8082
|
+
function createSharedLoadConfig(runtimePaths, loader) {
|
|
8083
|
+
let pending;
|
|
8084
|
+
return async (configPath) => {
|
|
8085
|
+
if (configPath !== runtimePaths.configPath) {
|
|
8086
|
+
return await loader(configPath);
|
|
8087
|
+
}
|
|
8088
|
+
pending ??= loader(configPath);
|
|
8089
|
+
return await pending;
|
|
8090
|
+
};
|
|
8091
|
+
}
|
|
8092
|
+
async function defaultCheckSmoke(options, deps) {
|
|
8093
|
+
return await checkSmoke(options, {
|
|
8094
|
+
resolveRuntimePaths: deps.resolveRuntimePaths,
|
|
8095
|
+
loadConfig: deps.loadConfig
|
|
8096
|
+
});
|
|
8097
|
+
}
|
|
8098
|
+
function createSmokeSkipResult(summary) {
|
|
8099
|
+
return {
|
|
8100
|
+
id: "smoke",
|
|
8101
|
+
label: "Smoke",
|
|
8102
|
+
severity: "skip",
|
|
8103
|
+
summary
|
|
7090
8104
|
};
|
|
7091
8105
|
}
|
|
8106
|
+
var init_doctor = __esm(async () => {
|
|
8107
|
+
init_load_config();
|
|
8108
|
+
init_daemon_check();
|
|
8109
|
+
init_runtime_check();
|
|
8110
|
+
init_wechat_check();
|
|
8111
|
+
init_render_doctor();
|
|
8112
|
+
await __promiseAll([
|
|
8113
|
+
init_main(),
|
|
8114
|
+
init_acpx_check(),
|
|
8115
|
+
init_bridge_check(),
|
|
8116
|
+
init_config_check(),
|
|
8117
|
+
init_smoke_check()
|
|
8118
|
+
]);
|
|
8119
|
+
});
|
|
8120
|
+
|
|
8121
|
+
// src/doctor/index.ts
|
|
8122
|
+
var exports_doctor = {};
|
|
8123
|
+
__export(exports_doctor, {
|
|
8124
|
+
main: () => main3
|
|
8125
|
+
});
|
|
8126
|
+
async function main3(options, deps = {}) {
|
|
8127
|
+
const result = await (deps.runDoctor ?? runDoctor)(options);
|
|
8128
|
+
const print = deps.print ?? ((line) => console.log(line));
|
|
8129
|
+
for (const line of result.output) {
|
|
8130
|
+
print(line);
|
|
8131
|
+
}
|
|
8132
|
+
return result.exitCode;
|
|
8133
|
+
}
|
|
8134
|
+
var init_doctor2 = __esm(async () => {
|
|
8135
|
+
await init_doctor();
|
|
8136
|
+
});
|
|
8137
|
+
|
|
8138
|
+
// src/cli.ts
|
|
8139
|
+
init_create_daemon_controller();
|
|
8140
|
+
init_daemon_files();
|
|
8141
|
+
import { homedir as homedir9 } from "node:os";
|
|
8142
|
+
import { sep } from "node:path";
|
|
8143
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
7092
8144
|
|
|
7093
8145
|
// src/daemon/daemon-runtime.ts
|
|
8146
|
+
init_daemon_status();
|
|
7094
8147
|
import { mkdir as mkdir4, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
|
|
7095
8148
|
import { dirname as dirname3 } from "node:path";
|
|
8149
|
+
|
|
7096
8150
|
class DaemonRuntime {
|
|
7097
8151
|
paths;
|
|
7098
8152
|
options;
|
|
@@ -7140,6 +8194,7 @@ class DaemonRuntime {
|
|
|
7140
8194
|
}
|
|
7141
8195
|
|
|
7142
8196
|
// src/cli.ts
|
|
8197
|
+
init_version();
|
|
7143
8198
|
init_consumer_lock();
|
|
7144
8199
|
var HELP_LINES = [
|
|
7145
8200
|
"用法:",
|
|
@@ -7148,13 +8203,26 @@ var HELP_LINES = [
|
|
|
7148
8203
|
"weacpx run - 前台运行",
|
|
7149
8204
|
"weacpx start - 后台启动",
|
|
7150
8205
|
"weacpx status - 查看状态",
|
|
7151
|
-
"weacpx stop - 停止服务"
|
|
8206
|
+
"weacpx stop - 停止服务",
|
|
8207
|
+
"weacpx doctor - 运行诊断",
|
|
8208
|
+
"weacpx version - 查看版本"
|
|
7152
8209
|
];
|
|
7153
8210
|
async function runCli(args, deps = {}) {
|
|
7154
8211
|
const command = args[0];
|
|
7155
8212
|
const print = deps.print ?? ((line) => console.log(line));
|
|
7156
|
-
const controller = deps.controller ?? createDefaultController();
|
|
7157
8213
|
switch (command) {
|
|
8214
|
+
case "version":
|
|
8215
|
+
case "--version":
|
|
8216
|
+
case "-v":
|
|
8217
|
+
print(readVersion());
|
|
8218
|
+
return 0;
|
|
8219
|
+
case "--help":
|
|
8220
|
+
case "-h": {
|
|
8221
|
+
for (const line of HELP_LINES) {
|
|
8222
|
+
print(line);
|
|
8223
|
+
}
|
|
8224
|
+
return 0;
|
|
8225
|
+
}
|
|
7158
8226
|
case "login":
|
|
7159
8227
|
await (deps.login ?? defaultLogin)();
|
|
7160
8228
|
return 0;
|
|
@@ -7164,7 +8232,18 @@ async function runCli(args, deps = {}) {
|
|
|
7164
8232
|
case "run":
|
|
7165
8233
|
await (deps.run ?? defaultRun)();
|
|
7166
8234
|
return 0;
|
|
8235
|
+
case "doctor": {
|
|
8236
|
+
const parsed = parseDoctorArgs(args.slice(1));
|
|
8237
|
+
if (!parsed.ok) {
|
|
8238
|
+
for (const line of HELP_LINES) {
|
|
8239
|
+
print(line);
|
|
8240
|
+
}
|
|
8241
|
+
return 1;
|
|
8242
|
+
}
|
|
8243
|
+
return await (deps.doctor ?? defaultDoctor)(parsed.options);
|
|
8244
|
+
}
|
|
7167
8245
|
case "start": {
|
|
8246
|
+
const controller = deps.controller ?? createDefaultController();
|
|
7168
8247
|
const result = await controller.start();
|
|
7169
8248
|
if (result.state === "already-running") {
|
|
7170
8249
|
print("weacpx 已在后台运行");
|
|
@@ -7176,6 +8255,7 @@ async function runCli(args, deps = {}) {
|
|
|
7176
8255
|
return 0;
|
|
7177
8256
|
}
|
|
7178
8257
|
case "status": {
|
|
8258
|
+
const controller = deps.controller ?? createDefaultController();
|
|
7179
8259
|
const status = await controller.getStatus();
|
|
7180
8260
|
if (status.state === "indeterminate") {
|
|
7181
8261
|
print("weacpx 进程仍在运行,但状态元数据缺失");
|
|
@@ -7198,6 +8278,7 @@ async function runCli(args, deps = {}) {
|
|
|
7198
8278
|
return 0;
|
|
7199
8279
|
}
|
|
7200
8280
|
case "stop": {
|
|
8281
|
+
const controller = deps.controller ?? createDefaultController();
|
|
7201
8282
|
const result = await controller.stop();
|
|
7202
8283
|
if (result.detail === "not-running") {
|
|
7203
8284
|
print("weacpx 未运行");
|
|
@@ -7214,8 +8295,8 @@ async function runCli(args, deps = {}) {
|
|
|
7214
8295
|
}
|
|
7215
8296
|
}
|
|
7216
8297
|
async function defaultLogin() {
|
|
7217
|
-
const { main:
|
|
7218
|
-
await
|
|
8298
|
+
const { main: main4 } = await init_login().then(() => exports_login);
|
|
8299
|
+
await main4();
|
|
7219
8300
|
}
|
|
7220
8301
|
async function defaultLogout() {
|
|
7221
8302
|
const { logout: logout2 } = await Promise.resolve().then(() => (init_weixin_sdk(), exports_weixin_sdk));
|
|
@@ -7232,7 +8313,7 @@ async function defaultRun() {
|
|
|
7232
8313
|
const daemonRuntime = new DaemonRuntime(daemonPaths, { pid: process.pid });
|
|
7233
8314
|
await runConsole2(runtimePaths, {
|
|
7234
8315
|
buildApp: (paths) => buildApp2(paths, {
|
|
7235
|
-
defaultLoggingLevel:
|
|
8316
|
+
defaultLoggingLevel: resolveCliEntryPath2().includes(`${sep}src${sep}`) ? "debug" : "info"
|
|
7236
8317
|
}),
|
|
7237
8318
|
loadWeixinSdk: loadWeixinSdk2,
|
|
7238
8319
|
daemonRuntime,
|
|
@@ -7244,27 +8325,66 @@ async function defaultRun() {
|
|
|
7244
8325
|
})
|
|
7245
8326
|
});
|
|
7246
8327
|
}
|
|
8328
|
+
async function defaultDoctor(options) {
|
|
8329
|
+
const { main: main4 } = await init_doctor2().then(() => exports_doctor);
|
|
8330
|
+
return await main4(options);
|
|
8331
|
+
}
|
|
7247
8332
|
function createDefaultController() {
|
|
7248
8333
|
const daemonPaths = resolveDaemonPaths({ home: requireHome() });
|
|
7249
8334
|
return createDaemonController(daemonPaths, {
|
|
7250
8335
|
processExecPath: process.execPath,
|
|
7251
|
-
cliEntryPath:
|
|
8336
|
+
cliEntryPath: resolveCliEntryPath2(),
|
|
7252
8337
|
cwd: process.cwd(),
|
|
7253
8338
|
env: process.env
|
|
7254
8339
|
});
|
|
7255
8340
|
}
|
|
7256
8341
|
function requireHome() {
|
|
7257
|
-
const home = process.env.HOME ??
|
|
8342
|
+
const home = process.env.HOME ?? homedir9();
|
|
7258
8343
|
if (!home) {
|
|
7259
8344
|
throw new Error("Unable to resolve the current user home directory");
|
|
7260
8345
|
}
|
|
7261
8346
|
return home;
|
|
7262
8347
|
}
|
|
7263
|
-
function
|
|
8348
|
+
function resolveCliEntryPath2() {
|
|
7264
8349
|
if (process.argv[1]) {
|
|
7265
8350
|
return process.argv[1];
|
|
7266
8351
|
}
|
|
7267
|
-
return
|
|
8352
|
+
return fileURLToPath5(import.meta.url);
|
|
8353
|
+
}
|
|
8354
|
+
function parseDoctorArgs(args) {
|
|
8355
|
+
const options = {};
|
|
8356
|
+
for (let index = 0;index < args.length; index++) {
|
|
8357
|
+
const arg = args[index];
|
|
8358
|
+
switch (arg) {
|
|
8359
|
+
case "--verbose":
|
|
8360
|
+
options.verbose = true;
|
|
8361
|
+
break;
|
|
8362
|
+
case "--smoke":
|
|
8363
|
+
options.smoke = true;
|
|
8364
|
+
break;
|
|
8365
|
+
case "--agent": {
|
|
8366
|
+
const value = args[index + 1];
|
|
8367
|
+
if (!value || value.startsWith("--")) {
|
|
8368
|
+
return { ok: false };
|
|
8369
|
+
}
|
|
8370
|
+
options.agent = value;
|
|
8371
|
+
index++;
|
|
8372
|
+
break;
|
|
8373
|
+
}
|
|
8374
|
+
case "--workspace": {
|
|
8375
|
+
const value = args[index + 1];
|
|
8376
|
+
if (!value || value.startsWith("--")) {
|
|
8377
|
+
return { ok: false };
|
|
8378
|
+
}
|
|
8379
|
+
options.workspace = value;
|
|
8380
|
+
index++;
|
|
8381
|
+
break;
|
|
8382
|
+
}
|
|
8383
|
+
default:
|
|
8384
|
+
return { ok: false };
|
|
8385
|
+
}
|
|
8386
|
+
}
|
|
8387
|
+
return { ok: true, options };
|
|
7268
8388
|
}
|
|
7269
8389
|
if (__require.main == __require.module) {
|
|
7270
8390
|
process.exitCode = await runCli(process.argv.slice(2));
|