UNPKG

@gguf/claw

Version:

WhatsApp gateway CLI (Baileys web) with Pi RPC agent

1,179 lines (1,159 loc) 2.33 MB
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(/&nbsp;/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