UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

302 lines (301 loc) 11.7 kB
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 };