openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
199 lines (198 loc) • 7.01 kB
JavaScript
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 };