openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
302 lines (301 loc) • 11.7 kB
JavaScript
import { j as resolveTimerTimeoutMs } from "./number-coercion-CJQ8TR--.js";
import { d as resolveGatewayPort, s as resolveConfigPath } from "./paths-mvMm5bYV.js";
import { i as resolveAgentModelPrimaryValue } from "./model-input-B2zxl6MM.js";
import { o as resolveAgentEffectiveModelPrimary } from "./agent-scope-MrLta7Pq.js";
import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js";
import { c as resolveDefaultAgentId, t as listAgentEntries } from "./agent-scope-config-CgCYpZfK.js";
import { u as readConfigFileSnapshot } from "./io-Gi7-pyU-.js";
import "./config-C9RxTsn1.js";
import { i as resolveOpenClawReferencePaths, n as OPENCLAW_SOURCE_URL, t as OPENCLAW_DOCS_URL } from "./docs-path-BV57VemT.js";
import { spawn } from "node:child_process";
//#region src/crestodian/probes.ts
const LOCAL_COMMAND_PROBE_OUTPUT_MAX_CHARS = 16 * 1024;
const LOCAL_COMMAND_PROBE_KILL_GRACE_MS = 500;
function appendBounded(previous, chunk, limit) {
const next = previous + chunk;
return next.length > limit ? next.slice(-limit) : next;
}
/** Probe a command by running a small version command with bounded output and timeout. */
async function probeLocalCommand(command, args = ["--version"], opts = {}) {
const timeoutMs = resolveTimerTimeoutMs(opts.timeoutMs, 1500);
const outputLimit = opts.outputLimit ?? LOCAL_COMMAND_PROBE_OUTPUT_MAX_CHARS;
const timeoutKillGraceMs = resolveTimerTimeoutMs(opts.timeoutKillGraceMs, LOCAL_COMMAND_PROBE_KILL_GRACE_MS, 0);
return await new Promise((resolve) => {
let stdout = "";
let stderr = "";
let settled = false;
let timedOut = false;
let killTimer;
const child = spawn(command, args, { stdio: [
"ignore",
"pipe",
"pipe"
] });
const timeoutResult = () => ({
command,
found: true,
error: `timed out after ${timeoutMs}ms`
});
const finish = (result) => {
if (settled) return;
settled = true;
clearTimeout(timer);
if (killTimer) clearTimeout(killTimer);
resolve(result);
};
const timer = setTimeout(() => {
timedOut = true;
child.kill("SIGTERM");
killTimer = setTimeout(() => {
child.kill("SIGKILL");
child.stdout.destroy();
child.stderr.destroy();
finish(timeoutResult());
}, timeoutKillGraceMs);
killTimer.unref?.();
}, timeoutMs);
child.stdout.setEncoding("utf8");
child.stderr.setEncoding("utf8");
child.stdout.on("data", (chunk) => {
stdout = appendBounded(stdout, String(chunk), outputLimit);
});
child.stderr.on("data", (chunk) => {
stderr = appendBounded(stderr, String(chunk), outputLimit);
});
child.on("error", (err) => {
finish({
command,
found: err.code !== "ENOENT",
error: err.code === "ENOENT" ? "not found" : err.message
});
});
child.on("close", (code) => {
if (timedOut) {
finish(timeoutResult());
return;
}
const text = `${stdout}\n${stderr}`.trim().split(/\r?\n/)[0]?.trim();
finish({
command,
found: code === 0 || Boolean(text),
version: text || void 0,
error: code === 0 ? void 0 : `exited ${String(code)}`
});
});
});
}
/** Probe a Gateway URL by translating it to its HTTP /healthz endpoint. */
async function probeGatewayUrl(url, opts = {}) {
const httpUrl = url.replace(/^ws:/, "http:").replace(/^wss:/, "https:");
const healthUrl = new URL("/healthz", httpUrl).toString();
const timeoutMs = resolveTimerTimeoutMs(opts.timeoutMs, 900);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(healthUrl, {
method: "GET",
signal: controller.signal
});
return {
reachable: response.ok,
url,
error: response.ok ? void 0 : response.statusText
};
} catch (err) {
return {
reachable: false,
url,
error: err instanceof Error ? err.message : String(err)
};
} finally {
clearTimeout(timeout);
}
}
//#endregion
//#region src/crestodian/overview.ts
function issueMessages(snapshot) {
return snapshot.issues.map((issue) => {
return `${issue.path ? `${issue.path}: ` : ""}${issue.message}`;
});
}
function buildAgentSummaries(cfg) {
const defaultAgentId = resolveDefaultAgentId(cfg);
const entries = listAgentEntries(cfg);
if (entries.length === 0) return [{
id: defaultAgentId,
isDefault: true,
model: resolveAgentEffectiveModelPrimary(cfg, defaultAgentId)
}];
const seen = /* @__PURE__ */ new Set();
const summaries = [];
for (const entry of entries) {
const id = normalizeAgentId(entry.id);
if (seen.has(id)) continue;
seen.add(id);
const summary = {
id,
isDefault: id === defaultAgentId
};
if (typeof entry.name === "string") summary.name = entry.name;
const model = resolveAgentEffectiveModelPrimary(cfg, id);
if (model) summary.model = model;
if (typeof entry.workspace === "string") summary.workspace = entry.workspace;
summaries.push(summary);
}
return summaries;
}
function resolveFastTestReferences(env) {
if (env.OPENCLAW_TEST_FAST !== "1") return;
const sourcePath = process.cwd();
return {
sourcePath,
docsPath: `${sourcePath}/docs`
};
}
async function loadCrestodianOverview(opts = {}) {
const env = opts.env ?? process.env;
const deps = opts.deps ?? {};
const snapshot = await (deps.readConfigFileSnapshot ?? readConfigFileSnapshot)();
const cfg = snapshot.runtimeConfig ?? snapshot.sourceConfig ?? {};
const defaultAgentId = resolveDefaultAgentId(cfg);
const defaultModel = resolveAgentEffectiveModelPrimary(cfg, defaultAgentId) ?? resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model);
const configPath = snapshot.path || (deps.resolveConfigPath ?? resolveConfigPath)(env);
let gatewayUrl = `ws://127.0.0.1:${(deps.resolveGatewayPort ?? resolveGatewayPort)(cfg, env)}`;
let gatewaySource = "local loopback";
let gatewayError;
try {
const details = (deps.buildGatewayConnectionDetails ?? (await import("./call-BEl78mhZ.js")).buildGatewayConnectionDetails)({
config: cfg,
configPath
});
gatewayUrl = details.url;
gatewaySource = details.urlSource;
gatewayError = details.remoteFallbackNote;
} catch (err) {
gatewayError = err instanceof Error ? err.message : String(err);
}
const resolveReferences = deps.resolveOpenClawReferencePaths ?? resolveOpenClawReferencePaths;
const commandProbe = deps.probeLocalCommand ?? probeLocalCommand;
const [codex, claude, gateway, references] = await Promise.all([
commandProbe("codex"),
commandProbe("claude"),
(deps.probeGatewayUrl ?? probeGatewayUrl)(gatewayUrl),
resolveFastTestReferences(env) ?? resolveReferences({
argv1: process.argv[1],
cwd: process.cwd(),
moduleUrl: import.meta.url
})
]);
return {
config: {
path: configPath,
exists: snapshot.exists,
valid: snapshot.valid,
issues: issueMessages(snapshot),
hash: snapshot.hash ?? null
},
agents: buildAgentSummaries(cfg),
defaultAgentId,
defaultModel,
tools: {
codex,
claude,
apiKeys: {
openai: Boolean(env.OPENAI_API_KEY?.trim()),
anthropic: Boolean(env.ANTHROPIC_API_KEY?.trim())
}
},
gateway: {
url: gateway.url,
source: gatewaySource,
reachable: gateway.reachable,
error: gateway.error ?? gatewayError
},
references: {
docsPath: references.docsPath ?? void 0,
docsUrl: OPENCLAW_DOCS_URL,
sourcePath: references.sourcePath ?? void 0,
sourceUrl: OPENCLAW_SOURCE_URL
}
};
}
function formatCommandProbe(probe) {
if (!probe.found) return "not found";
if (probe.version) return probe.version;
return probe.error ? `found (${probe.error})` : "found";
}
function formatCrestodianOverview(overview) {
const agentLines = overview.agents.map((agent) => {
return ` - ${[
agent.id,
agent.isDefault ? "default" : void 0,
agent.name ? `name=${agent.name}` : void 0,
agent.model ? `model=${agent.model}` : void 0,
agent.workspace ? `workspace=${agent.workspace}` : void 0
].filter(Boolean).join(" | ")}`;
});
const configStatus = overview.config.valid ? overview.config.exists ? "valid" : "missing (configless rescue mode)" : "invalid";
const issueLines = overview.config.issues.length > 0 ? ["Config issues:", ...overview.config.issues.map((issue) => ` - ${issue}`)] : [];
return [
"Crestodian online. Little claws, typed tools.",
"",
`Config: ${configStatus}`,
`Path: ${overview.config.path}`,
`Default agent: ${overview.defaultAgentId}`,
`Default model: ${overview.defaultModel ?? "not configured"}`,
"Agents:",
...agentLines,
`Codex: ${formatCommandProbe(overview.tools.codex)}`,
`Claude Code: ${formatCommandProbe(overview.tools.claude)}`,
`API keys: OpenAI ${overview.tools.apiKeys.openai ? "found" : "not found"}, Anthropic ${overview.tools.apiKeys.anthropic ? "found" : "not found"}`,
`Planner: ${overview.defaultModel ? `model-assisted via ${overview.defaultModel} for fuzzy local commands` : "deterministic only until a model is configured"}`,
`Docs: ${overview.references.docsPath ?? overview.references.docsUrl}`,
overview.references.sourcePath ? `Source: ${overview.references.sourcePath}` : `Source: ${overview.references.sourceUrl}`,
`Gateway: ${overview.gateway.reachable ? "reachable" : "not reachable"} (${overview.gateway.url}, ${overview.gateway.source})`,
overview.gateway.error ? `Gateway note: ${overview.gateway.error}` : void 0,
`Next: ${recommendCrestodianNextStep(overview)}`,
...issueLines
].filter((line) => line !== void 0).join("\n");
}
function recommendCrestodianNextStep(overview) {
if (!overview.config.exists) return "run \"setup\" to create a starter config";
if (!overview.config.valid) return "run \"validate config\" or \"doctor\" to inspect the config";
if (!overview.defaultModel) return "run \"setup\" or \"set default model <provider/model>\"";
if (!overview.gateway.reachable) return "run \"gateway status\" or \"restart gateway\"";
return "run \"talk to agent\" to enter your default agent";
}
function formatStartupConfigStatus(overview) {
if (!overview.config.exists) return "missing";
return overview.config.valid ? "valid" : "invalid";
}
function formatStartupUse(overview) {
if (overview.defaultModel) return `Using: ${overview.defaultModel} for fuzzy local planning.`;
return "Using: deterministic typed commands until we configure a model.";
}
function formatStartupGatewayStatus(overview) {
if (overview.gateway.reachable) return `Gateway: reachable at ${overview.gateway.url}.`;
return `Gateway: not reachable at ${overview.gateway.url}; I already did the first probe.`;
}
function formatStartupAction(overview) {
if (!overview.config.exists) return "I can start by creating a starter config with `setup`.";
if (!overview.config.valid) return "I can start debugging with `validate config` or `doctor`.";
if (!overview.defaultModel) return "I can start by choosing a model with `setup`.";
if (!overview.gateway.reachable) return "I can start debugging with `gateway status`, or queue `restart gateway` for approval.";
return "Everything basic is reachable. Use `talk to agent` when you want the normal agent.";
}
function formatCrestodianStartupMessage(overview) {
const agent = overview.agents.find((entry) => entry.id === overview.defaultAgentId);
const agentLabel = agent?.name ? `${overview.defaultAgentId} (${agent.name})` : overview.defaultAgentId;
return [
"## Hi, I'm Crestodian.",
"",
"- Start me when setup, config, Gateway, model choice, or agent routing feels off.",
`- ${formatStartupUse(overview)}`,
`- Config: ${formatStartupConfigStatus(overview)}. Default agent: ${agentLabel}.`,
`- ${formatStartupGatewayStatus(overview)}`,
"",
formatStartupAction(overview)
].join("\n");
}
//#endregion
export { formatCrestodianStartupMessage as n, loadCrestodianOverview as r, formatCrestodianOverview as t };