openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
383 lines (382 loc) • 16.4 kB
JavaScript
import { n as isNodeRuntime } from "./runtime-binary-BAwmhhZ0.mjs";
import { n as resolveOpenClawPackageRoot } from "./openclaw-root-CBoxhfke.mjs";
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs";
import { s as formatUnsupportedNodeVersionMessage } from "./node-version-DdJ6cN09.mjs";
import { n as getSystemdCgroupHygieneSummary } from "./service-runtime-Cbp6mAbj.mjs";
import { n as readInstallOwner, t as formatInstallOwnerMessage } from "./install-owner-ImshV9Dw.mjs";
import { l as tryResolveSoleAgentId } from "./agent-roster-BXD1uMJD.mjs";
import "./agent-scope-CG17uXFn.mjs";
import { c as summarizeGatewayServiceLayout } from "./service-layout-BNLNn9Ng.mjs";
import { i as readGatewayServiceState, n as resolveGatewayService } from "./service-C5e3lih7.mjs";
import { o as resolveNodeRuntimeInfo } from "./runtime-paths-H47KmrWm.mjs";
import { n as inspectLocalAudioSelection, t as formatLocalAudioSelection } from "./local-audio-XTfEMUk1.mjs";
import { n as buildWorkspaceSkillStatus } from "./status-DpUEm56v.mjs";
import { l as shouldManageGatewayService } from "./doctor-service-repair-policy-dEj5KU5E.mjs";
import { t as collectUnavailableAgentSkills } from "./doctor-skills-core-Yeo8bqi0.mjs";
//#region src/flows/doctor-core-checks.runtime.ts
const PROVIDER_CATALOG_ORDERS = [
"simple",
"profile",
"paired",
"late"
];
const PROVIDER_CATALOG_ORDER_SET = new Set(PROVIDER_CATALOG_ORDERS);
function detectUnavailableSkills(cfg, workspaceDir) {
const report = buildWorkspaceSkillStatus(workspaceDir, {
config: cfg,
agentId: tryResolveSoleAgentId(cfg)
});
return collectUnavailableAgentSkills(report);
}
async function collectLocalAudioAccelerationFindings() {
const selection = await inspectLocalAudioSelection();
const available = selection.candidates.filter((candidate) => candidate.available);
if (available.length === 0) return [];
const summary = formatLocalAudioSelection(selection);
if (summary) return [{
checkId: "core/doctor/local-audio-acceleration",
severity: "info",
message: `Local STT auto-selection: ${summary}.`,
path: "tools.media.models"
}];
return [{
checkId: "core/doctor/local-audio-acceleration",
severity: "info",
message: `Local STT commands were found but none are ready for auto-selection: ${available.map((candidate) => `${candidate.command}: ${candidate.reason}`).join("; ")}.`,
path: "tools.media.models",
fixHint: "Install the matching local model/runtime, or configure an audio-capable tools.media.models CLI entry."
}];
}
async function collectGatewayDaemonFindings(ctx) {
if (ctx.cfg.gateway?.mode === "remote" || !await shouldManageGatewayService()) return [];
const service = resolveGatewayService();
const state = await readGatewayServiceState(service, { env: process.env });
const layout = await summarizeGatewayServiceLayout(state.command);
const serviceOwner = await readInstallOwner(layout?.packageRootReal ?? layout?.packageRoot ?? null);
const ownerHint = serviceOwner ? formatInstallOwnerMessage(serviceOwner) : void 0;
const findings = [];
if (state.loadState.status === "unknown") {
findings.push({
checkId: "core/doctor/gateway-daemon",
severity: "warning",
message: `Gateway service status could not be determined: ${state.loadState.detail}`,
path: state.command?.sourcePath,
target: service.label,
fixHint: ownerHint ?? service.unsupportedReason ?? "Run `openclaw gateway status --deep`, restore service-manager access, and retry."
});
return findings;
}
if (!state.installed) {
const owner = await readInstallOwner(await resolveOpenClawPackageRoot({
moduleUrl: import.meta.url,
argv1: process.argv[1]
}));
if (owner) return [{
checkId: "core/doctor/gateway-daemon",
severity: "info",
message: formatInstallOwnerMessage(owner),
target: owner.displayName
}];
findings.push({
checkId: "core/doctor/gateway-daemon",
severity: "warning",
message: "Gateway service is not installed.",
path: "gateway.mode",
target: service.label,
fixHint: "Run `openclaw gateway install` to install the service."
});
return findings;
}
const nodePath = state.command?.programArguments[0];
if (nodePath && isNodeRuntime(nodePath)) {
const runtime = await resolveNodeRuntimeInfo(nodePath, state.env);
const message = runtime.status === "probe-failed" ? runtime.error.message : runtime.capabilityError ?? runtime.note;
if (message) findings.push({
checkId: "core/doctor/gateway-daemon",
severity: runtime.status === "supported" ? "info" : "warning",
message,
path: state.command?.sourcePath,
target: nodePath,
...runtime.status !== "supported" ? { fixHint: ownerHint ?? [...runtime.status === "unsupported" ? [formatUnsupportedNodeVersionMessage(runtime.version)] : [], "Repair the Node runtime, then run `openclaw gateway install`."].join("\n") } : {}
});
}
if (state.loadState.status === "not-loaded") findings.push({
checkId: "core/doctor/gateway-daemon",
severity: "warning",
message: "Gateway service is installed but not loaded.",
path: state.command?.sourcePath,
target: service.label,
fixHint: ownerHint ?? "Start the installed service with `openclaw gateway start`."
});
const runtime = state.runtime;
const status = runtime?.status ?? runtime?.state ?? runtime?.subState;
if (state.loadState.status === "loaded" && !state.running) findings.push({
checkId: "core/doctor/gateway-daemon",
severity: "warning",
message: status ? `Gateway service runtime is ${status}, not running.` : "Gateway service is loaded but runtime status could not confirm it is running.",
path: state.command?.sourcePath,
target: service.label,
fixHint: "Run `openclaw gateway status --deep` to inspect the service before choosing a recovery action."
});
if (state.runtime?.missingGuiSession) findings.push({
checkId: "core/doctor/gateway-daemon",
severity: "warning",
message: "Gateway service cannot attach to the user GUI session.",
path: state.command?.sourcePath,
target: service.label,
fixHint: state.runtime.detail ?? "Log into a GUI session, then rerun doctor."
});
if (state.runtime?.missingUnit) findings.push({
checkId: "core/doctor/gateway-daemon",
severity: "warning",
message: "Gateway service supervision metadata is missing.",
path: state.command?.sourcePath,
target: service.label,
fixHint: ownerHint ?? state.runtime.detail ?? "Reinstall or reload the Gateway service."
});
const hygiene = getSystemdCgroupHygieneSummary(state.runtime?.systemd);
if (hygiene) findings.push({
checkId: "core/doctor/gateway-daemon",
severity: "warning",
message: `Gateway systemd service has risky ${hygiene}.`,
path: state.command?.sourcePath,
target: service.label,
fixHint: "Repair the systemd unit so stale child processes are cleaned up reliably."
});
return findings;
}
function providerCatalogProjectionFinding(params, message, error) {
const path = params.pluginId ? `plugins.entries.${params.pluginId}` : void 0;
return {
checkId: "core/doctor/provider-catalog-projection",
severity: "error",
message,
...path ? { path } : {},
target: params.providerId,
requirement: formatErrorMessage(error),
fixHint: "Fix the plugin provider catalog hook or disable the plugin, then rerun doctor before relying on model discovery."
};
}
function isReadableRecord(value) {
return value !== null && typeof value === "object";
}
function isTrimmedNonEmptyString(value) {
return typeof value === "string" && value.trim() === value && value.length > 0;
}
function readProviderCatalogValue(params) {
if (!isReadableRecord(params.value)) return {
ok: true,
value: void 0
};
try {
return {
ok: true,
value: params.value[params.key]
};
} catch (error) {
return {
ok: false,
finding: providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} entry cannot be read during doctor validation.`, error)
};
}
}
function collectProviderCatalogModelFindings(params) {
const findings = [];
let models;
try {
if (!Array.isArray(params.models)) return [providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} models value is invalid during doctor validation.`, /* @__PURE__ */ new Error("models must be an array"))];
models = params.models;
} catch (error) {
return [providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} models value cannot be checked during doctor validation.`, error)];
}
let modelEntries;
try {
modelEntries = [...models];
} catch (error) {
return [providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} model rows cannot be enumerated during doctor validation.`, error)];
}
for (const [index, model] of modelEntries.entries()) try {
if (!isTrimmedNonEmptyString(isReadableRecord(model) ? model.id : void 0)) findings.push(providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} model row ${index} has an invalid model id.`, /* @__PURE__ */ new Error("model id must be a non-empty trimmed string")));
const modelName = isReadableRecord(model) ? model.name : void 0;
if (modelName !== void 0 && typeof modelName !== "string") findings.push(providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} model row ${index} has an invalid model name.`, /* @__PURE__ */ new Error("model name must be a string when present")));
} catch (error) {
findings.push(providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} entry cannot be read during doctor validation.`, error));
}
return findings;
}
function collectProviderCatalogResultFindings(params) {
if (params.result == null) return [];
if (!isReadableRecord(params.result)) return [providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} result is invalid during doctor validation.`, /* @__PURE__ */ new Error("result must be an object"))];
let hasProvider;
try {
hasProvider = "provider" in params.result;
} catch (error) {
return [providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} result keys cannot be checked during doctor validation.`, error)];
}
const provider = readProviderCatalogValue({
value: params.result,
key: "provider",
providerId: params.providerId,
pluginId: params.pluginId
});
if (!provider.ok) return [provider.finding];
if (hasProvider && !isReadableRecord(provider.value)) return [providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} provider value is invalid during doctor validation.`, /* @__PURE__ */ new Error("provider must be an object"))];
if (isReadableRecord(provider.value)) {
const models = readProviderCatalogValue({
value: provider.value,
key: "models",
providerId: params.providerId,
pluginId: params.pluginId
});
return models.ok ? collectProviderCatalogModelFindings({
...params,
models: models.value
}) : [models.finding];
}
const providers = readProviderCatalogValue({
value: params.result,
key: "providers",
providerId: params.providerId,
pluginId: params.pluginId
});
if (!providers.ok) return [providers.finding];
if (!isReadableRecord(providers.value)) return [providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} result is invalid during doctor validation.`, /* @__PURE__ */ new Error("result must include provider or providers object"))];
let providerIds;
try {
providerIds = Object.keys(providers.value);
} catch (error) {
return [providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} provider entries cannot be enumerated during doctor validation.`, error)];
}
const findings = [];
for (const providerId of providerIds) {
if (!isTrimmedNonEmptyString(providerId)) {
findings.push(providerCatalogProjectionFinding(params, `Provider catalog ${params.providerId} provider key is invalid during doctor validation.`, /* @__PURE__ */ new Error("provider key must be a non-empty trimmed string")));
continue;
}
const providerConfig = readProviderCatalogValue({
value: providers.value,
key: providerId,
providerId,
pluginId: params.pluginId
});
if (!providerConfig.ok) {
findings.push(providerConfig.finding);
continue;
}
if (!isReadableRecord(providerConfig.value)) {
findings.push(providerCatalogProjectionFinding({
providerId,
pluginId: params.pluginId
}, `Provider catalog ${providerId} provider entry is invalid during doctor validation.`, /* @__PURE__ */ new Error("provider entry must be an object")));
continue;
}
const models = readProviderCatalogValue({
value: providerConfig.value,
key: "models",
providerId,
pluginId: params.pluginId
});
findings.push(...models.ok ? collectProviderCatalogModelFindings({
providerId,
pluginId: params.pluginId,
models: models.value
}) : [models.finding]);
}
return findings;
}
function groupProviderCatalogsForDoctor(providers) {
const findings = [];
const byOrder = {
simple: [],
profile: [],
paired: [],
late: []
};
for (const provider of providers) {
let order = "late";
try {
const declaredOrder = provider.staticCatalog?.order ?? "late";
if (PROVIDER_CATALOG_ORDER_SET.has(declaredOrder)) order = declaredOrder;
else findings.push(providerCatalogProjectionFinding({
providerId: provider.id,
pluginId: provider.pluginId
}, `Provider catalog ${provider.id} order is invalid during doctor validation.`, /* @__PURE__ */ new Error("order must be simple, profile, paired, or late")));
} catch (error) {
findings.push(providerCatalogProjectionFinding({
providerId: provider.id,
pluginId: provider.pluginId
}, `Provider catalog ${provider.id} order cannot be read during doctor validation.`, error));
}
byOrder[order].push(provider);
}
for (const order of PROVIDER_CATALOG_ORDERS) byOrder[order].sort((a, b) => a.label.localeCompare(b.label));
return {
findings,
byOrder
};
}
async function collectProviderCatalogProjectionFindings(cfg, workspaceDir) {
const { runProviderStaticCatalog } = await import("./provider-discovery-Cxx2gM6O.mjs");
const { resolvePluginProvidersCore } = await import("./providers.runtime.js");
const env = process.env;
let providers;
try {
providers = resolvePluginProvidersCore({
config: cfg,
workspaceDir,
env,
includeUntrustedWorkspacePlugins: false
});
} catch (error) {
return [{
checkId: "core/doctor/provider-catalog-projection",
severity: "error",
message: "Provider catalog hooks could not be loaded for doctor validation.",
requirement: formatErrorMessage(error),
fixHint: "Fix plugin provider discovery loading, then rerun doctor."
}];
}
const findings = [];
const grouped = groupProviderCatalogsForDoctor(providers);
findings.push(...grouped.findings);
for (const order of PROVIDER_CATALOG_ORDERS) for (const provider of grouped.byOrder[order]) {
let staticCatalog;
let staticCatalogRun;
try {
staticCatalog = provider.staticCatalog;
staticCatalogRun = isReadableRecord(staticCatalog) ? staticCatalog.run : void 0;
} catch (error) {
findings.push(providerCatalogProjectionFinding({
providerId: provider.id,
pluginId: provider.pluginId
}, `Provider catalog ${provider.id} static catalog hook cannot be read during doctor validation.`, error));
continue;
}
if (staticCatalog === void 0) continue;
if (typeof staticCatalogRun !== "function") {
findings.push(providerCatalogProjectionFinding({
providerId: provider.id,
pluginId: provider.pluginId
}, `Provider catalog ${provider.id} static catalog hook is invalid during doctor validation.`, /* @__PURE__ */ new Error("static catalog run must be a function")));
continue;
}
let result;
try {
result = await runProviderStaticCatalog({ provider });
} catch (error) {
findings.push(providerCatalogProjectionFinding({
providerId: provider.id,
pluginId: provider.pluginId
}, `Provider catalog ${provider.id} failed during doctor validation.`, error));
continue;
}
findings.push(...collectProviderCatalogResultFindings({
providerId: provider.id,
pluginId: provider.pluginId,
result
}));
}
return findings;
}
//#endregion
export { collectGatewayDaemonFindings, collectLocalAudioAccelerationFindings, collectProviderCatalogProjectionFindings, detectUnavailableSkills };