openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
290 lines (289 loc) • 14.4 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-6kR-86XC.mjs";
import { o as resolveUserPath } from "./home-dir-CjKH62jd.mjs";
import "./utils-DUAwk2p5.mjs";
import { r as isRootFileMissingFailure } from "./boundary-file-read-CCth2Hwg.mjs";
import { g as resolveDefaultAgentId, r as resolveAgentConfig } from "./agent-scope-config-BU4qc9bA.mjs";
import { l as resolveAgentIdFromSessionKey } from "./session-key-BC4m_Ly5.mjs";
import "./agent-scope-DWB4x52T.mjs";
import { r as hasMultipleSessionSharingIdentities, u as readUserProfileIdentity } from "./user-profile-list-y8HW3dT5.mjs";
import { i as getAgentWorkspaceAccess } from "./workspace-access-CCdQbCKD.mjs";
import { g as resolveWorkspaceBootstrapPath, i as DEFAULT_MEMORY_FILENAME, n as DEFAULT_BOOTSTRAP_FILENAME, s as DEFAULT_USER_FILENAME } from "./workspace-bootstrap-policy-DaS4N8nC.mjs";
import { a as isWorkspaceSetupCompleted, d as getWorkspaceFileSourceRelativePath, f as readWorkspaceFileWithGuards, h as workspaceFilesShareSourceIdentity, n as filterBootstrapFilesForSession, p as setWorkspaceFileSourceIdentity, s as loadWorkspaceBootstrapFiles } from "./workspace-BUeMloSh.mjs";
import { r as isMemoryOriginEligibleForAutomaticInjection } from "./types-DTVBdCzw.mjs";
import "./session-accessor-BM3Wa1Bx.mjs";
import { i as readRecentSessionTranscriptActiveEvents } from "./session-accessor.sqlite-active-events-DgaYrqkj.mjs";
import { n as createInternalHookEvent, u as triggerInternalHook } from "./internal-hooks-DIPAwHyY.mjs";
import { i as resolveBootstrapTotalMaxChars, n as buildBootstrapContextFiles, r as resolveBootstrapMaxChars } from "./bootstrap-Q-AcdcrE.mjs";
import "./embedded-agent-helpers-DKuJhdd0.mjs";
import { i as getOrLoadBootstrapFiles } from "./bootstrap-cache-Dotr528k.mjs";
import { n as classifyActiveMemoryWorkspacePaths } from "./memory-runtime-CmafEEmn.mjs";
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/workspace-personal-bootstrap.ts
/** Optional personal overlay; the caller supplies the session-selected human profile. */
async function loadPersonalUserBootstrapFile(dir, profileId, warn) {
if (!profileId || !hasMultipleSessionSharingIdentities()) return;
const canonicalId = readUserProfileIdentity(profileId)?.profileId;
if (!canonicalId || !/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(canonicalId)) return;
const workspaceDir = resolveUserPath(dir);
const filePath = path.join(workspaceDir, "users", canonicalId, DEFAULT_USER_FILENAME);
const loaded = await readWorkspaceFileWithGuards({
filePath,
workspaceDir,
rejectAliases: true
});
if (!loaded.ok) {
if (!isRootFileMissingFailure(loaded)) warn?.("Personal USER.md could not be read safely; using shared defaults.");
return;
}
if (!hasMultipleSessionSharingIdentities() || readUserProfileIdentity(profileId)?.profileId !== canonicalId) return;
const file = {
name: DEFAULT_USER_FILENAME,
path: filePath,
content: loaded.content,
missing: false,
personalUser: true
};
setWorkspaceFileSourceIdentity(file, loaded.sourceIdentity);
return file;
}
//#endregion
//#region src/agents/bootstrap-files.ts
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();
function rememberBootstrapWarning(key) {
if (seenBootstrapWarnings.has(key)) return false;
if (seenBootstrapWarnings.size >= BOOTSTRAP_WARNING_DEDUPE_LIMIT) {
const oldest = seenBootstrapWarnings.values().next().value;
if (oldest) seenBootstrapWarnings.delete(oldest);
}
seenBootstrapWarnings.add(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 = resolveWorkspaceBootstrapPath(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 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 resolveWorkspaceBootstrapPath(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 {
const access = getAgentWorkspaceAccess(params.workspaceDir);
classificationResult = await classifyActiveMemoryWorkspacePaths({
cfg: params.config,
agentId,
workspaceDir: params.workspaceDir,
relativePaths,
...access ? { readSources: candidates.map((file, index) => ({
relativePath: relativePaths[index],
canonicalRelativePath: getWorkspaceFileSourceRelativePath(file)
})) } : {}
});
if (access && getAgentWorkspaceAccess(params.workspaceDir) !== access) throw new Error("Workspace access changed during memory classification");
} 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
}, "none");
}
async function resolveBootstrapFilesForRun(params) {
return resolveBootstrapFiles(params, "registered");
}
async function resolveBootstrapFiles(params, hooks) {
const access = getAgentWorkspaceAccess(params.workspaceDir);
const session = {
sessionKey: params.sessionKey ?? params.sessionId,
chatType: params.chatType,
workspaceDir: params.workspaceDir
};
const workspaceSetupCompleted = await isWorkspaceSetupCompletedForContext(params.workspaceDir, params.readOnlyState);
const sharedFiles = params.sessionKey ? await getOrLoadBootstrapFiles({
workspaceDir: params.workspaceDir,
sessionKey: params.sessionKey
}) : await loadWorkspaceBootstrapFiles(params.workspaceDir);
const personalFile = await loadPersonalUserBootstrapFile(params.workspaceDir, params.bootstrapUserProfileId, params.warn);
const userIndex = sharedFiles.findIndex((file) => file.name === DEFAULT_USER_FILENAME);
const rawFiles = [...sharedFiles];
if (personalFile) rawFiles.splice(userIndex < 0 ? rawFiles.length : userIndex + 1, 0, personalFile);
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 sessionFiles = filterCompletedWorkspaceBootstrapFile(filterBootstrapFilesForSession(rawFiles, session).filter((file) => !ineligibleAutomaticMemoryFiles.some((ineligible) => workspaceFilesShareSourceIdentity(file, ineligible))), workspaceSetupCompleted, params.workspaceDir);
const bootstrapFiles = params.contextMode === "lightweight" ? [] : sessionFiles;
const hooked = hooks === "registered" ? await applyBootstrapHookOverrides({
files: bootstrapFiles,
workspaceDir: params.workspaceDir,
config: params.config,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId: params.agentId
}) : bootstrapFiles;
const filteredUpdated = filterCompletedWorkspaceBootstrapFile(filterBootstrapFilesAfterHooks({
files: typeof hooks === "object" ? [...hooked, ...hooks.projected] : hooked,
session,
protectedFiles
}), workspaceSetupCompleted, params.workspaceDir);
if (getAgentWorkspaceAccess(params.workspaceDir) !== access) throw new Error("Workspace access changed while preparing bootstrap context");
return sanitizeBootstrapFiles(filteredUpdated, 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)
};
}
/** Applies declared additions through the normal bootstrap filters and budgets. */
async function resolveBootstrapContextWithProjectedHookFiles(params, projected) {
const bootstrapFiles = await resolveBootstrapFiles({
...params,
readOnlyState: true
}, { projected });
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, resolveBootstrapFilesForRun as c, makeBootstrapWarn as i, resolveContextInjectionMode as l, buildBootstrapContextForFiles as n, resolveBootstrapContextWithProjectedHookFiles as o, hasCompletedBootstrapTurn as r, resolveBootstrapFilesForPreparation as s, FULL_BOOTSTRAP_COMPLETED_CUSTOM_TYPE as t };