@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
1,447 lines (1,425 loc) • 300 kB
JavaScript
import { i as resolveGatewayPort, t as STATE_DIR } from "./paths-B1kfl4h5.js";
import { A as normalizeAccountId$1, C as resolveOpenClawPackageRootSync, D as buildAgentMainSessionKey, M as normalizeMainKey, N as resolveAgentIdFromSessionKey, T as DEFAULT_AGENT_ID, _ as DEFAULT_TOOLS_FILENAME, c as resolveDefaultAgentId, d as DEFAULT_AGENTS_FILENAME, f as DEFAULT_AGENT_WORKSPACE_DIR, g as DEFAULT_SOUL_FILENAME, h as DEFAULT_IDENTITY_FILENAME, j as normalizeAgentId, l as resolveSessionAgentId, m as DEFAULT_HEARTBEAT_FILENAME, n as resolveAgentConfig, p as DEFAULT_BOOTSTRAP_FILENAME, v as DEFAULT_USER_FILENAME, w as DEFAULT_ACCOUNT_ID, y as ensureAgentWorkspace } from "./agent-scope-Csu2B6AM.js";
import { A as normalizeE164, C as CONFIG_DIR, N as resolveUserPath, _ as normalizeAnyChannelId, b as getActivePluginRegistry, g as getChatChannelMeta, h as CHAT_CHANNEL_ORDER, j as resolveConfigDir, l as createSubsystemLogger, m as CHANNEL_IDS, n as runExec, u as defaultRuntime, x as requireActivePluginRegistry, y as normalizeChatChannelId } from "./exec-BMnoMcZW.js";
import { O as isTruthyEnvValue, k as parseBooleanValue } from "./model-selection-mzTqrNoj.js";
import { t as formatCliCommand } from "./command-format-CFzL448l.js";
import { _ as resolveEnableState, f as loadPluginManifestRegistry, g as normalizePluginsConfig, h as MANIFEST_KEY, i as writeConfigFile, k as resolveWhatsAppAccount, m as LEGACY_MANIFEST_KEYS, t as loadConfig, v as resolveMemorySlotDecision } from "./config-CG73z4h6.js";
import { C as DEFAULT_AI_SNAPSHOT_MAX_CHARS, D as DEFAULT_OPENCLAW_BROWSER_ENABLED, E as DEFAULT_OPENCLAW_BROWSER_COLOR, O as DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME, S as DEFAULT_AI_SNAPSHOT_EFFICIENT_MAX_CHARS, T as DEFAULT_BROWSER_EVALUATE_ENABLED, a as resolveOpenClawUserDataDir, c as captureScreenshot, d as normalizeCdpWsUrl, f as snapshotAria, g as stopChromeExtensionRelayServer, h as ensureChromeExtensionRelayServer, i as launchOpenClawChrome, l as createTargetViaCdp, m as getHeadersWithAuth, n as isChromeCdpReady, o as stopOpenClawChrome, p as appendCdpPath, r as isChromeReachable, s as resolveBrowserExecutableForPlatform, v as extractErrorCode, w as DEFAULT_BROWSER_DEFAULT_PROFILE_NAME, x as DEFAULT_AI_SNAPSHOT_EFFICIENT_DEPTH, y as formatErrorMessage } from "./chrome-B3IuUad-.js";
import { r as resolveSessionTranscriptPath, t as resolveDefaultSessionStorePath } from "./paths-B4kigINg.js";
import { t as emitSessionTranscriptUpdate } from "./transcript-events-JLH5W4He.js";
import os from "node:os";
import path from "node:path";
import fs from "node:fs";
import JSON5 from "json5";
import fs$1 from "node:fs/promises";
import { spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import crypto, { createHash } from "node:crypto";
import { CURRENT_SESSION_VERSION, SessionManager, formatSkillsForPrompt, loadSkillsFromDir } from "@mariozechner/pi-coding-agent";
import { fileTypeFromBuffer } from "file-type";
import YAML from "yaml";
import express from "express";
import { lookup } from "node:dns";
import { lookup as lookup$1 } from "node:dns/promises";
import { Agent } from "undici";
//#region src/discord/token.ts
function normalizeDiscordToken(raw) {
if (!raw) return;
const trimmed = raw.trim();
if (!trimmed) return;
return trimmed.replace(/^Bot\s+/i, "");
}
function resolveDiscordToken(cfg, opts = {}) {
const accountId = normalizeAccountId$1(opts.accountId);
const discordCfg = cfg?.channels?.discord;
const accountToken = normalizeDiscordToken((accountId !== DEFAULT_ACCOUNT_ID ? discordCfg?.accounts?.[accountId] : discordCfg?.accounts?.[DEFAULT_ACCOUNT_ID])?.token ?? void 0);
if (accountToken) return {
token: accountToken,
source: "config"
};
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const configToken = allowEnv ? normalizeDiscordToken(discordCfg?.token ?? void 0) : void 0;
if (configToken) return {
token: configToken,
source: "config"
};
const envToken = allowEnv ? normalizeDiscordToken(opts.envToken ?? process.env.DISCORD_BOT_TOKEN) : void 0;
if (envToken) return {
token: envToken,
source: "env"
};
return {
token: "",
source: "none"
};
}
//#endregion
//#region src/discord/accounts.ts
function listConfiguredAccountIds$2(cfg) {
const accounts = cfg.channels?.discord?.accounts;
if (!accounts || typeof accounts !== "object") return [];
return Object.keys(accounts).filter(Boolean);
}
function listDiscordAccountIds(cfg) {
const ids = listConfiguredAccountIds$2(cfg);
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
return ids.toSorted((a, b) => a.localeCompare(b));
}
function resolveAccountConfig$4(cfg, accountId) {
const accounts = cfg.channels?.discord?.accounts;
if (!accounts || typeof accounts !== "object") return;
return accounts[accountId];
}
function mergeDiscordAccountConfig(cfg, accountId) {
const { accounts: _ignored, ...base } = cfg.channels?.discord ?? {};
const account = resolveAccountConfig$4(cfg, accountId) ?? {};
return {
...base,
...account
};
}
function resolveDiscordAccount(params) {
const accountId = normalizeAccountId$1(params.accountId);
const baseEnabled = params.cfg.channels?.discord?.enabled !== false;
const merged = mergeDiscordAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const tokenResolution = resolveDiscordToken(params.cfg, { accountId });
return {
accountId,
enabled,
name: merged.name?.trim() || void 0,
token: tokenResolution.token,
tokenSource: tokenResolution.source,
config: merged
};
}
function listEnabledDiscordAccounts(cfg) {
return listDiscordAccountIds(cfg).map((accountId) => resolveDiscordAccount({
cfg,
accountId
})).filter((account) => account.enabled);
}
//#endregion
//#region src/channels/chat-type.ts
function normalizeChatType(raw) {
const value = raw?.trim().toLowerCase();
if (!value) return;
if (value === "direct" || value === "dm") return "direct";
if (value === "group") return "group";
if (value === "channel") return "channel";
}
//#endregion
//#region src/slack/token.ts
function normalizeSlackToken(raw) {
const trimmed = raw?.trim();
return trimmed ? trimmed : void 0;
}
function resolveSlackBotToken(raw) {
return normalizeSlackToken(raw);
}
function resolveSlackAppToken(raw) {
return normalizeSlackToken(raw);
}
//#endregion
//#region src/slack/accounts.ts
function resolveAccountConfig$3(cfg, accountId) {
const accounts = cfg.channels?.slack?.accounts;
if (!accounts || typeof accounts !== "object") return;
return accounts[accountId];
}
function mergeSlackAccountConfig(cfg, accountId) {
const { accounts: _ignored, ...base } = cfg.channels?.slack ?? {};
const account = resolveAccountConfig$3(cfg, accountId) ?? {};
return {
...base,
...account
};
}
function resolveSlackAccount(params) {
const accountId = normalizeAccountId$1(params.accountId);
const baseEnabled = params.cfg.channels?.slack?.enabled !== false;
const merged = mergeSlackAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const envBot = allowEnv ? resolveSlackBotToken(process.env.SLACK_BOT_TOKEN) : void 0;
const envApp = allowEnv ? resolveSlackAppToken(process.env.SLACK_APP_TOKEN) : void 0;
const configBot = resolveSlackBotToken(merged.botToken);
const configApp = resolveSlackAppToken(merged.appToken);
const botToken = configBot ?? envBot;
const appToken = configApp ?? envApp;
const botTokenSource = configBot ? "config" : envBot ? "env" : "none";
const appTokenSource = configApp ? "config" : envApp ? "env" : "none";
return {
accountId,
enabled,
name: merged.name?.trim() || void 0,
botToken,
appToken,
botTokenSource,
appTokenSource,
config: merged,
groupPolicy: merged.groupPolicy,
textChunkLimit: merged.textChunkLimit,
mediaMaxMb: merged.mediaMaxMb,
reactionNotifications: merged.reactionNotifications,
reactionAllowlist: merged.reactionAllowlist,
replyToMode: merged.replyToMode,
replyToModeByChatType: merged.replyToModeByChatType,
actions: merged.actions,
slashCommand: merged.slashCommand,
dm: merged.dm,
channels: merged.channels
};
}
function resolveSlackReplyToMode(account, chatType) {
const normalized = normalizeChatType(chatType ?? void 0);
if (normalized && account.replyToModeByChatType?.[normalized] !== void 0) return account.replyToModeByChatType[normalized] ?? "off";
if (normalized === "direct" && account.dm?.replyToMode !== void 0) return account.dm.replyToMode;
return account.replyToMode ?? "off";
}
//#endregion
//#region src/routing/bindings.ts
function normalizeBindingChannelId(raw) {
const normalized = normalizeChatChannelId(raw);
if (normalized) return normalized;
return (raw ?? "").trim().toLowerCase() || null;
}
function listBindings(cfg) {
return Array.isArray(cfg.bindings) ? cfg.bindings : [];
}
function listBoundAccountIds(cfg, channelId) {
const normalizedChannel = normalizeBindingChannelId(channelId);
if (!normalizedChannel) return [];
const ids = /* @__PURE__ */ new Set();
for (const binding of listBindings(cfg)) {
if (!binding || typeof binding !== "object") continue;
const match = binding.match;
if (!match || typeof match !== "object") continue;
const channel = normalizeBindingChannelId(match.channel);
if (!channel || channel !== normalizedChannel) continue;
const accountId = typeof match.accountId === "string" ? match.accountId.trim() : "";
if (!accountId || accountId === "*") continue;
ids.add(normalizeAccountId$1(accountId));
}
return Array.from(ids).toSorted((a, b) => a.localeCompare(b));
}
function resolveDefaultAgentBoundAccountId(cfg, channelId) {
const normalizedChannel = normalizeBindingChannelId(channelId);
if (!normalizedChannel) return null;
const defaultAgentId = normalizeAgentId(resolveDefaultAgentId(cfg));
for (const binding of listBindings(cfg)) {
if (!binding || typeof binding !== "object") continue;
if (normalizeAgentId(binding.agentId) !== defaultAgentId) continue;
const match = binding.match;
if (!match || typeof match !== "object") continue;
const channel = normalizeBindingChannelId(match.channel);
if (!channel || channel !== normalizedChannel) continue;
const accountId = typeof match.accountId === "string" ? match.accountId.trim() : "";
if (!accountId || accountId === "*") continue;
return normalizeAccountId$1(accountId);
}
return null;
}
//#endregion
//#region src/telegram/token.ts
function resolveTelegramToken(cfg, opts = {}) {
const accountId = normalizeAccountId$1(opts.accountId);
const telegramCfg = cfg?.channels?.telegram;
const resolveAccountCfg = (id) => {
const accounts = telegramCfg?.accounts;
if (!accounts || typeof accounts !== "object" || Array.isArray(accounts)) return;
const direct = accounts[id];
if (direct) return direct;
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId$1(key) === id);
return matchKey ? accounts[matchKey] : void 0;
};
const accountCfg = resolveAccountCfg(accountId !== DEFAULT_ACCOUNT_ID ? accountId : DEFAULT_ACCOUNT_ID);
const accountTokenFile = accountCfg?.tokenFile?.trim();
if (accountTokenFile) {
if (!fs.existsSync(accountTokenFile)) {
opts.logMissingFile?.(`channels.telegram.accounts.${accountId}.tokenFile not found: ${accountTokenFile}`);
return {
token: "",
source: "none"
};
}
try {
const token = fs.readFileSync(accountTokenFile, "utf-8").trim();
if (token) return {
token,
source: "tokenFile"
};
} catch (err) {
opts.logMissingFile?.(`channels.telegram.accounts.${accountId}.tokenFile read failed: ${String(err)}`);
return {
token: "",
source: "none"
};
}
return {
token: "",
source: "none"
};
}
const accountToken = accountCfg?.botToken?.trim();
if (accountToken) return {
token: accountToken,
source: "config"
};
const allowEnv = accountId === DEFAULT_ACCOUNT_ID;
const tokenFile = telegramCfg?.tokenFile?.trim();
if (tokenFile && allowEnv) {
if (!fs.existsSync(tokenFile)) {
opts.logMissingFile?.(`channels.telegram.tokenFile not found: ${tokenFile}`);
return {
token: "",
source: "none"
};
}
try {
const token = fs.readFileSync(tokenFile, "utf-8").trim();
if (token) return {
token,
source: "tokenFile"
};
} catch (err) {
opts.logMissingFile?.(`channels.telegram.tokenFile read failed: ${String(err)}`);
return {
token: "",
source: "none"
};
}
}
const configToken = telegramCfg?.botToken?.trim();
if (configToken && allowEnv) return {
token: configToken,
source: "config"
};
const envToken = allowEnv ? (opts.envToken ?? process.env.TELEGRAM_BOT_TOKEN)?.trim() : "";
if (envToken) return {
token: envToken,
source: "env"
};
return {
token: "",
source: "none"
};
}
//#endregion
//#region src/telegram/accounts.ts
const debugAccounts = (...args) => {
if (isTruthyEnvValue(process.env.OPENCLAW_DEBUG_TELEGRAM_ACCOUNTS)) console.warn("[telegram:accounts]", ...args);
};
function listConfiguredAccountIds$1(cfg) {
const accounts = cfg.channels?.telegram?.accounts;
if (!accounts || typeof accounts !== "object") return [];
const ids = /* @__PURE__ */ new Set();
for (const key of Object.keys(accounts)) {
if (!key) continue;
ids.add(normalizeAccountId$1(key));
}
return [...ids];
}
function listTelegramAccountIds(cfg) {
const ids = Array.from(new Set([...listConfiguredAccountIds$1(cfg), ...listBoundAccountIds(cfg, "telegram")]));
debugAccounts("listTelegramAccountIds", ids);
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
return ids.toSorted((a, b) => a.localeCompare(b));
}
function resolveDefaultTelegramAccountId(cfg) {
const boundDefault = resolveDefaultAgentBoundAccountId(cfg, "telegram");
if (boundDefault) return boundDefault;
const ids = listTelegramAccountIds(cfg);
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
return ids[0] ?? DEFAULT_ACCOUNT_ID;
}
function resolveAccountConfig$2(cfg, accountId) {
const accounts = cfg.channels?.telegram?.accounts;
if (!accounts || typeof accounts !== "object") return;
const direct = accounts[accountId];
if (direct) return direct;
const normalized = normalizeAccountId$1(accountId);
const matchKey = Object.keys(accounts).find((key) => normalizeAccountId$1(key) === normalized);
return matchKey ? accounts[matchKey] : void 0;
}
function mergeTelegramAccountConfig(cfg, accountId) {
const { accounts: _ignored, ...base } = cfg.channels?.telegram ?? {};
const account = resolveAccountConfig$2(cfg, accountId) ?? {};
return {
...base,
...account
};
}
function resolveTelegramAccount(params) {
const hasExplicitAccountId = Boolean(params.accountId?.trim());
const baseEnabled = params.cfg.channels?.telegram?.enabled !== false;
const resolve = (accountId) => {
const merged = mergeTelegramAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const tokenResolution = resolveTelegramToken(params.cfg, { accountId });
debugAccounts("resolve", {
accountId,
enabled,
tokenSource: tokenResolution.source
});
return {
accountId,
enabled,
name: merged.name?.trim() || void 0,
token: tokenResolution.token,
tokenSource: tokenResolution.source,
config: merged
};
};
const primary = resolve(normalizeAccountId$1(params.accountId));
if (hasExplicitAccountId) return primary;
if (primary.tokenSource !== "none") return primary;
const fallbackId = resolveDefaultTelegramAccountId(params.cfg);
if (fallbackId === primary.accountId) return primary;
const fallback = resolve(fallbackId);
if (fallback.tokenSource === "none") return primary;
return fallback;
}
function listEnabledTelegramAccounts(cfg) {
return listTelegramAccountIds(cfg).map((accountId) => resolveTelegramAccount({
cfg,
accountId
})).filter((account) => account.enabled);
}
//#endregion
//#region src/whatsapp/normalize.ts
const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
const WHATSAPP_LID_RE = /^(\d+)@lid$/i;
function stripWhatsAppTargetPrefixes(value) {
let candidate = value.trim();
for (;;) {
const before = candidate;
candidate = candidate.replace(/^whatsapp:/i, "").trim();
if (candidate === before) return candidate;
}
}
function isWhatsAppGroupJid(value) {
const candidate = stripWhatsAppTargetPrefixes(value);
if (!candidate.toLowerCase().endsWith("@g.us")) return false;
const localPart = candidate.slice(0, candidate.length - 5);
if (!localPart || localPart.includes("@")) return false;
return /^[0-9]+(-[0-9]+)*$/.test(localPart);
}
/**
* Check if value looks like a WhatsApp user target (e.g. "41796666864:0@s.whatsapp.net" or "123@lid").
*/
function isWhatsAppUserTarget(value) {
const candidate = stripWhatsAppTargetPrefixes(value);
return WHATSAPP_USER_JID_RE.test(candidate) || WHATSAPP_LID_RE.test(candidate);
}
/**
* Extract the phone number from a WhatsApp user JID.
* "41796666864:0@s.whatsapp.net" -> "41796666864"
* "123456@lid" -> "123456"
*/
function extractUserJidPhone(jid) {
const userMatch = jid.match(WHATSAPP_USER_JID_RE);
if (userMatch) return userMatch[1];
const lidMatch = jid.match(WHATSAPP_LID_RE);
if (lidMatch) return lidMatch[1];
return null;
}
function normalizeWhatsAppTarget(value) {
const candidate = stripWhatsAppTargetPrefixes(value);
if (!candidate) return null;
if (isWhatsAppGroupJid(candidate)) return `${candidate.slice(0, candidate.length - 5)}@g.us`;
if (isWhatsAppUserTarget(candidate)) {
const phone = extractUserJidPhone(candidate);
if (!phone) return null;
const normalized = normalizeE164(phone);
return normalized.length > 1 ? normalized : null;
}
if (candidate.includes("@")) return null;
const normalized = normalizeE164(candidate);
return normalized.length > 1 ? normalized : null;
}
//#endregion
//#region src/channels/plugins/index.ts
function listPluginChannels() {
return requireActivePluginRegistry().channels.map((entry) => entry.plugin);
}
function dedupeChannels(channels) {
const seen = /* @__PURE__ */ new Set();
const resolved = [];
for (const plugin of channels) {
const id = String(plugin.id).trim();
if (!id || seen.has(id)) continue;
seen.add(id);
resolved.push(plugin);
}
return resolved;
}
function listChannelPlugins() {
return dedupeChannels(listPluginChannels()).toSorted((a, b) => {
const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id);
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id);
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);
const orderB = b.meta.order ?? (indexB === -1 ? 999 : indexB);
if (orderA !== orderB) return orderA - orderB;
return a.id.localeCompare(b.id);
});
}
function getChannelPlugin(id) {
const resolvedId = String(id).trim();
if (!resolvedId) return;
return listChannelPlugins().find((plugin) => plugin.id === resolvedId);
}
function normalizeChannelId(raw) {
return normalizeAnyChannelId(raw);
}
//#endregion
//#region src/signal/accounts.ts
function listConfiguredAccountIds(cfg) {
const accounts = cfg.channels?.signal?.accounts;
if (!accounts || typeof accounts !== "object") return [];
return Object.keys(accounts).filter(Boolean);
}
function listSignalAccountIds(cfg) {
const ids = listConfiguredAccountIds(cfg);
if (ids.length === 0) return [DEFAULT_ACCOUNT_ID];
return ids.toSorted((a, b) => a.localeCompare(b));
}
function resolveAccountConfig$1(cfg, accountId) {
const accounts = cfg.channels?.signal?.accounts;
if (!accounts || typeof accounts !== "object") return;
return accounts[accountId];
}
function mergeSignalAccountConfig(cfg, accountId) {
const { accounts: _ignored, ...base } = cfg.channels?.signal ?? {};
const account = resolveAccountConfig$1(cfg, accountId) ?? {};
return {
...base,
...account
};
}
function resolveSignalAccount(params) {
const accountId = normalizeAccountId$1(params.accountId);
const baseEnabled = params.cfg.channels?.signal?.enabled !== false;
const merged = mergeSignalAccountConfig(params.cfg, accountId);
const accountEnabled = merged.enabled !== false;
const enabled = baseEnabled && accountEnabled;
const host = merged.httpHost?.trim() || "127.0.0.1";
const port = merged.httpPort ?? 8080;
const baseUrl = merged.httpUrl?.trim() || `http://${host}:${port}`;
const configured = Boolean(merged.account?.trim() || merged.httpUrl?.trim() || merged.cliPath?.trim() || merged.httpHost?.trim() || typeof merged.httpPort === "number" || typeof merged.autoStart === "boolean");
return {
accountId,
enabled,
name: merged.name?.trim() || void 0,
baseUrl,
configured,
config: merged
};
}
function listEnabledSignalAccounts(cfg) {
return listSignalAccountIds(cfg).map((accountId) => resolveSignalAccount({
cfg,
accountId
})).filter((account) => account.enabled);
}
//#endregion
//#region src/media/constants.ts
const MAX_IMAGE_BYTES$1 = 6 * 1024 * 1024;
const MAX_AUDIO_BYTES = 16 * 1024 * 1024;
const MAX_VIDEO_BYTES = 16 * 1024 * 1024;
const MAX_DOCUMENT_BYTES = 100 * 1024 * 1024;
function mediaKindFromMime(mime) {
if (!mime) return "unknown";
if (mime.startsWith("image/")) return "image";
if (mime.startsWith("audio/")) return "audio";
if (mime.startsWith("video/")) return "video";
if (mime === "application/pdf") return "document";
if (mime.startsWith("application/")) return "document";
return "unknown";
}
function maxBytesForKind(kind) {
switch (kind) {
case "image": return MAX_IMAGE_BYTES$1;
case "audio": return MAX_AUDIO_BYTES;
case "video": return MAX_VIDEO_BYTES;
case "document": return MAX_DOCUMENT_BYTES;
default: return MAX_DOCUMENT_BYTES;
}
}
//#endregion
//#region src/media/mime.ts
const EXT_BY_MIME = {
"image/heic": ".heic",
"image/heif": ".heif",
"image/jpeg": ".jpg",
"image/png": ".png",
"image/webp": ".webp",
"image/gif": ".gif",
"audio/ogg": ".ogg",
"audio/mpeg": ".mp3",
"audio/x-m4a": ".m4a",
"audio/mp4": ".m4a",
"video/mp4": ".mp4",
"video/quicktime": ".mov",
"application/pdf": ".pdf",
"application/json": ".json",
"application/zip": ".zip",
"application/gzip": ".gz",
"application/x-tar": ".tar",
"application/x-7z-compressed": ".7z",
"application/vnd.rar": ".rar",
"application/msword": ".doc",
"application/vnd.ms-excel": ".xls",
"application/vnd.ms-powerpoint": ".ppt",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
"text/csv": ".csv",
"text/plain": ".txt",
"text/markdown": ".md"
};
const MIME_BY_EXT = {
...Object.fromEntries(Object.entries(EXT_BY_MIME).map(([mime, ext]) => [ext, mime])),
".jpeg": "image/jpeg"
};
const AUDIO_FILE_EXTENSIONS = new Set([
".aac",
".flac",
".m4a",
".mp3",
".oga",
".ogg",
".opus",
".wav"
]);
function normalizeHeaderMime(mime) {
if (!mime) return;
return mime.split(";")[0]?.trim().toLowerCase() || void 0;
}
async function sniffMime(buffer) {
if (!buffer) return;
try {
return (await fileTypeFromBuffer(buffer))?.mime ?? void 0;
} catch {
return;
}
}
function getFileExtension(filePath) {
if (!filePath) return;
try {
if (/^https?:\/\//i.test(filePath)) {
const url = new URL(filePath);
return path.extname(url.pathname).toLowerCase() || void 0;
}
} catch {}
return path.extname(filePath).toLowerCase() || void 0;
}
function isAudioFileName(fileName) {
const ext = getFileExtension(fileName);
if (!ext) return false;
return AUDIO_FILE_EXTENSIONS.has(ext);
}
function detectMime(opts) {
return detectMimeImpl(opts);
}
function isGenericMime(mime) {
if (!mime) return true;
const m = mime.toLowerCase();
return m === "application/octet-stream" || m === "application/zip";
}
async function detectMimeImpl(opts) {
const ext = getFileExtension(opts.filePath);
const extMime = ext ? MIME_BY_EXT[ext] : void 0;
const headerMime = normalizeHeaderMime(opts.headerMime);
const sniffed = await sniffMime(opts.buffer);
if (sniffed && (!isGenericMime(sniffed) || !extMime)) return sniffed;
if (extMime) return extMime;
if (headerMime && !isGenericMime(headerMime)) return headerMime;
if (sniffed) return sniffed;
if (headerMime) return headerMime;
}
function extensionForMime(mime) {
if (!mime) return;
return EXT_BY_MIME[mime.toLowerCase()];
}
function isGifMedia(opts) {
if (opts.contentType?.toLowerCase() === "image/gif") return true;
return getFileExtension(opts.fileName) === ".gif";
}
function imageMimeFromFormat(format) {
if (!format) return;
switch (format.toLowerCase()) {
case "jpg":
case "jpeg": return "image/jpeg";
case "heic": return "image/heic";
case "heif": return "image/heif";
case "png": return "image/png";
case "webp": return "image/webp";
case "gif": return "image/gif";
default: return;
}
}
function kindFromMime(mime) {
return mediaKindFromMime(mime);
}
//#endregion
//#region src/gateway/protocol/client-info.ts
const GATEWAY_CLIENT_IDS = {
WEBCHAT_UI: "webchat-ui",
CONTROL_UI: "openclaw-control-ui",
WEBCHAT: "webchat",
CLI: "cli",
GATEWAY_CLIENT: "gateway-client",
MACOS_APP: "openclaw-macos",
IOS_APP: "openclaw-ios",
ANDROID_APP: "openclaw-android",
NODE_HOST: "node-host",
TEST: "test",
FINGERPRINT: "fingerprint",
PROBE: "openclaw-probe"
};
const GATEWAY_CLIENT_NAMES = GATEWAY_CLIENT_IDS;
const GATEWAY_CLIENT_MODES = {
WEBCHAT: "webchat",
CLI: "cli",
UI: "ui",
BACKEND: "backend",
NODE: "node",
PROBE: "probe",
TEST: "test"
};
const GATEWAY_CLIENT_ID_SET = new Set(Object.values(GATEWAY_CLIENT_IDS));
const GATEWAY_CLIENT_MODE_SET = new Set(Object.values(GATEWAY_CLIENT_MODES));
//#endregion
//#region src/utils/message-channel.ts
const INTERNAL_MESSAGE_CHANNEL = "webchat";
const MARKDOWN_CAPABLE_CHANNELS = new Set([
"slack",
"telegram",
"signal",
"discord",
"googlechat",
"tui",
INTERNAL_MESSAGE_CHANNEL
]);
function isInternalMessageChannel(raw) {
return normalizeMessageChannel(raw) === INTERNAL_MESSAGE_CHANNEL;
}
function normalizeMessageChannel(raw) {
const normalized = raw?.trim().toLowerCase();
if (!normalized) return;
if (normalized === INTERNAL_MESSAGE_CHANNEL) return INTERNAL_MESSAGE_CHANNEL;
const builtIn = normalizeChatChannelId(normalized);
if (builtIn) return builtIn;
return (getActivePluginRegistry()?.channels.find((entry) => {
if (entry.plugin.id.toLowerCase() === normalized) return true;
return (entry.plugin.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === normalized);
}))?.plugin.id ?? normalized;
}
const listPluginChannelIds = () => {
const registry = getActivePluginRegistry();
if (!registry) return [];
return registry.channels.map((entry) => entry.plugin.id);
};
const listDeliverableMessageChannels = () => Array.from(new Set([...CHANNEL_IDS, ...listPluginChannelIds()]));
const listGatewayMessageChannels = () => [...listDeliverableMessageChannels(), INTERNAL_MESSAGE_CHANNEL];
function isGatewayMessageChannel(value) {
return listGatewayMessageChannels().includes(value);
}
function isDeliverableMessageChannel(value) {
return listDeliverableMessageChannels().includes(value);
}
function resolveGatewayMessageChannel(raw) {
const normalized = normalizeMessageChannel(raw);
if (!normalized) return;
return isGatewayMessageChannel(normalized) ? normalized : void 0;
}
function resolveMessageChannel(primary, fallback) {
return normalizeMessageChannel(primary) ?? normalizeMessageChannel(fallback);
}
function isMarkdownCapableMessageChannel(raw) {
const channel = normalizeMessageChannel(raw);
if (!channel) return false;
return MARKDOWN_CAPABLE_CHANNELS.has(channel);
}
//#endregion
//#region src/agents/pi-embedded-helpers/bootstrap.ts
function isBase64Signature(value) {
const trimmed = value.trim();
if (!trimmed) return false;
const compact = trimmed.replace(/\s+/g, "");
if (!/^[A-Za-z0-9+/=_-]+$/.test(compact)) return false;
const isUrl = compact.includes("-") || compact.includes("_");
try {
const buf = Buffer.from(compact, isUrl ? "base64url" : "base64");
if (buf.length === 0) return false;
const encoded = buf.toString(isUrl ? "base64url" : "base64");
const normalize = (input) => input.replace(/=+$/g, "");
return normalize(encoded) === normalize(compact);
} catch {
return false;
}
}
/**
* Strips Claude-style thought_signature fields from content blocks.
*
* Gemini expects thought signatures as base64-encoded bytes, but Claude stores message ids
* like "msg_abc123...". We only strip "msg_*" to preserve any provider-valid signatures.
*/
function stripThoughtSignatures(content, options) {
if (!Array.isArray(content)) return content;
const allowBase64Only = options?.allowBase64Only ?? false;
const includeCamelCase = options?.includeCamelCase ?? false;
const shouldStripSignature = (value) => {
if (!allowBase64Only) return typeof value === "string" && value.startsWith("msg_");
return typeof value !== "string" || !isBase64Signature(value);
};
return content.map((block) => {
if (!block || typeof block !== "object") return block;
const rec = block;
const stripSnake = shouldStripSignature(rec.thought_signature);
const stripCamel = includeCamelCase ? shouldStripSignature(rec.thoughtSignature) : false;
if (!stripSnake && !stripCamel) return block;
const next = { ...rec };
if (stripSnake) delete next.thought_signature;
if (stripCamel) delete next.thoughtSignature;
return next;
});
}
const DEFAULT_BOOTSTRAP_MAX_CHARS = 2e4;
const BOOTSTRAP_HEAD_RATIO = .7;
const BOOTSTRAP_TAIL_RATIO = .2;
function resolveBootstrapMaxChars(cfg) {
const raw = cfg?.agents?.defaults?.bootstrapMaxChars;
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return Math.floor(raw);
return DEFAULT_BOOTSTRAP_MAX_CHARS;
}
function trimBootstrapContent(content, fileName, maxChars) {
const trimmed = content.trimEnd();
if (trimmed.length <= maxChars) return {
content: trimmed,
truncated: false,
maxChars,
originalLength: trimmed.length
};
const headChars = Math.floor(maxChars * BOOTSTRAP_HEAD_RATIO);
const tailChars = Math.floor(maxChars * BOOTSTRAP_TAIL_RATIO);
const head = trimmed.slice(0, headChars);
const tail = trimmed.slice(-tailChars);
return {
content: [
head,
[
"",
`[...truncated, read ${fileName} for full content...]`,
`…(truncated ${fileName}: kept ${headChars}+${tailChars} chars of ${trimmed.length})…`,
""
].join("\n"),
tail
].join("\n"),
truncated: true,
maxChars,
originalLength: trimmed.length
};
}
async function ensureSessionHeader$1(params) {
const file = params.sessionFile;
try {
await fs$1.stat(file);
return;
} catch {}
await fs$1.mkdir(path.dirname(file), { recursive: true });
const entry = {
type: "session",
version: 2,
id: params.sessionId,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
cwd: params.cwd
};
await fs$1.writeFile(file, `${JSON.stringify(entry)}\n`, "utf-8");
}
function buildBootstrapContextFiles(files, opts) {
const maxChars = opts?.maxChars ?? DEFAULT_BOOTSTRAP_MAX_CHARS;
const result = [];
for (const file of files) {
if (file.missing) {
result.push({
path: file.name,
content: `[MISSING] Expected at: ${file.path}`
});
continue;
}
const trimmed = trimBootstrapContent(file.content ?? "", file.name, maxChars);
if (!trimmed.content) continue;
if (trimmed.truncated) opts?.warn?.(`workspace bootstrap file ${file.name} is ${trimmed.originalLength} chars (limit ${trimmed.maxChars}); truncating in injected context`);
result.push({
path: file.name,
content: trimmed.content
});
}
return result;
}
function sanitizeGoogleTurnOrdering(messages) {
const GOOGLE_TURN_ORDER_BOOTSTRAP_TEXT = "(session bootstrap)";
const first = messages[0];
const role = first?.role;
const content = first?.content;
if (role === "user" && typeof content === "string" && content.trim() === GOOGLE_TURN_ORDER_BOOTSTRAP_TEXT) return messages;
if (role !== "assistant") return messages;
return [{
role: "user",
content: GOOGLE_TURN_ORDER_BOOTSTRAP_TEXT,
timestamp: Date.now()
}, ...messages];
}
//#endregion
//#region src/agents/sandbox/constants.ts
const DEFAULT_SANDBOX_WORKSPACE_ROOT = path.join(os.homedir(), ".openclaw", "sandboxes");
const DEFAULT_SANDBOX_IMAGE = "openclaw-sandbox:bookworm-slim";
const DEFAULT_SANDBOX_CONTAINER_PREFIX = "openclaw-sbx-";
const DEFAULT_SANDBOX_WORKDIR = "/workspace";
const DEFAULT_SANDBOX_IDLE_HOURS = 24;
const DEFAULT_SANDBOX_MAX_AGE_DAYS = 7;
const DEFAULT_TOOL_ALLOW = [
"exec",
"process",
"read",
"write",
"edit",
"apply_patch",
"image",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status"
];
const DEFAULT_TOOL_DENY = [
"browser",
"canvas",
"nodes",
"cron",
"gateway",
...CHANNEL_IDS
];
const DEFAULT_SANDBOX_BROWSER_IMAGE = "openclaw-sandbox-browser:bookworm-slim";
const DEFAULT_SANDBOX_BROWSER_PREFIX = "openclaw-sbx-browser-";
const DEFAULT_SANDBOX_BROWSER_CDP_PORT = 9222;
const DEFAULT_SANDBOX_BROWSER_VNC_PORT = 5900;
const DEFAULT_SANDBOX_BROWSER_NOVNC_PORT = 6080;
const DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS = 12e3;
const SANDBOX_AGENT_WORKSPACE_MOUNT = "/agent";
const resolvedSandboxStateDir = STATE_DIR ?? path.join(os.homedir(), ".openclaw");
const SANDBOX_STATE_DIR = path.join(resolvedSandboxStateDir, "sandbox");
const SANDBOX_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "containers.json");
const SANDBOX_BROWSER_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "browsers.json");
//#endregion
//#region src/agents/tool-policy.ts
const TOOL_NAME_ALIASES = {
bash: "exec",
"apply-patch": "apply_patch"
};
const TOOL_GROUPS = {
"group:memory": ["memory_search", "memory_get"],
"group:web": ["web_search", "web_fetch"],
"group:fs": [
"read",
"write",
"edit",
"apply_patch"
],
"group:runtime": ["exec", "process"],
"group:sessions": [
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status"
],
"group:ui": ["browser", "canvas"],
"group:automation": ["cron", "gateway"],
"group:messaging": ["message"],
"group:nodes": ["nodes"],
"group:openclaw": [
"browser",
"canvas",
"nodes",
"cron",
"message",
"gateway",
"agents_list",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status",
"memory_search",
"memory_get",
"web_search",
"web_fetch",
"image"
]
};
const OWNER_ONLY_TOOL_NAMES = new Set(["whatsapp_login"]);
const TOOL_PROFILES = {
minimal: { allow: ["session_status"] },
coding: { allow: [
"group:fs",
"group:runtime",
"group:sessions",
"group:memory",
"image"
] },
messaging: { allow: [
"group:messaging",
"sessions_list",
"sessions_history",
"sessions_send",
"session_status"
] },
full: {}
};
function normalizeToolName(name) {
const normalized = name.trim().toLowerCase();
return TOOL_NAME_ALIASES[normalized] ?? normalized;
}
function isOwnerOnlyToolName(name) {
return OWNER_ONLY_TOOL_NAMES.has(normalizeToolName(name));
}
function applyOwnerOnlyToolPolicy(tools, senderIsOwner) {
const withGuard = tools.map((tool) => {
if (!isOwnerOnlyToolName(tool.name)) return tool;
if (senderIsOwner || !tool.execute) return tool;
return {
...tool,
execute: async () => {
throw new Error("Tool restricted to owner senders.");
}
};
});
if (senderIsOwner) return withGuard;
return withGuard.filter((tool) => !isOwnerOnlyToolName(tool.name));
}
function normalizeToolList(list) {
if (!list) return [];
return list.map(normalizeToolName).filter(Boolean);
}
function expandToolGroups(list) {
const normalized = normalizeToolList(list);
const expanded = [];
for (const value of normalized) {
const group = TOOL_GROUPS[value];
if (group) {
expanded.push(...group);
continue;
}
expanded.push(value);
}
return Array.from(new Set(expanded));
}
function collectExplicitAllowlist(policies) {
const entries = [];
for (const policy of policies) {
if (!policy?.allow) continue;
for (const value of policy.allow) {
if (typeof value !== "string") continue;
const trimmed = value.trim();
if (trimmed) entries.push(trimmed);
}
}
return entries;
}
function buildPluginToolGroups(params) {
const all = [];
const byPlugin = /* @__PURE__ */ new Map();
for (const tool of params.tools) {
const meta = params.toolMeta(tool);
if (!meta) continue;
const name = normalizeToolName(tool.name);
all.push(name);
const pluginId = meta.pluginId.toLowerCase();
const list = byPlugin.get(pluginId) ?? [];
list.push(name);
byPlugin.set(pluginId, list);
}
return {
all,
byPlugin
};
}
function expandPluginGroups(list, groups) {
if (!list || list.length === 0) return list;
const expanded = [];
for (const entry of list) {
const normalized = normalizeToolName(entry);
if (normalized === "group:plugins") {
if (groups.all.length > 0) expanded.push(...groups.all);
else expanded.push(normalized);
continue;
}
const tools = groups.byPlugin.get(normalized);
if (tools && tools.length > 0) {
expanded.push(...tools);
continue;
}
expanded.push(normalized);
}
return Array.from(new Set(expanded));
}
function expandPolicyWithPluginGroups(policy, groups) {
if (!policy) return;
return {
allow: expandPluginGroups(policy.allow, groups),
deny: expandPluginGroups(policy.deny, groups)
};
}
function stripPluginOnlyAllowlist(policy, groups, coreTools) {
if (!policy?.allow || policy.allow.length === 0) return {
policy,
unknownAllowlist: [],
strippedAllowlist: false
};
const normalized = normalizeToolList(policy.allow);
if (normalized.length === 0) return {
policy,
unknownAllowlist: [],
strippedAllowlist: false
};
const pluginIds = new Set(groups.byPlugin.keys());
const pluginTools = new Set(groups.all);
const unknownAllowlist = [];
let hasCoreEntry = false;
for (const entry of normalized) {
if (entry === "*") {
hasCoreEntry = true;
continue;
}
const isPluginEntry = entry === "group:plugins" || pluginIds.has(entry) || pluginTools.has(entry);
const isCoreEntry = expandToolGroups([entry]).some((tool) => coreTools.has(tool));
if (isCoreEntry) hasCoreEntry = true;
if (!isCoreEntry && !isPluginEntry) unknownAllowlist.push(entry);
}
const strippedAllowlist = !hasCoreEntry;
if (strippedAllowlist) {}
return {
policy: strippedAllowlist ? {
...policy,
allow: void 0
} : policy,
unknownAllowlist: Array.from(new Set(unknownAllowlist)),
strippedAllowlist
};
}
function resolveToolProfilePolicy(profile) {
if (!profile) return;
const resolved = TOOL_PROFILES[profile];
if (!resolved) return;
if (!resolved.allow && !resolved.deny) return;
return {
allow: resolved.allow ? [...resolved.allow] : void 0,
deny: resolved.deny ? [...resolved.deny] : void 0
};
}
//#endregion
//#region src/agents/sandbox/tool-policy.ts
function compilePattern(pattern) {
const normalized = pattern.trim().toLowerCase();
if (!normalized) return {
kind: "exact",
value: ""
};
if (normalized === "*") return { kind: "all" };
if (!normalized.includes("*")) return {
kind: "exact",
value: normalized
};
const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return {
kind: "regex",
value: new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`)
};
}
function compilePatterns(patterns) {
if (!Array.isArray(patterns)) return [];
return expandToolGroups(patterns).map(compilePattern).filter((pattern) => pattern.kind !== "exact" || pattern.value);
}
function matchesAny(name, patterns) {
for (const pattern of patterns) {
if (pattern.kind === "all") return true;
if (pattern.kind === "exact" && name === pattern.value) return true;
if (pattern.kind === "regex" && pattern.value.test(name)) return true;
}
return false;
}
function isToolAllowed(policy, name) {
const normalized = name.trim().toLowerCase();
if (matchesAny(normalized, compilePatterns(policy.deny))) return false;
const allow = compilePatterns(policy.allow);
if (allow.length === 0) return true;
return matchesAny(normalized, allow);
}
function resolveSandboxToolPolicyForAgent(cfg, agentId) {
const agentConfig = cfg && agentId ? resolveAgentConfig(cfg, agentId) : void 0;
const agentAllow = agentConfig?.tools?.sandbox?.tools?.allow;
const agentDeny = agentConfig?.tools?.sandbox?.tools?.deny;
const globalAllow = cfg?.tools?.sandbox?.tools?.allow;
const globalDeny = cfg?.tools?.sandbox?.tools?.deny;
const allowSource = Array.isArray(agentAllow) ? {
source: "agent",
key: "agents.list[].tools.sandbox.tools.allow"
} : Array.isArray(globalAllow) ? {
source: "global",
key: "tools.sandbox.tools.allow"
} : {
source: "default",
key: "tools.sandbox.tools.allow"
};
const denySource = Array.isArray(agentDeny) ? {
source: "agent",
key: "agents.list[].tools.sandbox.tools.deny"
} : Array.isArray(globalDeny) ? {
source: "global",
key: "tools.sandbox.tools.deny"
} : {
source: "default",
key: "tools.sandbox.tools.deny"
};
const deny = Array.isArray(agentDeny) ? agentDeny : Array.isArray(globalDeny) ? globalDeny : [...DEFAULT_TOOL_DENY];
const allow = Array.isArray(agentAllow) ? agentAllow : Array.isArray(globalAllow) ? globalAllow : [...DEFAULT_TOOL_ALLOW];
const expandedDeny = expandToolGroups(deny);
let expandedAllow = expandToolGroups(allow);
if (!expandedDeny.map((v) => v.toLowerCase()).includes("image") && !expandedAllow.map((v) => v.toLowerCase()).includes("image")) expandedAllow = [...expandedAllow, "image"];
return {
allow: expandedAllow,
deny: expandedDeny,
sources: {
allow: allowSource,
deny: denySource
}
};
}
//#endregion
//#region src/agents/sandbox/config.ts
function resolveSandboxScope(params) {
if (params.scope) return params.scope;
if (typeof params.perSession === "boolean") return params.perSession ? "session" : "shared";
return "agent";
}
function resolveSandboxDockerConfig(params) {
const agentDocker = params.scope === "shared" ? void 0 : params.agentDocker;
const globalDocker = params.globalDocker;
const env = agentDocker?.env ? {
...globalDocker?.env ?? { LANG: "C.UTF-8" },
...agentDocker.env
} : globalDocker?.env ?? { LANG: "C.UTF-8" };
const ulimits = agentDocker?.ulimits ? {
...globalDocker?.ulimits,
...agentDocker.ulimits
} : globalDocker?.ulimits;
const binds = [...globalDocker?.binds ?? [], ...agentDocker?.binds ?? []];
return {
image: agentDocker?.image ?? globalDocker?.image ?? DEFAULT_SANDBOX_IMAGE,
containerPrefix: agentDocker?.containerPrefix ?? globalDocker?.containerPrefix ?? DEFAULT_SANDBOX_CONTAINER_PREFIX,
workdir: agentDocker?.workdir ?? globalDocker?.workdir ?? DEFAULT_SANDBOX_WORKDIR,
readOnlyRoot: agentDocker?.readOnlyRoot ?? globalDocker?.readOnlyRoot ?? true,
tmpfs: agentDocker?.tmpfs ?? globalDocker?.tmpfs ?? [
"/tmp",
"/var/tmp",
"/run"
],
network: agentDocker?.network ?? globalDocker?.network ?? "none",
user: agentDocker?.user ?? globalDocker?.user,
capDrop: agentDocker?.capDrop ?? globalDocker?.capDrop ?? ["ALL"],
env,
setupCommand: agentDocker?.setupCommand ?? globalDocker?.setupCommand,
pidsLimit: agentDocker?.pidsLimit ?? globalDocker?.pidsLimit,
memory: agentDocker?.memory ?? globalDocker?.memory,
memorySwap: agentDocker?.memorySwap ?? globalDocker?.memorySwap,
cpus: agentDocker?.cpus ?? globalDocker?.cpus,
ulimits,
seccompProfile: agentDocker?.seccompProfile ?? globalDocker?.seccompProfile,
apparmorProfile: agentDocker?.apparmorProfile ?? globalDocker?.apparmorProfile,
dns: agentDocker?.dns ?? globalDocker?.dns,
extraHosts: agentDocker?.extraHosts ?? globalDocker?.extraHosts,
binds: binds.length ? binds : void 0
};
}
function resolveSandboxBrowserConfig(params) {
const agentBrowser = params.scope === "shared" ? void 0 : params.agentBrowser;
const globalBrowser = params.globalBrowser;
return {
enabled: agentBrowser?.enabled ?? globalBrowser?.enabled ?? false,
image: agentBrowser?.image ?? globalBrowser?.image ?? DEFAULT_SANDBOX_BROWSER_IMAGE,
containerPrefix: agentBrowser?.containerPrefix ?? globalBrowser?.containerPrefix ?? DEFAULT_SANDBOX_BROWSER_PREFIX,
cdpPort: agentBrowser?.cdpPort ?? globalBrowser?.cdpPort ?? DEFAULT_SANDBOX_BROWSER_CDP_PORT,
vncPort: agentBrowser?.vncPort ?? globalBrowser?.vncPort ?? DEFAULT_SANDBOX_BROWSER_VNC_PORT,
noVncPort: agentBrowser?.noVncPort ?? globalBrowser?.noVncPort ?? DEFAULT_SANDBOX_BROWSER_NOVNC_PORT,
headless: agentBrowser?.headless ?? globalBrowser?.headless ?? false,
enableNoVnc: agentBrowser?.enableNoVnc ?? globalBrowser?.enableNoVnc ?? true,
allowHostControl: agentBrowser?.allowHostControl ?? globalBrowser?.allowHostControl ?? false,
autoStart: agentBrowser?.autoStart ?? globalBrowser?.autoStart ?? true,
autoStartTimeoutMs: agentBrowser?.autoStartTimeoutMs ?? globalBrowser?.autoStartTimeoutMs ?? DEFAULT_SANDBOX_BROWSER_AUTOSTART_TIMEOUT_MS
};
}
function resolveSandboxPruneConfig(params) {
const agentPrune = params.scope === "shared" ? void 0 : params.agentPrune;
const globalPrune = params.globalPrune;
return {
idleHours: agentPrune?.idleHours ?? globalPrune?.idleHours ?? DEFAULT_SANDBOX_IDLE_HOURS,
maxAgeDays: agentPrune?.maxAgeDays ?? globalPrune?.maxAgeDays ?? DEFAULT_SANDBOX_MAX_AGE_DAYS
};
}
function resolveSandboxConfigForAgent(cfg, agentId) {
const agent = cfg?.agents?.defaults?.sandbox;
let agentSandbox;
const agentConfig = cfg && agentId ? resolveAgentConfig(cfg, agentId) : void 0;
if (agentConfig?.sandbox) agentSandbox = agentConfig.sandbox;
const scope = resolveSandboxScope({
scope: agentSandbox?.scope ?? agent?.scope,
perSession: agentSandbox?.perSession ?? agent?.perSession
});
const toolPolicy = resolveSandboxToolPolicyForAgent(cfg, agentId);
return {
mode: agentSandbox?.mode ?? agent?.mode ?? "off",
scope,
workspaceAccess: agentSandbox?.workspaceAccess ?? agent?.workspaceAccess ?? "none",
workspaceRoot: agentSandbox?.workspaceRoot ?? agent?.workspaceRoot ?? DEFAULT_SANDBOX_WORKSPACE_ROOT,
docker: resolveSandboxDockerConfig({
scope,
globalDocker: agent?.docker,
agentDocker: agentSandbox?.docker
}),
browser: resolveSandboxBrowserConfig({
scope,
globalBrowser: agent?.browser,
agentBrowser: agentSandbox?.browser
}),
tools: {
allow: toolPolicy.allow,
deny: toolPolicy.deny
},
prune: resolveSandboxPruneConfig({
scope,
globalPrune: agent?.prune,
agentPrune: agentSandbox?.prune
})
};
}
//#endregion
//#region src/markdown/frontmatter.ts
function stripQuotes(value) {
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1);
return value;
}
function coerceFrontmatterValue(value) {
if (value === null || value === void 0) return;
if (typeof value === "string") return value.trim();
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (typeof value === "object") try {
return JSON.stringify(value);
} catch {
return;
}
}
function parseYamlFrontmatter(block) {
try {
const parsed = YAML.parse(block);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
const result = {};
for (const [rawKey, value] of Object.entries(parsed)) {
const key = rawKey.trim();
if (!key) continue;
const coerced = coerceFrontmatterValue(value);
if (coerced === void 0) continue;
result[key] = coerced;
}
return result;
} catch {
return null;
}
}
function extractMultiLineValue(lines, startIndex) {
const match = lines[startIndex].match(/^([\w-]+):\s*(.*)$/);
if (!match) return {
value: "",
linesConsumed: 1
};
const inlineValue = match[2].trim();
if (inlineValue) return {
value: inlineValue,
linesConsumed: 1
};
const valueLines = [];
let i = startIndex + 1;
while (i < lines.length) {
const line = lines[i];
if (line.length > 0 && !line.startsWith(" ") && !line.startsWith(" ")) break;
valueLines.push(line);
i++;
}
return {
value: valueLines.join("\n").trim(),
linesConsumed: i - startIndex
};
}
function parseLineFrontmatter(block) {
const frontmatter = {};
const lines = block.split("\n");
let i = 0;
while (i < lines.length) {
const match = lines[i].match(/^([\w-]+):\s*(.*)$/);
if (!match) {
i++;
continue;
}
const key = match[1];
const inlineValue = match[2].trim();
if (!key) {
i++;
continue;
}
if (!inlineValue && i + 1 < lines.length) {
const nextLine = lines[i + 1];
if (nextLine.startsWith(" ") || nextLine.startsWith(" ")) {
const { value, linesConsumed } = extractMultiLineValue(lines, i);
if (value) frontmatter[key] = value;
i += linesConsumed;
continue;
}
}
const value = stripQuotes(inlineValue);
if (value) frontmatter[key] = value;
i++;
}
return frontmatter;
}
function parseFrontmatterBlock(content) {
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
if (!normalized.startsWith("---")) return {};
const endIndex = normalized.indexOf("\n---", 3);
if (endIndex === -1) return {};
const block = normalized.slice(4, endIndex);
const lineParsed = parseLineFrontmatter(block);
const yamlParsed = parseYamlFrontmatter(block);
if (yamlParsed === null) return lineParsed;
const merged = { ...yamlParsed };
for (const [key, value] of Object.entries(lineParsed)) if (value.startsWith("{") || value.startsWith("[")) merged[key] = value;
return merged;
}
//#endregion
//#region src/agents/skills/frontmatter.ts
function parseFrontmatter(content) {
return parseFrontmatterBlock(content);
}
function normalizeStringList(input) {
if (!input) return [];
if (Array.isArray(input)) return input.map((value) => String(value).trim()).filter(Boolean);
if (typeof input === "string") return input.split(",").map((value) => value.trim()).filter(Boolean);
return [];
}
function parseInstallSpec(input) {
if (!input || typeof input !== "object") return;
c