@ganglion/xacpx 0.10.0 → 0.10.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 +2 -0
- package/dist/cli.js +822 -201
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -258,6 +258,7 @@ xacpx doctor
|
|
|
258
258
|
xacpx doctor --verbose
|
|
259
259
|
xacpx doctor --smoke
|
|
260
260
|
xacpx doctor --smoke --agent codex --workspace backend
|
|
261
|
+
xacpx doctor --fix
|
|
261
262
|
```
|
|
262
263
|
|
|
263
264
|
Notes:
|
|
@@ -266,6 +267,7 @@ Notes:
|
|
|
266
267
|
- `--smoke` additionally runs a minimal real transport-level prompt check
|
|
267
268
|
- `--agent` / `--workspace` only affect `--smoke`
|
|
268
269
|
- Without `--smoke`, the related checks show as `SKIP`
|
|
270
|
+
- `--fix` applies safe local repairs (runtime dir permissions, stale locks, invalid state records) and re-checks; state-mutating repairs are withheld while the daemon runs — see [docs/doctor-command.md](docs/doctor-command.md)
|
|
269
271
|
|
|
270
272
|
### How to use `update`
|
|
271
273
|
|
package/dist/cli.js
CHANGED
|
@@ -5301,7 +5301,7 @@ class DaemonController {
|
|
|
5301
5301
|
this.deps = deps;
|
|
5302
5302
|
this.statusStore = new DaemonStatusStore(paths.statusFile);
|
|
5303
5303
|
this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
|
|
5304
|
-
this.startupTimeoutMs = deps.startupTimeoutMs ??
|
|
5304
|
+
this.startupTimeoutMs = deps.startupTimeoutMs ?? 1e4;
|
|
5305
5305
|
this.onboardingStartupTimeoutMs = deps.onboardingStartupTimeoutMs ?? 300000;
|
|
5306
5306
|
this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
|
|
5307
5307
|
this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
|
|
@@ -5696,6 +5696,14 @@ function resolveRuntimeDirFromConfigPath(configPath) {
|
|
|
5696
5696
|
function resolveDaemonOrchestrationSocketPath(runtimeDir, platform = process.platform) {
|
|
5697
5697
|
return resolveOrchestrationEndpoint(runtimeDir, platform).path;
|
|
5698
5698
|
}
|
|
5699
|
+
function isProcessAlive(pid) {
|
|
5700
|
+
try {
|
|
5701
|
+
process.kill(pid, 0);
|
|
5702
|
+
return true;
|
|
5703
|
+
} catch (error) {
|
|
5704
|
+
return error.code === "EPERM";
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5699
5707
|
var init_daemon_files = __esm(() => {
|
|
5700
5708
|
init_core_home();
|
|
5701
5709
|
init_orchestration_ipc();
|
|
@@ -19815,6 +19823,122 @@ var init_plugin_loader = __esm(() => {
|
|
|
19815
19823
|
init_plugin_home();
|
|
19816
19824
|
});
|
|
19817
19825
|
|
|
19826
|
+
// src/plugins/plugin-doctor.ts
|
|
19827
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
19828
|
+
import { join as join12 } from "node:path";
|
|
19829
|
+
function suggestedPluginPackageForChannel(type) {
|
|
19830
|
+
return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
|
|
19831
|
+
}
|
|
19832
|
+
async function readDependencyEntries(pluginHome) {
|
|
19833
|
+
try {
|
|
19834
|
+
const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
|
|
19835
|
+
const parsed = JSON.parse(raw);
|
|
19836
|
+
const out = {};
|
|
19837
|
+
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
19838
|
+
if (typeof value === "string")
|
|
19839
|
+
out[name] = value;
|
|
19840
|
+
}
|
|
19841
|
+
return out;
|
|
19842
|
+
} catch (error2) {
|
|
19843
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19844
|
+
throw new Error(`failed to read plugin home package.json: ${message}`);
|
|
19845
|
+
}
|
|
19846
|
+
}
|
|
19847
|
+
async function inspectPlugins(input) {
|
|
19848
|
+
const issues = [];
|
|
19849
|
+
let dependencies;
|
|
19850
|
+
try {
|
|
19851
|
+
dependencies = await readDependencyEntries(input.pluginHome);
|
|
19852
|
+
} catch (error2) {
|
|
19853
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19854
|
+
return [{ level: "error", message }];
|
|
19855
|
+
}
|
|
19856
|
+
const importPlugin = input.importPlugin ?? importPluginFromHome;
|
|
19857
|
+
const allConfigured = input.config.plugins;
|
|
19858
|
+
const filterByName = input.pluginName ? normalizePluginPackageName(input.pluginName) : null;
|
|
19859
|
+
if (filterByName && !allConfigured.some((plugin) => normalizePluginPackageName(plugin.name) === filterByName)) {
|
|
19860
|
+
return [{ level: "error", plugin: filterByName, message: `plugin is not configured; run xacpx plugin add ${filterByName}` }];
|
|
19861
|
+
}
|
|
19862
|
+
const pushIfRelevant = (issue2) => {
|
|
19863
|
+
if (!filterByName || issue2.plugin === filterByName)
|
|
19864
|
+
issues.push(issue2);
|
|
19865
|
+
};
|
|
19866
|
+
const channelProviders = new Map;
|
|
19867
|
+
for (const configPlugin of allConfigured) {
|
|
19868
|
+
if (!(configPlugin.name in dependencies)) {
|
|
19869
|
+
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `package not installed in plugin home; run xacpx plugin add ${configPlugin.name}`, suggestion: `xacpx plugin add ${configPlugin.name} && xacpx restart` });
|
|
19870
|
+
continue;
|
|
19871
|
+
}
|
|
19872
|
+
let moduleValue;
|
|
19873
|
+
try {
|
|
19874
|
+
moduleValue = await importPlugin(configPlugin.name, input.pluginHome);
|
|
19875
|
+
} catch (error2) {
|
|
19876
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19877
|
+
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `failed to import plugin: ${message}`, suggestion: `xacpx plugin add ${configPlugin.name} && xacpx restart` });
|
|
19878
|
+
continue;
|
|
19879
|
+
}
|
|
19880
|
+
try {
|
|
19881
|
+
const plugin = validateWeacpxPlugin(moduleValue, configPlugin.name, {
|
|
19882
|
+
...input.currentXacpxVersion !== undefined ? { currentXacpxVersion: input.currentXacpxVersion } : {}
|
|
19883
|
+
});
|
|
19884
|
+
const channels = plugin.channels ?? [];
|
|
19885
|
+
const channelTypes = channels.map((channel) => channel.type);
|
|
19886
|
+
for (const type of channelTypes) {
|
|
19887
|
+
const existing = channelProviders.get(type);
|
|
19888
|
+
if (existing) {
|
|
19889
|
+
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `channel type ${type} is already provided by ${existing.plugin}` });
|
|
19890
|
+
} else {
|
|
19891
|
+
channelProviders.set(type, { plugin: configPlugin.name, enabled: configPlugin.enabled });
|
|
19892
|
+
}
|
|
19893
|
+
}
|
|
19894
|
+
pushIfRelevant({
|
|
19895
|
+
level: configPlugin.enabled ? "ok" : "warn",
|
|
19896
|
+
plugin: configPlugin.name,
|
|
19897
|
+
message: configPlugin.enabled ? `plugin is installed and valid; channels: ${channelTypes.length > 0 ? channelTypes.join(", ") : "none"}` : `plugin is installed and valid but disabled; run xacpx plugin enable ${configPlugin.name}`,
|
|
19898
|
+
...configPlugin.enabled ? {} : { suggestion: `xacpx plugin enable ${configPlugin.name}` }
|
|
19899
|
+
});
|
|
19900
|
+
} catch (error2) {
|
|
19901
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
19902
|
+
pushIfRelevant({ level: "error", plugin: configPlugin.name, message });
|
|
19903
|
+
}
|
|
19904
|
+
}
|
|
19905
|
+
const builtInChannelTypes = new Set(listKnownChannelIds());
|
|
19906
|
+
for (const channel of input.config.channels) {
|
|
19907
|
+
if (channel.enabled === false)
|
|
19908
|
+
continue;
|
|
19909
|
+
if (builtInChannelTypes.has(channel.type))
|
|
19910
|
+
continue;
|
|
19911
|
+
const provider = channelProviders.get(channel.type);
|
|
19912
|
+
if (!provider) {
|
|
19913
|
+
if (!filterByName) {
|
|
19914
|
+
const suggestedPackage = suggestedPluginPackageForChannel(channel.type);
|
|
19915
|
+
issues.push({
|
|
19916
|
+
level: "error",
|
|
19917
|
+
message: `channel ${channel.type} is configured but no enabled plugin provides it; run xacpx plugin add ${suggestedPackage} or another plugin that provides type "${channel.type}"`,
|
|
19918
|
+
suggestion: `xacpx plugin add ${suggestedPackage}`
|
|
19919
|
+
});
|
|
19920
|
+
}
|
|
19921
|
+
continue;
|
|
19922
|
+
}
|
|
19923
|
+
if (!provider.enabled) {
|
|
19924
|
+
pushIfRelevant({
|
|
19925
|
+
level: "error",
|
|
19926
|
+
plugin: provider.plugin,
|
|
19927
|
+
message: `channel ${channel.type} is configured but provider plugin is disabled; run xacpx plugin enable ${provider.plugin}`,
|
|
19928
|
+
suggestion: `xacpx plugin enable ${provider.plugin}`
|
|
19929
|
+
});
|
|
19930
|
+
}
|
|
19931
|
+
}
|
|
19932
|
+
return issues;
|
|
19933
|
+
}
|
|
19934
|
+
var init_plugin_doctor = __esm(() => {
|
|
19935
|
+
init_channel_scope();
|
|
19936
|
+
init_plugin_loader();
|
|
19937
|
+
init_validate_plugin();
|
|
19938
|
+
init_known_plugins();
|
|
19939
|
+
init_plugin_renames();
|
|
19940
|
+
});
|
|
19941
|
+
|
|
19818
19942
|
// src/channels/bootstrap.ts
|
|
19819
19943
|
function bootstrapBuiltinChannels() {
|
|
19820
19944
|
bootstrapBuiltinChannelFactories();
|
|
@@ -29815,8 +29939,9 @@ async function runConsole(paths, deps) {
|
|
|
29815
29939
|
throw error2;
|
|
29816
29940
|
}
|
|
29817
29941
|
}
|
|
29818
|
-
|
|
29942
|
+
const reapPromise = Promise.resolve(runtime.reapStaleQueueOwners()).catch(() => {});
|
|
29819
29943
|
if (deps.beforeReady) {
|
|
29944
|
+
await reapPromise;
|
|
29820
29945
|
await deps.beforeReady(runtime);
|
|
29821
29946
|
}
|
|
29822
29947
|
if (deps.daemonRuntime) {
|
|
@@ -29830,6 +29955,7 @@ async function runConsole(paths, deps) {
|
|
|
29830
29955
|
deps.daemonRuntime?.heartbeat().catch(() => {});
|
|
29831
29956
|
}, deps.heartbeatIntervalMs ?? 30000);
|
|
29832
29957
|
}
|
|
29958
|
+
await reapPromise;
|
|
29833
29959
|
const channelStartPromise = deps.channels.startAll({
|
|
29834
29960
|
agent: runtime.agent,
|
|
29835
29961
|
abortSignal: shutdownController.signal,
|
|
@@ -33161,8 +33287,10 @@ var init_config_check = __esm(async () => {
|
|
|
33161
33287
|
});
|
|
33162
33288
|
|
|
33163
33289
|
// src/doctor/checks/daemon-check.ts
|
|
33290
|
+
import { readdir as readdir3, readFile as readFile14, rm as rm10 } from "node:fs/promises";
|
|
33164
33291
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
33165
33292
|
import { homedir as homedir10 } from "node:os";
|
|
33293
|
+
import { join as join19 } from "node:path";
|
|
33166
33294
|
async function checkDaemon(options = {}) {
|
|
33167
33295
|
const home = options.home ?? process.env.HOME ?? homedir10();
|
|
33168
33296
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
@@ -33170,15 +33298,25 @@ async function checkDaemon(options = {}) {
|
|
|
33170
33298
|
home,
|
|
33171
33299
|
...runtimeDir ? { runtimeDir } : {}
|
|
33172
33300
|
});
|
|
33301
|
+
const isProcessRunning = options.isProcessRunning ?? isProcessAlive;
|
|
33302
|
+
const listConsumerLocks = options.listConsumerLocks ?? defaultListConsumerLocks;
|
|
33303
|
+
const readConsumerLock = options.readConsumerLock ?? defaultReadConsumerLock;
|
|
33304
|
+
const removeConsumerLock = options.removeConsumerLock ?? defaultRemoveConsumerLock;
|
|
33173
33305
|
const controller = createDaemonController(paths, {
|
|
33174
33306
|
processExecPath: options.processExecPath ?? process.execPath,
|
|
33175
33307
|
cliEntryPath: options.cliEntryPath ?? resolveCliEntryPath(),
|
|
33176
33308
|
cwd: options.cwd ?? process.cwd(),
|
|
33177
33309
|
env: options.env ?? process.env,
|
|
33178
|
-
isProcessRunning
|
|
33310
|
+
isProcessRunning
|
|
33179
33311
|
});
|
|
33180
33312
|
try {
|
|
33181
33313
|
const status = await controller.getStatus();
|
|
33314
|
+
const staleLockFix = status.state === "stopped" ? await detectStaleConsumerLockFix(paths.runtimeDir, {
|
|
33315
|
+
isProcessRunning,
|
|
33316
|
+
listConsumerLocks,
|
|
33317
|
+
readConsumerLock,
|
|
33318
|
+
removeConsumerLock
|
|
33319
|
+
}) : undefined;
|
|
33182
33320
|
switch (status.state) {
|
|
33183
33321
|
case "running":
|
|
33184
33322
|
return {
|
|
@@ -33200,6 +33338,7 @@ async function checkDaemon(options = {}) {
|
|
|
33200
33338
|
summary: status.stale ? "daemon was stopped and stale runtime files were cleared" : "daemon is not running",
|
|
33201
33339
|
details: status.stale ? ["stale runtime files were cleared"] : undefined,
|
|
33202
33340
|
suggestions: ["run: xacpx start"],
|
|
33341
|
+
...staleLockFix ? { fixes: [staleLockFix] } : {},
|
|
33203
33342
|
metadata: {
|
|
33204
33343
|
paths,
|
|
33205
33344
|
status
|
|
@@ -33246,25 +33385,210 @@ async function checkDaemon(options = {}) {
|
|
|
33246
33385
|
};
|
|
33247
33386
|
}
|
|
33248
33387
|
}
|
|
33249
|
-
function
|
|
33388
|
+
async function detectStaleConsumerLockFix(runtimeDir, deps) {
|
|
33389
|
+
const lockFiles = await deps.listConsumerLocks(runtimeDir);
|
|
33390
|
+
const stalePaths = [];
|
|
33391
|
+
for (const fileName of lockFiles) {
|
|
33392
|
+
if (!fileName.endsWith(CONSUMER_LOCK_SUFFIX)) {
|
|
33393
|
+
continue;
|
|
33394
|
+
}
|
|
33395
|
+
const lockPath = join19(runtimeDir, fileName);
|
|
33396
|
+
const lock2 = await deps.readConsumerLock(lockPath);
|
|
33397
|
+
if (lock2 && !deps.isProcessRunning(lock2.pid)) {
|
|
33398
|
+
stalePaths.push(lockPath);
|
|
33399
|
+
}
|
|
33400
|
+
}
|
|
33401
|
+
if (stalePaths.length === 0) {
|
|
33402
|
+
return;
|
|
33403
|
+
}
|
|
33404
|
+
return {
|
|
33405
|
+
id: "daemon.clear-stale-lock",
|
|
33406
|
+
title: "remove stale consumer lock(s)",
|
|
33407
|
+
run: async () => {
|
|
33408
|
+
const removed = [];
|
|
33409
|
+
let skipped = 0;
|
|
33410
|
+
for (const lockPath of stalePaths) {
|
|
33411
|
+
const lock2 = await deps.readConsumerLock(lockPath);
|
|
33412
|
+
if (!lock2 || deps.isProcessRunning(lock2.pid)) {
|
|
33413
|
+
skipped += 1;
|
|
33414
|
+
continue;
|
|
33415
|
+
}
|
|
33416
|
+
await deps.removeConsumerLock(lockPath);
|
|
33417
|
+
removed.push(lockPath);
|
|
33418
|
+
}
|
|
33419
|
+
const skippedNote = skipped > 0 ? `; left ${skipped} no-longer-stale lock(s) alone` : "";
|
|
33420
|
+
return {
|
|
33421
|
+
ok: true,
|
|
33422
|
+
message: removed.length > 0 ? `removed ${removed.length} stale consumer lock(s): ${removed.join(", ")}${skippedNote}` : `no locks removed${skippedNote}`
|
|
33423
|
+
};
|
|
33424
|
+
}
|
|
33425
|
+
};
|
|
33426
|
+
}
|
|
33427
|
+
async function defaultListConsumerLocks(runtimeDir) {
|
|
33428
|
+
try {
|
|
33429
|
+
return await readdir3(runtimeDir);
|
|
33430
|
+
} catch {
|
|
33431
|
+
return [];
|
|
33432
|
+
}
|
|
33433
|
+
}
|
|
33434
|
+
async function defaultReadConsumerLock(path15) {
|
|
33250
33435
|
try {
|
|
33251
|
-
|
|
33252
|
-
|
|
33436
|
+
const raw = await readFile14(path15, "utf8");
|
|
33437
|
+
const parsed = JSON.parse(raw);
|
|
33438
|
+
return typeof parsed.pid === "number" ? { pid: parsed.pid } : null;
|
|
33253
33439
|
} catch {
|
|
33254
|
-
return
|
|
33440
|
+
return null;
|
|
33255
33441
|
}
|
|
33256
33442
|
}
|
|
33443
|
+
async function defaultRemoveConsumerLock(path15) {
|
|
33444
|
+
await rm10(path15, { force: true });
|
|
33445
|
+
}
|
|
33257
33446
|
function resolveCliEntryPath() {
|
|
33258
33447
|
return process.argv[1] ?? fileURLToPath6(import.meta.url);
|
|
33259
33448
|
}
|
|
33260
33449
|
function formatError5(error2) {
|
|
33261
33450
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
33262
33451
|
}
|
|
33452
|
+
var CONSUMER_LOCK_SUFFIX = "-consumer.lock.json";
|
|
33263
33453
|
var init_daemon_check = __esm(() => {
|
|
33264
33454
|
init_create_daemon_controller();
|
|
33265
33455
|
init_daemon_files();
|
|
33266
33456
|
});
|
|
33267
33457
|
|
|
33458
|
+
// src/doctor/checks/logs-check.ts
|
|
33459
|
+
import { stat as stat3, readdir as readdir4 } from "node:fs/promises";
|
|
33460
|
+
import { basename as basename3, join as join20 } from "node:path";
|
|
33461
|
+
import { homedir as homedir11 } from "node:os";
|
|
33462
|
+
async function checkLogs(options = {}) {
|
|
33463
|
+
const home = options.home ?? process.env.HOME ?? homedir11();
|
|
33464
|
+
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
33465
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
33466
|
+
home,
|
|
33467
|
+
...runtimeDir ? { runtimeDir } : {}
|
|
33468
|
+
});
|
|
33469
|
+
const probe = options.probe ?? createLogsFsProbe();
|
|
33470
|
+
const singleFileWarnBytes = options.singleFileWarnBytes ?? DEFAULT_SINGLE_FILE_WARN_BYTES;
|
|
33471
|
+
const totalWarnBytes = options.totalWarnBytes ?? DEFAULT_TOTAL_WARN_BYTES;
|
|
33472
|
+
let entries;
|
|
33473
|
+
try {
|
|
33474
|
+
const dirStat = await probe.stat(paths.runtimeDir);
|
|
33475
|
+
if (!dirStat.isDirectory()) {
|
|
33476
|
+
return skip("runtime log directory could not be read", [
|
|
33477
|
+
`runtimeDir: ${paths.runtimeDir} (exists but is not a directory)`
|
|
33478
|
+
]);
|
|
33479
|
+
}
|
|
33480
|
+
entries = await probe.readdir(paths.runtimeDir);
|
|
33481
|
+
} catch (error2) {
|
|
33482
|
+
if (isMissingPathError(error2)) {
|
|
33483
|
+
return skip("no runtime logs yet", [`runtimeDir: ${paths.runtimeDir} (missing)`]);
|
|
33484
|
+
}
|
|
33485
|
+
return skip("runtime log directory could not be read", [
|
|
33486
|
+
`runtimeDir: ${paths.runtimeDir}`,
|
|
33487
|
+
`error: ${formatError6(error2)}`
|
|
33488
|
+
]);
|
|
33489
|
+
}
|
|
33490
|
+
const baseNames = [basename3(paths.appLog), basename3(paths.stdoutLog), basename3(paths.stderrLog)];
|
|
33491
|
+
const tracked = new Set(baseNames);
|
|
33492
|
+
const matched = entries.filter((entry) => isTrackedLogName(entry, tracked));
|
|
33493
|
+
const files = [];
|
|
33494
|
+
for (const name of matched) {
|
|
33495
|
+
const path15 = join20(paths.runtimeDir, name);
|
|
33496
|
+
try {
|
|
33497
|
+
const fileStat = await probe.stat(path15);
|
|
33498
|
+
if (fileStat.isDirectory()) {
|
|
33499
|
+
continue;
|
|
33500
|
+
}
|
|
33501
|
+
files.push({ name, path: path15, size: fileStat.size });
|
|
33502
|
+
} catch {
|
|
33503
|
+
continue;
|
|
33504
|
+
}
|
|
33505
|
+
}
|
|
33506
|
+
const total = files.reduce((sum, file) => sum + file.size, 0);
|
|
33507
|
+
const largestSingle = files.reduce((max, file) => Math.max(max, file.size), 0);
|
|
33508
|
+
const overSingle = files.some((file) => file.size > singleFileWarnBytes);
|
|
33509
|
+
const overTotal = total > totalWarnBytes;
|
|
33510
|
+
const sorted = [...files].sort((a, b) => b.size - a.size);
|
|
33511
|
+
const details = [
|
|
33512
|
+
...sorted.map((file) => `${file.name}: ${formatBytes(file.size)}`),
|
|
33513
|
+
`total: ${formatBytes(total)}`
|
|
33514
|
+
];
|
|
33515
|
+
if (overSingle || overTotal) {
|
|
33516
|
+
const reason = overSingle ? `largest single log is ${formatBytes(largestSingle)}` : `total is ${formatBytes(total)}`;
|
|
33517
|
+
return {
|
|
33518
|
+
id: "logs",
|
|
33519
|
+
label: "Logs",
|
|
33520
|
+
severity: "warn",
|
|
33521
|
+
summary: `log growth high: ${reason} (total ${formatBytes(total)})`,
|
|
33522
|
+
details,
|
|
33523
|
+
suggestions: [
|
|
33524
|
+
"logs are large; check disk space and that log rotation is configured (logging.maxSizeBytes / maxFiles)"
|
|
33525
|
+
]
|
|
33526
|
+
};
|
|
33527
|
+
}
|
|
33528
|
+
return {
|
|
33529
|
+
id: "logs",
|
|
33530
|
+
label: "Logs",
|
|
33531
|
+
severity: "pass",
|
|
33532
|
+
summary: `logs total ${formatBytes(total)}`,
|
|
33533
|
+
details
|
|
33534
|
+
};
|
|
33535
|
+
}
|
|
33536
|
+
function skip(summary, details) {
|
|
33537
|
+
return {
|
|
33538
|
+
id: "logs",
|
|
33539
|
+
label: "Logs",
|
|
33540
|
+
severity: "skip",
|
|
33541
|
+
summary,
|
|
33542
|
+
details
|
|
33543
|
+
};
|
|
33544
|
+
}
|
|
33545
|
+
function isTrackedLogName(name, baseNames) {
|
|
33546
|
+
if (baseNames.has(name)) {
|
|
33547
|
+
return true;
|
|
33548
|
+
}
|
|
33549
|
+
for (const base of baseNames) {
|
|
33550
|
+
const prefix = `${base}.`;
|
|
33551
|
+
if (name.startsWith(prefix)) {
|
|
33552
|
+
const suffix = name.slice(prefix.length);
|
|
33553
|
+
if (/^\d+$/.test(suffix) && Number(suffix) > 0) {
|
|
33554
|
+
return true;
|
|
33555
|
+
}
|
|
33556
|
+
}
|
|
33557
|
+
}
|
|
33558
|
+
return false;
|
|
33559
|
+
}
|
|
33560
|
+
function formatBytes(bytes) {
|
|
33561
|
+
if (bytes < 1024) {
|
|
33562
|
+
return `${bytes} B`;
|
|
33563
|
+
}
|
|
33564
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
33565
|
+
let value = bytes / 1024;
|
|
33566
|
+
let unitIndex = 0;
|
|
33567
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
33568
|
+
value /= 1024;
|
|
33569
|
+
unitIndex += 1;
|
|
33570
|
+
}
|
|
33571
|
+
return `${value.toFixed(1)} ${units[unitIndex]}`;
|
|
33572
|
+
}
|
|
33573
|
+
function createLogsFsProbe() {
|
|
33574
|
+
return {
|
|
33575
|
+
stat: async (path15) => await stat3(path15),
|
|
33576
|
+
readdir: async (path15) => await readdir4(path15)
|
|
33577
|
+
};
|
|
33578
|
+
}
|
|
33579
|
+
function formatError6(error2) {
|
|
33580
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
33581
|
+
}
|
|
33582
|
+
function isMissingPathError(error2) {
|
|
33583
|
+
return typeof error2 === "object" && error2 !== null && "code" in error2 && (error2.code === "ENOENT" || error2.code === "ENOTDIR");
|
|
33584
|
+
}
|
|
33585
|
+
var DEFAULT_SINGLE_FILE_WARN_BYTES, DEFAULT_TOTAL_WARN_BYTES;
|
|
33586
|
+
var init_logs_check = __esm(() => {
|
|
33587
|
+
init_daemon_files();
|
|
33588
|
+
DEFAULT_SINGLE_FILE_WARN_BYTES = 50 * 1024 * 1024;
|
|
33589
|
+
DEFAULT_TOTAL_WARN_BYTES = 200 * 1024 * 1024;
|
|
33590
|
+
});
|
|
33591
|
+
|
|
33268
33592
|
// src/doctor/checks/orchestration-health.ts
|
|
33269
33593
|
async function checkOrchestrationHealth(options) {
|
|
33270
33594
|
const state = await options.loadState();
|
|
@@ -33317,13 +33641,187 @@ var init_orchestration_health = __esm(() => {
|
|
|
33317
33641
|
init_i18n();
|
|
33318
33642
|
});
|
|
33319
33643
|
|
|
33644
|
+
// src/doctor/checks/orchestration-socket-check.ts
|
|
33645
|
+
import { homedir as homedir12 } from "node:os";
|
|
33646
|
+
async function checkOrchestrationSocket(options = {}) {
|
|
33647
|
+
const home = options.home ?? process.env.HOME ?? homedir12();
|
|
33648
|
+
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
33649
|
+
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
33650
|
+
home,
|
|
33651
|
+
...runtimeDir ? { runtimeDir } : {}
|
|
33652
|
+
});
|
|
33653
|
+
const getDaemonStatus = options.getDaemonStatus ?? ((p) => defaultGetDaemonStatus(p, options));
|
|
33654
|
+
const probe = options.canConnectToEndpoint ?? canConnectToEndpoint;
|
|
33655
|
+
const resolveEndpoint = options.resolveOrchestrationEndpoint ?? ((dir) => resolveOrchestrationEndpoint(dir));
|
|
33656
|
+
let status;
|
|
33657
|
+
try {
|
|
33658
|
+
status = await getDaemonStatus(paths);
|
|
33659
|
+
} catch (error2) {
|
|
33660
|
+
return {
|
|
33661
|
+
id: "orchestration-socket",
|
|
33662
|
+
label: "Orchestration IPC",
|
|
33663
|
+
severity: "skip",
|
|
33664
|
+
summary: "daemon status could not be read",
|
|
33665
|
+
details: [`runtime dir: ${paths.runtimeDir}`, `error: ${formatError7(error2)}`]
|
|
33666
|
+
};
|
|
33667
|
+
}
|
|
33668
|
+
if (status.state === "stopped") {
|
|
33669
|
+
return {
|
|
33670
|
+
id: "orchestration-socket",
|
|
33671
|
+
label: "Orchestration IPC",
|
|
33672
|
+
severity: "skip",
|
|
33673
|
+
summary: "daemon stopped"
|
|
33674
|
+
};
|
|
33675
|
+
}
|
|
33676
|
+
let endpoint;
|
|
33677
|
+
let reachable;
|
|
33678
|
+
try {
|
|
33679
|
+
endpoint = resolveEndpoint(paths.runtimeDir);
|
|
33680
|
+
reachable = await probe(endpoint.path);
|
|
33681
|
+
} catch (error2) {
|
|
33682
|
+
return {
|
|
33683
|
+
id: "orchestration-socket",
|
|
33684
|
+
label: "Orchestration IPC",
|
|
33685
|
+
severity: "skip",
|
|
33686
|
+
summary: "orchestration IPC liveness could not be probed",
|
|
33687
|
+
details: [`runtime dir: ${paths.runtimeDir}`, `error: ${formatError7(error2)}`]
|
|
33688
|
+
};
|
|
33689
|
+
}
|
|
33690
|
+
if (reachable) {
|
|
33691
|
+
return {
|
|
33692
|
+
id: "orchestration-socket",
|
|
33693
|
+
label: "Orchestration IPC",
|
|
33694
|
+
severity: "pass",
|
|
33695
|
+
summary: "orchestration IPC is accepting connections",
|
|
33696
|
+
details: [`endpoint: ${endpoint.path}`]
|
|
33697
|
+
};
|
|
33698
|
+
}
|
|
33699
|
+
return {
|
|
33700
|
+
id: "orchestration-socket",
|
|
33701
|
+
label: "Orchestration IPC",
|
|
33702
|
+
severity: "fail",
|
|
33703
|
+
summary: "daemon is running but orchestration IPC is not accepting connections",
|
|
33704
|
+
details: [`endpoint: ${endpoint.path}`],
|
|
33705
|
+
suggestions: ["run: xacpx restart"]
|
|
33706
|
+
};
|
|
33707
|
+
}
|
|
33708
|
+
async function defaultGetDaemonStatus(paths, options) {
|
|
33709
|
+
const controller = createDaemonController(paths, {
|
|
33710
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
33711
|
+
cliEntryPath: options.cliEntryPath ?? process.argv[1] ?? "",
|
|
33712
|
+
cwd: options.cwd ?? process.cwd(),
|
|
33713
|
+
env: options.env ?? process.env,
|
|
33714
|
+
isProcessRunning: options.isProcessRunning ?? isProcessAlive
|
|
33715
|
+
});
|
|
33716
|
+
return await controller.getStatus();
|
|
33717
|
+
}
|
|
33718
|
+
function formatError7(error2) {
|
|
33719
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
33720
|
+
}
|
|
33721
|
+
var init_orchestration_socket_check = __esm(() => {
|
|
33722
|
+
init_create_daemon_controller();
|
|
33723
|
+
init_daemon_files();
|
|
33724
|
+
init_endpoint_probe();
|
|
33725
|
+
init_orchestration_ipc();
|
|
33726
|
+
});
|
|
33727
|
+
|
|
33728
|
+
// src/doctor/checks/plugin-check.ts
|
|
33729
|
+
async function checkPlugins(options = {}) {
|
|
33730
|
+
const runtimePaths = (options.resolveRuntimePaths ?? resolveRuntimePaths)();
|
|
33731
|
+
let config4;
|
|
33732
|
+
try {
|
|
33733
|
+
config4 = await (options.loadConfig ?? loadConfig)(runtimePaths.configPath);
|
|
33734
|
+
} catch (error2) {
|
|
33735
|
+
return {
|
|
33736
|
+
id: "plugins",
|
|
33737
|
+
label: "Plugins",
|
|
33738
|
+
severity: "skip",
|
|
33739
|
+
summary: "plugin check skipped because configuration could not be loaded",
|
|
33740
|
+
details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError8(error2)}`],
|
|
33741
|
+
suggestions: ["fix the Config check first, then run: xacpx doctor"]
|
|
33742
|
+
};
|
|
33743
|
+
}
|
|
33744
|
+
if (!hasPluginSurface(config4)) {
|
|
33745
|
+
return {
|
|
33746
|
+
id: "plugins",
|
|
33747
|
+
label: "Plugins",
|
|
33748
|
+
severity: "skip",
|
|
33749
|
+
summary: "no plugins configured"
|
|
33750
|
+
};
|
|
33751
|
+
}
|
|
33752
|
+
const pluginHome = (options.resolvePluginHome ?? resolvePluginHome)({ home: options.home });
|
|
33753
|
+
const inspect = options.inspectPlugins ?? inspectPlugins;
|
|
33754
|
+
let issues;
|
|
33755
|
+
try {
|
|
33756
|
+
issues = await inspect({
|
|
33757
|
+
config: config4,
|
|
33758
|
+
pluginHome,
|
|
33759
|
+
currentXacpxVersion: options.currentXacpxVersion ?? XACPX_CORE_VERSION
|
|
33760
|
+
});
|
|
33761
|
+
} catch (error2) {
|
|
33762
|
+
return {
|
|
33763
|
+
id: "plugins",
|
|
33764
|
+
label: "Plugins",
|
|
33765
|
+
severity: "fail",
|
|
33766
|
+
summary: "plugin health check failed",
|
|
33767
|
+
details: [`plugin home: ${pluginHome}`, `error: ${formatError8(error2)}`]
|
|
33768
|
+
};
|
|
33769
|
+
}
|
|
33770
|
+
const errorCount = issues.filter((issue2) => issue2.level === "error").length;
|
|
33771
|
+
const warnCount = issues.filter((issue2) => issue2.level === "warn").length;
|
|
33772
|
+
const severity = errorCount > 0 ? "fail" : warnCount > 0 ? "warn" : "pass";
|
|
33773
|
+
const problemCount = errorCount + warnCount;
|
|
33774
|
+
return {
|
|
33775
|
+
id: "plugins",
|
|
33776
|
+
label: "Plugins",
|
|
33777
|
+
severity,
|
|
33778
|
+
summary: problemCount > 0 ? `${problemCount} plugin issue(s)` : "all plugins healthy",
|
|
33779
|
+
details: issues.filter((issue2) => issue2.level !== "ok").map(formatIssueDetail),
|
|
33780
|
+
suggestions: collectSuggestions(issues),
|
|
33781
|
+
metadata: { pluginHome, errorCount, warnCount }
|
|
33782
|
+
};
|
|
33783
|
+
}
|
|
33784
|
+
function hasPluginSurface(config4) {
|
|
33785
|
+
if ((config4.plugins ?? []).length > 0) {
|
|
33786
|
+
return true;
|
|
33787
|
+
}
|
|
33788
|
+
const builtInChannelTypes = new Set(listKnownChannelIds());
|
|
33789
|
+
return (config4.channels ?? []).some((channel) => channel.enabled !== false && !builtInChannelTypes.has(channel.type));
|
|
33790
|
+
}
|
|
33791
|
+
function formatIssueDetail(issue2) {
|
|
33792
|
+
return issue2.plugin ? `${issue2.plugin}: ${issue2.message}` : issue2.message;
|
|
33793
|
+
}
|
|
33794
|
+
function collectSuggestions(issues) {
|
|
33795
|
+
const suggestions = [];
|
|
33796
|
+
const seen = new Set;
|
|
33797
|
+
for (const issue2 of issues) {
|
|
33798
|
+
const suggestion = issue2.suggestion;
|
|
33799
|
+
if (suggestion && !seen.has(suggestion)) {
|
|
33800
|
+
seen.add(suggestion);
|
|
33801
|
+
suggestions.push(`run: ${suggestion}`);
|
|
33802
|
+
}
|
|
33803
|
+
}
|
|
33804
|
+
return suggestions;
|
|
33805
|
+
}
|
|
33806
|
+
function formatError8(error2) {
|
|
33807
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
33808
|
+
}
|
|
33809
|
+
var init_plugin_check = __esm(async () => {
|
|
33810
|
+
init_load_config();
|
|
33811
|
+
init_channel_scope();
|
|
33812
|
+
init_plugin_doctor();
|
|
33813
|
+
init_plugin_home();
|
|
33814
|
+
init_version();
|
|
33815
|
+
await init_main();
|
|
33816
|
+
});
|
|
33817
|
+
|
|
33320
33818
|
// src/doctor/checks/runtime-check.ts
|
|
33321
33819
|
import { constants } from "node:fs";
|
|
33322
|
-
import { access as access4, stat as
|
|
33820
|
+
import { access as access4, stat as stat4 } from "node:fs/promises";
|
|
33323
33821
|
import { dirname as dirname13 } from "node:path";
|
|
33324
|
-
import { homedir as
|
|
33822
|
+
import { homedir as homedir13 } from "node:os";
|
|
33325
33823
|
async function checkRuntime(options = {}) {
|
|
33326
|
-
const home = options.home ?? process.env.HOME ??
|
|
33824
|
+
const home = options.home ?? process.env.HOME ?? homedir13();
|
|
33327
33825
|
const runtimeDir = options.configPath ? resolveRuntimeDirFromConfigPath(options.configPath) : undefined;
|
|
33328
33826
|
const paths = (options.resolveDaemonPaths ?? resolveDaemonPaths)({
|
|
33329
33827
|
home,
|
|
@@ -33349,6 +33847,20 @@ async function checkRuntime(options = {}) {
|
|
|
33349
33847
|
details: checks3.map((check) => check.detail)
|
|
33350
33848
|
};
|
|
33351
33849
|
}
|
|
33850
|
+
const privacy = await inspectRuntimeDirPrivacy(paths.runtimeDir, probe, platform);
|
|
33851
|
+
if (privacy.needsRepair) {
|
|
33852
|
+
return {
|
|
33853
|
+
id: "runtime",
|
|
33854
|
+
label: "Runtime",
|
|
33855
|
+
severity: "warn",
|
|
33856
|
+
summary: "daemon runtime dir should be private (mode 0700)",
|
|
33857
|
+
details: [...checks3.map((check) => check.detail), privacy.detail],
|
|
33858
|
+
fixes: [createEnsurePrivateDirFix(paths.runtimeDir, options.ensurePrivateRuntimeDir)],
|
|
33859
|
+
metadata: {
|
|
33860
|
+
paths
|
|
33861
|
+
}
|
|
33862
|
+
};
|
|
33863
|
+
}
|
|
33352
33864
|
return {
|
|
33353
33865
|
id: "runtime",
|
|
33354
33866
|
label: "Runtime",
|
|
@@ -33360,9 +33872,50 @@ async function checkRuntime(options = {}) {
|
|
|
33360
33872
|
}
|
|
33361
33873
|
};
|
|
33362
33874
|
}
|
|
33875
|
+
async function inspectRuntimeDirPrivacy(runtimeDir, probe, platform) {
|
|
33876
|
+
if (platform === "win32") {
|
|
33877
|
+
return { needsRepair: false, detail: "" };
|
|
33878
|
+
}
|
|
33879
|
+
try {
|
|
33880
|
+
const stats = await probe.stat(runtimeDir);
|
|
33881
|
+
if (typeof stats.mode !== "number") {
|
|
33882
|
+
return { needsRepair: false, detail: "" };
|
|
33883
|
+
}
|
|
33884
|
+
const mode = stats.mode & 511;
|
|
33885
|
+
if (mode === PRIVATE_DIR_MODE) {
|
|
33886
|
+
return { needsRepair: false, detail: "" };
|
|
33887
|
+
}
|
|
33888
|
+
return {
|
|
33889
|
+
needsRepair: true,
|
|
33890
|
+
detail: `runtimeDir: ${runtimeDir} (mode ${formatMode(mode)} is not 0700; group/other access should be removed)`
|
|
33891
|
+
};
|
|
33892
|
+
} catch (error2) {
|
|
33893
|
+
if (isMissingPathError2(error2)) {
|
|
33894
|
+
return {
|
|
33895
|
+
needsRepair: true,
|
|
33896
|
+
detail: `runtimeDir: ${runtimeDir} (missing; will be created with mode 0700)`
|
|
33897
|
+
};
|
|
33898
|
+
}
|
|
33899
|
+
return { needsRepair: false, detail: "" };
|
|
33900
|
+
}
|
|
33901
|
+
}
|
|
33902
|
+
function createEnsurePrivateDirFix(runtimeDir, ensureImpl) {
|
|
33903
|
+
const ensure = ensureImpl ?? ((dir) => ensurePrivateRuntimeDir(dir));
|
|
33904
|
+
return {
|
|
33905
|
+
id: "runtime.ensure-private-dir",
|
|
33906
|
+
title: "create/repair runtime dir with mode 0700",
|
|
33907
|
+
run: async () => {
|
|
33908
|
+
await ensure(runtimeDir);
|
|
33909
|
+
return { ok: true, message: `runtime dir ${runtimeDir} created/repaired with mode 0700` };
|
|
33910
|
+
}
|
|
33911
|
+
};
|
|
33912
|
+
}
|
|
33913
|
+
function formatMode(mode) {
|
|
33914
|
+
return `0${(mode & 511).toString(8)}`;
|
|
33915
|
+
}
|
|
33363
33916
|
function createRuntimeFsProbe() {
|
|
33364
33917
|
return {
|
|
33365
|
-
stat: async (path15) => await
|
|
33918
|
+
stat: async (path15) => await stat4(path15),
|
|
33366
33919
|
access: async (path15, mode) => await access4(path15, mode)
|
|
33367
33920
|
};
|
|
33368
33921
|
}
|
|
@@ -33381,10 +33934,10 @@ async function checkDirectoryCreatable(label, path15, probe, platform) {
|
|
|
33381
33934
|
detail: `${label}: ${path15} (writable)`
|
|
33382
33935
|
};
|
|
33383
33936
|
} catch (error2) {
|
|
33384
|
-
if (!
|
|
33937
|
+
if (!isMissingPathError2(error2)) {
|
|
33385
33938
|
return {
|
|
33386
33939
|
ok: false,
|
|
33387
|
-
detail: `${label}: ${path15} (unusable: ${
|
|
33940
|
+
detail: `${label}: ${path15} (unusable: ${formatError9(error2)})`
|
|
33388
33941
|
};
|
|
33389
33942
|
}
|
|
33390
33943
|
const parentCheck = await checkCreatableAncestorDirectory(path15, probe, platform);
|
|
@@ -33415,10 +33968,10 @@ async function checkFileCreatable(label, path15, probe, platform) {
|
|
|
33415
33968
|
detail: `${label}: ${path15} (writable)`
|
|
33416
33969
|
};
|
|
33417
33970
|
} catch (error2) {
|
|
33418
|
-
if (!
|
|
33971
|
+
if (!isMissingPathError2(error2)) {
|
|
33419
33972
|
return {
|
|
33420
33973
|
ok: false,
|
|
33421
|
-
detail: `${label}: ${path15} (unusable: ${
|
|
33974
|
+
detail: `${label}: ${path15} (unusable: ${formatError9(error2)})`
|
|
33422
33975
|
};
|
|
33423
33976
|
}
|
|
33424
33977
|
const parentCheck = await checkCreatableAncestorDirectory(dirname13(path15), probe, platform);
|
|
@@ -33450,7 +34003,7 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
|
|
|
33450
34003
|
creatableFrom: path15
|
|
33451
34004
|
};
|
|
33452
34005
|
} catch (error2) {
|
|
33453
|
-
if (!
|
|
34006
|
+
if (!isMissingPathError2(error2)) {
|
|
33454
34007
|
return {
|
|
33455
34008
|
ok: false,
|
|
33456
34009
|
creatableFrom: path15,
|
|
@@ -33478,21 +34031,22 @@ async function checkCreatableAncestorDirectory(path15, probe, platform) {
|
|
|
33478
34031
|
function directoryAccessMode(platform) {
|
|
33479
34032
|
return platform === "win32" ? constants.W_OK : DIRECTORY_USABLE;
|
|
33480
34033
|
}
|
|
33481
|
-
function
|
|
34034
|
+
function isMissingPathError2(error2) {
|
|
33482
34035
|
return isErrnoError(error2) && (error2.code === "ENOENT" || error2.code === "ENOTDIR");
|
|
33483
34036
|
}
|
|
33484
34037
|
function isErrnoError(error2) {
|
|
33485
34038
|
return typeof error2 === "object" && error2 !== null && "code" in error2;
|
|
33486
34039
|
}
|
|
33487
|
-
function
|
|
34040
|
+
function formatError9(error2) {
|
|
33488
34041
|
if (error2 instanceof Error) {
|
|
33489
34042
|
return error2.message;
|
|
33490
34043
|
}
|
|
33491
34044
|
return String(error2);
|
|
33492
34045
|
}
|
|
33493
|
-
var DIRECTORY_USABLE;
|
|
34046
|
+
var DIRECTORY_USABLE, PRIVATE_DIR_MODE = 448;
|
|
33494
34047
|
var init_runtime_check = __esm(() => {
|
|
33495
34048
|
init_daemon_files();
|
|
34049
|
+
init_private_runtime_dir();
|
|
33496
34050
|
DIRECTORY_USABLE = constants.W_OK | constants.X_OK;
|
|
33497
34051
|
});
|
|
33498
34052
|
|
|
@@ -33604,7 +34158,7 @@ async function checkSmoke(options = {}, deps = {}) {
|
|
|
33604
34158
|
label: "Smoke",
|
|
33605
34159
|
severity: "fail",
|
|
33606
34160
|
summary: "smoke transport probe failed",
|
|
33607
|
-
details: [`config path: ${runtimePaths.configPath}`, `error: ${
|
|
34161
|
+
details: [`config path: ${runtimePaths.configPath}`, `error: ${formatError10(error2)}`]
|
|
33608
34162
|
};
|
|
33609
34163
|
}
|
|
33610
34164
|
}
|
|
@@ -33731,7 +34285,7 @@ function buildDetails3(options) {
|
|
|
33731
34285
|
}
|
|
33732
34286
|
return details;
|
|
33733
34287
|
}
|
|
33734
|
-
function
|
|
34288
|
+
function formatError10(error2) {
|
|
33735
34289
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
33736
34290
|
}
|
|
33737
34291
|
var SMOKE_PROMPT = "Reply with exactly: ok";
|
|
@@ -33756,7 +34310,7 @@ async function checkWechat(options = {}) {
|
|
|
33756
34310
|
} catch (error2) {
|
|
33757
34311
|
return {
|
|
33758
34312
|
accountId,
|
|
33759
|
-
error:
|
|
34313
|
+
error: formatError11(error2)
|
|
33760
34314
|
};
|
|
33761
34315
|
}
|
|
33762
34316
|
});
|
|
@@ -33797,7 +34351,7 @@ function buildVerboseDetails(loggedIn, verbose, accounts) {
|
|
|
33797
34351
|
}
|
|
33798
34352
|
return details;
|
|
33799
34353
|
}
|
|
33800
|
-
function
|
|
34354
|
+
function formatError11(error2) {
|
|
33801
34355
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
33802
34356
|
}
|
|
33803
34357
|
var init_wechat_check = __esm(() => {
|
|
@@ -33806,43 +34360,60 @@ var init_wechat_check = __esm(() => {
|
|
|
33806
34360
|
|
|
33807
34361
|
// src/doctor/render-doctor.ts
|
|
33808
34362
|
function renderDoctor(report, options = {}) {
|
|
33809
|
-
|
|
34363
|
+
const fixMode = options.fix === true;
|
|
34364
|
+
return options.verbose ? renderVerboseDoctor(report, fixMode) : renderDefaultDoctor(report, fixMode);
|
|
33810
34365
|
}
|
|
33811
|
-
function renderDefaultDoctor(report) {
|
|
34366
|
+
function renderDefaultDoctor(report, fixMode) {
|
|
33812
34367
|
const lines = [];
|
|
33813
34368
|
for (const check of report.checks) {
|
|
33814
|
-
lines.push(renderCheckLine(check));
|
|
34369
|
+
lines.push(renderCheckLine(check, fixMode));
|
|
33815
34370
|
}
|
|
34371
|
+
appendRepairs(lines, report, fixMode);
|
|
33816
34372
|
lines.push(renderSummaryLine(report.checks));
|
|
33817
|
-
|
|
33818
|
-
if (suggestions.length > 0) {
|
|
33819
|
-
lines.push("Next steps:");
|
|
33820
|
-
for (const suggestion of suggestions) {
|
|
33821
|
-
lines.push(`- ${suggestion}`);
|
|
33822
|
-
}
|
|
33823
|
-
}
|
|
34373
|
+
appendNextSteps(lines, report.checks);
|
|
33824
34374
|
return lines;
|
|
33825
34375
|
}
|
|
33826
|
-
function renderVerboseDoctor(report) {
|
|
34376
|
+
function renderVerboseDoctor(report, fixMode) {
|
|
33827
34377
|
const lines = [];
|
|
33828
34378
|
for (const check of report.checks) {
|
|
33829
|
-
lines.push(renderCheckLine(check));
|
|
34379
|
+
lines.push(renderCheckLine(check, fixMode));
|
|
33830
34380
|
for (const detail of check.details ?? []) {
|
|
33831
34381
|
lines.push(` detail: ${detail}`);
|
|
33832
34382
|
}
|
|
33833
34383
|
}
|
|
34384
|
+
appendRepairs(lines, report, fixMode);
|
|
33834
34385
|
lines.push(renderSummaryLine(report.checks));
|
|
33835
|
-
|
|
34386
|
+
appendNextSteps(lines, report.checks);
|
|
34387
|
+
return lines;
|
|
34388
|
+
}
|
|
34389
|
+
function appendNextSteps(lines, checks3) {
|
|
34390
|
+
const suggestions = collectSuggestions2(checks3);
|
|
33836
34391
|
if (suggestions.length > 0) {
|
|
33837
34392
|
lines.push("Next steps:");
|
|
33838
34393
|
for (const suggestion of suggestions) {
|
|
33839
34394
|
lines.push(`- ${suggestion}`);
|
|
33840
34395
|
}
|
|
33841
34396
|
}
|
|
33842
|
-
return lines;
|
|
33843
34397
|
}
|
|
33844
|
-
function
|
|
33845
|
-
|
|
34398
|
+
function appendRepairs(lines, report, fixMode) {
|
|
34399
|
+
if (!fixMode) {
|
|
34400
|
+
return;
|
|
34401
|
+
}
|
|
34402
|
+
const repairs = report.repairs ?? [];
|
|
34403
|
+
if (repairs.length === 0) {
|
|
34404
|
+
return;
|
|
34405
|
+
}
|
|
34406
|
+
lines.push("Repairs:");
|
|
34407
|
+
for (const repair of repairs) {
|
|
34408
|
+
lines.push(`- ${repair.title}: ${repair.status} (${repair.message})`);
|
|
34409
|
+
}
|
|
34410
|
+
}
|
|
34411
|
+
function renderCheckLine(check, fixMode) {
|
|
34412
|
+
const base = `${SEVERITY_LABELS[check.severity]} ${check.label}: ${check.summary}`;
|
|
34413
|
+
if (!fixMode && (check.fixes?.length ?? 0) > 0) {
|
|
34414
|
+
return `${base} (fixable — run: xacpx doctor --fix)`;
|
|
34415
|
+
}
|
|
34416
|
+
return base;
|
|
33846
34417
|
}
|
|
33847
34418
|
function renderSummaryLine(checks3) {
|
|
33848
34419
|
const counts = summarizeChecks(checks3);
|
|
@@ -33854,7 +34425,7 @@ function summarizeChecks(checks3) {
|
|
|
33854
34425
|
return counts;
|
|
33855
34426
|
}, { pass: 0, warn: 0, fail: 0, skip: 0 });
|
|
33856
34427
|
}
|
|
33857
|
-
function
|
|
34428
|
+
function collectSuggestions2(checks3) {
|
|
33858
34429
|
const seen = new Set;
|
|
33859
34430
|
const suggestions = [];
|
|
33860
34431
|
for (const check of checks3) {
|
|
@@ -33879,47 +34450,112 @@ var init_render_doctor = __esm(() => {
|
|
|
33879
34450
|
});
|
|
33880
34451
|
|
|
33881
34452
|
// src/doctor/doctor.ts
|
|
33882
|
-
import { homedir as
|
|
33883
|
-
import { join as
|
|
34453
|
+
import { homedir as homedir14 } from "node:os";
|
|
34454
|
+
import { join as join21 } from "node:path";
|
|
33884
34455
|
async function runDoctor(options = {}, deps = {}) {
|
|
33885
|
-
const home = deps.home ?? process.env.HOME ??
|
|
34456
|
+
const home = deps.home ?? process.env.HOME ?? homedir14();
|
|
33886
34457
|
const runtimePaths = resolveDoctorRuntimePaths(home, deps.resolveRuntimePaths);
|
|
33887
34458
|
const sharedLoadConfig = createSharedLoadConfig(runtimePaths, deps.loadConfig ?? loadConfig);
|
|
34459
|
+
const runners = [
|
|
34460
|
+
{
|
|
34461
|
+
id: "config",
|
|
34462
|
+
run: () => (deps.checkConfig ?? checkConfig)({
|
|
34463
|
+
loadConfig: sharedLoadConfig,
|
|
34464
|
+
resolveRuntimePaths: () => runtimePaths
|
|
34465
|
+
})
|
|
34466
|
+
},
|
|
34467
|
+
{
|
|
34468
|
+
id: "runtime",
|
|
34469
|
+
run: () => (deps.checkRuntime ?? checkRuntime)({
|
|
34470
|
+
home,
|
|
34471
|
+
configPath: runtimePaths.configPath
|
|
34472
|
+
})
|
|
34473
|
+
},
|
|
34474
|
+
{
|
|
34475
|
+
id: "logs",
|
|
34476
|
+
run: () => (deps.checkLogs ?? checkLogs)({
|
|
34477
|
+
home,
|
|
34478
|
+
configPath: runtimePaths.configPath
|
|
34479
|
+
})
|
|
34480
|
+
},
|
|
34481
|
+
{
|
|
34482
|
+
id: "daemon",
|
|
34483
|
+
run: () => (deps.checkDaemon ?? checkDaemon)({
|
|
34484
|
+
home,
|
|
34485
|
+
configPath: runtimePaths.configPath
|
|
34486
|
+
})
|
|
34487
|
+
},
|
|
34488
|
+
{
|
|
34489
|
+
id: "wechat",
|
|
34490
|
+
run: () => (deps.checkWechat ?? checkWechat)({
|
|
34491
|
+
verbose: options.verbose
|
|
34492
|
+
})
|
|
34493
|
+
},
|
|
34494
|
+
{
|
|
34495
|
+
id: "acpx",
|
|
34496
|
+
run: () => (deps.checkAcpx ?? checkAcpx)({
|
|
34497
|
+
verbose: options.verbose,
|
|
34498
|
+
loadConfig: sharedLoadConfig,
|
|
34499
|
+
resolveRuntimePaths: () => runtimePaths
|
|
34500
|
+
})
|
|
34501
|
+
},
|
|
34502
|
+
{
|
|
34503
|
+
id: "bridge",
|
|
34504
|
+
run: () => (deps.checkBridge ?? checkBridge)({
|
|
34505
|
+
verbose: options.verbose,
|
|
34506
|
+
loadConfig: sharedLoadConfig,
|
|
34507
|
+
resolveRuntimePaths: () => runtimePaths
|
|
34508
|
+
})
|
|
34509
|
+
},
|
|
34510
|
+
{
|
|
34511
|
+
id: "plugins",
|
|
34512
|
+
run: () => (deps.checkPlugins ?? checkPlugins)({
|
|
34513
|
+
home,
|
|
34514
|
+
loadConfig: sharedLoadConfig,
|
|
34515
|
+
resolveRuntimePaths: () => runtimePaths
|
|
34516
|
+
})
|
|
34517
|
+
},
|
|
34518
|
+
{
|
|
34519
|
+
id: "orchestration",
|
|
34520
|
+
run: () => (deps.checkOrchestrationHealth ?? (() => defaultCheckOrchestrationHealth({
|
|
34521
|
+
runtimePaths,
|
|
34522
|
+
loadConfig: sharedLoadConfig,
|
|
34523
|
+
isDaemonRunning: deps.isDaemonRunning ?? (() => defaultIsDaemonRunning(home, runtimePaths, deps.getDaemonStatus))
|
|
34524
|
+
})))()
|
|
34525
|
+
},
|
|
34526
|
+
{
|
|
34527
|
+
id: "orchestration-socket",
|
|
34528
|
+
run: () => (deps.checkOrchestrationSocket ?? checkOrchestrationSocket)({
|
|
34529
|
+
home,
|
|
34530
|
+
configPath: runtimePaths.configPath
|
|
34531
|
+
})
|
|
34532
|
+
},
|
|
34533
|
+
{
|
|
34534
|
+
id: "smoke",
|
|
34535
|
+
run: () => options.smoke === true ? (deps.checkSmoke ?? ((runOptions) => defaultCheckSmoke(runOptions, {
|
|
34536
|
+
resolveRuntimePaths: () => runtimePaths,
|
|
34537
|
+
loadConfig: sharedLoadConfig
|
|
34538
|
+
})))(options) : Promise.resolve(createSmokeSkipResult("smoke probe not requested"))
|
|
34539
|
+
}
|
|
34540
|
+
];
|
|
34541
|
+
const runnersById = new Map(runners.map((runner) => [runner.id, runner.run]));
|
|
33888
34542
|
const checks3 = [];
|
|
33889
|
-
|
|
33890
|
-
|
|
33891
|
-
|
|
33892
|
-
}));
|
|
33893
|
-
checks3.push(await (deps.checkRuntime ?? checkRuntime)({
|
|
33894
|
-
home,
|
|
33895
|
-
configPath: runtimePaths.configPath
|
|
33896
|
-
}));
|
|
33897
|
-
checks3.push(await (deps.checkDaemon ?? checkDaemon)({
|
|
33898
|
-
home,
|
|
33899
|
-
configPath: runtimePaths.configPath
|
|
33900
|
-
}));
|
|
33901
|
-
checks3.push(await (deps.checkWechat ?? checkWechat)({
|
|
33902
|
-
verbose: options.verbose
|
|
33903
|
-
}));
|
|
33904
|
-
checks3.push(await (deps.checkAcpx ?? checkAcpx)({
|
|
33905
|
-
verbose: options.verbose,
|
|
33906
|
-
loadConfig: sharedLoadConfig,
|
|
33907
|
-
resolveRuntimePaths: () => runtimePaths
|
|
33908
|
-
}));
|
|
33909
|
-
checks3.push(await (deps.checkBridge ?? checkBridge)({
|
|
33910
|
-
verbose: options.verbose,
|
|
33911
|
-
loadConfig: sharedLoadConfig,
|
|
33912
|
-
resolveRuntimePaths: () => runtimePaths
|
|
33913
|
-
}));
|
|
33914
|
-
checks3.push(await (deps.checkOrchestrationHealth ?? (() => defaultCheckOrchestrationHealth({
|
|
33915
|
-
runtimePaths,
|
|
33916
|
-
loadConfig: sharedLoadConfig
|
|
33917
|
-
})))());
|
|
33918
|
-
checks3.push(options.smoke === true ? await (deps.checkSmoke ?? ((runOptions) => defaultCheckSmoke(runOptions, {
|
|
33919
|
-
resolveRuntimePaths: () => runtimePaths,
|
|
33920
|
-
loadConfig: sharedLoadConfig
|
|
33921
|
-
})))(options) : createSmokeSkipResult("smoke probe not requested"));
|
|
34543
|
+
for (const runner of runners) {
|
|
34544
|
+
checks3.push(await runner.run());
|
|
34545
|
+
}
|
|
33922
34546
|
const report = { checks: checks3 };
|
|
34547
|
+
if (options.fix === true) {
|
|
34548
|
+
const { repairs, repairedCheckIds } = await applyRepairs(checks3);
|
|
34549
|
+
report.repairs = repairs;
|
|
34550
|
+
for (const checkId of repairedCheckIds) {
|
|
34551
|
+
const index = checks3.findIndex((check) => check.id === checkId);
|
|
34552
|
+
const rerun = runnersById.get(checkId);
|
|
34553
|
+
if (index === -1 || !rerun) {
|
|
34554
|
+
continue;
|
|
34555
|
+
}
|
|
34556
|
+
checks3[index] = await rerun();
|
|
34557
|
+
}
|
|
34558
|
+
}
|
|
33923
34559
|
const output = (deps.renderDoctor ?? renderDoctor)(report, options);
|
|
33924
34560
|
return {
|
|
33925
34561
|
report,
|
|
@@ -33927,6 +34563,41 @@ async function runDoctor(options = {}, deps = {}) {
|
|
|
33927
34563
|
exitCode: checks3.some((check) => check.severity === "fail") ? 1 : 0
|
|
33928
34564
|
};
|
|
33929
34565
|
}
|
|
34566
|
+
async function applyRepairs(checks3) {
|
|
34567
|
+
const repairs = [];
|
|
34568
|
+
const repairedCheckIds = [];
|
|
34569
|
+
for (const check of checks3) {
|
|
34570
|
+
for (const fix of check.fixes ?? []) {
|
|
34571
|
+
if (fix.withheld !== undefined) {
|
|
34572
|
+
repairs.push({
|
|
34573
|
+
checkId: check.id,
|
|
34574
|
+
fixId: fix.id,
|
|
34575
|
+
title: fix.title,
|
|
34576
|
+
status: "skipped",
|
|
34577
|
+
message: fix.withheld
|
|
34578
|
+
});
|
|
34579
|
+
continue;
|
|
34580
|
+
}
|
|
34581
|
+
let outcome;
|
|
34582
|
+
try {
|
|
34583
|
+
outcome = await fix.run();
|
|
34584
|
+
} catch (error2) {
|
|
34585
|
+
outcome = { ok: false, message: formatError12(error2) };
|
|
34586
|
+
}
|
|
34587
|
+
repairs.push({
|
|
34588
|
+
checkId: check.id,
|
|
34589
|
+
fixId: fix.id,
|
|
34590
|
+
title: fix.title,
|
|
34591
|
+
status: outcome.ok ? "applied" : "failed",
|
|
34592
|
+
message: outcome.message
|
|
34593
|
+
});
|
|
34594
|
+
if (outcome.ok && !repairedCheckIds.includes(check.id)) {
|
|
34595
|
+
repairedCheckIds.push(check.id);
|
|
34596
|
+
}
|
|
34597
|
+
}
|
|
34598
|
+
}
|
|
34599
|
+
return { repairs, repairedCheckIds };
|
|
34600
|
+
}
|
|
33930
34601
|
function resolveDoctorRuntimePaths(home, resolver) {
|
|
33931
34602
|
if (resolver) {
|
|
33932
34603
|
return resolver();
|
|
@@ -33935,8 +34606,8 @@ function resolveDoctorRuntimePaths(home, resolver) {
|
|
|
33935
34606
|
return resolveRuntimePaths();
|
|
33936
34607
|
}
|
|
33937
34608
|
return {
|
|
33938
|
-
configPath:
|
|
33939
|
-
statePath:
|
|
34609
|
+
configPath: join21(coreHomeDir(home), "config.json"),
|
|
34610
|
+
statePath: join21(coreHomeDir(home), "state.json")
|
|
33940
34611
|
};
|
|
33941
34612
|
}
|
|
33942
34613
|
function depsUseExplicitRuntimeOverrides() {
|
|
@@ -33976,7 +34647,7 @@ async function defaultCheckOrchestrationHealth(deps) {
|
|
|
33976
34647
|
label: "Orchestration",
|
|
33977
34648
|
severity: "skip",
|
|
33978
34649
|
summary: "orchestration check skipped because configuration could not be loaded",
|
|
33979
|
-
details: [`config path: ${deps.runtimePaths.configPath}`, `error: ${
|
|
34650
|
+
details: [`config path: ${deps.runtimePaths.configPath}`, `error: ${formatError12(error2)}`],
|
|
33980
34651
|
suggestions: ["fix the Config check first, then run: xacpx doctor"]
|
|
33981
34652
|
};
|
|
33982
34653
|
}
|
|
@@ -33988,18 +34659,19 @@ async function defaultCheckOrchestrationHealth(deps) {
|
|
|
33988
34659
|
now: () => new Date,
|
|
33989
34660
|
heartbeatThresholdSeconds: config4.orchestration.progressHeartbeatSeconds
|
|
33990
34661
|
});
|
|
33991
|
-
|
|
34662
|
+
const daemonRunning = inspection.report ? await deps.isDaemonRunning() : false;
|
|
34663
|
+
return applyStateInspectionReport(result, inspection.report, deps.runtimePaths.statePath, daemonRunning, deps.isDaemonRunning);
|
|
33992
34664
|
} catch (error2) {
|
|
33993
34665
|
return {
|
|
33994
34666
|
id: "orchestration",
|
|
33995
34667
|
label: "Orchestration",
|
|
33996
34668
|
severity: "fail",
|
|
33997
34669
|
summary: "orchestration health check failed",
|
|
33998
|
-
details: [`state path: ${deps.runtimePaths.statePath}`, `error: ${
|
|
34670
|
+
details: [`state path: ${deps.runtimePaths.statePath}`, `error: ${formatError12(error2)}`]
|
|
33999
34671
|
};
|
|
34000
34672
|
}
|
|
34001
34673
|
}
|
|
34002
|
-
function applyStateInspectionReport(result, report, statePath) {
|
|
34674
|
+
function applyStateInspectionReport(result, report, statePath, daemonRunning, isDaemonRunning) {
|
|
34003
34675
|
if (!report) {
|
|
34004
34676
|
return result;
|
|
34005
34677
|
}
|
|
@@ -34017,18 +34689,74 @@ function applyStateInspectionReport(result, report, statePath) {
|
|
|
34017
34689
|
suggestions: [
|
|
34018
34690
|
...result.suggestions ?? [],
|
|
34019
34691
|
fileCorrupt ? "back up the state file before the next daemon start if you want to attempt manual recovery" : "the daemon backs the original file up as state.json.quarantine-* before dropping these records"
|
|
34020
|
-
]
|
|
34692
|
+
],
|
|
34693
|
+
fixes: [createStateQuarantineFix(statePath, daemonRunning, isDaemonRunning)]
|
|
34021
34694
|
};
|
|
34022
34695
|
}
|
|
34023
|
-
function
|
|
34696
|
+
function createStateQuarantineFix(statePath, daemonRunning, isDaemonRunning) {
|
|
34697
|
+
return {
|
|
34698
|
+
id: "state.quarantine",
|
|
34699
|
+
title: "quarantine invalid state.json records",
|
|
34700
|
+
...daemonRunning ? { withheld: "stop the daemon first: xacpx stop" } : {},
|
|
34701
|
+
run: async () => {
|
|
34702
|
+
if (await isDaemonRunning()) {
|
|
34703
|
+
return {
|
|
34704
|
+
ok: false,
|
|
34705
|
+
message: "a daemon started since detection; stop it first: xacpx stop"
|
|
34706
|
+
};
|
|
34707
|
+
}
|
|
34708
|
+
const store = new StateStore(statePath);
|
|
34709
|
+
await store.load();
|
|
34710
|
+
const report = store.lastLoadReport;
|
|
34711
|
+
if (!report) {
|
|
34712
|
+
return { ok: true, message: "state.json was already valid; nothing to quarantine" };
|
|
34713
|
+
}
|
|
34714
|
+
if (report.corruptPath) {
|
|
34715
|
+
return { ok: true, message: `state.json was unreadable; renamed to ${report.corruptPath} and reset` };
|
|
34716
|
+
}
|
|
34717
|
+
const backup = report.quarantinePath ? ` (original backed up to ${report.quarantinePath})` : "";
|
|
34718
|
+
return {
|
|
34719
|
+
ok: true,
|
|
34720
|
+
message: `quarantined ${report.dropped.length} invalid state.json record(s)${backup}`
|
|
34721
|
+
};
|
|
34722
|
+
}
|
|
34723
|
+
};
|
|
34724
|
+
}
|
|
34725
|
+
async function defaultIsDaemonRunning(home, runtimePaths, getDaemonStatus = () => defaultGetDaemonStatus2(home, runtimePaths)) {
|
|
34726
|
+
try {
|
|
34727
|
+
const status = await getDaemonStatus();
|
|
34728
|
+
return status.state === "running" || status.state === "indeterminate";
|
|
34729
|
+
} catch {
|
|
34730
|
+
return true;
|
|
34731
|
+
}
|
|
34732
|
+
}
|
|
34733
|
+
async function defaultGetDaemonStatus2(home, runtimePaths) {
|
|
34734
|
+
const paths = resolveDaemonPaths({
|
|
34735
|
+
home,
|
|
34736
|
+
runtimeDir: resolveRuntimeDirFromConfigPath(runtimePaths.configPath)
|
|
34737
|
+
});
|
|
34738
|
+
const controller = createDaemonController(paths, {
|
|
34739
|
+
processExecPath: process.execPath,
|
|
34740
|
+
cliEntryPath: process.argv[1] ?? "",
|
|
34741
|
+
cwd: process.cwd(),
|
|
34742
|
+
env: process.env,
|
|
34743
|
+
isProcessRunning: isProcessAlive
|
|
34744
|
+
});
|
|
34745
|
+
return await controller.getStatus();
|
|
34746
|
+
}
|
|
34747
|
+
function formatError12(error2) {
|
|
34024
34748
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
34025
34749
|
}
|
|
34026
34750
|
var init_doctor = __esm(async () => {
|
|
34027
34751
|
init_core_home();
|
|
34028
34752
|
init_load_config();
|
|
34753
|
+
init_create_daemon_controller();
|
|
34754
|
+
init_daemon_files();
|
|
34029
34755
|
init_state_store();
|
|
34030
34756
|
init_daemon_check();
|
|
34757
|
+
init_logs_check();
|
|
34031
34758
|
init_orchestration_health();
|
|
34759
|
+
init_orchestration_socket_check();
|
|
34032
34760
|
init_runtime_check();
|
|
34033
34761
|
init_wechat_check();
|
|
34034
34762
|
init_render_doctor();
|
|
@@ -34037,6 +34765,7 @@ var init_doctor = __esm(async () => {
|
|
|
34037
34765
|
init_acpx_check(),
|
|
34038
34766
|
init_bridge_check(),
|
|
34039
34767
|
init_config_check(),
|
|
34768
|
+
init_plugin_check(),
|
|
34040
34769
|
init_smoke_check()
|
|
34041
34770
|
]);
|
|
34042
34771
|
});
|
|
@@ -34061,8 +34790,8 @@ var init_doctor2 = __esm(async () => {
|
|
|
34061
34790
|
// src/cli.ts
|
|
34062
34791
|
init_core_home();
|
|
34063
34792
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
34064
|
-
import { homedir as
|
|
34065
|
-
import { dirname as dirname14, join as
|
|
34793
|
+
import { homedir as homedir15 } from "node:os";
|
|
34794
|
+
import { dirname as dirname14, join as join22, sep } from "node:path";
|
|
34066
34795
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
34067
34796
|
|
|
34068
34797
|
// src/runtime/migrate-core-home.ts
|
|
@@ -49109,118 +49838,7 @@ import { readFile as readFile11 } from "node:fs/promises";
|
|
|
49109
49838
|
import { isAbsolute, join as join13, resolve } from "node:path";
|
|
49110
49839
|
init_plugin_loader();
|
|
49111
49840
|
init_validate_plugin();
|
|
49112
|
-
|
|
49113
|
-
// src/plugins/plugin-doctor.ts
|
|
49114
|
-
init_channel_scope();
|
|
49115
|
-
init_plugin_loader();
|
|
49116
|
-
init_validate_plugin();
|
|
49117
|
-
init_known_plugins();
|
|
49118
|
-
init_plugin_renames();
|
|
49119
|
-
import { readFile as readFile10 } from "node:fs/promises";
|
|
49120
|
-
import { join as join12 } from "node:path";
|
|
49121
|
-
function suggestedPluginPackageForChannel(type) {
|
|
49122
|
-
return findKnownPluginByChannel(type)?.packageName ?? `<npm-package-that-provides-${type}>`;
|
|
49123
|
-
}
|
|
49124
|
-
async function readDependencyEntries(pluginHome) {
|
|
49125
|
-
try {
|
|
49126
|
-
const raw = await readFile10(join12(pluginHome, "package.json"), "utf8");
|
|
49127
|
-
const parsed = JSON.parse(raw);
|
|
49128
|
-
const out = {};
|
|
49129
|
-
for (const [name, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
49130
|
-
if (typeof value === "string")
|
|
49131
|
-
out[name] = value;
|
|
49132
|
-
}
|
|
49133
|
-
return out;
|
|
49134
|
-
} catch (error2) {
|
|
49135
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
49136
|
-
throw new Error(`failed to read plugin home package.json: ${message}`);
|
|
49137
|
-
}
|
|
49138
|
-
}
|
|
49139
|
-
async function inspectPlugins(input) {
|
|
49140
|
-
const issues = [];
|
|
49141
|
-
let dependencies;
|
|
49142
|
-
try {
|
|
49143
|
-
dependencies = await readDependencyEntries(input.pluginHome);
|
|
49144
|
-
} catch (error2) {
|
|
49145
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
49146
|
-
return [{ level: "error", message }];
|
|
49147
|
-
}
|
|
49148
|
-
const importPlugin = input.importPlugin ?? importPluginFromHome;
|
|
49149
|
-
const allConfigured = input.config.plugins;
|
|
49150
|
-
const filterByName = input.pluginName ? normalizePluginPackageName(input.pluginName) : null;
|
|
49151
|
-
if (filterByName && !allConfigured.some((plugin) => normalizePluginPackageName(plugin.name) === filterByName)) {
|
|
49152
|
-
return [{ level: "error", plugin: filterByName, message: `plugin is not configured; run xacpx plugin add ${filterByName}` }];
|
|
49153
|
-
}
|
|
49154
|
-
const pushIfRelevant = (issue2) => {
|
|
49155
|
-
if (!filterByName || issue2.plugin === filterByName)
|
|
49156
|
-
issues.push(issue2);
|
|
49157
|
-
};
|
|
49158
|
-
const channelProviders = new Map;
|
|
49159
|
-
for (const configPlugin of allConfigured) {
|
|
49160
|
-
if (!(configPlugin.name in dependencies)) {
|
|
49161
|
-
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `package not installed in plugin home; run xacpx plugin add ${configPlugin.name}` });
|
|
49162
|
-
continue;
|
|
49163
|
-
}
|
|
49164
|
-
let moduleValue;
|
|
49165
|
-
try {
|
|
49166
|
-
moduleValue = await importPlugin(configPlugin.name, input.pluginHome);
|
|
49167
|
-
} catch (error2) {
|
|
49168
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
49169
|
-
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `failed to import plugin: ${message}` });
|
|
49170
|
-
continue;
|
|
49171
|
-
}
|
|
49172
|
-
try {
|
|
49173
|
-
const plugin = validateWeacpxPlugin(moduleValue, configPlugin.name, {
|
|
49174
|
-
...input.currentXacpxVersion !== undefined ? { currentXacpxVersion: input.currentXacpxVersion } : {}
|
|
49175
|
-
});
|
|
49176
|
-
const channels = plugin.channels ?? [];
|
|
49177
|
-
const channelTypes = channels.map((channel) => channel.type);
|
|
49178
|
-
for (const type of channelTypes) {
|
|
49179
|
-
const existing = channelProviders.get(type);
|
|
49180
|
-
if (existing) {
|
|
49181
|
-
pushIfRelevant({ level: "error", plugin: configPlugin.name, message: `channel type ${type} is already provided by ${existing.plugin}` });
|
|
49182
|
-
} else {
|
|
49183
|
-
channelProviders.set(type, { plugin: configPlugin.name, enabled: configPlugin.enabled });
|
|
49184
|
-
}
|
|
49185
|
-
}
|
|
49186
|
-
pushIfRelevant({
|
|
49187
|
-
level: configPlugin.enabled ? "ok" : "warn",
|
|
49188
|
-
plugin: configPlugin.name,
|
|
49189
|
-
message: configPlugin.enabled ? `plugin is installed and valid; channels: ${channelTypes.length > 0 ? channelTypes.join(", ") : "none"}` : `plugin is installed and valid but disabled; run xacpx plugin enable ${configPlugin.name}`
|
|
49190
|
-
});
|
|
49191
|
-
} catch (error2) {
|
|
49192
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
49193
|
-
pushIfRelevant({ level: "error", plugin: configPlugin.name, message });
|
|
49194
|
-
}
|
|
49195
|
-
}
|
|
49196
|
-
const builtInChannelTypes = new Set(listKnownChannelIds());
|
|
49197
|
-
for (const channel of input.config.channels) {
|
|
49198
|
-
if (channel.enabled === false)
|
|
49199
|
-
continue;
|
|
49200
|
-
if (builtInChannelTypes.has(channel.type))
|
|
49201
|
-
continue;
|
|
49202
|
-
const provider = channelProviders.get(channel.type);
|
|
49203
|
-
if (!provider) {
|
|
49204
|
-
if (!filterByName) {
|
|
49205
|
-
issues.push({
|
|
49206
|
-
level: "error",
|
|
49207
|
-
message: `channel ${channel.type} is configured but no enabled plugin provides it; run xacpx plugin add ${suggestedPluginPackageForChannel(channel.type)} or another plugin that provides type "${channel.type}"`
|
|
49208
|
-
});
|
|
49209
|
-
}
|
|
49210
|
-
continue;
|
|
49211
|
-
}
|
|
49212
|
-
if (!provider.enabled) {
|
|
49213
|
-
pushIfRelevant({
|
|
49214
|
-
level: "error",
|
|
49215
|
-
plugin: provider.plugin,
|
|
49216
|
-
message: `channel ${channel.type} is configured but provider plugin is disabled; run xacpx plugin enable ${provider.plugin}`
|
|
49217
|
-
});
|
|
49218
|
-
}
|
|
49219
|
-
}
|
|
49220
|
-
return issues;
|
|
49221
|
-
}
|
|
49222
|
-
|
|
49223
|
-
// src/plugins/plugin-cli.ts
|
|
49841
|
+
init_plugin_doctor();
|
|
49224
49842
|
init_known_plugins();
|
|
49225
49843
|
init_plugin_renames();
|
|
49226
49844
|
init_i18n();
|
|
@@ -50416,7 +51034,7 @@ async function createCliScheduledTaskService() {
|
|
|
50416
51034
|
return new ScheduledTaskService(state, stateStore);
|
|
50417
51035
|
}
|
|
50418
51036
|
function resolveConfigPathForCurrentEnv() {
|
|
50419
|
-
return coreEnv("CONFIG") ??
|
|
51037
|
+
return coreEnv("CONFIG") ?? join22(coreHomeDir(requireHome2()), "config.json");
|
|
50420
51038
|
}
|
|
50421
51039
|
function resolveDaemonPathsForCurrentConfig() {
|
|
50422
51040
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
@@ -50763,7 +51381,7 @@ function decodeFirstRunOnboarding(raw) {
|
|
|
50763
51381
|
return null;
|
|
50764
51382
|
}
|
|
50765
51383
|
function requireHome2() {
|
|
50766
|
-
const home = process.env.HOME ??
|
|
51384
|
+
const home = process.env.HOME ?? homedir15();
|
|
50767
51385
|
if (!home) {
|
|
50768
51386
|
throw new Error("Unable to resolve the current user home directory");
|
|
50769
51387
|
}
|
|
@@ -50787,7 +51405,7 @@ function safeDaemonLogPaths() {
|
|
|
50787
51405
|
const configPath = resolveConfigPathForCurrentEnv();
|
|
50788
51406
|
const paths = resolveDaemonPathsForCurrentConfig();
|
|
50789
51407
|
return {
|
|
50790
|
-
appLog:
|
|
51408
|
+
appLog: join22(dirname14(configPath), "runtime", "app.log"),
|
|
50791
51409
|
stderrLog: paths.stderrLog
|
|
50792
51410
|
};
|
|
50793
51411
|
} catch {
|
|
@@ -50811,6 +51429,9 @@ function parseDoctorArgs(args) {
|
|
|
50811
51429
|
case "--smoke":
|
|
50812
51430
|
options.smoke = true;
|
|
50813
51431
|
break;
|
|
51432
|
+
case "--fix":
|
|
51433
|
+
options.fix = true;
|
|
51434
|
+
break;
|
|
50814
51435
|
case "--agent": {
|
|
50815
51436
|
const value = args[index + 1];
|
|
50816
51437
|
if (!value || value.startsWith("--")) {
|