@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
482 lines (478 loc) • 18.1 kB
JavaScript
import { Yt as resolveStateDir, nt as pathExists, ot as resolveUserPath, pn as resolveRequiredHomeDir } from "./entry.js";
import { b as isSubagentSessionKey, l as normalizeAgentId, n as DEFAULT_AGENT_ID, x as parseAgentSessionKey, y as isCronSessionKey } from "./session-key-BGiG_JcT.js";
import { i as normalizeSkillFilter, t as resolveOpenClawPackageRoot } from "./openclaw-root-B5pKN_cp.js";
import { t as runCommandWithTimeout } from "./exec-CBKBIMpA.js";
import os from "node:os";
import path from "node:path";
import fs from "node:fs/promises";
import { fileURLToPath } from "node:url";
//#region src/agents/workspace-templates.ts
const FALLBACK_TEMPLATE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../docs/reference/templates");
let cachedTemplateDir;
let resolvingTemplateDir;
async function resolveWorkspaceTemplateDir(opts) {
if (cachedTemplateDir) return cachedTemplateDir;
if (resolvingTemplateDir) return resolvingTemplateDir;
resolvingTemplateDir = (async () => {
const moduleUrl = opts?.moduleUrl ?? import.meta.url;
const argv1 = opts?.argv1 ?? process.argv[1];
const cwd = opts?.cwd ?? process.cwd();
const packageRoot = await resolveOpenClawPackageRoot({
moduleUrl,
argv1,
cwd
});
const candidates = [
packageRoot ? path.join(packageRoot, "docs", "reference", "templates") : null,
cwd ? path.resolve(cwd, "docs", "reference", "templates") : null,
FALLBACK_TEMPLATE_DIR
].filter(Boolean);
for (const candidate of candidates) if (await pathExists(candidate)) {
cachedTemplateDir = candidate;
return candidate;
}
cachedTemplateDir = candidates[0] ?? FALLBACK_TEMPLATE_DIR;
return cachedTemplateDir;
})();
try {
return await resolvingTemplateDir;
} finally {
resolvingTemplateDir = void 0;
}
}
//#endregion
//#region src/agents/workspace.ts
function resolveDefaultAgentWorkspaceDir(env = process.env, homedir = os.homedir) {
const home = resolveRequiredHomeDir(env, homedir);
const profile = env.OPENCLAW_PROFILE?.trim();
if (profile && profile.toLowerCase() !== "default") return path.join(home, ".openclaw", `workspace-${profile}`);
return path.join(home, ".openclaw", "workspace");
}
const DEFAULT_AGENT_WORKSPACE_DIR = resolveDefaultAgentWorkspaceDir();
const DEFAULT_AGENTS_FILENAME = "AGENTS.md";
const DEFAULT_SOUL_FILENAME = "SOUL.md";
const DEFAULT_TOOLS_FILENAME = "TOOLS.md";
const DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
const DEFAULT_USER_FILENAME = "USER.md";
const DEFAULT_HEARTBEAT_FILENAME = "HEARTBEAT.md";
const DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
const DEFAULT_MEMORY_FILENAME = "MEMORY.md";
const DEFAULT_MEMORY_ALT_FILENAME = "memory.md";
const WORKSPACE_STATE_DIRNAME = ".openclaw";
const WORKSPACE_STATE_FILENAME = "workspace-state.json";
const WORKSPACE_STATE_VERSION = 1;
const workspaceTemplateCache = /* @__PURE__ */ new Map();
let gitAvailabilityPromise = null;
const workspaceFileCache = /* @__PURE__ */ new Map();
/**
* Read file with caching based on mtime. Returns cached content if file
* hasn't changed, otherwise reads from disk and updates cache.
*/
async function readFileWithCache(filePath) {
try {
const mtimeMs = (await fs.stat(filePath)).mtimeMs;
const cached = workspaceFileCache.get(filePath);
if (cached && cached.mtimeMs === mtimeMs) return cached.content;
const content = await fs.readFile(filePath, "utf-8");
workspaceFileCache.set(filePath, {
content,
mtimeMs
});
return content;
} catch (error) {
workspaceFileCache.delete(filePath);
throw error;
}
}
function stripFrontMatter(content) {
if (!content.startsWith("---")) return content;
const endIndex = content.indexOf("\n---", 3);
if (endIndex === -1) return content;
const start = endIndex + 4;
let trimmed = content.slice(start);
trimmed = trimmed.replace(/^\s+/, "");
return trimmed;
}
async function loadTemplate(name) {
const cached = workspaceTemplateCache.get(name);
if (cached) return cached;
const pending = (async () => {
const templateDir = await resolveWorkspaceTemplateDir();
const templatePath = path.join(templateDir, name);
try {
return stripFrontMatter(await fs.readFile(templatePath, "utf-8"));
} catch {
throw new Error(`Missing workspace template: ${name} (${templatePath}). Ensure docs/reference/templates are packaged.`);
}
})();
workspaceTemplateCache.set(name, pending);
try {
return await pending;
} catch (error) {
workspaceTemplateCache.delete(name);
throw error;
}
}
async function writeFileIfMissing(filePath, content) {
try {
await fs.writeFile(filePath, content, {
encoding: "utf-8",
flag: "wx"
});
return true;
} catch (err) {
if (err.code !== "EEXIST") throw err;
return false;
}
}
async function fileExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
function resolveWorkspaceStatePath(dir) {
return path.join(dir, WORKSPACE_STATE_DIRNAME, WORKSPACE_STATE_FILENAME);
}
function parseWorkspaceOnboardingState(raw) {
try {
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== "object") return null;
return {
version: WORKSPACE_STATE_VERSION,
bootstrapSeededAt: typeof parsed.bootstrapSeededAt === "string" ? parsed.bootstrapSeededAt : void 0,
onboardingCompletedAt: typeof parsed.onboardingCompletedAt === "string" ? parsed.onboardingCompletedAt : void 0
};
} catch {
return null;
}
}
async function readWorkspaceOnboardingState(statePath) {
try {
return parseWorkspaceOnboardingState(await fs.readFile(statePath, "utf-8")) ?? { version: WORKSPACE_STATE_VERSION };
} catch (err) {
if (err.code !== "ENOENT") throw err;
return { version: WORKSPACE_STATE_VERSION };
}
}
async function readWorkspaceOnboardingStateForDir(dir) {
return await readWorkspaceOnboardingState(resolveWorkspaceStatePath(resolveUserPath(dir)));
}
async function isWorkspaceOnboardingCompleted(dir) {
const state = await readWorkspaceOnboardingStateForDir(dir);
return typeof state.onboardingCompletedAt === "string" && state.onboardingCompletedAt.trim().length > 0;
}
async function writeWorkspaceOnboardingState(statePath, state) {
await fs.mkdir(path.dirname(statePath), { recursive: true });
const payload = `${JSON.stringify(state, null, 2)}\n`;
const tmpPath = `${statePath}.tmp-${process.pid}-${Date.now().toString(36)}`;
try {
await fs.writeFile(tmpPath, payload, { encoding: "utf-8" });
await fs.rename(tmpPath, statePath);
} catch (err) {
await fs.unlink(tmpPath).catch(() => {});
throw err;
}
}
async function hasGitRepo(dir) {
try {
await fs.stat(path.join(dir, ".git"));
return true;
} catch {
return false;
}
}
async function isGitAvailable() {
if (gitAvailabilityPromise) return gitAvailabilityPromise;
gitAvailabilityPromise = (async () => {
try {
return (await runCommandWithTimeout(["git", "--version"], { timeoutMs: 2e3 })).code === 0;
} catch {
return false;
}
})();
return gitAvailabilityPromise;
}
async function ensureGitRepo(dir, isBrandNewWorkspace) {
if (!isBrandNewWorkspace) return;
if (await hasGitRepo(dir)) return;
if (!await isGitAvailable()) return;
try {
await runCommandWithTimeout(["git", "init"], {
cwd: dir,
timeoutMs: 1e4
});
} catch {}
}
async function ensureAgentWorkspace(params) {
const dir = resolveUserPath(params?.dir?.trim() ? params.dir.trim() : DEFAULT_AGENT_WORKSPACE_DIR);
await fs.mkdir(dir, { recursive: true });
if (!params?.ensureBootstrapFiles) return { dir };
const agentsPath = path.join(dir, DEFAULT_AGENTS_FILENAME);
const soulPath = path.join(dir, DEFAULT_SOUL_FILENAME);
const toolsPath = path.join(dir, DEFAULT_TOOLS_FILENAME);
const identityPath = path.join(dir, DEFAULT_IDENTITY_FILENAME);
const userPath = path.join(dir, DEFAULT_USER_FILENAME);
const heartbeatPath = path.join(dir, DEFAULT_HEARTBEAT_FILENAME);
const bootstrapPath = path.join(dir, DEFAULT_BOOTSTRAP_FILENAME);
const statePath = resolveWorkspaceStatePath(dir);
const isBrandNewWorkspace = await (async () => {
const paths = [
agentsPath,
soulPath,
toolsPath,
identityPath,
userPath,
heartbeatPath
];
return (await Promise.all(paths.map(async (p) => {
try {
await fs.access(p);
return true;
} catch {
return false;
}
}))).every((v) => !v);
})();
const agentsTemplate = await loadTemplate(DEFAULT_AGENTS_FILENAME);
const soulTemplate = await loadTemplate(DEFAULT_SOUL_FILENAME);
const toolsTemplate = await loadTemplate(DEFAULT_TOOLS_FILENAME);
const identityTemplate = await loadTemplate(DEFAULT_IDENTITY_FILENAME);
const userTemplate = await loadTemplate(DEFAULT_USER_FILENAME);
const heartbeatTemplate = await loadTemplate(DEFAULT_HEARTBEAT_FILENAME);
await writeFileIfMissing(agentsPath, agentsTemplate);
await writeFileIfMissing(soulPath, soulTemplate);
await writeFileIfMissing(toolsPath, toolsTemplate);
await writeFileIfMissing(identityPath, identityTemplate);
await writeFileIfMissing(userPath, userTemplate);
await writeFileIfMissing(heartbeatPath, heartbeatTemplate);
let state = await readWorkspaceOnboardingState(statePath);
let stateDirty = false;
const markState = (next) => {
state = {
...state,
...next
};
stateDirty = true;
};
const nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
let bootstrapExists = await fileExists(bootstrapPath);
if (!state.bootstrapSeededAt && bootstrapExists) markState({ bootstrapSeededAt: nowIso() });
if (!state.onboardingCompletedAt && state.bootstrapSeededAt && !bootstrapExists) markState({ onboardingCompletedAt: nowIso() });
if (!state.bootstrapSeededAt && !state.onboardingCompletedAt && !bootstrapExists) {
const [identityContent, userContent] = await Promise.all([fs.readFile(identityPath, "utf-8"), fs.readFile(userPath, "utf-8")]);
if (identityContent !== identityTemplate || userContent !== userTemplate) markState({ onboardingCompletedAt: nowIso() });
else {
if (!await writeFileIfMissing(bootstrapPath, await loadTemplate(DEFAULT_BOOTSTRAP_FILENAME))) bootstrapExists = await fileExists(bootstrapPath);
else bootstrapExists = true;
if (bootstrapExists && !state.bootstrapSeededAt) markState({ bootstrapSeededAt: nowIso() });
}
}
if (stateDirty) await writeWorkspaceOnboardingState(statePath, state);
await ensureGitRepo(dir, isBrandNewWorkspace);
return {
dir,
agentsPath,
soulPath,
toolsPath,
identityPath,
userPath,
heartbeatPath,
bootstrapPath
};
}
async function resolveMemoryBootstrapEntries(resolvedDir) {
const candidates = [DEFAULT_MEMORY_FILENAME, DEFAULT_MEMORY_ALT_FILENAME];
const entries = [];
for (const name of candidates) {
const filePath = path.join(resolvedDir, name);
try {
await fs.access(filePath);
entries.push({
name,
filePath
});
} catch {}
}
if (entries.length <= 1) return entries;
const seen = /* @__PURE__ */ new Set();
const deduped = [];
for (const entry of entries) {
let key = entry.filePath;
try {
key = await fs.realpath(entry.filePath);
} catch {}
if (seen.has(key)) continue;
seen.add(key);
deduped.push(entry);
}
return deduped;
}
async function loadWorkspaceBootstrapFiles(dir) {
const resolvedDir = resolveUserPath(dir);
const entries = [
{
name: DEFAULT_AGENTS_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_AGENTS_FILENAME)
},
{
name: DEFAULT_SOUL_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_SOUL_FILENAME)
},
{
name: DEFAULT_TOOLS_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_TOOLS_FILENAME)
},
{
name: DEFAULT_IDENTITY_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_IDENTITY_FILENAME)
},
{
name: DEFAULT_USER_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_USER_FILENAME)
},
{
name: DEFAULT_HEARTBEAT_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_HEARTBEAT_FILENAME)
},
{
name: DEFAULT_BOOTSTRAP_FILENAME,
filePath: path.join(resolvedDir, DEFAULT_BOOTSTRAP_FILENAME)
}
];
entries.push(...await resolveMemoryBootstrapEntries(resolvedDir));
const result = [];
for (const entry of entries) try {
const content = await readFileWithCache(entry.filePath);
result.push({
name: entry.name,
path: entry.filePath,
content,
missing: false
});
} catch {
result.push({
name: entry.name,
path: entry.filePath,
missing: true
});
}
return result;
}
const MINIMAL_BOOTSTRAP_ALLOWLIST = new Set([DEFAULT_AGENTS_FILENAME, DEFAULT_TOOLS_FILENAME]);
function filterBootstrapFilesForSession(files, sessionKey) {
if (!sessionKey || !isSubagentSessionKey(sessionKey) && !isCronSessionKey(sessionKey)) return files;
return files.filter((file) => MINIMAL_BOOTSTRAP_ALLOWLIST.has(file.name));
}
//#endregion
//#region src/agents/agent-scope.ts
let defaultAgentWarned = false;
function listAgentEntries(cfg) {
const list = cfg.agents?.list;
if (!Array.isArray(list)) return [];
return list.filter((entry) => Boolean(entry && typeof entry === "object"));
}
function listAgentIds(cfg) {
const agents = listAgentEntries(cfg);
if (agents.length === 0) return [DEFAULT_AGENT_ID];
const seen = /* @__PURE__ */ new Set();
const ids = [];
for (const entry of agents) {
const id = normalizeAgentId(entry?.id);
if (seen.has(id)) continue;
seen.add(id);
ids.push(id);
}
return ids.length > 0 ? ids : [DEFAULT_AGENT_ID];
}
function resolveDefaultAgentId(cfg) {
const agents = listAgentEntries(cfg);
if (agents.length === 0) return DEFAULT_AGENT_ID;
const defaults = agents.filter((agent) => agent?.default);
if (defaults.length > 1 && !defaultAgentWarned) {
defaultAgentWarned = true;
console.warn("Multiple agents marked default=true; using the first entry as default.");
}
const chosen = (defaults[0] ?? agents[0])?.id?.trim();
return normalizeAgentId(chosen || DEFAULT_AGENT_ID);
}
function resolveSessionAgentIds(params) {
const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
const sessionKey = params.sessionKey?.trim();
const normalizedSessionKey = sessionKey ? sessionKey.toLowerCase() : void 0;
const parsed = normalizedSessionKey ? parseAgentSessionKey(normalizedSessionKey) : null;
return {
defaultAgentId,
sessionAgentId: parsed?.agentId ? normalizeAgentId(parsed.agentId) : defaultAgentId
};
}
function resolveSessionAgentId(params) {
return resolveSessionAgentIds(params).sessionAgentId;
}
function resolveAgentEntry(cfg, agentId) {
const id = normalizeAgentId(agentId);
return listAgentEntries(cfg).find((entry) => normalizeAgentId(entry.id) === id);
}
function resolveAgentConfig(cfg, agentId) {
const entry = resolveAgentEntry(cfg, normalizeAgentId(agentId));
if (!entry) return;
return {
name: typeof entry.name === "string" ? entry.name : void 0,
workspace: typeof entry.workspace === "string" ? entry.workspace : void 0,
agentDir: typeof entry.agentDir === "string" ? entry.agentDir : void 0,
model: typeof entry.model === "string" || entry.model && typeof entry.model === "object" ? entry.model : void 0,
skills: Array.isArray(entry.skills) ? entry.skills : void 0,
memorySearch: entry.memorySearch,
humanDelay: entry.humanDelay,
heartbeat: entry.heartbeat,
identity: entry.identity,
groupChat: entry.groupChat,
subagents: typeof entry.subagents === "object" && entry.subagents ? entry.subagents : void 0,
sandbox: entry.sandbox,
tools: entry.tools
};
}
function resolveAgentSkillsFilter(cfg, agentId) {
return normalizeSkillFilter(resolveAgentConfig(cfg, agentId)?.skills);
}
function resolveAgentModelPrimary(cfg, agentId) {
const raw = resolveAgentConfig(cfg, agentId)?.model;
if (!raw) return;
if (typeof raw === "string") return raw.trim() || void 0;
return raw.primary?.trim() || void 0;
}
function resolveAgentModelFallbacksOverride(cfg, agentId) {
const raw = resolveAgentConfig(cfg, agentId)?.model;
if (!raw || typeof raw === "string") return;
if (!Object.hasOwn(raw, "fallbacks")) return;
return Array.isArray(raw.fallbacks) ? raw.fallbacks : void 0;
}
function resolveEffectiveModelFallbacks(params) {
const agentFallbacksOverride = resolveAgentModelFallbacksOverride(params.cfg, params.agentId);
if (!params.hasSessionModelOverride) return agentFallbacksOverride;
const defaultFallbacks = typeof params.cfg.agents?.defaults?.model === "object" ? params.cfg.agents.defaults.model.fallbacks ?? [] : [];
return agentFallbacksOverride ?? defaultFallbacks;
}
function resolveAgentWorkspaceDir(cfg, agentId) {
const id = normalizeAgentId(agentId);
const configured = resolveAgentConfig(cfg, id)?.workspace?.trim();
if (configured) return resolveUserPath(configured);
if (id === resolveDefaultAgentId(cfg)) {
const fallback = cfg.agents?.defaults?.workspace?.trim();
if (fallback) return resolveUserPath(fallback);
return resolveDefaultAgentWorkspaceDir(process.env);
}
const stateDir = resolveStateDir(process.env);
return path.join(stateDir, `workspace-${id}`);
}
function resolveAgentDir(cfg, agentId) {
const id = normalizeAgentId(agentId);
const configured = resolveAgentConfig(cfg, id)?.agentDir?.trim();
if (configured) return resolveUserPath(configured);
const root = resolveStateDir(process.env);
return path.join(root, "agents", id, "agent");
}
//#endregion
export { ensureAgentWorkspace as C, resolveDefaultAgentWorkspaceDir as D, loadWorkspaceBootstrapFiles as E, resolveWorkspaceTemplateDir as O, DEFAULT_USER_FILENAME as S, isWorkspaceOnboardingCompleted as T, DEFAULT_IDENTITY_FILENAME as _, resolveAgentModelFallbacksOverride as a, DEFAULT_SOUL_FILENAME as b, resolveAgentWorkspaceDir as c, resolveSessionAgentId as d, resolveSessionAgentIds as f, DEFAULT_HEARTBEAT_FILENAME as g, DEFAULT_BOOTSTRAP_FILENAME as h, resolveAgentDir as i, resolveDefaultAgentId as l, DEFAULT_AGENT_WORKSPACE_DIR as m, listAgentIds as n, resolveAgentModelPrimary as o, DEFAULT_AGENTS_FILENAME as p, resolveAgentConfig as r, resolveAgentSkillsFilter as s, listAgentEntries as t, resolveEffectiveModelFallbacks as u, DEFAULT_MEMORY_ALT_FILENAME as v, filterBootstrapFilesForSession as w, DEFAULT_TOOLS_FILENAME as x, DEFAULT_MEMORY_FILENAME as y };