UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

482 lines (478 loc) 18.1 kB
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 };