@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
1,228 lines (1,215 loc) • 2.25 MB
JavaScript
import { A as getChildLogger, C as setVerbose, D as colorize, E as warn, F as CONFIG_PATH, L as STATE_DIR, O as isRich, P as normalizeLogLevel, Q as CHAT_CHANNEL_ORDER, T as success, X as resolveStateDir, a as parseBooleanValue$1, at as normalizeChannelId, ct as requireActivePluginRegistry, it as normalizeAnyChannelId, k as theme, lt as setActivePluginRegistry, n as isTruthyEnvValue, o as createSubsystemLogger, p as defaultRuntime, v as danger, w as shouldLogVerbose, x as logVerbose, y as info } from "./entry.js";
import { A as modelKey, D as buildModelAliasIndex, E as buildAllowedModelSet, F as resolveDefaultModelForAgent, L as resolveModelRefFromString, M as parseModelRef, P as resolveConfiguredModelRef, R as resolveThinkingDefault, T as resolveOpenClawAgentDir, V as normalizeGoogleModelId, _t as DEFAULT_MODEL, a as markAuthProfileUsed, at as resolveEnvApiKey, ct as getShellPathFromLoginShell, et as getApiKeyForModel, g as markAuthProfileGood, gt as DEFAULT_CONTEXT_TOKENS, h as listProfilesForProvider, i as markAuthProfileFailure, j as normalizeProviderId, k as isCliProvider, nt as requireApiKey, ot as resolveModelAuthMode, r as isProfileInCooldown, rt as resolveApiKeyForProvider, s as resolveApiKeyForProfile, t as resolveAuthProfileOrder, tt as getCustomProviderApiKey, ut as resolveShellEnvFallbackTimeoutMs, vt as DEFAULT_PROVIDER, w as resolveAuthStorePathForDisplay, y as ensureAuthProfileStore, yt as resolveAuthProfileDisplayLabel } from "./auth-profiles-CfFGCDJa.js";
import { t as formatCliCommand } from "./command-format-3xiXujG0.js";
import { A as buildAgentPeerSessionKey, B as isAcpSessionKey, C as loadWorkspaceBootstrapFiles, D as DEFAULT_AGENT_ID, E as DEFAULT_ACCOUNT_ID$1, F as resolveAgentIdFromSessionKey, H as parseAgentSessionKey, I as resolveThreadSessionKeys, L as sanitizeAgentId, M as normalizeAccountId$3, N as normalizeAgentId, O as DEFAULT_MAIN_KEY, P as normalizeMainKey, S as filterBootstrapFilesForSession, U as resolveThreadParentSessionKey, V as isSubagentSessionKey, c as resolveDefaultAgentId, f as DEFAULT_AGENT_WORKSPACE_DIR, i as resolveAgentModelFallbacksOverride, j as buildGroupHistoryKey, k as buildAgentMainSessionKey, 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, x as ensureAgentWorkspace } from "./agent-scope-jm0ZdXwM.js";
import { _ as sleep, b as truncateUtf16Safe, c as isSelfChatMode, g as shortenHomePath, h as shortenHomeInString, l as jidToE164, m as resolveUserPath, n as clampInt, p as resolveJidToE164, t as CONFIG_DIR, u as normalizeE164, v as sliceUtf16Safe, y as toWhatsappJid } from "./utils-PmTbZoD1.js";
import { a as logDebug, c as logWarn, i as spawnWithFallback, n as runExec, o as logError, r as formatSpawnError, s as logInfo, t as runCommandWithTimeout } from "./exec-BIMFe4XS.js";
import { t as resolveOpenClawPackageRoot } from "./openclaw-root-Dean7Fp2.js";
import { a as saveJsonFile, i as loadJsonFile } from "./github-copilot-token-rP-6QdKv.js";
import { i as discoverModels, r as discoverAuthStorage } from "./pi-model-discovery-CsRo-xMp.js";
import { A as resolveAgentMaxConcurrent, C as parseConfigPath, M as VERSION, S as getConfigValueAtPath, T as unsetConfigValueAtPath, a as readConfigFileSnapshot, b as setConfigOverride, d as TELEGRAM_COMMAND_NAME_PATTERN, f as normalizeTelegramCommandName, g as validateJsonSchemaValue, h as parseDurationMs, l as validateConfigObjectWithPlugins, m as isSafeExecutableValue, o as resolveConfigSnapshotHash, p as resolveTelegramCustomCommands, r as loadConfig, s as writeConfigFile, v as getConfigOverrides, w as setConfigValueAtPath, x as unsetConfigOverride, y as resetConfigOverrides } from "./config-DCT1RAo6.js";
import { c as resolveEnableState, l as resolveMemorySlotDecision, n as discoverOpenClawPlugins, s as normalizePluginsConfig, t as loadPluginManifestRegistry } from "./manifest-registry-tuAcHxrV.js";
import { a as resolveBrowserConfig } from "./server-context-CM_E6wD5.js";
import { E as DEFAULT_AI_SNAPSHOT_MAX_CHARS, n as formatErrorMessage, r as formatUncaughtError, t as extractErrorCode } from "./errors-DdT2Dtkb.js";
import { r as startBrowserControlServiceFromConfig, t as createBrowserControlContext } from "./control-service-Ds9ompnU.js";
import { t as pickPrimaryTailnetIPv4 } from "./tailnet-BijMqkqa.js";
import { Tt as SESSION_LABEL_MAX_LENGTH, t as GatewayClient } from "./client-cU7Xg1MO.js";
import { n as callGateway, r as randomIdempotencyKey } from "./call-CfqL-4Nc.js";
import { a as isInternalMessageChannel, c as listDeliverableMessageChannels, d as resolveMessageChannel, f as GATEWAY_CLIENT_IDS, l as normalizeMessageChannel, m as GATEWAY_CLIENT_NAMES, n as isDeliverableMessageChannel, o as isMarkdownCapableMessageChannel, p as GATEWAY_CLIENT_MODES, t as INTERNAL_MESSAGE_CHANNEL, u as resolveGatewayMessageChannel } from "./message-channel-CAFcg7mw.js";
import { t as formatDocsLink } from "./links-jGisPfXW.js";
import { _ as normalizeChatType, a as normalizeWhatsAppTarget, b as normalizeDiscordToken, c as resolveTelegramAccount, d as listBindings, g as resolveSlackBotToken, h as resolveSlackAppToken, i as isWhatsAppGroupJid, l as resolveTelegramToken, n as listChannelPlugins, o as listEnabledTelegramAccounts, p as resolveSlackAccount, r as normalizeChannelId$1, s as listTelegramAccountIds, t as getChannelPlugin, v as listEnabledDiscordAccounts, y as resolveDiscordAccount } from "./plugins-TrKFfrLt.js";
import { a as logoutWeb, d as webAuthExists, i as logWebSelfId, n as resolveWhatsAppAccount, r as getWebAuthAgeMs, s as readWebSelfId } from "./accounts-B5QZU96b.js";
import { n as withProgress, r as withProgressTotals } from "./progress-Dn3kWpaL.js";
import { r as stylePromptTitle } from "./prompt-style-D5D7b3cX.js";
import { i as resolveMemorySearchConfig, n as resolveRetryConfig, r as retryAsync } from "./manager-rDmdE7O9.js";
import { a as resolveSessionTranscriptsDirForAgent, n as resolveSessionFilePath, o as resolveStorePath, r as resolveSessionTranscriptPath } from "./paths-RvF0P6tQ.js";
import { t as emitSessionTranscriptUpdate } from "./transcript-events-Cj85Mq0h.js";
import { c as listMemoryFiles, l as normalizeExtraMemoryPaths } from "./sqlite-B_L84oiu.js";
import { C as mediaKindFromMime, _ as imageMimeFromFormat, b as kindFromMime, g as getFileExtension, h as extensionForMime, i as SsrFBlockedError, m as detectMime, n as getMediaDir, p as resizeToJpeg, r as saveMediaBuffer, u as getImageMetadata, v as isAudioFileName, x as MAX_IMAGE_BYTES, y as isGifMedia } from "./routes-yI5QIzeL.js";
import { A as resolveBootstrapMaxChars, B as normalizeVerboseLevel, C as isRawApiErrorPayload, D as sanitizeUserFacingText, E as parseImageSizeError, H as supportsXHighThinking, I as normalizeElevatedLevel, L as normalizeReasoningLevel, M as formatThinkingLevels, N as formatXHighModelHint, O as buildBootstrapContextFiles, R as normalizeThinkLevel, S as isRateLimitAssistantError, T as parseImageDimensionError, U as sanitizeImageBlocks, V as resolveResponseUsageMode, W as sanitizeToolResultImages, _ as isCompactionFailureError, a as isMessagingToolDuplicateNormalized, b as isFailoverErrorMessage, c as downgradeOpenAIReasoningBlocks, d as classifyFailoverReason, f as formatAssistantErrorText, g as isCloudCodeAssistFormatError, h as isAuthAssistantError, j as sanitizeGoogleTurnOrdering, k as ensureSessionHeader, l as isAntigravityClaude, m as getApiErrorPayloadFingerprint, n as validateGeminiTurns, o as normalizeTextForComparison, p as formatRawAssistantErrorForUi, r as pickFallbackThinkingLevel, s as sanitizeSessionMessagesImages, t as validateAnthropicTurns, u as isGoogleModelApi, v as isContextOverflowError, w as isTimeoutErrorMessage, x as isLikelyContextOverflowError, y as isFailoverAssistantError, z as normalizeUsageDisplay } from "./pi-embedded-helpers-DJgCXZEz.js";
import { A as chunkMarkdownIR, B as chunkMarkdownText, C as normalizeTargetForProvider, D as HEARTBEAT_TOKEN, E as parseInlineDirectives$1, F as MediaFetchError, G as resolveTextChunkLimit, H as chunkText, I as fetchRemoteMedia, J as parseFenceSpans, K as findFenceSpanAt, L as fetchWithSsrFGuard, M as markdownToIRWithMeta, N as loadWebMedia, O as SILENT_REPLY_TOKEN, P as loadWebMediaRaw, R as resolveMarkdownTableMode, S as normalizeChannelTargetInput, T as splitMediaFromOutput, U as chunkTextWithMode, V as chunkMarkdownTextWithMode, W as resolveChunkMode, _ as signalRpcRequest, a as normalizeReplyPayloadsForDelivery, b as wrapFetchWithAbortSignal, c as filterMessagingToolDuplicates, d as createReplyToModeFilterForChannel, f as resolveReplyToMode, g as signalCheck, h as sendTypingSignal, j as markdownToIR, k as isSilentReplyText, l as isRenderablePayload, m as sendReadReceiptSignal, o as applyReplyTagsToPayload, p as sendMessageSignal, q as isSafeFenceBreak, s as applyReplyThreading, t as deliverOutboundPayloads, u as shouldSuppressMessagingToolReplies, v as streamSignalEvents, w as parseReplyDirectives, x as buildTargetResolverSignature, y as resolveFetch, z as chunkByNewline } from "./deliver-eE21zdeQ.js";
import { A as resolveSessionResetType, B as resolveConversationLabel, C as normalizeDeliveryContext, D as evaluateSessionFreshness, E as resolveSessionKey$1, H as resolveGroupSessionKey, I as resolveMainSessionKey, J as expandPolicyWithPluginGroups, K as buildPluginToolGroups, M as DEFAULT_RESET_TRIGGERS, N as canonicalizeMainSessionAlias, O as resolveChannelResetConfig, Q as stripPluginOnlyAllowlist, R as deriveSessionMetaPatch, S as mergeDeliveryContext, U as resolveSandboxConfigForAgent, V as buildGroupDisplayName, X as normalizeToolName, Z as resolveToolProfilePolicy, _ as updateSessionStoreEntry, a as ensureSandboxWorkspaceForSession, at as resolveChannelGroupRequireMention, b as deliveryContextFromSession, c as resolveSandboxRuntimeStatus, ct as resolveSignalAccount, d as loadSessionStore, f as readSessionUpdatedAt, g as updateSessionStore, h as updateLastRoute, it as resolveChannelGroupPolicy, j as resolveThreadFlag, k as resolveSessionResetPolicy, l as appendAssistantMessageToSessionTranscript, lt as resolveIMessageAccount, nt as getChannelDock, o as resolveSandboxContext, p as recordSessionMetaFromInbound, q as collectExplicitAllowlist, rt as listChannelDocks, st as listEnabledSignalAccounts, v as isCacheEnabled, w as normalizeSessionDeliveryFields, x as deliveryContextKey, y as resolveCacheTtlMs$1 } from "./sandbox-Cnq9TXEn.js";
import { C as parseCommandArgs, D as shouldHandleTextCommands, E as serializeCommandArgs, S as normalizeCommandBody, T as resolveCommandArgMenu, _ as findCommandByNativeName, b as listNativeCommandSpecs, f as estimateUsageCost, g as buildCommandTextFromArgs, h as resolveModelCostConfig, m as formatUsd$1, o as extractTextFromMessage, p as formatTokenCount$2, t as buildChannelSummary, v as listChatCommands, w as resolveCommandArgChoices, x as listNativeCommandSpecsForConfig, y as listChatCommandsForConfig } from "./channel-summary-BkqO8zZ9.js";
import { i as getMachineDisplayName, r as createBrowserRouteDispatcher, t as isWSL } from "./wsl-DASmek7h.js";
import { a as resolveSkillsPromptForRun, i as loadWorkspaceSkillEntries, l as applySkillEnvOverrides, n as buildWorkspaceSkillCommandSpecs, r as buildWorkspaceSkillSnapshot, s as resolvePluginSkillDirs, u as applySkillEnvOverridesFromSnapshot } from "./skills-DtwGIkTI.js";
import { _ as ensureOpenClawModelsJson, 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 } from "./image-CXg7Z0WD.js";
import { a as evaluateShellAllowlist, d as requiresExecApproval, f as resolveExecApprovals, h as resolveSafeBins, o as maxAsk, p as resolveExecApprovalsFromFile, s as minSecurity, t as addAllowlistEntry, u as recordAllowlistUse } from "./exec-approvals-WdYFyy5N.js";
import { a as canvasSnapshotTempPath, c as parseCameraClipPayload, d as buildNodeShellCommand, i as parseEnvPairs, l as parseCameraSnapPayload, n as screenRecordTempPath, o as parseCanvasSnapshotPayload, r as writeScreenRecordToFile, s as cameraTempPath, t as parseScreenRecordPayload, u as writeBase64ToFile } from "./nodes-screen-DOhGEibx.js";
import { n as redactSensitiveText } from "./redact-CDPAzwi8.js";
import { n as resolveToolDisplay } from "./tool-display-BMYWrp0L.js";
import { t as parseAbsoluteTimeMs } from "./parse-BB0Cqon8.js";
import { n as formatDoctorNonInteractiveHint, s as writeRestartSentinel } from "./restart-sentinel-DywisDen.js";
import { d as resolveGatewaySystemdServiceName, l as resolveGatewayLaunchAgentLabel } from "./constants-CLUi6T-M.js";
import { n as resolveMessageChannelSelection, t as listConfiguredMessageChannels } from "./channel-selection-BAwiO0li.js";
import { t as parseTimeoutMs } from "./parse-timeout-BIB19Upi.js";
import { a as isToolAllowedByPolicies, c as resolveSubagentToolPolicy, i as filterToolsByPolicy, n as resolveNativeCommandsEnabled, o as resolveEffectiveToolPolicy, r as resolveNativeSkillsEnabled, s as resolveGroupToolPolicy, t as isNativeCommandsExplicitlyDisabled } from "./commands-DMKDOFmC.js";
import { a as removeChannelAllowFromStoreEntry, c as listPairingChannels, i as readChannelAllowFromStore, o as upsertChannelPairingRequest, t as addChannelAllowFromStoreEntry } from "./pairing-store-DMex6WWe.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-sEcxw1_U.js";
import { i as withManager, t as formatErrorMessage$1 } from "./cli-utils-DFbPmfWB.js";
import { t as resolvePairingIdLabel } from "./pairing-labels-C6I3dD-m.js";
import { createRequire } from "node:module";
import { execSync, spawn, spawnSync } from "node:child_process";
import path from "node:path";
import process$1 from "node:process";
import os, { homedir, tmpdir } from "node:os";
import fs, { constants, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
import { inspect } from "node:util";
import fs$1 from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { complete, completeSimple, streamSimple } from "@mariozechner/pi-ai";
import crypto, { randomUUID } from "node:crypto";
import { CURRENT_SESSION_VERSION, SessionManager, SettingsManager, codingTools, createAgentSession, createEditTool, createReadTool, createWriteTool, estimateTokens, readTool } from "@mariozechner/pi-coding-agent";
import { createServer } from "node:http";
import { Buffer as Buffer$1 } from "node:buffer";
import * as net$1 from "node:net";
import readline, { createInterface } from "node:readline";
import { Type } from "@sinclair/typebox";
import { cancel, isCancel } from "@clack/prompts";
import { createJiti } from "jiti";
import chokidar from "chokidar";
import { WebClient } from "@slack/web-api";
import { ProxyAgent, fetch as fetch$1 } from "undici";
import { EdgeTTS } from "node-edge-tts";
import { EventEmitter } from "node:events";
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 { setTimeout as setTimeout$1 } from "node:timers/promises";
import { DisconnectReason, downloadMediaMessage, extractMessageContent, getContentType, isJidGroup, normalizeMessageContent } from "@whiskeysockets/baileys";
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";
//#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/plugins/commands.ts
const pluginCommands = /* @__PURE__ */ new Map();
let registryLocked = false;
const MAX_ARGS_LENGTH = 4096;
/**
* Reserved command names that plugins cannot override.
* These are built-in commands from commands-registry.data.ts.
*/
const RESERVED_COMMANDS = new Set([
"help",
"commands",
"status",
"whoami",
"context",
"stop",
"restart",
"reset",
"new",
"compact",
"config",
"debug",
"allowlist",
"activation",
"skill",
"subagents",
"model",
"models",
"queue",
"send",
"bash",
"exec",
"think",
"verbose",
"reasoning",
"elevated",
"usage"
]);
/**
* Validate a command name.
* Returns an error message if invalid, or null if valid.
*/
function validateCommandName(name) {
const trimmed = name.trim().toLowerCase();
if (!trimmed) return "Command name cannot be empty";
if (!/^[a-z][a-z0-9_-]*$/.test(trimmed)) return "Command name must start with a letter and contain only letters, numbers, hyphens, and underscores";
if (RESERVED_COMMANDS.has(trimmed)) return `Command name "${trimmed}" is reserved by a built-in command`;
return null;
}
/**
* Register a plugin command.
* Returns an error if the command name is invalid or reserved.
*/
function registerPluginCommand(pluginId, command) {
if (registryLocked) return {
ok: false,
error: "Cannot register commands while processing is in progress"
};
if (typeof command.handler !== "function") return {
ok: false,
error: "Command handler must be a function"
};
const validationError = validateCommandName(command.name);
if (validationError) return {
ok: false,
error: validationError
};
const key = `/${command.name.toLowerCase()}`;
if (pluginCommands.has(key)) {
const existing = pluginCommands.get(key);
return {
ok: false,
error: `Command "${command.name}" already registered by plugin "${existing.pluginId}"`
};
}
pluginCommands.set(key, {
...command,
pluginId
});
logVerbose(`Registered plugin command: ${key} (plugin: ${pluginId})`);
return { ok: true };
}
/**
* Clear all registered plugin commands.
* Called during plugin reload.
*/
function clearPluginCommands() {
pluginCommands.clear();
}
/**
* Check if a command body matches a registered plugin command.
* Returns the command definition and parsed args if matched.
*
* Note: If a command has `acceptsArgs: false` and the user provides arguments,
* the command will not match. This allows the message to fall through to
* built-in handlers or the agent. Document this behavior to plugin authors.
*/
function matchPluginCommand(commandBody) {
const trimmed = commandBody.trim();
if (!trimmed.startsWith("/")) return null;
const spaceIndex = trimmed.indexOf(" ");
const commandName = spaceIndex === -1 ? trimmed : trimmed.slice(0, spaceIndex);
const args = spaceIndex === -1 ? void 0 : trimmed.slice(spaceIndex + 1).trim();
const key = commandName.toLowerCase();
const command = pluginCommands.get(key);
if (!command) return null;
if (args && !command.acceptsArgs) return null;
return {
command,
args: args || void 0
};
}
/**
* Sanitize command arguments to prevent injection attacks.
* Removes control characters and enforces length limits.
*/
function sanitizeArgs(args) {
if (!args) return;
if (args.length > MAX_ARGS_LENGTH) return args.slice(0, MAX_ARGS_LENGTH);
let sanitized = "";
for (const char of args) {
const code = char.charCodeAt(0);
if (!(code <= 31 && code !== 9 && code !== 10 || code === 127)) sanitized += char;
}
return sanitized;
}
/**
* Execute a plugin command handler.
*
* Note: Plugin authors should still validate and sanitize ctx.args for their
* specific use case. This function provides basic defense-in-depth sanitization.
*/
async function executePluginCommand(params) {
const { command, args, senderId, channel, isAuthorizedSender, commandBody, config } = params;
if (command.requireAuth !== false && !isAuthorizedSender) {
logVerbose(`Plugin command /${command.name} blocked: unauthorized sender ${senderId || "<unknown>"}`);
return { text: "⚠️ This command requires authorization." };
}
const ctx = {
senderId,
channel,
isAuthorizedSender,
args: sanitizeArgs(args),
commandBody,
config
};
registryLocked = true;
try {
const result = await command.handler(ctx);
logVerbose(`Plugin command /${command.name} executed successfully for ${senderId || "unknown"}`);
return result;
} catch (err) {
const error = err;
logVerbose(`Plugin command /${command.name} error: ${error.message}`);
return { text: "⚠️ Command failed. Please try again later." };
} finally {
registryLocked = false;
}
}
/**
* List all registered plugin commands.
* Used for /help and /commands output.
*/
function listPluginCommands() {
return Array.from(pluginCommands.values()).map((cmd) => ({
name: cmd.name,
description: cmd.description,
pluginId: cmd.pluginId
}));
}
/**
* Get plugin command specs for native command registration (e.g., Telegram).
*/
function getPluginCommandSpecs() {
return Array.from(pluginCommands.values()).map((cmd) => ({
name: cmd.name,
description: cmd.description
}));
}
//#endregion
//#region src/plugins/hooks.ts
/**
* Get hooks for a specific hook name, sorted by priority (higher first).
*/
function getHooksForName(registry, hookName) {
return registry.typedHooks.filter((h) => h.hookName === hookName).toSorted((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
}
/**
* Create a hook runner for a specific registry.
*/
function createHookRunner(registry, options = {}) {
const logger = options.logger;
const catchErrors = options.catchErrors ?? true;
/**
* Run a hook that doesn't return a value (fire-and-forget style).
* All handlers are executed in parallel for performance.
*/
async function runVoidHook(hookName, event, ctx) {
const hooks = getHooksForName(registry, hookName);
if (hooks.length === 0) return;
logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers)`);
const promises = hooks.map(async (hook) => {
try {
await hook.handler(event, ctx);
} catch (err) {
const msg = `[hooks] ${hookName} handler from ${hook.pluginId} failed: ${String(err)}`;
if (catchErrors) logger?.error(msg);
else throw new Error(msg, { cause: err });
}
});
await Promise.all(promises);
}
/**
* Run a hook that can return a modifying result.
* Handlers are executed sequentially in priority order, and results are merged.
*/
async function runModifyingHook(hookName, event, ctx, mergeResults) {
const hooks = getHooksForName(registry, hookName);
if (hooks.length === 0) return;
logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers, sequential)`);
let result;
for (const hook of hooks) try {
const handlerResult = await hook.handler(event, ctx);
if (handlerResult !== void 0 && handlerResult !== null) if (mergeResults && result !== void 0) result = mergeResults(result, handlerResult);
else result = handlerResult;
} catch (err) {
const msg = `[hooks] ${hookName} handler from ${hook.pluginId} failed: ${String(err)}`;
if (catchErrors) logger?.error(msg);
else throw new Error(msg, { cause: err });
}
return result;
}
/**
* Run before_agent_start hook.
* Allows plugins to inject context into the system prompt.
* Runs sequentially, merging systemPrompt and prependContext from all handlers.
*/
async function runBeforeAgentStart(event, ctx) {
return runModifyingHook("before_agent_start", event, ctx, (acc, next) => ({
systemPrompt: next.systemPrompt ?? acc?.systemPrompt,
prependContext: acc?.prependContext && next.prependContext ? `${acc.prependContext}\n\n${next.prependContext}` : next.prependContext ?? acc?.prependContext
}));
}
/**
* Run agent_end hook.
* Allows plugins to analyze completed conversations.
* Runs in parallel (fire-and-forget).
*/
async function runAgentEnd(event, ctx) {
return runVoidHook("agent_end", event, ctx);
}
/**
* Run before_compaction hook.
*/
async function runBeforeCompaction(event, ctx) {
return runVoidHook("before_compaction", event, ctx);
}
/**
* Run after_compaction hook.
*/
async function runAfterCompaction(event, ctx) {
return runVoidHook("after_compaction", event, ctx);
}
/**
* Run message_received hook.
* Runs in parallel (fire-and-forget).
*/
async function runMessageReceived(event, ctx) {
return runVoidHook("message_received", event, ctx);
}
/**
* Run message_sending hook.
* Allows plugins to modify or cancel outgoing messages.
* Runs sequentially.
*/
async function runMessageSending(event, ctx) {
return runModifyingHook("message_sending", event, ctx, (acc, next) => ({
content: next.content ?? acc?.content,
cancel: next.cancel ?? acc?.cancel
}));
}
/**
* Run message_sent hook.
* Runs in parallel (fire-and-forget).
*/
async function runMessageSent(event, ctx) {
return runVoidHook("message_sent", event, ctx);
}
/**
* Run before_tool_call hook.
* Allows plugins to modify or block tool calls.
* Runs sequentially.
*/
async function runBeforeToolCall(event, ctx) {
return runModifyingHook("before_tool_call", event, ctx, (acc, next) => ({
params: next.params ?? acc?.params,
block: next.block ?? acc?.block,
blockReason: next.blockReason ?? acc?.blockReason
}));
}
/**
* Run after_tool_call hook.
* Runs in parallel (fire-and-forget).
*/
async function runAfterToolCall(event, ctx) {
return runVoidHook("after_tool_call", event, ctx);
}
/**
* Run tool_result_persist hook.
*
* This hook is intentionally synchronous: it runs in hot paths where session
* transcripts are appended synchronously.
*
* Handlers are executed sequentially in priority order (higher first). Each
* handler may return `{ message }` to replace the message passed to the next
* handler.
*/
function runToolResultPersist(event, ctx) {
const hooks = getHooksForName(registry, "tool_result_persist");
if (hooks.length === 0) return;
let current = event.message;
for (const hook of hooks) try {
const out = hook.handler({
...event,
message: current
}, ctx);
if (out && typeof out.then === "function") {
const msg = `[hooks] tool_result_persist handler from ${hook.pluginId} returned a Promise; this hook is synchronous and the result was ignored.`;
if (catchErrors) {
logger?.warn?.(msg);
continue;
}
throw new Error(msg);
}
const next = out?.message;
if (next) current = next;
} catch (err) {
const msg = `[hooks] tool_result_persist handler from ${hook.pluginId} failed: ${String(err)}`;
if (catchErrors) logger?.error(msg);
else throw new Error(msg, { cause: err });
}
return { message: current };
}
/**
* Run session_start hook.
* Runs in parallel (fire-and-forget).
*/
async function runSessionStart(event, ctx) {
return runVoidHook("session_start", event, ctx);
}
/**
* Run session_end hook.
* Runs in parallel (fire-and-forget).
*/
async function runSessionEnd(event, ctx) {
return runVoidHook("session_end", event, ctx);
}
/**
* Run gateway_start hook.
* Runs in parallel (fire-and-forget).
*/
async function runGatewayStart(event, ctx) {
return runVoidHook("gateway_start", event, ctx);
}
/**
* Run gateway_stop hook.
* Runs in parallel (fire-and-forget).
*/
async function runGatewayStop(event, ctx) {
return runVoidHook("gateway_stop", event, ctx);
}
/**
* Check if any hooks are registered for a given hook name.
*/
function hasHooks(hookName) {
return registry.typedHooks.some((h) => h.hookName === hookName);
}
/**
* Get count of registered hooks for a given hook name.
*/
function getHookCount(hookName) {
return registry.typedHooks.filter((h) => h.hookName === hookName).length;
}
return {
runBeforeAgentStart,
runAgentEnd,
runBeforeCompaction,
runAfterCompaction,
runMessageReceived,
runMessageSending,
runMessageSent,
runBeforeToolCall,
runAfterToolCall,
runToolResultPersist,
runSessionStart,
runSessionEnd,
runGatewayStart,
runGatewayStop,
hasHooks,
getHookCount
};
}
//#endregion
//#region src/plugins/hook-runner-global.ts
const log$11 = createSubsystemLogger("plugins");
let globalHookRunner = null;
let globalRegistry = null;
/**
* Initialize the global hook runner with a plugin registry.
* Called once when plugins are loaded during gateway startup.
*/
function initializeGlobalHookRunner(registry) {
globalRegistry = registry;
globalHookRunner = createHookRunner(registry, {
logger: {
debug: (msg) => log$11.debug(msg),
warn: (msg) => log$11.warn(msg),
error: (msg) => log$11.error(msg)
},
catchErrors: true
});
const hookCount = registry.hooks.length;
if (hookCount > 0) log$11.info(`hook runner initialized with ${hookCount} registered hooks`);
}
/**
* Get the global hook runner.
* Returns null if plugins haven't been loaded yet.
*/
function getGlobalHookRunner() {
return globalHookRunner;
}
//#endregion
//#region src/hooks/internal-hooks.ts
/** Registry of hook handlers by event key */
const handlers$1 = /* @__PURE__ */ new Map();
/**
* Register a hook handler for a specific event type or event:action combination
*
* @param eventKey - Event type (e.g., 'command') or specific action (e.g., 'command:new')
* @param handler - Function to call when the event is triggered
*
* @example
* ```ts
* // Listen to all command events
* registerInternalHook('command', async (event) => {
* console.log('Command:', event.action);
* });
*
* // Listen only to /new commands
* registerInternalHook('command:new', async (event) => {
* await saveSessionToMemory(event);
* });
* ```
*/
function registerInternalHook(eventKey, handler) {
if (!handlers$1.has(eventKey)) handlers$1.set(eventKey, []);
handlers$1.get(eventKey).push(handler);
}
/**
* Clear all registered hooks (useful for testing)
*/
function clearInternalHooks() {
handlers$1.clear();
}
/**
* Trigger a hook event
*
* Calls all handlers registered for:
* 1. The general event type (e.g., 'command')
* 2. The specific event:action combination (e.g., 'command:new')
*
* Handlers are called in registration order. Errors are caught and logged
* but don't prevent other handlers from running.
*
* @param event - The event to trigger
*/
async function triggerInternalHook(event) {
const typeHandlers = handlers$1.get(event.type) ?? [];
const specificHandlers = handlers$1.get(`${event.type}:${event.action}`) ?? [];
const allHandlers = [...typeHandlers, ...specificHandlers];
if (allHandlers.length === 0) return;
for (const handler of allHandlers) try {
await handler(event);
} catch (err) {
console.error(`Hook error [${event.type}:${event.action}]:`, err instanceof Error ? err.message : String(err));
}
}
/**
* Create a hook event with common fields filled in
*
* @param type - The event type
* @param action - The action within that type
* @param sessionKey - The session key
* @param context - Additional context
*/
function createInternalHookEvent(type, action, sessionKey, context = {}) {
return {
type,
action,
sessionKey,
context,
timestamp: /* @__PURE__ */ new Date(),
messages: []
};
}
//#endregion
//#region src/plugins/http-path.ts
function normalizePluginHttpPath(path, fallback) {
const trimmed = path?.trim();
if (!trimmed) {
const fallbackTrimmed = fallback?.trim();
if (!fallbackTrimmed) return null;
return fallbackTrimmed.startsWith("/") ? fallbackTrimmed : `/${fallbackTrimmed}`;
}
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}
//#endregion
//#region src/plugins/registry.ts
function createPluginRegistry(registryParams) {
const registry = {
plugins: [],
tools: [],
hooks: [],
typedHooks: [],
channels: [],
providers: [],
gatewayHandlers: {},
httpHandlers: [],
httpRoutes: [],
cliRegistrars: [],
services: [],
commands: [],
diagnostics: []
};
const coreGatewayMethods = new Set(Object.keys(registryParams.coreGatewayHandlers ?? {}));
const pushDiagnostic = (diag) => {
registry.diagnostics.push(diag);
};
const registerTool = (record, tool, opts) => {
const names = opts?.names ?? (opts?.name ? [opts.name] : []);
const optional = opts?.optional === true;
const factory = typeof tool === "function" ? tool : (_ctx) => tool;
if (typeof tool !== "function") names.push(tool.name);
const normalized = names.map((name) => name.trim()).filter(Boolean);
if (normalized.length > 0) record.toolNames.push(...normalized);
registry.tools.push({
pluginId: record.id,
factory,
names: normalized,
optional,
source: record.source
});
};
const registerHook = (record, events, handler, opts, config) => {
const normalizedEvents = (Array.isArray(events) ? events : [events]).map((event) => event.trim()).filter(Boolean);
const entry = opts?.entry ?? null;
const name = entry?.hook.name ?? opts?.name?.trim();
if (!name) {
pushDiagnostic({
level: "warn",
pluginId: record.id,
source: record.source,
message: "hook registration missing name"
});
return;
}
const description = entry?.hook.description ?? opts?.description ?? "";
const hookEntry = entry ? {
...entry,
hook: {
...entry.hook,
name,
description,
source: "openclaw-plugin",
pluginId: record.id
},
metadata: {
...entry.metadata,
events: normalizedEvents
}
} : {
hook: {
name,
description,
source: "openclaw-plugin",
pluginId: record.id,
filePath: record.source,
baseDir: path.dirname(record.source),
handlerPath: record.source
},
frontmatter: {},
metadata: { events: normalizedEvents },
invocation: { enabled: true }
};
record.hookNames.push(name);
registry.hooks.push({
pluginId: record.id,
entry: hookEntry,
events: normalizedEvents,
source: record.source
});
if (!(config?.hooks?.internal?.enabled === true) || opts?.register === false) return;
for (const event of normalizedEvents) registerInternalHook(event, handler);
};
const registerGatewayMethod = (record, method, handler) => {
const trimmed = method.trim();
if (!trimmed) return;
if (coreGatewayMethods.has(trimmed) || registry.gatewayHandlers[trimmed]) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: `gateway method already registered: ${trimmed}`
});
return;
}
registry.gatewayHandlers[trimmed] = handler;
record.gatewayMethods.push(trimmed);
};
const registerHttpHandler = (record, handler) => {
record.httpHandlers += 1;
registry.httpHandlers.push({
pluginId: record.id,
handler,
source: record.source
});
};
const registerHttpRoute = (record, params) => {
const normalizedPath = normalizePluginHttpPath(params.path);
if (!normalizedPath) {
pushDiagnostic({
level: "warn",
pluginId: record.id,
source: record.source,
message: "http route registration missing path"
});
return;
}
if (registry.httpRoutes.some((entry) => entry.path === normalizedPath)) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: `http route already registered: ${normalizedPath}`
});
return;
}
record.httpHandlers += 1;
registry.httpRoutes.push({
pluginId: record.id,
path: normalizedPath,
handler: params.handler,
source: record.source
});
};
const registerChannel = (record, registration) => {
const normalized = typeof registration.plugin === "object" ? registration : { plugin: registration };
const plugin = normalized.plugin;
const id = typeof plugin?.id === "string" ? plugin.id.trim() : String(plugin?.id ?? "").trim();
if (!id) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: "channel registration missing id"
});
return;
}
record.channelIds.push(id);
registry.channels.push({
pluginId: record.id,
plugin,
dock: normalized.dock,
source: record.source
});
};
const registerProvider = (record, provider) => {
const id = typeof provider?.id === "string" ? provider.id.trim() : "";
if (!id) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: "provider registration missing id"
});
return;
}
const existing = registry.providers.find((entry) => entry.provider.id === id);
if (existing) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: `provider already registered: ${id} (${existing.pluginId})`
});
return;
}
record.providerIds.push(id);
registry.providers.push({
pluginId: record.id,
provider,
source: record.source
});
};
const registerCli = (record, registrar, opts) => {
const commands = (opts?.commands ?? []).map((cmd) => cmd.trim()).filter(Boolean);
record.cliCommands.push(...commands);
registry.cliRegistrars.push({
pluginId: record.id,
register: registrar,
commands,
source: record.source
});
};
const registerService = (record, service) => {
const id = service.id.trim();
if (!id) return;
record.services.push(id);
registry.services.push({
pluginId: record.id,
service,
source: record.source
});
};
const registerCommand = (record, command) => {
const name = command.name.trim();
if (!name) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: "command registration missing name"
});
return;
}
const result = registerPluginCommand(record.id, command);
if (!result.ok) {
pushDiagnostic({
level: "error",
pluginId: record.id,
source: record.source,
message: `command registration failed: ${result.error}`
});
return;
}
record.commands.push(name);
registry.commands.push({
pluginId: record.id,
command,
source: record.source
});
};
const registerTypedHook = (record, hookName, handler, opts) => {
record.hookCount += 1;
registry.typedHooks.push({
pluginId: record.id,
hookName,
handler,
priority: opts?.priority,
source: record.source
});
};
const normalizeLogger = (logger) => ({
info: logger.info,
warn: logger.warn,
error: logger.error,
debug: logger.debug
});
const createApi = (record, params) => {
return {
id: record.id,
name: record.name,
version: record.version,
description: record.description,
source: record.source,
config: params.config,
pluginConfig: params.pluginConfig,
runtime: registryParams.runtime,
logger: normalizeLogger(registryParams.logger),
registerTool: (tool, opts) => registerTool(record, tool, opts),
registerHook: (events, handler, opts) => registerHook(record, events, handler, opts, params.config),
registerHttpHandler: (handler) => registerHttpHandler(record, handler),
registerHttpRoute: (params) => registerHttpRoute(record, params),
registerChannel: (registration) => registerChannel(record, registration),
registerProvider: (provider) => registerProvider(record, provider),
registerGatewayMethod: (method, handler) => registerGatewayMethod(record, method, handler),
registerCli: (registrar, opts) => registerCli(record, registrar, opts),
registerService: (service) => registerService(record, service),
registerCommand: (command) => registerCommand(record, command),
resolvePath: (input) => resolveUserPath(input),
on: (hookName, handler, opts) => registerTypedHook(record, hookName, handler, opts)
};
};
return {
registry,
createApi,
pushDiagnostic,
registerTool,
registerChannel,
registerProvider,
registerGatewayMethod,
registerCli,
registerService,
registerCommand,
registerHook,
registerTypedHook
};
}
//#endregion
//#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/utils/shell-argv.ts
function splitShellArgs(raw) {
const tokens = [];
let buf = "";
let inSingle = false;
let inDouble = false;
let escaped = false;
const pushToken = () => {
if (buf.length > 0) {
tokens.push(buf);
buf = "";
}
};
for (let i = 0; i < raw.length; i += 1) {
const ch = raw[i];
if (escaped) {
buf += ch;
escaped = false;
continue;
}
if (!inSingle && !inDouble && ch === "\\") {
escaped = true;
continue;
}
if (inSingle) {
if (ch === "'") inSingle = false;
else buf += ch;
continue;
}
if (inDouble) {
if (ch === "\"") inDouble = false;
else buf += ch;
continue;
}
if (ch === "'") {
inSingle = true;
continue;
}
if (ch === "\"") {
inDouble = true;
continue;
}
if (/\s/.test(ch)) {
pushToken();
continue;
}
buf += ch;
}
if (escaped || inSingle || inDouble) return null;
pushToken();
return tokens;
}
//#endregion
//#region src/memory/backend-config.ts
const DEFAULT_BACKEND = "builtin";
const DEFAULT_CITATIONS = "auto";
const DEFAULT_QMD_INTERVAL = "5m";
const DEFAULT_QMD_DEBOUNCE_MS = 15e3;
const DEFAULT_QMD_TIMEOUT_MS = 4e3;
const DEFAULT_QMD_EMBED_INTERVAL = "60m";
const DEFAULT_QMD_LIMITS = {
maxResults: 6,
maxSnippetChars: 700,
maxInjectedChars: 4e3,
timeoutMs: DEFAULT_QMD_TIMEOUT_MS
};
const DEFAULT_QMD_SCOPE = {
default: "deny",
rules: [{
action: "allow",
match: { chatType: "direct" }
}]
};
function sanitizeName(input) {
return input.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") || "collection";
}
function ensureUniqueName(base, existing) {
let name = sanitizeName(base);
if (!existing.has(name)) {
existing.add(name);
return name;
}
let suffix = 2;
while (existing.has(`${name}-${suffix}`)) suffix += 1;
const unique = `${name}-${suffix}`;
existing.add(unique);
return unique;
}
function resolvePath(raw, workspaceDir) {
const trimmed = raw.trim();
if (!trimmed) throw new Error("path required");
if (trimmed.startsWith("~") || path.isAbsolute(trimmed)) return path.normalize(resolveUserPath(trimmed));
return path.normalize(path.resolve(workspaceDir, trimmed));
}
function resolveIntervalMs(raw) {
const value = raw?.trim();
if (!value) return parseDurationMs(DEFAULT_QMD_INTERVAL, { defaultUnit: "m" });
try {
return parseDurationMs(value, { de