@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
1,179 lines (1,159 loc) • 2.33 MB
JavaScript
import { a as resolveOAuthDir, i as resolveGatewayPort, n as resolveConfigPath, s as resolveStateDir, t as STATE_DIR } from "./paths-VslOJiD2.js";
import { A as normalizeAccountId$3, D as buildAgentMainSessionKey, E as DEFAULT_MAIN_KEY, F as sanitizeAgentId, I as isAcpSessionKey, L as isSubagentSessionKey, M as normalizeMainKey, N as resolveAgentIdFromSessionKey, O as buildAgentPeerSessionKey, P as resolveThreadSessionKeys, R as parseAgentSessionKey, S as resolveOpenClawPackageRoot, T as DEFAULT_AGENT_ID, b as filterBootstrapFilesForSession, c as resolveDefaultAgentId, f as DEFAULT_AGENT_WORKSPACE_DIR, i as resolveAgentModelFallbacksOverride, j as normalizeAgentId, k as buildGroupHistoryKey, l as resolveSessionAgentId, n as resolveAgentConfig, o as resolveAgentSkillsFilter, p as DEFAULT_BOOTSTRAP_FILENAME, r as resolveAgentDir, s as resolveAgentWorkspaceDir, t as listAgentIds, u as resolveSessionAgentIds, w as DEFAULT_ACCOUNT_ID$1, x as loadWorkspaceBootstrapFiles, y as ensureAgentWorkspace, z as resolveThreadParentSessionKey } from "./agent-scope-COnICB_7.js";
import { A as normalizeE164, B as danger, C as CONFIG_DIR, D as formatTerminalLink, E as ensureDir$3, F as shortenHomePath, G as success, H as logVerbose, I as sleep, J as isRich, K as warn, L as sliceUtf16Safe, M as resolveJidToE164, N as resolveUserPath, O as isSelfChatMode, P as shortenHomeInString, Q as normalizeLogLevel, R as toWhatsappJid, S as setActivePluginRegistry, U as setVerbose, V as info, W as shouldLogVerbose, X as getChildLogger, Y as theme, _ as normalizeAnyChannelId, a as logDebug, b as getActivePluginRegistry, c as logWarn, d as clearActiveProgressLine, f as registerActiveProgressLine, h as CHAT_CHANNEL_ORDER, i as spawnWithFallback, k as jidToE164, l as createSubsystemLogger, n as runExec, o as logError, p as unregisterActiveProgressLine, q as colorize, r as formatSpawnError, s as logInfo, t as runCommandWithTimeout, u as defaultRuntime, v as normalizeChannelId, w as clampInt, x as requireActivePluginRegistry, z as truncateUtf16Safe } from "./exec-B7WKla_0.js";
import { D as isTruthyEnvValue, G as DEFAULT_MODEL, H as resolveOpenClawAgentDir, K as DEFAULT_PROVIDER, L as listProfilesForProvider, M as markAuthProfileFailure, N as markAuthProfileUsed, O as parseBooleanValue$1, P as resolveApiKeyForProfile, R as markAuthProfileGood, S as getShellPathFromLoginShell, U as resolveAuthProfileDisplayLabel, V as resolveAuthStorePathForDisplay, W as DEFAULT_CONTEXT_TOKENS, _ as getCustomProviderApiKey, a as normalizeProviderId, b as resolveEnvApiKey, c as resolveDefaultModelForAgent, d as normalizeGoogleModelId, g as getApiKeyForModel, i as modelKey, j as isProfileInCooldown, k as resolveAuthProfileOrder, l as resolveModelRefFromString, n as buildModelAliasIndex, o as parseModelRef, r as isCliProvider, s as resolveConfiguredModelRef, t as buildAllowedModelSet, u as resolveThinkingDefault, v as requireApiKey, w as resolveShellEnvFallbackTimeoutMs, x as resolveModelAuthMode, y as resolveApiKeyForProvider, z as ensureAuthProfileStore } from "./model-selection-Cs1y6OBv.js";
import { a as saveJsonFile, i as loadJsonFile } from "./github-copilot-token-BEtihsn6.js";
import { n as resolveCliName, t as formatCliCommand } from "./command-format-SkzzRqR1.js";
import { A as fetchRemoteMedia, B as findFenceSpanAt, C as wrapFetchWithAbortSignal, D as loadWebMedia, E as markdownToIRWithMeta, F as chunkMarkdownTextWithMode, G as isSilentReplyText, H as parseFenceSpans, I as chunkText, L as chunkTextWithMode, M as resolveMarkdownTableMode, N as chunkByNewline, O as loadWebMediaRaw, P as chunkMarkdownText, R as resolveChunkMode, S as resolveFetch, T as markdownToIR, U as HEARTBEAT_TOKEN, V as isSafeFenceBreak, W as SILENT_REPLY_TOKEN, _ as normalizeChannelTargetInput, a as filterMessagingToolDuplicates, b as splitMediaFromOutput, c as createReplyToModeFilterForChannel, d as sendReadReceiptSignal, f as sendTypingSignal, g as buildTargetResolverSignature, h as streamSignalEvents, i as applyReplyThreading, j as fetchWithSsrFGuard, k as MediaFetchError, l as resolveReplyToMode, m as signalRpcRequest, n as normalizeReplyPayloadsForDelivery, o as isRenderablePayload, p as signalCheck, r as applyReplyTagsToPayload, s as shouldSuppressMessagingToolReplies, t as deliverOutboundPayloads, u as sendMessageSignal, v as normalizeTargetForProvider, w as chunkMarkdownIR, x as parseInlineDirectives$1, y as parseReplyDirectives, z as resolveTextChunkLimit } from "./deliver-Dl8TEyHM.js";
import { $ as resolveCacheTtlMs$1, $t as buildBootstrapContextFiles, A as isContextOverflowError, An as normalizeWhatsAppTarget, At as resizeToJpeg, B as ensureSandboxWorkspaceForSession, Bn as listEnabledDiscordAccounts, Bt as loadWorkspaceSkillEntries, C as classifyFailoverReason, Cn as mediaKindFromMime, Ct as registerBrowserRoutes, D as isAuthAssistantError, Dn as listChannelPlugins, Dt as getImageMetadata, E as getApiErrorPayloadFingerprint, En as getChannelPlugin, F as isRawApiErrorPayload, Fn as listBindings, G as loadSessionStore, Gt as resolveSandboxConfigForAgent, H as resolveSandboxRuntimeStatus, Hn as normalizeDiscordToken, Ht as resolvePluginSkillDirs, I as isTimeoutErrorMessage, In as resolveSlackAccount, J as saveSessionStore, Jt as expandPolicyWithPluginGroups, K as readSessionUpdatedAt, Kt as buildPluginToolGroups, L as parseImageDimensionError, Ln as resolveSlackAppToken, M as isFailoverErrorMessage, Mn as listTelegramAccountIds, Mt as saveMediaBuffer, N as isLikelyContextOverflowError, Nn as resolveTelegramAccount, Nt as SsrFBlockedError, O as isCloudCodeAssistFormatError, On as normalizeChannelId$1, P as isRateLimitAssistantError, Pn as resolveTelegramToken, Q as isCacheEnabled, Qt as stripPluginOnlyAllowlist, R as parseImageSizeError, Rn as resolveSlackBotToken, Rt as buildWorkspaceSkillCommandSpecs, S as isGoogleModelApi, St as createBrowserRouteContext, T as formatRawAssistantErrorForUi, Tn as resolveSignalAccount, Tt as resolveProfile, U as appendAssistantMessageToSessionTranscript, Ut as applySkillEnvOverrides, V as resolveSandboxContext, Vn as resolveDiscordAccount, Vt as resolveSkillsPromptForRun, Wt as applySkillEnvOverridesFromSnapshot, X as updateSessionStore, Xt as normalizeToolName, Y as updateLastRoute, Yt as expandToolGroups, Z as updateSessionStoreEntry, Zt as resolveToolProfilePolicy, _ as sanitizeSessionMessagesImages, _n as imageMimeFromFormat, _t as resolveChannelGroupRequireMention, a as formatXHighModelHint, an as isInternalMessageChannel, at as resolveSessionKey$1, b as downgradeOpenAIReasoningBlocks, bn as kindFromMime, bt as resolveConversationLabel, c as normalizeReasoningLevel, cn as normalizeMessageChannel, ct as resolveSessionResetPolicy, d as normalizeVerboseLevel, dn as GATEWAY_CLIENT_IDS, dt as DEFAULT_RESET_TRIGGERS, en as ensureSessionHeader, et as deliveryContextFromSession, f as resolveResponseUsageMode, fn as GATEWAY_CLIENT_MODES, ft as resolveMainSessionKey, g as normalizeTextForComparison, gn as getFileExtension, gt as resolveChannelGroupPolicy, h as isMessagingToolDuplicateNormalized, hn as extensionForMime, ht as listChannelDocks, i as formatThinkingLevels, in as isDeliverableMessageChannel, it as normalizeSessionDeliveryFields, j as isFailoverAssistantError, jn as listEnabledTelegramAccounts, jt as getMediaDir, k as isCompactionFailureError, kn as isWhatsAppGroupJid, l as normalizeThinkLevel, ln as resolveGatewayMessageChannel, lt as resolveSessionResetType, mn as detectMime, mt as getChannelDock, n as validateGeminiTurns, nn as sanitizeGoogleTurnOrdering, nt as mergeDeliveryContext, o as listThinkingLevels, on as isMarkdownCapableMessageChannel, ot as evaluateSessionFreshness, p as supportsXHighThinking, pn as GATEWAY_CLIENT_NAMES, pt as deriveSessionMetaPatch, q as recordSessionMetaFromInbound, qt as collectExplicitAllowlist, r as pickFallbackThinkingLevel, rn as INTERNAL_MESSAGE_CHANNEL, rt as normalizeDeliveryContext, s as normalizeElevatedLevel, sn as listDeliverableMessageChannels, st as resolveChannelResetConfig, t as validateAnthropicTurns, tn as resolveBootstrapMaxChars, tt as deliveryContextKey, u as normalizeUsageDisplay, un as resolveMessageChannel, ut as resolveThreadFlag, v as sanitizeImageBlocks, vn as isAudioFileName, vt as resolveChannelGroupToolsPolicy, w as formatAssistantErrorText, wn as listEnabledSignalAccounts, wt as resolveBrowserConfig, x as isAntigravityClaude, xn as MAX_IMAGE_BYTES, xt as resolveGroupSessionKey, y as sanitizeToolResultImages, yn as isGifMedia, yt as resolveIMessageAccount, z as sanitizeUserFacingText, zn as normalizeChatType, zt as buildWorkspaceSkillSnapshot } from "./pi-embedded-helpers-DiK8Qn1l.js";
import { A as getWebAuthAgeMs, C as getConfigValueAtPath, D as resolveAgentMaxConcurrent, E as unsetConfigValueAtPath, M as logoutWeb, O as VERSION, P as readWebSelfId, R as webAuthExists, S as unsetConfigOverride, T as setConfigValueAtPath, _ as resolveEnableState, a as validateConfigObjectWithPlugins, b as resetConfigOverrides, c as resolveTelegramCustomCommands, d as validateJsonSchemaValue, f as loadPluginManifestRegistry, g as normalizePluginsConfig, i as writeConfigFile, j as logWebSelfId, k as resolveWhatsAppAccount, l as isSafeExecutableValue, n as readConfigFileSnapshot, o as TELEGRAM_COMMAND_NAME_PATTERN, p as discoverOpenClawPlugins, r as resolveConfigSnapshotHash, s as normalizeTelegramCommandName, t as loadConfig, u as parseDurationMs, v as resolveMemorySlotDecision, w as parseConfigPath, x as setConfigOverride, y as getConfigOverrides } from "./config-CGsoho7J.js";
import { i as discoverModels, r as discoverAuthStorage } from "./pi-model-discovery-CnK2Dol8.js";
import { C as DEFAULT_AI_SNAPSHOT_MAX_CHARS, _ as rawDataToString, b as formatUncaughtError, h as ensureChromeExtensionRelayServer, v as extractErrorCode, y as formatErrorMessage$1 } from "./chrome-DlqPCh1y.js";
import { a as resolveStorePath, i as resolveSessionTranscriptsDirForAgent, n as resolveSessionFilePath, r as resolveSessionTranscriptPath } from "./paths-xPuk88Yf.js";
import { t as emitSessionTranscriptUpdate } from "./transcript-events-DW_H__a1.js";
import { _ as resolveToolDisplay, a as resolveProviderVisionModelFromConfig, c as extractAssistantThinking, d as formatReasoningMessage, f as inferToolMetaFromArgs, g as stripThinkingTagsFromText, h as stripMinimaxToolCallXml, i as decodeDataUrl, l as extractThinkingFromTaggedStream, m as stripDowngradedToolCallText, n as coerceImageAssistantText, o as minimaxUnderstandImage, p as promoteThinkingTagsToBlocks, r as coerceImageModelConfig, s as extractAssistantText$1, t as describeImageWithModel, u as extractThinkingFromTaggedText, v as ensureOpenClawModelsJson } from "./image-BYmtfVH8.js";
import { i as resolveMemorySearchConfig, n as resolveRetryConfig, r as retryAsync } from "./manager-JSP5pLyv.js";
import { c as listMemoryFiles, l as normalizeExtraMemoryPaths } from "./sqlite-C59YNxdL.js";
import { t as redactSensitiveText } from "./redact-BR0IM00e.js";
import { a as getStatusCode$1, i as formatError$1, n as waitForWebLogin, o as waitForWaConnection, r as createWaSocket, t as startWebLoginWithQr } from "./login-qr-7WOtj6zE.js";
import { createRequire } from "node:module";
import * as os$1 from "node:os";
import os, { homedir, tmpdir } from "node:os";
import * as path$1 from "node:path";
import path from "node:path";
import fs, { constants, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
import * as fs$2 from "node:fs/promises";
import fs$1 from "node:fs/promises";
import { execFile, execSync, spawn, spawnSync } from "node:child_process";
import { inspect, promisify } from "node:util";
import { fileURLToPath } from "node:url";
import lockfile from "proper-lockfile";
import crypto, { X509Certificate, randomUUID } from "node:crypto";
import { complete, completeSimple, streamSimple } from "@mariozechner/pi-ai";
import { CURRENT_SESSION_VERSION, SessionManager, SettingsManager, codingTools, createAgentSession, createEditTool, createReadTool, createWriteTool, estimateTokens, readTool } from "@mariozechner/pi-coding-agent";
import { EdgeTTS } from "node-edge-tts";
import AjvPkg from "ajv";
import { createServer } from "node:http";
import { ProxyAgent, fetch as fetch$1 } from "undici";
import { WebSocket } from "ws";
import { Buffer as Buffer$1 } from "node:buffer";
import * as net$1 from "node:net";
import net from "node:net";
import { createJiti } from "jiti";
import { Type } from "@sinclair/typebox";
import chokidar from "chokidar";
import { WebClient } from "@slack/web-api";
import readline, { createInterface } from "node:readline";
import { ApplicationCommandOptionType, ButtonStyle, ChannelType, PermissionFlagsBits, Routes } from "discord-api-types/v10";
import { Button, ChannelType as ChannelType$1, Client, Command, MessageCreateListener, MessageReactionAddListener, MessageReactionRemoveListener, MessageType, PresenceUpdateListener, RateLimitError, RequestClient, Row } from "@buape/carbon";
import { PollLayoutType } from "discord-api-types/payloads/v10";
import { API_CONSTANTS, Bot, GrammyError, HttpError, InputFile, webhookCallback } from "grammy";
import process$1 from "node:process";
import { setTimeout as setTimeout$1 } from "node:timers/promises";
import { DisconnectReason, downloadMediaMessage, extractMessageContent, getContentType, isJidGroup, normalizeMessageContent } from "@whiskeysockets/baileys";
import { spinner } from "@clack/prompts";
import { createOscProgressController, supportsOscProgress } from "osc-progress";
import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway";
import { messagingApi } from "@line/bot-sdk";
import SlackBolt from "@slack/bolt";
import { run, sequentialize } from "@grammyjs/runner";
import { apiThrottler } from "@grammyjs/transformer-throttler";
import { EventEmitter } from "node:events";
//#region src/agents/identity.ts
const DEFAULT_ACK_REACTION = "👀";
function resolveAgentIdentity(cfg, agentId) {
return resolveAgentConfig(cfg, agentId)?.identity;
}
function resolveAckReaction(cfg, agentId) {
const configured = cfg.messages?.ackReaction;
if (configured !== void 0) return configured.trim();
return resolveAgentIdentity(cfg, agentId)?.emoji?.trim() || DEFAULT_ACK_REACTION;
}
function resolveIdentityNamePrefix(cfg, agentId) {
const name = resolveAgentIdentity(cfg, agentId)?.name?.trim();
if (!name) return;
return `[${name}]`;
}
/** Returns just the identity name (without brackets) for template context. */
function resolveIdentityName(cfg, agentId) {
return resolveAgentIdentity(cfg, agentId)?.name?.trim() || void 0;
}
function resolveMessagePrefix(cfg, agentId, opts) {
const configured = opts?.configured ?? cfg.messages?.messagePrefix;
if (configured !== void 0) return configured;
if (opts?.hasAllowFrom === true) return "";
return resolveIdentityNamePrefix(cfg, agentId) ?? opts?.fallback ?? "[openclaw]";
}
function resolveResponsePrefix(cfg, agentId) {
const configured = cfg.messages?.responsePrefix;
if (configured !== void 0) {
if (configured === "auto") return resolveIdentityNamePrefix(cfg, agentId);
return configured;
}
}
function resolveEffectiveMessagesConfig(cfg, agentId, opts) {
return {
messagePrefix: resolveMessagePrefix(cfg, agentId, {
hasAllowFrom: opts?.hasAllowFrom,
fallback: opts?.fallbackMessagePrefix
}),
responsePrefix: resolveResponsePrefix(cfg, agentId)
};
}
function resolveHumanDelayConfig(cfg, agentId) {
const defaults = cfg.agents?.defaults?.humanDelay;
const overrides = resolveAgentConfig(cfg, agentId)?.humanDelay;
if (!defaults && !overrides) return;
return {
mode: overrides?.mode ?? defaults?.mode,
minMs: overrides?.minMs ?? defaults?.minMs,
maxMs: overrides?.maxMs ?? defaults?.maxMs
};
}
//#endregion
//#region src/auto-reply/heartbeat.ts
const HEARTBEAT_PROMPT = "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.";
const DEFAULT_HEARTBEAT_ACK_MAX_CHARS = 300;
function resolveHeartbeatPrompt(raw) {
return (typeof raw === "string" ? raw.trim() : "") || HEARTBEAT_PROMPT;
}
function stripTokenAtEdges(raw) {
let text = raw.trim();
if (!text) return {
text: "",
didStrip: false
};
const token = HEARTBEAT_TOKEN;
if (!text.includes(token)) return {
text,
didStrip: false
};
let didStrip = false;
let changed = true;
while (changed) {
changed = false;
const next = text.trim();
if (next.startsWith(token)) {
text = next.slice(token.length).trimStart();
didStrip = true;
changed = true;
continue;
}
if (next.endsWith(token)) {
text = next.slice(0, Math.max(0, next.length - token.length)).trimEnd();
didStrip = true;
changed = true;
}
}
return {
text: text.replace(/\s+/g, " ").trim(),
didStrip
};
}
function stripHeartbeatToken(raw, opts = {}) {
if (!raw) return {
shouldSkip: true,
text: "",
didStrip: false
};
const trimmed = raw.trim();
if (!trimmed) return {
shouldSkip: true,
text: "",
didStrip: false
};
const mode = opts.mode ?? "message";
const maxAckCharsRaw = opts.maxAckChars;
const parsedAckChars = typeof maxAckCharsRaw === "string" ? Number(maxAckCharsRaw) : maxAckCharsRaw;
const maxAckChars = Math.max(0, typeof parsedAckChars === "number" && Number.isFinite(parsedAckChars) ? parsedAckChars : DEFAULT_HEARTBEAT_ACK_MAX_CHARS);
const stripMarkup = (text) => text.replace(/<[^>]*>/g, " ").replace(/ /gi, " ").replace(/^[*`~_]+/, "").replace(/[*`~_]+$/, "");
const trimmedNormalized = stripMarkup(trimmed);
if (!(trimmed.includes(HEARTBEAT_TOKEN) || trimmedNormalized.includes(HEARTBEAT_TOKEN))) return {
shouldSkip: false,
text: trimmed,
didStrip: false
};
const strippedOriginal = stripTokenAtEdges(trimmed);
const strippedNormalized = stripTokenAtEdges(trimmedNormalized);
const picked = strippedOriginal.didStrip && strippedOriginal.text ? strippedOriginal : strippedNormalized;
if (!picked.didStrip) return {
shouldSkip: false,
text: trimmed,
didStrip: false
};
if (!picked.text) return {
shouldSkip: true,
text: "",
didStrip: true
};
const rest = picked.text.trim();
if (mode === "heartbeat") {
if (rest.length <= maxAckChars) return {
shouldSkip: true,
text: "",
didStrip: true
};
}
return {
shouldSkip: false,
text: rest,
didStrip: true
};
}
//#endregion
//#region src/channels/targets.ts
function normalizeTargetId(kind, id) {
return `${kind}:${id}`.toLowerCase();
}
function buildMessagingTarget(kind, id, raw) {
return {
kind,
id,
raw,
normalized: normalizeTargetId(kind, id)
};
}
function ensureTargetId(params) {
if (!params.pattern.test(params.candidate)) throw new Error(params.errorMessage);
return params.candidate;
}
function requireTargetKind(params) {
const kindLabel = params.kind;
if (!params.target) throw new Error(`${params.platform} ${kindLabel} id is required.`);
if (params.target.kind !== params.kind) throw new Error(`${params.platform} ${kindLabel} id is required (use ${kindLabel}:<id>).`);
return params.target.id;
}
//#endregion
//#region src/slack/targets.ts
function parseSlackTarget(raw, options = {}) {
const trimmed = raw.trim();
if (!trimmed) return;
const mentionMatch = trimmed.match(/^<@([A-Z0-9]+)>$/i);
if (mentionMatch) return buildMessagingTarget("user", mentionMatch[1], trimmed);
if (trimmed.startsWith("user:")) {
const id = trimmed.slice(5).trim();
return id ? buildMessagingTarget("user", id, trimmed) : void 0;
}
if (trimmed.startsWith("channel:")) {
const id = trimmed.slice(8).trim();
return id ? buildMessagingTarget("channel", id, trimmed) : void 0;
}
if (trimmed.startsWith("slack:")) {
const id = trimmed.slice(6).trim();
return id ? buildMessagingTarget("user", id, trimmed) : void 0;
}
if (trimmed.startsWith("@")) return buildMessagingTarget("user", ensureTargetId({
candidate: trimmed.slice(1).trim(),
pattern: /^[A-Z0-9]+$/i,
errorMessage: "Slack DMs require a user id (use user:<id> or <@id>)"
}), trimmed);
if (trimmed.startsWith("#")) return buildMessagingTarget("channel", ensureTargetId({
candidate: trimmed.slice(1).trim(),
pattern: /^[A-Z0-9]+$/i,
errorMessage: "Slack channels require a channel id (use channel:<id>)"
}), trimmed);
if (options.defaultKind) return buildMessagingTarget(options.defaultKind, trimmed, trimmed);
return buildMessagingTarget("channel", trimmed, trimmed);
}
function resolveSlackChannelId(raw) {
return requireTargetKind({
platform: "Slack",
target: parseSlackTarget(raw, { defaultKind: "channel" }),
kind: "channel"
});
}
//#endregion
//#region src/channels/channel-config.ts
function applyChannelMatchMeta(result, match) {
if (match.matchKey && match.matchSource) {
result.matchKey = match.matchKey;
result.matchSource = match.matchSource;
}
return result;
}
function resolveChannelMatchConfig(match, resolveEntry) {
if (!match.entry) return null;
return applyChannelMatchMeta(resolveEntry(match.entry), match);
}
function buildChannelKeyCandidates(...keys) {
const seen = /* @__PURE__ */ new Set();
const candidates = [];
for (const key of keys) {
if (typeof key !== "string") continue;
const trimmed = key.trim();
if (!trimmed || seen.has(trimmed)) continue;
seen.add(trimmed);
candidates.push(trimmed);
}
return candidates;
}
function resolveChannelEntryMatch(params) {
const entries = params.entries ?? {};
const match = {};
for (const key of params.keys) {
if (!Object.prototype.hasOwnProperty.call(entries, key)) continue;
match.entry = entries[key];
match.key = key;
break;
}
if (params.wildcardKey && Object.prototype.hasOwnProperty.call(entries, params.wildcardKey)) {
match.wildcardEntry = entries[params.wildcardKey];
match.wildcardKey = params.wildcardKey;
}
return match;
}
function resolveChannelEntryMatchWithFallback(params) {
const direct = resolveChannelEntryMatch({
entries: params.entries,
keys: params.keys,
wildcardKey: params.wildcardKey
});
if (direct.entry && direct.key) return {
...direct,
matchKey: direct.key,
matchSource: "direct"
};
const normalizeKey = params.normalizeKey;
if (normalizeKey) {
const normalizedKeys = params.keys.map((key) => normalizeKey(key)).filter(Boolean);
if (normalizedKeys.length > 0) for (const [entryKey, entry] of Object.entries(params.entries ?? {})) {
const normalizedEntry = normalizeKey(entryKey);
if (normalizedEntry && normalizedKeys.includes(normalizedEntry)) return {
...direct,
entry,
key: entryKey,
matchKey: entryKey,
matchSource: "direct"
};
}
}
const parentKeys = params.parentKeys ?? [];
if (parentKeys.length > 0) {
const parent = resolveChannelEntryMatch({
entries: params.entries,
keys: parentKeys
});
if (parent.entry && parent.key) return {
...direct,
entry: parent.entry,
key: parent.key,
parentEntry: parent.entry,
parentKey: parent.key,
matchKey: parent.key,
matchSource: "parent"
};
if (normalizeKey) {
const normalizedParentKeys = parentKeys.map((key) => normalizeKey(key)).filter(Boolean);
if (normalizedParentKeys.length > 0) for (const [entryKey, entry] of Object.entries(params.entries ?? {})) {
const normalizedEntry = normalizeKey(entryKey);
if (normalizedEntry && normalizedParentKeys.includes(normalizedEntry)) return {
...direct,
entry,
key: entryKey,
parentEntry: entry,
parentKey: entryKey,
matchKey: entryKey,
matchSource: "parent"
};
}
}
}
if (direct.wildcardEntry && direct.wildcardKey) return {
...direct,
entry: direct.wildcardEntry,
key: direct.wildcardKey,
matchKey: direct.wildcardKey,
matchSource: "wildcard"
};
return direct;
}
//#endregion
//#region src/channels/allowlist-match.ts
function formatAllowlistMatchMeta(match) {
return `matchKey=${match?.matchKey ?? "none"} matchSource=${match?.matchSource ?? "none"}`;
}
//#endregion
//#region src/config/channel-capabilities.ts
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
function normalizeCapabilities(capabilities) {
if (!isStringArray(capabilities)) return;
const normalized = capabilities.map((entry) => entry.trim()).filter(Boolean);
return normalized.length > 0 ? normalized : void 0;
}
function resolveAccountCapabilities(params) {
const cfg = params.cfg;
if (!cfg) return;
const normalizedAccountId = normalizeAccountId$3(params.accountId);
const accounts = cfg.accounts;
if (accounts && typeof accounts === "object") {
const direct = accounts[normalizedAccountId];
if (direct) return normalizeCapabilities(direct.capabilities) ?? normalizeCapabilities(cfg.capabilities);
const matchKey = Object.keys(accounts).find((key) => key.toLowerCase() === normalizedAccountId.toLowerCase());
const match = matchKey ? accounts[matchKey] : void 0;
if (match) return normalizeCapabilities(match.capabilities) ?? normalizeCapabilities(cfg.capabilities);
}
return normalizeCapabilities(cfg.capabilities);
}
function resolveChannelCapabilities(params) {
const cfg = params.cfg;
const channel = normalizeChannelId$1(params.channel);
if (!cfg || !channel) return;
return resolveAccountCapabilities({
cfg: cfg.channels?.[channel] ?? cfg[channel],
accountId: params.accountId
});
}
//#endregion
//#region src/infra/machine-name.ts
const execFileAsync$1 = promisify(execFile);
let cachedPromise = null;
async function tryScutil(key) {
try {
const { stdout } = await execFileAsync$1("/usr/sbin/scutil", ["--get", key], {
timeout: 1e3,
windowsHide: true
});
const value = String(stdout ?? "").trim();
return value.length > 0 ? value : null;
} catch {
return null;
}
}
function fallbackHostName() {
return os.hostname().replace(/\.local$/i, "").trim() || "openclaw";
}
async function getMachineDisplayName() {
if (cachedPromise) return cachedPromise;
cachedPromise = (async () => {
if (process.env.VITEST || false) return fallbackHostName();
if (process.platform === "darwin") {
const computerName = await tryScutil("ComputerName");
if (computerName) return computerName;
const localHostName = await tryScutil("LocalHostName");
if (localHostName) return localHostName;
}
return fallbackHostName();
})();
return cachedPromise;
}
//#endregion
//#region src/infra/diagnostic-events.ts
let seq = 0;
const listeners$3 = /* @__PURE__ */ new Set();
function isDiagnosticsEnabled(config) {
return config?.diagnostics?.enabled === true;
}
function emitDiagnosticEvent(event) {
const enriched = {
...event,
seq: seq += 1,
ts: Date.now()
};
for (const listener of listeners$3) try {
listener(enriched);
} catch {}
}
//#endregion
//#region src/logging/diagnostic.ts
const diag = createSubsystemLogger("diagnostic");
const sessionStates = /* @__PURE__ */ new Map();
const webhookStats = {
received: 0,
processed: 0,
errors: 0,
lastReceived: 0
};
let lastActivityAt = 0;
function markActivity() {
lastActivityAt = Date.now();
}
function resolveSessionKey({ sessionKey, sessionId }) {
return sessionKey ?? sessionId ?? "unknown";
}
function getSessionState(ref) {
const key = resolveSessionKey(ref);
const existing = sessionStates.get(key);
if (existing) {
if (ref.sessionId) existing.sessionId = ref.sessionId;
if (ref.sessionKey) existing.sessionKey = ref.sessionKey;
return existing;
}
const created = {
sessionId: ref.sessionId,
sessionKey: ref.sessionKey,
lastActivity: Date.now(),
state: "idle",
queueDepth: 0
};
sessionStates.set(key, created);
return created;
}
function logWebhookReceived(params) {
webhookStats.received += 1;
webhookStats.lastReceived = Date.now();
diag.debug(`webhook received: channel=${params.channel} type=${params.updateType ?? "unknown"} chatId=${params.chatId ?? "unknown"} total=${webhookStats.received}`);
emitDiagnosticEvent({
type: "webhook.received",
channel: params.channel,
updateType: params.updateType,
chatId: params.chatId
});
markActivity();
}
function logWebhookProcessed(params) {
webhookStats.processed += 1;
diag.debug(`webhook processed: channel=${params.channel} type=${params.updateType ?? "unknown"} chatId=${params.chatId ?? "unknown"} duration=${params.durationMs ?? 0}ms processed=${webhookStats.processed}`);
emitDiagnosticEvent({
type: "webhook.processed",
channel: params.channel,
updateType: params.updateType,
chatId: params.chatId,
durationMs: params.durationMs
});
markActivity();
}
function logWebhookError(params) {
webhookStats.errors += 1;
diag.error(`webhook error: channel=${params.channel} type=${params.updateType ?? "unknown"} chatId=${params.chatId ?? "unknown"} error="${params.error}" errors=${webhookStats.errors}`);
emitDiagnosticEvent({
type: "webhook.error",
channel: params.channel,
updateType: params.updateType,
chatId: params.chatId,
error: params.error
});
markActivity();
}
function logMessageQueued(params) {
const state = getSessionState(params);
state.queueDepth += 1;
state.lastActivity = Date.now();
diag.debug(`message queued: sessionId=${state.sessionId ?? "unknown"} sessionKey=${state.sessionKey ?? "unknown"} source=${params.source} queueDepth=${state.queueDepth} sessionState=${state.state}`);
emitDiagnosticEvent({
type: "message.queued",
sessionId: state.sessionId,
sessionKey: state.sessionKey,
channel: params.channel,
source: params.source,
queueDepth: state.queueDepth
});
markActivity();
}
function logMessageProcessed(params) {
const payload = `message processed: channel=${params.channel} chatId=${params.chatId ?? "unknown"} messageId=${params.messageId ?? "unknown"} sessionId=${params.sessionId ?? "unknown"} sessionKey=${params.sessionKey ?? "unknown"} outcome=${params.outcome} duration=${params.durationMs ?? 0}ms${params.reason ? ` reason=${params.reason}` : ""}${params.error ? ` error="${params.error}"` : ""}`;
if (params.outcome === "error") diag.error(payload);
else if (params.outcome === "skipped") diag.debug(payload);
else diag.debug(payload);
emitDiagnosticEvent({
type: "message.processed",
channel: params.channel,
chatId: params.chatId,
messageId: params.messageId,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
durationMs: params.durationMs,
outcome: params.outcome,
reason: params.reason,
error: params.error
});
markActivity();
}
function logSessionStateChange(params) {
const state = getSessionState(params);
const isProbeSession = state.sessionId?.startsWith("probe-") ?? false;
const prevState = state.state;
state.state = params.state;
state.lastActivity = Date.now();
if (params.state === "idle") state.queueDepth = Math.max(0, state.queueDepth - 1);
if (!isProbeSession) diag.debug(`session state: sessionId=${state.sessionId ?? "unknown"} sessionKey=${state.sessionKey ?? "unknown"} prev=${prevState} new=${params.state} reason="${params.reason ?? ""}" queueDepth=${state.queueDepth}`);
emitDiagnosticEvent({
type: "session.state",
sessionId: state.sessionId,
sessionKey: state.sessionKey,
prevState,
state: params.state,
reason: params.reason,
queueDepth: state.queueDepth
});
markActivity();
}
function logSessionStuck(params) {
const state = getSessionState(params);
diag.warn(`stuck session: sessionId=${state.sessionId ?? "unknown"} sessionKey=${state.sessionKey ?? "unknown"} state=${params.state} age=${Math.round(params.ageMs / 1e3)}s queueDepth=${state.queueDepth}`);
emitDiagnosticEvent({
type: "session.stuck",
sessionId: state.sessionId,
sessionKey: state.sessionKey,
state: params.state,
ageMs: params.ageMs,
queueDepth: state.queueDepth
});
markActivity();
}
function logLaneEnqueue(lane, queueSize) {
diag.debug(`lane enqueue: lane=${lane} queueSize=${queueSize}`);
emitDiagnosticEvent({
type: "queue.lane.enqueue",
lane,
queueSize
});
markActivity();
}
function logLaneDequeue(lane, waitMs, queueSize) {
diag.debug(`lane dequeue: lane=${lane} waitMs=${waitMs} queueSize=${queueSize}`);
emitDiagnosticEvent({
type: "queue.lane.dequeue",
lane,
queueSize,
waitMs
});
markActivity();
}
let heartbeatInterval = null;
function startDiagnosticHeartbeat() {
if (heartbeatInterval) return;
heartbeatInterval = setInterval(() => {
const now = Date.now();
const activeCount = Array.from(sessionStates.values()).filter((s) => s.state === "processing").length;
const waitingCount = Array.from(sessionStates.values()).filter((s) => s.state === "waiting").length;
const totalQueued = Array.from(sessionStates.values()).reduce((sum, s) => sum + s.queueDepth, 0);
if (!(lastActivityAt > 0 || webhookStats.received > 0 || activeCount > 0 || waitingCount > 0 || totalQueued > 0)) return;
if (now - lastActivityAt > 12e4 && activeCount === 0 && waitingCount === 0) return;
diag.debug(`heartbeat: webhooks=${webhookStats.received}/${webhookStats.processed}/${webhookStats.errors} active=${activeCount} waiting=${waitingCount} queued=${totalQueued}`);
emitDiagnosticEvent({
type: "diagnostic.heartbeat",
webhooks: {
received: webhookStats.received,
processed: webhookStats.processed,
errors: webhookStats.errors
},
active: activeCount,
waiting: waitingCount,
queued: totalQueued
});
for (const [, state] of sessionStates) {
const ageMs = now - state.lastActivity;
if (state.state === "processing" && ageMs > 12e4) logSessionStuck({
sessionId: state.sessionId,
sessionKey: state.sessionKey,
state: state.state,
ageMs
});
}
}, 3e4);
heartbeatInterval.unref?.();
}
function stopDiagnosticHeartbeat() {
if (heartbeatInterval) {
clearInterval(heartbeatInterval);
heartbeatInterval = null;
}
}
//#endregion
//#region src/process/lanes.ts
let CommandLane = /* @__PURE__ */ function(CommandLane) {
CommandLane["Main"] = "main";
CommandLane["Cron"] = "cron";
CommandLane["Subagent"] = "subagent";
CommandLane["Nested"] = "nested";
return CommandLane;
}({});
//#endregion
//#region src/process/command-queue.ts
const lanes = /* @__PURE__ */ new Map();
function getLaneState(lane) {
const existing = lanes.get(lane);
if (existing) return existing;
const created = {
lane,
queue: [],
active: 0,
maxConcurrent: 1,
draining: false
};
lanes.set(lane, created);
return created;
}
function drainLane(lane) {
const state = getLaneState(lane);
if (state.draining) return;
state.draining = true;
const pump = () => {
while (state.active < state.maxConcurrent && state.queue.length > 0) {
const entry = state.queue.shift();
const waitedMs = Date.now() - entry.enqueuedAt;
if (waitedMs >= entry.warnAfterMs) {
entry.onWait?.(waitedMs, state.queue.length);
diag.warn(`lane wait exceeded: lane=${lane} waitedMs=${waitedMs} queueAhead=${state.queue.length}`);
}
logLaneDequeue(lane, waitedMs, state.queue.length);
state.active += 1;
(async () => {
const startTime = Date.now();
try {
const result = await entry.task();
state.active -= 1;
diag.debug(`lane task done: lane=${lane} durationMs=${Date.now() - startTime} active=${state.active} queued=${state.queue.length}`);
pump();
entry.resolve(result);
} catch (err) {
state.active -= 1;
if (!(lane.startsWith("auth-probe:") || lane.startsWith("session:probe-"))) diag.error(`lane task error: lane=${lane} durationMs=${Date.now() - startTime} error="${String(err)}"`);
pump();
entry.reject(err);
}
})();
}
state.draining = false;
};
pump();
}
function enqueueCommandInLane(lane, task, opts) {
const cleaned = lane.trim() || CommandLane.Main;
const warnAfterMs = opts?.warnAfterMs ?? 2e3;
const state = getLaneState(cleaned);
return new Promise((resolve, reject) => {
state.queue.push({
task: () => task(),
resolve: (value) => resolve(value),
reject,
enqueuedAt: Date.now(),
warnAfterMs,
onWait: opts?.onWait
});
logLaneEnqueue(cleaned, state.queue.length + state.active);
drainLane(cleaned);
});
}
function getQueueSize(lane = CommandLane.Main) {
const resolved = lane.trim() || CommandLane.Main;
const state = lanes.get(resolved);
if (!state) return 0;
return state.queue.length + state.active;
}
function clearCommandLane(lane = CommandLane.Main) {
const cleaned = lane.trim() || CommandLane.Main;
const state = lanes.get(cleaned);
if (!state) return 0;
const removed = state.queue.length;
state.queue.length = 0;
return removed;
}
//#endregion
//#region src/signal/reaction-level.ts
/**
* Resolve the effective reaction level and its implications for Signal.
*
* Levels:
* - "off": No reactions at all
* - "ack": Only automatic ack reactions (👀 when processing), no agent reactions
* - "minimal": Agent can react, but sparingly (default)
* - "extensive": Agent can react liberally
*/
function resolveSignalReactionLevel(params) {
const level = resolveSignalAccount({
cfg: params.cfg,
accountId: params.accountId
}).config.reactionLevel ?? "minimal";
switch (level) {
case "off": return {
level,
ackEnabled: false,
agentReactionsEnabled: false
};
case "ack": return {
level,
ackEnabled: true,
agentReactionsEnabled: false
};
case "minimal": return {
level,
ackEnabled: false,
agentReactionsEnabled: true,
agentReactionGuidance: "minimal"
};
case "extensive": return {
level,
ackEnabled: false,
agentReactionsEnabled: true,
agentReactionGuidance: "extensive"
};
default: return {
level: "minimal",
ackEnabled: false,
agentReactionsEnabled: true,
agentReactionGuidance: "minimal"
};
}
}
//#endregion
//#region src/telegram/targets.ts
function stripTelegramInternalPrefixes(to) {
let trimmed = to.trim();
let strippedTelegramPrefix = false;
while (true) {
const next = (() => {
if (/^(telegram|tg):/i.test(trimmed)) {
strippedTelegramPrefix = true;
return trimmed.replace(/^(telegram|tg):/i, "").trim();
}
if (strippedTelegramPrefix && /^group:/i.test(trimmed)) return trimmed.replace(/^group:/i, "").trim();
return trimmed;
})();
if (next === trimmed) return trimmed;
trimmed = next;
}
}
/**
* Parse a Telegram delivery target into chatId and optional topic/thread ID.
*
* Supported formats:
* - `chatId` (plain chat ID, t.me link, @username, or internal prefixes like `telegram:...`)
* - `chatId:topicId` (numeric topic/thread ID)
* - `chatId:topic:topicId` (explicit topic marker; preferred)
*/
function parseTelegramTarget(to) {
const normalized = stripTelegramInternalPrefixes(to);
const topicMatch = /^(.+?):topic:(\d+)$/.exec(normalized);
if (topicMatch) return {
chatId: topicMatch[1],
messageThreadId: Number.parseInt(topicMatch[2], 10)
};
const colonMatch = /^(.+):(\d+)$/.exec(normalized);
if (colonMatch) return {
chatId: colonMatch[1],
messageThreadId: Number.parseInt(colonMatch[2], 10)
};
return { chatId: normalized };
}
//#endregion
//#region src/telegram/inline-buttons.ts
const DEFAULT_INLINE_BUTTONS_SCOPE = "allowlist";
function normalizeInlineButtonsScope(value) {
if (typeof value !== "string") return;
const trimmed = value.trim().toLowerCase();
if (trimmed === "off" || trimmed === "dm" || trimmed === "group" || trimmed === "all" || trimmed === "allowlist") return trimmed;
}
function resolveInlineButtonsScopeFromCapabilities(capabilities) {
if (!capabilities) return DEFAULT_INLINE_BUTTONS_SCOPE;
if (Array.isArray(capabilities)) return capabilities.some((entry) => String(entry).trim().toLowerCase() === "inlinebuttons") ? "all" : "off";
if (typeof capabilities === "object") {
const inlineButtons = capabilities.inlineButtons;
return normalizeInlineButtonsScope(inlineButtons) ?? DEFAULT_INLINE_BUTTONS_SCOPE;
}
return DEFAULT_INLINE_BUTTONS_SCOPE;
}
function resolveTelegramInlineButtonsScope(params) {
return resolveInlineButtonsScopeFromCapabilities(resolveTelegramAccount({
cfg: params.cfg,
accountId: params.accountId
}).config.capabilities);
}
function isTelegramInlineButtonsEnabled(params) {
if (params.accountId) return resolveTelegramInlineButtonsScope(params) !== "off";
const accountIds = listTelegramAccountIds(params.cfg);
if (accountIds.length === 0) return resolveTelegramInlineButtonsScope(params) !== "off";
return accountIds.some((accountId) => resolveTelegramInlineButtonsScope({
cfg: params.cfg,
accountId
}) !== "off");
}
function resolveTelegramTargetChatType(target) {
if (!target.trim()) return "unknown";
const chatId = parseTelegramTarget(target).chatId.trim();
if (!chatId) return "unknown";
if (/^-?\d+$/.test(chatId)) return chatId.startsWith("-") ? "group" : "direct";
return "unknown";
}
//#endregion
//#region src/telegram/reaction-level.ts
/**
* Resolve the effective reaction level and its implications.
*/
function resolveTelegramReactionLevel(params) {
const level = resolveTelegramAccount({
cfg: params.cfg,
accountId: params.accountId
}).config.reactionLevel ?? "minimal";
switch (level) {
case "off": return {
level,
ackEnabled: false,
agentReactionsEnabled: false
};
case "ack": return {
level,
ackEnabled: true,
agentReactionsEnabled: false
};
case "minimal": return {
level,
ackEnabled: false,
agentReactionsEnabled: true,
agentReactionGuidance: "minimal"
};
case "extensive": return {
level,
ackEnabled: false,
agentReactionsEnabled: true,
agentReactionGuidance: "extensive"
};
default: return {
level: "ack",
ackEnabled: true,
agentReactionsEnabled: false
};
}
}
//#endregion
//#region src/agents/model-compat.ts
function isOpenAiCompletionsModel(model) {
return model.api === "openai-completions";
}
function normalizeModelCompat(model) {
const baseUrl = model.baseUrl ?? "";
if (!(model.provider === "zai" || baseUrl.includes("api.z.ai")) || !isOpenAiCompletionsModel(model)) return model;
const openaiModel = model;
const compat = openaiModel.compat ?? void 0;
if (compat?.supportsDeveloperRole === false) return model;
openaiModel.compat = compat ? {
...compat,
supportsDeveloperRole: false
} : { supportsDeveloperRole: false };
return openaiModel;
}
//#endregion
//#region src/agents/pi-embedded-runner/model.ts
function buildInlineProviderModels(providers) {
return Object.entries(providers).flatMap(([providerId, entry]) => {
const trimmed = providerId.trim();
if (!trimmed) return [];
return (entry?.models ?? []).map((model) => ({
...model,
provider: trimmed,
baseUrl: entry?.baseUrl,
api: model.api ?? entry?.api
}));
});
}
function buildModelAliasLines$1(cfg) {
const models = cfg?.agents?.defaults?.models ?? {};
const entries = [];
for (const [keyRaw, entryRaw] of Object.entries(models)) {
const model = String(keyRaw ?? "").trim();
if (!model) continue;
const alias = String(entryRaw?.alias ?? "").trim();
if (!alias) continue;
entries.push({
alias,
model
});
}
return entries.toSorted((a, b) => a.alias.localeCompare(b.alias)).map((entry) => `- ${entry.alias}: ${entry.model}`);
}
function resolveModel$4(provider, modelId, agentDir, cfg) {
const resolvedAgentDir = agentDir ?? resolveOpenClawAgentDir();
const authStorage = discoverAuthStorage(resolvedAgentDir);
const modelRegistry = discoverModels(authStorage, resolvedAgentDir);
const model = modelRegistry.find(provider, modelId);
if (!model) {
const providers = cfg?.models?.providers ?? {};
const inlineModels = buildInlineProviderModels(providers);
const normalizedProvider = normalizeProviderId(provider);
const inlineMatch = inlineModels.find((entry) => normalizeProviderId(entry.provider) === normalizedProvider && entry.id === modelId);
if (inlineMatch) return {
model: normalizeModelCompat(inlineMatch),
authStorage,
modelRegistry
};
const providerCfg = providers[provider];
if (providerCfg || modelId.startsWith("mock-")) return {
model: normalizeModelCompat({
id: modelId,
name: modelId,
api: providerCfg?.api ?? "openai-responses",
provider,
baseUrl: providerCfg?.baseUrl,
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0
},
contextWindow: providerCfg?.models?.[0]?.contextWindow ?? DEFAULT_CONTEXT_TOKENS,
maxTokens: providerCfg?.models?.[0]?.maxTokens ?? DEFAULT_CONTEXT_TOKENS
}),
authStorage,
modelRegistry
};
return {
error: `Unknown model: ${provider}/${modelId}`,
authStorage,
modelRegistry
};
}
return {
model: normalizeModelCompat(model),
authStorage,
modelRegistry
};
}
//#endregion
//#region src/media/audio.ts
const VOICE_AUDIO_EXTENSIONS = new Set([
".oga",
".ogg",
".opus"
]);
function isVoiceCompatibleAudio(opts) {
const mime = opts.contentType?.toLowerCase();
if (mime && (mime.includes("ogg") || mime.includes("opus"))) return true;
const fileName = opts.fileName?.trim();
if (!fileName) return false;
const ext = getFileExtension(fileName);
if (!ext) return false;
return VOICE_AUDIO_EXTENSIONS.has(ext);
}
//#endregion
//#region src/tts/tts.ts
const DEFAULT_TIMEOUT_MS$1 = 3e4;
const DEFAULT_TTS_MAX_LENGTH = 1500;
const DEFAULT_TTS_SUMMARIZE = true;
const DEFAULT_MAX_TEXT_LENGTH = 4096;
const TEMP_FILE_CLEANUP_DELAY_MS = 300 * 1e3;
const DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io";
const DEFAULT_ELEVENLABS_VOICE_ID = "pMsXgVXv3BLzUgSXRplE";
const DEFAULT_ELEVENLABS_MODEL_ID = "eleven_multilingual_v2";
const DEFAULT_OPENAI_MODEL = "gpt-4o-mini-tts";
const DEFAULT_OPENAI_VOICE = "alloy";
const DEFAULT_EDGE_VOICE = "en-US-MichelleNeural";
const DEFAULT_EDGE_LANG = "en-US";
const DEFAULT_EDGE_OUTPUT_FORMAT = "audio-24khz-48kbitrate-mono-mp3";
const DEFAULT_ELEVENLABS_VOICE_SETTINGS = {
stability: .5,
similarityBoost: .75,
style: 0,
useSpeakerBoost: true,
speed: 1
};
const TELEGRAM_OUTPUT = {
openai: "opus",
elevenlabs: "opus_48000_64",
extension: ".opus",
voiceCompatible: true
};
const DEFAULT_OUTPUT = {
openai: "mp3",
elevenlabs: "mp3_44100_128",
extension: ".mp3",
voiceCompatible: false
};
const TELEPHONY_OUTPUT = {
openai: {
format: "pcm",
sampleRate: 24e3
},
elevenlabs: {
format: "pcm_22050",
sampleRate: 22050
}
};
const TTS_AUTO_MODES = new Set([
"off",
"always",
"inbound",
"tagged"
]);
let lastTtsAttempt;
function normalizeTtsAutoMode(value) {
if (typeof value !== "string") return;
const normalized = value.trim().toLowerCase();
if (TTS_AUTO_MODES.has(normalized)) return normalized;
}
function resolveModelOverridePolicy(overrides) {
if (!(overrides?.enabled ?? true)) return {
enabled: false,
allowText: false,
allowProvider: false,
allowVoice: false,
allowModelId: false,
allowVoiceSettings: false,
allowNormalization: false,
allowSeed: false
};
const allow = (value) => value ?? true;
return {
enabled: true,
allowText: allow(overrides?.allowText),
allowProvider: allow(overrides?.allowProvider),
allowVoice: allow(overrides?.allowVoice),
allowModelId: allow(overrides?.allowModelId),
allowVoiceSettings: allow(overrides?.allowVoiceSettings),
allowNormalization: allow(overrides?.allowNormalization),
allowSeed: allow(overrides?.allowSeed)
};
}
function resolveTtsConfig(cfg) {
const raw = cfg.messages?.tts ?? {};
const providerSource = raw.provider ? "config" : "default";
const edgeOutputFormat = raw.edge?.outputFormat?.trim();
return {
auto: normalizeTtsAutoMode(raw.auto) ?? (raw.enabled ? "always" : "off"),
mode: raw.mode ?? "final",
provider: raw.provider ?? "edge",
providerSource,
summaryModel: raw.summaryModel?.trim() || void 0,
modelOverrides: resolveModelOverridePolicy(raw.modelOverrides),
elevenlabs: {
apiKey: raw.elevenlabs?.apiKey,
baseUrl: raw.elevenlabs?.baseUrl?.trim() || DEFAULT_ELEVENLABS_BASE_URL,
voiceId: raw.elevenlabs?.voiceId ?? DEFAULT_ELEVENLABS_VOICE_ID,
modelId: raw.elevenlabs?.modelId ?? DEFAULT_ELEVENLABS_MODEL_ID,
seed: raw.elevenlabs?.seed,
applyTextNormalization: raw.elevenlabs?.applyTextNormalization,
languageCode: raw.elevenlabs?.languageCode,
voiceSettings: {
stability: raw.elevenlabs?.voiceSettings?.stability ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.stability,
similarityBoost: raw.elevenlabs?.voiceSettings?.similarityBoost ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.similarityBoost,
style: raw.elevenlabs?.voiceSettings?.style ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.style,
useSpeakerBoost: raw.elevenlabs?.voiceSettings?.useSpeakerBoost ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.useSpeakerBoost,
speed: raw.elevenlabs?.voiceSettings?.speed ?? DEFAULT_ELEVENLABS_VOICE_SETTINGS.speed
}
},
openai: {
apiKey: raw.openai?.apiKey,
model: raw.openai?.model ?? DEFAULT_OPENAI_MODEL,
voice: raw.openai?.voice ?? DEFAULT_OPENAI_VOICE
},
edge: {
enabled: raw.edge?.enabled ?? true,
voice: raw.edge?.voice?.trim() || DEFAULT_EDGE_VOICE,
lang: raw.edge?.lang?.trim() || DEFAULT_EDGE_LANG,
outputFormat: edgeOutputFormat || DEFAULT_EDGE_OUTPUT_FORMAT,
outputFormatConfigured: Boolean(edgeOutputFormat),
pitch: raw.edge?.pitch?.trim() || void 0,
rate: raw.edge?.rate?.trim() || void 0,
volume: raw.edge?.volume?.trim() || void 0,
saveSubtitles: raw.edge?.saveSubtitles ?? false,
proxy: raw.edge?.proxy?.trim() || void 0,
timeoutMs: raw.edge?.timeoutMs
},
prefsPath: raw.prefsPath,
maxTextLength: raw.maxTextLength ?? DEFAULT_MAX_TEXT_LENGTH,
timeoutMs: raw.timeoutMs ?? DEFAULT_TIMEOUT_MS$1
};
}
function resolveTtsPrefsPath(config) {
if (config.prefsPath?.trim()) return resolveUserPath(config.prefsPath.trim());
const envPath = process.env.OPENCLAW_TTS_PREFS?.trim();
if (envPath) return resolveUserPath(envPath);
return path.join(CONFIG_DIR, "settings", "tts.json");
}
function resolveTtsAutoModeFromPrefs(prefs) {
const auto = normalizeTtsAutoMode(prefs.tts?.auto);
if (auto) return auto;
if (typeof prefs.tts?.enabled === "boolean") return prefs.tts.enabled ? "always" : "off";
}
function res