openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
244 lines (243 loc) • 11.6 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { c as resolveUserPath } from "./home-dir-BPhrG-aM.js";
import "./utils-P__uGsPB.js";
import { c as resolveAgentConfig, y as resolveDefaultAgentId } from "./agent-scope-config-DcbEhP0R.js";
import { f as resolveAgentIdFromSessionKey } from "./session-key-BnWWjqNc.js";
import "./agent-scope-DbtJyKUL.js";
import { s as readRecentSessionTranscriptActiveEvents } from "./session-accessor-YsytfDtG.js";
import { T as workspaceFilesShareSourceIdentity, _ as isWorkspaceSetupCompleted, i as DEFAULT_MEMORY_FILENAME, m as filterBootstrapFilesForSession, n as DEFAULT_BOOTSTRAP_FILENAME, y as loadWorkspaceBootstrapFiles } from "./workspace-ConDEamr.js";
import { i as resolveBootstrapTotalMaxChars, n as buildBootstrapContextFiles, r as resolveBootstrapMaxChars } from "./bootstrap-C0kxr-xC.js";
import "./embedded-agent-helpers-D0eiT3IT.js";
import { i as getOrLoadBootstrapFiles } from "./bootstrap-cache-Bpsy6pH_.js";
import { n as createInternalHookEvent, u as triggerInternalHook } from "./internal-hooks-Dgwjgwuj.js";
import { r as isMemoryOriginEligibleForAutomaticInjection } from "./types-BTd4wVMl.js";
import { n as classifyActiveMemoryWorkspacePaths } from "./memory-runtime-DgrGeT4a.js";
import path from "node:path";
//#region src/agents/bootstrap-hooks.ts
/** Runs bootstrap hooks and returns the effective bootstrap file list. */
async function applyBootstrapHookOverrides(params) {
const sessionKey = params.sessionKey ?? params.sessionId ?? "unknown";
const agentId = params.agentId ?? (params.sessionKey ? resolveAgentIdFromSessionKey(params.sessionKey) : void 0);
const context = {
workspaceDir: params.workspaceDir,
bootstrapFiles: params.files,
cfg: params.config,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId
};
const event = createInternalHookEvent("agent", "bootstrap", sessionKey, context);
await triggerInternalHook(event);
const updated = event.context.bootstrapFiles;
return Array.isArray(updated) ? updated : params.files;
}
//#endregion
//#region src/agents/bootstrap-files.ts
/**
* Resolves workspace bootstrap files for agent runs and converts them into
* bounded context files.
*/
const CONTINUATION_SCAN_MAX_RECORDS = 500;
const FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE = "openclaw:bootstrap-context:full";
const BOOTSTRAP_WARNING_DEDUPE_LIMIT = 1024;
const seenBootstrapWarnings = /* @__PURE__ */ new Set();
const bootstrapWarningOrder = [];
function rememberBootstrapWarning(key) {
if (seenBootstrapWarnings.has(key)) return false;
if (seenBootstrapWarnings.size >= BOOTSTRAP_WARNING_DEDUPE_LIMIT) {
const oldest = bootstrapWarningOrder.shift();
if (oldest) seenBootstrapWarnings.delete(oldest);
}
seenBootstrapWarnings.add(key);
bootstrapWarningOrder.push(key);
return true;
}
/** Resolves the effective bootstrap injection mode for a session agent. */
function resolveContextInjectionMode(config, agentId) {
const agentMode = config && agentId ? resolveAgentConfig(config, agentId)?.contextInjection : void 0;
if (agentMode === "always" || agentMode === "continuation-skip" || agentMode === "never") return agentMode;
return config?.agents?.defaults?.contextInjection ?? "always";
}
/** Checks the active SQLite transcript branch for a valid full-bootstrap marker. */
async function hasCompletedBootstrapTurn(sessionTarget) {
const { agentId, sessionId, sessionKey, storePath } = sessionTarget ?? {};
if (!agentId || !sessionId || !sessionKey || !storePath) return false;
try {
const records = readRecentSessionTranscriptActiveEvents({
agentId,
sessionId,
sessionKey,
storePath
}, CONTINUATION_SCAN_MAX_RECORDS);
for (const entry of records.toReversed()) {
const record = entry;
if (record?.type === "compaction" || record?.type === "reset") return false;
if (record?.type === "custom" && record.customType === "openclaw:bootstrap-context:full") return true;
}
return false;
} catch {
return false;
}
}
/** Builds a session-scoped warning sink that dedupes repeated bootstrap warnings. */
function makeBootstrapWarn(params) {
const warn = params.warn;
if (!warn) return;
const workspacePrefix = params.workspaceDir ?? "";
return (message) => {
if (!rememberBootstrapWarning(`${workspacePrefix}\u0000${params.sessionLabel}\u0000${message}`)) return;
warn(`${message} (sessionKey=${params.sessionLabel})`);
};
}
function sanitizeBootstrapFiles(files, workspaceDir, warn) {
const workspaceRoot = resolveUserPath(workspaceDir);
const seenPaths = /* @__PURE__ */ new Set();
const sanitized = [];
for (const file of files) {
const pathValue = normalizeOptionalString(file.path) ?? "";
if (!pathValue) {
warn?.(`skipping bootstrap file "${file.name}" — missing or invalid "path" field (hook may have used "filePath" instead)`);
continue;
}
const resolvedPath = path.isAbsolute(pathValue) ? path.resolve(pathValue) : pathValue.startsWith("~") ? resolveUserPath(pathValue) : path.resolve(workspaceRoot, pathValue);
const dedupeKey = path.normalize(path.relative(workspaceRoot, resolvedPath));
if (seenPaths.has(dedupeKey)) continue;
seenPaths.add(dedupeKey);
sanitized.push({
...file,
path: resolvedPath
});
}
return sanitized;
}
function applyContextModeFilter(params) {
if ((params.contextMode ?? "full") !== "lightweight") return params.files;
return [];
}
function filterCompletedWorkspaceBootstrapFile(files, setupCompleted, workspaceDir) {
if (!setupCompleted) return files;
const workspaceRoot = resolveUserPath(workspaceDir);
const rootBootstrapPath = path.join(workspaceRoot, DEFAULT_BOOTSTRAP_FILENAME);
return files.filter((file) => {
if (file.name !== "BOOTSTRAP.md") return true;
const pathValue = normalizeOptionalString(file.path);
if (!pathValue) return true;
return (path.isAbsolute(pathValue) ? path.resolve(pathValue) : pathValue.startsWith("~") ? resolveUserPath(pathValue) : path.resolve(workspaceRoot, pathValue)) !== rootBootstrapPath;
});
}
async function isWorkspaceSetupCompletedForContext(workspaceDir, readOnlyState = false) {
try {
return await isWorkspaceSetupCompleted(workspaceDir, readOnlyState ? { readOnly: true } : {});
} catch {
return false;
}
}
function filterBootstrapFilesAfterHooks(params) {
const sessionFiltered = filterBootstrapFilesForSession(params.files, params.session);
const protectedFiles = params.protectedFiles ?? [];
if (protectedFiles.length === 0) return sessionFiltered;
return sessionFiltered.filter((file) => !protectedFiles.some((protectedFile) => {
if (workspaceFilesShareSourceIdentity(file, protectedFile)) return true;
const filePath = normalizeOptionalString(file.path);
const protectedPath = normalizeOptionalString(protectedFile.path);
return Boolean(filePath && protectedPath && path.resolve(filePath) === path.resolve(protectedPath));
}));
}
async function resolveIneligibleAutomaticMemoryFiles(params) {
const candidates = params.files.filter((file) => !file.missing && (file.name === DEFAULT_MEMORY_FILENAME || file.name === "USER.md"));
if (candidates.length === 0 || !params.config) return [];
let agentId;
try {
agentId = params.agentId ?? resolveDefaultAgentId(params.config);
} catch (error) {
params.warn?.(`excluding automatic memory context: ${String(error)}`);
return candidates;
}
const relativePaths = candidates.map((file) => path.relative(resolveUserPath(params.workspaceDir), file.path).replaceAll(path.sep, "/"));
let classificationResult;
try {
classificationResult = await classifyActiveMemoryWorkspacePaths({
cfg: params.config,
agentId,
workspaceDir: params.workspaceDir,
relativePaths
});
} catch (error) {
params.warn?.(`excluding automatic memory context: ${String(error)}`);
return candidates;
}
if (classificationResult.status === "unavailable") return [];
if (classificationResult.status === "unsupported") {
params.warn?.("excluding automatic memory context: selected memory runtime does not support provenance classification");
return candidates;
}
const origins = new Map(classificationResult.classifications.map((entry) => [entry.relativePath, entry.originClass]));
return candidates.filter((_file, index) => !isMemoryOriginEligibleForAutomaticInjection(origins.get(relativePaths[index])));
}
/** Prepare the same bounded workspace facts without invoking run-owned bootstrap hooks. */
async function resolveBootstrapFilesForPreparation(params) {
return resolveBootstrapFiles({
...params,
readOnlyState: true
}, false);
}
async function resolveBootstrapFilesForRun(params) {
return resolveBootstrapFiles(params, true);
}
async function resolveBootstrapFiles(params, applyHooks) {
const session = {
sessionKey: params.sessionKey ?? params.sessionId,
chatType: params.chatType,
workspaceDir: params.workspaceDir
};
const workspaceSetupCompleted = await isWorkspaceSetupCompletedForContext(params.workspaceDir, params.readOnlyState);
const rawFiles = params.sessionKey ? await getOrLoadBootstrapFiles({
workspaceDir: params.workspaceDir,
sessionKey: params.sessionKey
}) : await loadWorkspaceBootstrapFiles(params.workspaceDir);
const ineligibleAutomaticMemoryFiles = await resolveIneligibleAutomaticMemoryFiles({
files: rawFiles,
workspaceDir: params.workspaceDir,
config: params.config,
agentId: params.agentId,
warn: params.warn
});
const rootMemoryFile = rawFiles.find((file) => file.name === DEFAULT_MEMORY_FILENAME && !file.missing);
const protectedRootMemoryFile = rootMemoryFile && filterBootstrapFilesForSession([rootMemoryFile], session).length === 0 ? rootMemoryFile : void 0;
const protectedFiles = [...protectedRootMemoryFile ? [protectedRootMemoryFile] : [], ...ineligibleAutomaticMemoryFiles];
const bootstrapFiles = applyContextModeFilter({
files: filterCompletedWorkspaceBootstrapFile(filterBootstrapFilesForSession(rawFiles, session).filter((file) => !ineligibleAutomaticMemoryFiles.some((ineligible) => workspaceFilesShareSourceIdentity(file, ineligible))), workspaceSetupCompleted, params.workspaceDir),
contextMode: params.contextMode,
runKind: params.runKind
});
return sanitizeBootstrapFiles(filterCompletedWorkspaceBootstrapFile(filterBootstrapFilesAfterHooks({
files: applyHooks ? await applyBootstrapHookOverrides({
files: bootstrapFiles,
workspaceDir: params.workspaceDir,
config: params.config,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId: params.agentId
}) : bootstrapFiles,
session,
protectedFiles
}), workspaceSetupCompleted, params.workspaceDir), params.workspaceDir, params.warn);
}
/** Resolves both raw bootstrap metadata and bounded context files for a run. */
async function resolveBootstrapContextForRun(params) {
const bootstrapFiles = await resolveBootstrapFilesForRun(params);
return {
bootstrapFiles,
contextFiles: buildBootstrapContextForFiles(bootstrapFiles, params)
};
}
/** Builds bounded context files from already-resolved bootstrap file metadata. */
function buildBootstrapContextForFiles(bootstrapFiles, params) {
return buildBootstrapContextFiles(bootstrapFiles, {
maxChars: resolveBootstrapMaxChars(params.config, params.agentId),
totalMaxChars: resolveBootstrapTotalMaxChars(params.config, params.agentId),
warn: params.warn
});
}
//#endregion
export { resolveBootstrapContextForRun as a, resolveContextInjectionMode as c, makeBootstrapWarn as i, buildBootstrapContextForFiles as n, resolveBootstrapFilesForPreparation as o, hasCompletedBootstrapTurn as r, resolveBootstrapFilesForRun as s, FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE as t };