UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

199 lines (198 loc) 7.01 kB
import { i as formatErrorMessage } from "../../errors-BXgSefBE.js"; import { n as defaultRuntime } from "../../runtime-B4lgFmsS.js"; import { t as createSubsystemLogger } from "../../subsystem-BzXSmsuh.js"; import "../../agent-scope-MrLta7Pq.js"; import { f as resolveAgentIdFromSessionKey } from "../../session-key-B_NoIfpX.js"; import { n as listAgentIds, o as resolveAgentWorkspaceDir } from "../../agent-scope-config-CgCYpZfK.js"; import { o as isGatewayStartupEvent } from "../../internal-hooks-Bq6_9FFu.js"; import { i as resolveMainSessionKey, n as resolveAgentMainSessionKey } from "../../main-session-Eahn-btj.js"; import { u as resolveStorePath } from "../../paths-NEwU8m3X.js"; import { t as loadSessionStore } from "../../store-load-C4uq6Lrq.js"; import { d as updateSessionStore } from "../../store-Qsgtu-0y.js"; import { a as OPENCLAW_RUNTIME_CONTEXT_NOTICE, n as INTERNAL_RUNTIME_CONTEXT_END, s as escapeInternalRuntimeContextDelimiters, t as INTERNAL_RUNTIME_CONTEXT_BEGIN } from "../../internal-runtime-context-BH_40W4f.js"; import { n as SILENT_REPLY_TOKEN } from "../../tokens-U6o7_k27.js"; import { c as setBootEchoContextForSession, s as clearBootEchoContextForSession } from "../../openclaw-tools-C0nKaVVY.js"; import { t as agentCommand } from "../../agent-command-DimMXeog.js"; import { t as createDefaultDeps } from "../../deps-CWqGdIJS.js"; import "../../agent-COt_BYfw.js"; import { t as runStartupTasks } from "../../startup-tasks-BrQYuflC.js"; import path from "node:path"; import fs from "node:fs/promises"; import crypto from "node:crypto"; //#region src/gateway/boot.ts function generateBootSessionId() { return `boot-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("T", "_").replace("Z", "")}-${crypto.randomUUID().slice(0, 8)}`; } const log$1 = createSubsystemLogger("gateway/boot"); const BOOT_FILENAME = "BOOT.md"; function buildBootPrompt(content) { return [ "You are running a boot check. Follow BOOT.md instructions exactly.", "", INTERNAL_RUNTIME_CONTEXT_BEGIN, OPENCLAW_RUNTIME_CONTEXT_NOTICE, "", "BOOT.md:", escapeInternalRuntimeContextDelimiters(content), INTERNAL_RUNTIME_CONTEXT_END, "", "If BOOT.md asks you to send a message, use the message tool (action=send with channel + target).", "Use the `target` field (not `to`) for message tool destinations.", `After sending with the message tool, reply with ONLY: ${SILENT_REPLY_TOKEN}.`, `If nothing needs attention, reply with ONLY: ${SILENT_REPLY_TOKEN}.` ].join("\n"); } function resolveBootSessionKey(sessionKey) { return `agent:${resolveAgentIdFromSessionKey(sessionKey)}:boot`; } async function loadBootFile(workspaceDir) { const bootPath = path.join(workspaceDir, BOOT_FILENAME); try { const trimmed = (await fs.readFile(bootPath, "utf-8")).trim(); if (!trimmed) return { status: "empty" }; return { status: "ok", content: trimmed }; } catch (err) { if (err.code === "ENOENT") return { status: "missing" }; throw err; } } function snapshotSessionMapping(params) { const agentId = resolveAgentIdFromSessionKey(params.sessionKey); const storePath = resolveStorePath(params.cfg.session?.store, { agentId }); try { const entry = loadSessionStore(storePath, { skipCache: true })[params.sessionKey]; if (!entry) return { storePath, sessionKey: params.sessionKey, canRestore: true, hadEntry: false }; return { storePath, sessionKey: params.sessionKey, canRestore: true, hadEntry: true, entry: structuredClone(entry) }; } catch (err) { log$1.debug("boot: could not snapshot session mapping", { sessionKey: params.sessionKey, error: String(err) }); return { storePath, sessionKey: params.sessionKey, canRestore: false, hadEntry: false }; } } async function restoreSessionMapping(snapshot) { if (!snapshot.canRestore) return; try { await updateSessionStore(snapshot.storePath, (store) => { if (snapshot.hadEntry && snapshot.entry) { store[snapshot.sessionKey] = snapshot.entry; return; } delete store[snapshot.sessionKey]; }, { activeSessionKey: snapshot.sessionKey }); return; } catch (err) { return formatErrorMessage(err); } } async function runBootOnce(params) { const bootRuntime = { log: () => {}, error: (message) => log$1.error(String(message)), exit: defaultRuntime.exit }; let result; try { result = await loadBootFile(params.workspaceDir); } catch (err) { const message = formatErrorMessage(err); log$1.error(`boot: failed to read ${BOOT_FILENAME}: ${message}`); return { status: "failed", reason: message }; } if (result.status === "missing" || result.status === "empty") return { status: "skipped", reason: result.status }; const sessionKey = resolveBootSessionKey(params.agentId ? resolveAgentMainSessionKey({ cfg: params.cfg, agentId: params.agentId }) : resolveMainSessionKey(params.cfg)); const message = buildBootPrompt(result.content ?? ""); const sessionId = generateBootSessionId(); const mappingSnapshot = snapshotSessionMapping({ cfg: params.cfg, sessionKey }); setBootEchoContextForSession(sessionKey, message); let agentFailure; try { await agentCommand({ message, sessionKey, sessionId, deliver: false, suppressPromptPersistence: true }, bootRuntime, params.deps); } catch (err) { agentFailure = formatErrorMessage(err); log$1.error(`boot: agent run failed: ${agentFailure}`); } finally { clearBootEchoContextForSession(sessionKey); } const mappingRestoreFailure = await restoreSessionMapping(mappingSnapshot); if (mappingRestoreFailure) log$1.error(`boot: failed to restore session mapping: ${mappingRestoreFailure}`); if (!agentFailure && !mappingRestoreFailure) return { status: "ran" }; return { status: "failed", reason: [agentFailure ? `agent run failed: ${agentFailure}` : void 0, mappingRestoreFailure ? `mapping restore failed: ${mappingRestoreFailure}` : void 0].filter((part) => Boolean(part)).join("; ") }; } //#endregion //#region src/hooks/bundled/boot-md/handler.ts const log = createSubsystemLogger("hooks/boot-md"); /** Gateway-startup hook that runs BOOT.md checks once per unique agent workspace. */ const runBootChecklist = async (event) => { if (!isGatewayStartupEvent(event)) return; if (!event.context.cfg) return; const cfg = event.context.cfg; const deps = event.context.deps ?? createDefaultDeps(); const seenWorkspaces = /* @__PURE__ */ new Set(); await runStartupTasks({ tasks: listAgentIds(cfg).map((agentId) => { return { agentId, workspaceDir: resolveAgentWorkspaceDir(cfg, agentId) }; }).filter(({ workspaceDir }) => { if (seenWorkspaces.has(workspaceDir)) return false; seenWorkspaces.add(workspaceDir); return true; }).map(({ agentId, workspaceDir }) => ({ source: "boot-md", agentId, workspaceDir, run: () => runBootOnce({ cfg, deps, workspaceDir, agentId }) })), log }); }; //#endregion export { runBootChecklist as default };