@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
1,027 lines (1,026 loc) • 352 kB
TypeScript
import { ZodTypeAny, z } from "zod";
import fs from "node:fs";
import { Logger } from "tslog";
import json5 from "json5";
import "chalk";
import { ImageContent, OAuthCredentials } from "@mariozechner/pi-ai";
import AjvPkg from "ajv";
import { AuthStorage, ModelRegistry, Skill } from "@mariozechner/pi-coding-agent";
import * as _sinclair_typebox1712 from "@sinclair/typebox";
import { Static, TSchema } from "@sinclair/typebox";
import { IncomingMessage, ServerResponse } from "node:http";
import { lookup } from "node:dns";
import { lookup as lookup$1 } from "node:dns/promises";
import "undici";
import { ChannelType } from "discord-api-types/v10";
import { Button, ChannelType as ChannelType$1, Client, Command, MessageCreateListener, MessageReactionAddListener, MessageReactionRemoveListener, PresenceUpdateListener, RequestClient } from "@buape/carbon";
import { WebSocket as WebSocket$1 } from "ws";
import { WebClient } from "@slack/web-api";
import { Bot } from "grammy";
import { WebhookRequestBody, messagingApi } from "@line/bot-sdk";
import "@grammyjs/runner";
import { AnyMessageContent, makeWASocket } from "@whiskeysockets/baileys";
import { AgentMessage, AgentTool, AgentToolResult } from "@mariozechner/pi-agent-core";
import { Command as Command$1 } from "commander";
//#region src/channels/plugins/message-action-names.d.ts
declare const CHANNEL_MESSAGE_ACTION_NAMES: readonly ["send", "broadcast", "poll", "react", "reactions", "read", "edit", "unsend", "reply", "sendWithEffect", "renameGroup", "setGroupIcon", "addParticipant", "removeParticipant", "leaveGroup", "sendAttachment", "delete", "pin", "unpin", "list-pins", "permissions", "thread-create", "thread-list", "thread-reply", "search", "sticker", "sticker-search", "member-info", "role-info", "emoji-list", "emoji-upload", "sticker-upload", "role-add", "role-remove", "channel-info", "channel-list", "channel-create", "channel-edit", "channel-delete", "channel-move", "category-create", "category-edit", "category-delete", "voice-status", "event-list", "event-create", "timeout", "kick", "ban", "set-presence"];
type ChannelMessageActionName$2 = (typeof CHANNEL_MESSAGE_ACTION_NAMES)[number];
//#endregion
//#region src/channels/plugins/bluebubbles-actions.d.ts
declare const BLUEBUBBLES_ACTIONS: {
readonly react: {
readonly gate: "reactions";
};
readonly edit: {
readonly gate: "edit";
readonly unsupportedOnMacOS26: true;
};
readonly unsend: {
readonly gate: "unsend";
};
readonly reply: {
readonly gate: "reply";
};
readonly sendWithEffect: {
readonly gate: "sendWithEffect";
};
readonly renameGroup: {
readonly gate: "renameGroup";
readonly groupOnly: true;
};
readonly setGroupIcon: {
readonly gate: "setGroupIcon";
readonly groupOnly: true;
};
readonly addParticipant: {
readonly gate: "addParticipant";
readonly groupOnly: true;
};
readonly removeParticipant: {
readonly gate: "removeParticipant";
readonly groupOnly: true;
};
readonly leaveGroup: {
readonly gate: "leaveGroup";
readonly groupOnly: true;
};
readonly sendAttachment: {
readonly gate: "sendAttachment";
};
};
declare const BLUEBUBBLES_ACTION_NAMES: (keyof typeof BLUEBUBBLES_ACTIONS)[];
declare const BLUEBUBBLES_GROUP_ACTIONS: Set<"send" | "broadcast" | "poll" | "react" | "reactions" | "read" | "edit" | "unsend" | "reply" | "sendWithEffect" | "renameGroup" | "setGroupIcon" | "addParticipant" | "removeParticipant" | "leaveGroup" | "sendAttachment" | "delete" | "pin" | "unpin" | "list-pins" | "permissions" | "thread-create" | "thread-list" | "thread-reply" | "search" | "sticker" | "sticker-search" | "member-info" | "role-info" | "emoji-list" | "emoji-upload" | "sticker-upload" | "role-add" | "role-remove" | "channel-info" | "channel-list" | "channel-create" | "channel-edit" | "channel-delete" | "channel-move" | "category-create" | "category-edit" | "category-delete" | "voice-status" | "event-list" | "event-create" | "timeout" | "kick" | "ban" | "set-presence">;
//#endregion
//#region src/auto-reply/reply/typing.d.ts
type TypingController = {
onReplyStart: () => Promise<void>;
startTypingLoop: () => Promise<void>;
startTypingOnText: (text?: string) => Promise<void>;
refreshTypingTtl: () => void;
isActive: () => boolean;
markRunComplete: () => void;
markDispatchIdle: () => void;
cleanup: () => void;
};
//#endregion
//#region src/auto-reply/types.d.ts
type BlockReplyContext = {
abortSignal?: AbortSignal;
timeoutMs?: number;
};
/** Context passed to onModelSelected callback with actual model used. */
type ModelSelectedContext = {
provider: string;
model: string;
thinkLevel: string | undefined;
};
type GetReplyOptions = {
/** Override run id for agent events (defaults to random UUID). */runId?: string; /** Abort signal for the underlying agent run. */
abortSignal?: AbortSignal; /** Optional inbound images (used for webchat attachments). */
images?: ImageContent[]; /** Notifies when an agent run actually starts (useful for webchat command handling). */
onAgentRunStart?: (runId: string) => void;
onReplyStart?: () => Promise<void> | void;
onTypingController?: (typing: TypingController) => void;
isHeartbeat?: boolean;
onPartialReply?: (payload: ReplyPayload) => Promise<void> | void;
onReasoningStream?: (payload: ReplyPayload) => Promise<void> | void;
onBlockReply?: (payload: ReplyPayload, context?: BlockReplyContext) => Promise<void> | void;
onToolResult?: (payload: ReplyPayload) => Promise<void> | void;
/** Called when the actual model is selected (including after fallback).
* Use this to get model/provider/thinkLevel for responsePrefix template interpolation. */
onModelSelected?: (ctx: ModelSelectedContext) => void;
disableBlockStreaming?: boolean; /** Timeout for block reply delivery (ms). */
blockReplyTimeoutMs?: number; /** If provided, only load these skills for this session (empty = no skills). */
skillFilter?: string[]; /** Mutable ref to track if a reply was sent (for Slack "first" threading mode). */
hasRepliedRef?: {
value: boolean;
};
};
type ReplyPayload = {
text?: string;
mediaUrl?: string;
mediaUrls?: string[];
replyToId?: string;
replyToTag?: boolean; /** True when [[reply_to_current]] was present but not yet mapped to a message id. */
replyToCurrent?: boolean; /** Send audio as voice message (bubble) instead of audio file. Defaults to false. */
audioAsVoice?: boolean;
isError?: boolean; /** Channel-specific payload data (per-channel envelope). */
channelData?: Record<string, unknown>;
};
//#endregion
//#region src/channels/chat-type.d.ts
type NormalizedChatType = "direct" | "group" | "channel";
//#endregion
//#region src/config/types.base.d.ts
type TypingMode = "never" | "instant" | "thinking" | "message";
type SessionScope = "per-sender" | "global";
type DmScope = "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer";
type ReplyToMode = "off" | "first" | "all";
type GroupPolicy = "open" | "disabled" | "allowlist";
type DmPolicy = "pairing" | "allowlist" | "open" | "disabled";
type OutboundRetryConfig = {
/** Max retry attempts for outbound requests (default: 3). */attempts?: number; /** Minimum retry delay in ms (default: 300-500ms depending on provider). */
minDelayMs?: number; /** Maximum retry delay cap in ms (default: 30000). */
maxDelayMs?: number; /** Jitter factor (0-1) applied to delays (default: 0.1). */
jitter?: number;
};
type BlockStreamingCoalesceConfig = {
minChars?: number;
maxChars?: number;
idleMs?: number;
};
type BlockStreamingChunkConfig = {
minChars?: number;
maxChars?: number;
breakPreference?: "paragraph" | "newline" | "sentence";
};
type MarkdownTableMode = "off" | "bullets" | "code";
type MarkdownConfig = {
/** Table rendering mode (off|bullets|code). */tables?: MarkdownTableMode;
};
type HumanDelayConfig = {
/** Delay style for block replies (off|natural|custom). */mode?: "off" | "natural" | "custom"; /** Minimum delay in milliseconds (default: 800). */
minMs?: number; /** Maximum delay in milliseconds (default: 2500). */
maxMs?: number;
};
type SessionSendPolicyAction = "allow" | "deny";
type SessionSendPolicyMatch = {
channel?: string;
chatType?: NormalizedChatType;
keyPrefix?: string;
};
type SessionSendPolicyRule = {
action: SessionSendPolicyAction;
match?: SessionSendPolicyMatch;
};
type SessionSendPolicyConfig = {
default?: SessionSendPolicyAction;
rules?: SessionSendPolicyRule[];
};
type SessionResetMode = "daily" | "idle";
type SessionResetConfig = {
mode?: SessionResetMode; /** Local hour (0-23) for the daily reset boundary. */
atHour?: number; /** Sliding idle window (minutes). When set with daily mode, whichever expires first wins. */
idleMinutes?: number;
};
type SessionResetByTypeConfig = {
dm?: SessionResetConfig;
group?: SessionResetConfig;
thread?: SessionResetConfig;
};
type SessionConfig = {
scope?: SessionScope; /** DM session scoping (default: "main"). */
dmScope?: DmScope; /** Map platform-prefixed identities (e.g. "telegram:123") to canonical DM peers. */
identityLinks?: Record<string, string[]>;
resetTriggers?: string[];
idleMinutes?: number;
reset?: SessionResetConfig;
resetByType?: SessionResetByTypeConfig; /** Channel-specific reset overrides (e.g. { discord: { mode: "idle", idleMinutes: 10080 } }). */
resetByChannel?: Record<string, SessionResetConfig>;
store?: string;
typingIntervalSeconds?: number;
typingMode?: TypingMode;
mainKey?: string;
sendPolicy?: SessionSendPolicyConfig;
agentToAgent?: {
/** Max ping-pong turns between requester/target (0–5). Default: 5. */maxPingPongTurns?: number;
};
};
type LoggingConfig = {
level?: "silent" | "fatal" | "error" | "warn" | "info" | "debug" | "trace";
file?: string;
consoleLevel?: "silent" | "fatal" | "error" | "warn" | "info" | "debug" | "trace";
consoleStyle?: "pretty" | "compact" | "json"; /** Redact sensitive tokens in tool summaries. Default: "tools". */
redactSensitive?: "off" | "tools"; /** Regex patterns used to redact sensitive tokens (defaults apply when unset). */
redactPatterns?: string[];
};
type DiagnosticsOtelConfig = {
enabled?: boolean;
endpoint?: string;
protocol?: "http/protobuf" | "grpc";
headers?: Record<string, string>;
serviceName?: string;
traces?: boolean;
metrics?: boolean;
logs?: boolean; /** Trace sample rate (0.0 - 1.0). */
sampleRate?: number; /** Metric export interval (ms). */
flushIntervalMs?: number;
};
type DiagnosticsCacheTraceConfig = {
enabled?: boolean;
filePath?: string;
includeMessages?: boolean;
includePrompt?: boolean;
includeSystem?: boolean;
};
type DiagnosticsConfig = {
enabled?: boolean; /** Optional ad-hoc diagnostics flags (e.g. "telegram.http"). */
flags?: string[];
otel?: DiagnosticsOtelConfig;
cacheTrace?: DiagnosticsCacheTraceConfig;
};
type WebReconnectConfig = {
initialMs?: number;
maxMs?: number;
factor?: number;
jitter?: number;
maxAttempts?: number;
};
type WebConfig = {
/** If false, do not start the WhatsApp web provider. Default: true. */enabled?: boolean;
heartbeatSeconds?: number;
reconnect?: WebReconnectConfig;
};
type AgentElevatedAllowFromConfig = Partial<Record<string, Array<string | number>>>;
type IdentityConfig = {
name?: string;
theme?: string;
emoji?: string; /** Avatar image: workspace-relative path, http(s) URL, or data URI. */
avatar?: string;
};
//#endregion
//#region src/config/types.sandbox.d.ts
type SandboxDockerSettings = {
/** Docker image to use for sandbox containers. */image?: string; /** Prefix for sandbox container names. */
containerPrefix?: string; /** Container workdir mount path (default: /workspace). */
workdir?: string; /** Run container rootfs read-only. */
readOnlyRoot?: boolean; /** Extra tmpfs mounts for read-only containers. */
tmpfs?: string[]; /** Container network mode (bridge|none|custom). */
network?: string; /** Container user (uid:gid). */
user?: string; /** Drop Linux capabilities. */
capDrop?: string[]; /** Extra environment variables for sandbox exec. */
env?: Record<string, string>; /** Optional setup command run once after container creation. */
setupCommand?: string; /** Limit container PIDs (0 = Docker default). */
pidsLimit?: number; /** Limit container memory (e.g. 512m, 2g, or bytes as number). */
memory?: string | number; /** Limit container memory swap (same format as memory). */
memorySwap?: string | number; /** Limit container CPU shares (e.g. 0.5, 1, 2). */
cpus?: number;
/**
* Set ulimit values by name (e.g. nofile, nproc).
* Use "soft:hard" string, a number, or { soft, hard }.
*/
ulimits?: Record<string, string | number | {
soft?: number;
hard?: number;
}>; /** Seccomp profile (path or profile name). */
seccompProfile?: string; /** AppArmor profile name. */
apparmorProfile?: string; /** DNS servers (e.g. ["1.1.1.1", "8.8.8.8"]). */
dns?: string[]; /** Extra host mappings (e.g. ["api.local:10.0.0.2"]). */
extraHosts?: string[]; /** Additional bind mounts (host:container:mode format, e.g. ["/host/path:/container/path:rw"]). */
binds?: string[];
};
type SandboxBrowserSettings = {
enabled?: boolean;
image?: string;
containerPrefix?: string;
cdpPort?: number;
vncPort?: number;
noVncPort?: number;
headless?: boolean;
enableNoVnc?: boolean;
/**
* Allow sandboxed sessions to target the host browser control server.
* Default: false.
*/
allowHostControl?: boolean;
/**
* When true (default), sandboxed browser control will try to start/reattach to
* the sandbox browser container when a tool call needs it.
*/
autoStart?: boolean; /** Max time to wait for CDP to become reachable after auto-start (ms). */
autoStartTimeoutMs?: number;
};
type SandboxPruneSettings = {
/** Prune if idle for more than N hours (0 disables). */idleHours?: number; /** Prune if older than N days (0 disables). */
maxAgeDays?: number;
};
//#endregion
//#region src/config/types.tools.d.ts
type MediaUnderstandingScopeMatch = {
channel?: string;
chatType?: NormalizedChatType;
keyPrefix?: string;
};
type MediaUnderstandingScopeRule = {
action: SessionSendPolicyAction;
match?: MediaUnderstandingScopeMatch;
};
type MediaUnderstandingScopeConfig = {
default?: SessionSendPolicyAction;
rules?: MediaUnderstandingScopeRule[];
};
type MediaUnderstandingCapability$1 = "image" | "audio" | "video";
type MediaUnderstandingAttachmentsConfig = {
/** Select the first matching attachment or process multiple. */mode?: "first" | "all"; /** Max number of attachments to process (default: 1). */
maxAttachments?: number; /** Attachment ordering preference. */
prefer?: "first" | "last" | "path" | "url";
};
type MediaUnderstandingModelConfig = {
/** provider API id (e.g. openai, google). */provider?: string; /** Model id for provider-based understanding. */
model?: string; /** Optional capability tags for shared model lists. */
capabilities?: MediaUnderstandingCapability$1[]; /** Use a CLI command instead of provider API. */
type?: "provider" | "cli"; /** CLI binary (required when type=cli). */
command?: string; /** CLI args (template-enabled). */
args?: string[]; /** Optional prompt override for this model entry. */
prompt?: string; /** Optional max output characters for this model entry. */
maxChars?: number; /** Optional max bytes for this model entry. */
maxBytes?: number; /** Optional timeout override (seconds) for this model entry. */
timeoutSeconds?: number; /** Optional language hint for audio transcription. */
language?: string; /** Optional provider-specific query params (merged into requests). */
providerOptions?: Record<string, Record<string, string | number | boolean>>; /** @deprecated Use providerOptions.deepgram instead. */
deepgram?: {
detectLanguage?: boolean;
punctuate?: boolean;
smartFormat?: boolean;
}; /** Optional base URL override for provider requests. */
baseUrl?: string; /** Optional headers merged into provider requests. */
headers?: Record<string, string>; /** Auth profile id to use for this provider. */
profile?: string; /** Preferred profile id if multiple are available. */
preferredProfile?: string;
};
type MediaUnderstandingConfig = {
/** Enable media understanding when models are configured. */enabled?: boolean; /** Optional scope gating for understanding. */
scope?: MediaUnderstandingScopeConfig; /** Default max bytes to send. */
maxBytes?: number; /** Default max output characters. */
maxChars?: number; /** Default prompt. */
prompt?: string; /** Default timeout (seconds). */
timeoutSeconds?: number; /** Default language hint (audio). */
language?: string; /** Optional provider-specific query params (merged into requests). */
providerOptions?: Record<string, Record<string, string | number | boolean>>; /** @deprecated Use providerOptions.deepgram instead. */
deepgram?: {
detectLanguage?: boolean;
punctuate?: boolean;
smartFormat?: boolean;
}; /** Optional base URL override for provider requests. */
baseUrl?: string; /** Optional headers merged into provider requests. */
headers?: Record<string, string>; /** Attachment selection policy. */
attachments?: MediaUnderstandingAttachmentsConfig; /** Ordered model list (fallbacks in order). */
models?: MediaUnderstandingModelConfig[];
};
type LinkModelConfig = {
/** Use a CLI command for link processing. */type?: "cli"; /** CLI binary (required when type=cli). */
command: string; /** CLI args (template-enabled). */
args?: string[]; /** Optional timeout override (seconds) for this model entry. */
timeoutSeconds?: number;
};
type LinkToolsConfig = {
/** Enable link understanding when models are configured. */enabled?: boolean; /** Optional scope gating for understanding. */
scope?: MediaUnderstandingScopeConfig; /** Max number of links to process per message. */
maxLinks?: number; /** Default timeout (seconds). */
timeoutSeconds?: number; /** Ordered model list (fallbacks in order). */
models?: LinkModelConfig[];
};
type MediaToolsConfig = {
/** Shared model list applied across image/audio/video. */models?: MediaUnderstandingModelConfig[]; /** Max concurrent media understanding runs. */
concurrency?: number;
image?: MediaUnderstandingConfig;
audio?: MediaUnderstandingConfig;
video?: MediaUnderstandingConfig;
};
type ToolProfileId = "minimal" | "coding" | "messaging" | "full";
type ToolPolicyConfig = {
allow?: string[];
/**
* Additional allowlist entries merged into the effective allowlist.
*
* Intended for additive configuration (e.g., "also allow lobster") without forcing
* users to replace/duplicate an existing allowlist or profile.
*/
alsoAllow?: string[];
deny?: string[];
profile?: ToolProfileId;
};
type GroupToolPolicyConfig = {
allow?: string[]; /** Additional allowlist entries merged into allow. */
alsoAllow?: string[];
deny?: string[];
};
type GroupToolPolicyBySenderConfig = Record<string, GroupToolPolicyConfig>;
type ExecToolConfig = {
/** Exec host routing (default: sandbox). */host?: "sandbox" | "gateway" | "node"; /** Exec security mode (default: deny). */
security?: "deny" | "allowlist" | "full"; /** Exec ask mode (default: on-miss). */
ask?: "off" | "on-miss" | "always"; /** Default node binding for exec.host=node (node id/name). */
node?: string; /** Directories to prepend to PATH when running exec (gateway/sandbox). */
pathPrepend?: string[]; /** Safe stdin-only binaries that can run without allowlist entries. */
safeBins?: string[]; /** Default time (ms) before an exec command auto-backgrounds. */
backgroundMs?: number; /** Default timeout (seconds) before auto-killing exec commands. */
timeoutSec?: number; /** Emit a running notice (ms) when approval-backed exec runs long (default: 10000, 0 = off). */
approvalRunningNoticeMs?: number; /** How long to keep finished sessions in memory (ms). */
cleanupMs?: number; /** Emit a system event and heartbeat when a backgrounded exec exits. */
notifyOnExit?: boolean; /** apply_patch subtool configuration (experimental). */
applyPatch?: {
/** Enable apply_patch for OpenAI models (default: false). */enabled?: boolean;
/**
* Optional allowlist of model ids that can use apply_patch.
* Accepts either raw ids (e.g. "gpt-5.2") or full ids (e.g. "openai/gpt-5.2").
*/
allowModels?: string[];
};
};
type AgentToolsConfig = {
/** Base tool profile applied before allow/deny lists. */profile?: ToolProfileId;
allow?: string[]; /** Additional allowlist entries merged into allow and/or profile allowlist. */
alsoAllow?: string[];
deny?: string[]; /** Optional tool policy overrides keyed by provider id or "provider/model". */
byProvider?: Record<string, ToolPolicyConfig>; /** Per-agent elevated exec gate (can only further restrict global tools.elevated). */
elevated?: {
/** Enable or disable elevated mode for this agent (default: true). */enabled?: boolean; /** Approved senders for /elevated (per-provider allowlists). */
allowFrom?: AgentElevatedAllowFromConfig;
}; /** Exec tool defaults for this agent. */
exec?: ExecToolConfig;
sandbox?: {
tools?: {
allow?: string[];
deny?: string[];
};
};
};
type MemorySearchConfig = {
/** Enable vector memory search (default: true). */enabled?: boolean; /** Sources to index and search (default: ["memory"]). */
sources?: Array<"memory" | "sessions">; /** Extra paths to include in memory search (directories or .md files). */
extraPaths?: string[]; /** Experimental memory search settings. */
experimental?: {
/** Enable session transcript indexing (experimental, default: false). */sessionMemory?: boolean;
}; /** Embedding provider mode. */
provider?: "openai" | "gemini" | "local";
remote?: {
baseUrl?: string;
apiKey?: string;
headers?: Record<string, string>;
batch?: {
/** Enable batch API for embedding indexing (OpenAI/Gemini; default: true). */enabled?: boolean; /** Wait for batch completion (default: true). */
wait?: boolean; /** Max concurrent batch jobs (default: 2). */
concurrency?: number; /** Poll interval in ms (default: 5000). */
pollIntervalMs?: number; /** Timeout in minutes (default: 60). */
timeoutMinutes?: number;
};
}; /** Fallback behavior when embeddings fail. */
fallback?: "openai" | "gemini" | "local" | "none"; /** Embedding model id (remote) or alias (local). */
model?: string; /** Local embedding settings (node-llama-cpp). */
local?: {
/** GGUF model path or hf: URI. */modelPath?: string; /** Optional cache directory for local models. */
modelCacheDir?: string;
}; /** Index storage configuration. */
store?: {
driver?: "sqlite";
path?: string;
vector?: {
/** Enable sqlite-vec extension for vector search (default: true). */enabled?: boolean; /** Optional override path to sqlite-vec extension (.dylib/.so/.dll). */
extensionPath?: string;
};
cache?: {
/** Enable embedding cache (default: true). */enabled?: boolean; /** Optional max cache entries per provider/model. */
maxEntries?: number;
};
}; /** Chunking configuration. */
chunking?: {
tokens?: number;
overlap?: number;
}; /** Sync behavior. */
sync?: {
onSessionStart?: boolean;
onSearch?: boolean;
watch?: boolean;
watchDebounceMs?: number;
intervalMinutes?: number;
sessions?: {
/** Minimum appended bytes before session transcripts are reindexed. */deltaBytes?: number; /** Minimum appended JSONL lines before session transcripts are reindexed. */
deltaMessages?: number;
};
}; /** Query behavior. */
query?: {
maxResults?: number;
minScore?: number;
hybrid?: {
/** Enable hybrid BM25 + vector search (default: true). */enabled?: boolean; /** Weight for vector similarity when merging results (0-1). */
vectorWeight?: number; /** Weight for BM25 text relevance when merging results (0-1). */
textWeight?: number; /** Multiplier for candidate pool size (default: 4). */
candidateMultiplier?: number;
};
}; /** Index cache behavior. */
cache?: {
/** Cache chunk embeddings in SQLite (default: true). */enabled?: boolean; /** Optional cap on cached embeddings (best-effort). */
maxEntries?: number;
};
};
type ToolsConfig = {
/** Base tool profile applied before allow/deny lists. */profile?: ToolProfileId;
allow?: string[]; /** Additional allowlist entries merged into allow and/or profile allowlist. */
alsoAllow?: string[];
deny?: string[]; /** Optional tool policy overrides keyed by provider id or "provider/model". */
byProvider?: Record<string, ToolPolicyConfig>;
web?: {
search?: {
/** Enable web search tool (default: true when API key is present). */enabled?: boolean; /** Search provider ("brave" or "perplexity"). */
provider?: "brave" | "perplexity"; /** Brave Search API key (optional; defaults to BRAVE_API_KEY env var). */
apiKey?: string; /** Default search results count (1-10). */
maxResults?: number; /** Timeout in seconds for search requests. */
timeoutSeconds?: number; /** Cache TTL in minutes for search results. */
cacheTtlMinutes?: number; /** Perplexity-specific configuration (used when provider="perplexity"). */
perplexity?: {
/** API key for Perplexity or OpenRouter (defaults to PERPLEXITY_API_KEY or OPENROUTER_API_KEY env var). */apiKey?: string; /** Base URL for API requests (defaults to OpenRouter: https://openrouter.ai/api/v1). */
baseUrl?: string; /** Model to use (defaults to "perplexity/sonar-pro"). */
model?: string;
};
};
fetch?: {
/** Enable web fetch tool (default: true). */enabled?: boolean; /** Max characters to return from fetched content. */
maxChars?: number; /** Hard cap for maxChars (tool or config), defaults to 50000. */
maxCharsCap?: number; /** Timeout in seconds for fetch requests. */
timeoutSeconds?: number; /** Cache TTL in minutes for fetched content. */
cacheTtlMinutes?: number; /** Maximum number of redirects to follow (default: 3). */
maxRedirects?: number; /** Override User-Agent header for fetch requests. */
userAgent?: string; /** Use Readability to extract main content (default: true). */
readability?: boolean;
firecrawl?: {
/** Enable Firecrawl fallback (default: true when apiKey is set). */enabled?: boolean; /** Firecrawl API key (optional; defaults to FIRECRAWL_API_KEY env var). */
apiKey?: string; /** Firecrawl base URL (default: https://api.firecrawl.dev). */
baseUrl?: string; /** Whether to keep only main content (default: true). */
onlyMainContent?: boolean; /** Max age (ms) for cached Firecrawl content. */
maxAgeMs?: number; /** Timeout in seconds for Firecrawl requests. */
timeoutSeconds?: number;
};
};
};
media?: MediaToolsConfig;
links?: LinkToolsConfig; /** Message tool configuration. */
message?: {
/**
* @deprecated Use tools.message.crossContext settings.
* Allows cross-context sends across providers.
*/
allowCrossContextSend?: boolean;
crossContext?: {
/** Allow sends to other channels within the same provider (default: true). */allowWithinProvider?: boolean; /** Allow sends across different providers (default: false). */
allowAcrossProviders?: boolean; /** Cross-context marker configuration. */
marker?: {
/** Enable origin markers for cross-context sends (default: true). */enabled?: boolean; /** Text prefix template, supports {channel}. */
prefix?: string; /** Text suffix template, supports {channel}. */
suffix?: string;
};
};
broadcast?: {
/** Enable broadcast action (default: true). */enabled?: boolean;
};
};
agentToAgent?: {
/** Enable agent-to-agent messaging tools. Default: false. */enabled?: boolean; /** Allowlist of agent ids or patterns (implementation-defined). */
allow?: string[];
}; /** Elevated exec permissions for the host machine. */
elevated?: {
/** Enable or disable elevated mode (default: true). */enabled?: boolean; /** Approved senders for /elevated (per-provider allowlists). */
allowFrom?: AgentElevatedAllowFromConfig;
}; /** Exec tool defaults. */
exec?: ExecToolConfig; /** Sub-agent tool policy defaults (deny wins). */
subagents?: {
/** Default model selection for spawned sub-agents (string or {primary,fallbacks}). */model?: string | {
primary?: string;
fallbacks?: string[];
};
tools?: {
allow?: string[];
deny?: string[];
};
}; /** Sandbox tool policy defaults (deny wins). */
sandbox?: {
tools?: {
allow?: string[];
deny?: string[];
};
};
};
//#endregion
//#region src/config/types.agent-defaults.d.ts
type AgentModelEntryConfig = {
alias?: string; /** Provider-specific API parameters (e.g., GLM-4.7 thinking mode). */
params?: Record<string, unknown>;
};
type AgentModelListConfig = {
primary?: string;
fallbacks?: string[];
};
type AgentContextPruningConfig = {
mode?: "off" | "cache-ttl"; /** TTL to consider cache expired (duration string, default unit: minutes). */
ttl?: string;
keepLastAssistants?: number;
softTrimRatio?: number;
hardClearRatio?: number;
minPrunableToolChars?: number;
tools?: {
allow?: string[];
deny?: string[];
};
softTrim?: {
maxChars?: number;
headChars?: number;
tailChars?: number;
};
hardClear?: {
enabled?: boolean;
placeholder?: string;
};
};
type CliBackendConfig = {
/** CLI command to execute (absolute path or on PATH). */command: string; /** Base args applied to every invocation. */
args?: string[]; /** Output parsing mode (default: json). */
output?: "json" | "text" | "jsonl"; /** Output parsing mode when resuming a CLI session. */
resumeOutput?: "json" | "text" | "jsonl"; /** Prompt input mode (default: arg). */
input?: "arg" | "stdin"; /** Max prompt length for arg mode (if exceeded, stdin is used). */
maxPromptArgChars?: number; /** Extra env vars injected for this CLI. */
env?: Record<string, string>; /** Env vars to remove before launching this CLI. */
clearEnv?: string[]; /** Flag used to pass model id (e.g. --model). */
modelArg?: string; /** Model aliases mapping (config model id → CLI model id). */
modelAliases?: Record<string, string>; /** Flag used to pass session id (e.g. --session-id). */
sessionArg?: string; /** Extra args used when resuming a session (use {sessionId} placeholder). */
sessionArgs?: string[]; /** Alternate args to use when resuming a session (use {sessionId} placeholder). */
resumeArgs?: string[]; /** When to pass session ids. */
sessionMode?: "always" | "existing" | "none"; /** JSON fields to read session id from (in order). */
sessionIdFields?: string[]; /** Flag used to pass system prompt. */
systemPromptArg?: string; /** System prompt behavior (append vs replace). */
systemPromptMode?: "append" | "replace"; /** When to send system prompt. */
systemPromptWhen?: "first" | "always" | "never"; /** Flag used to pass image paths. */
imageArg?: string; /** How to pass multiple images. */
imageMode?: "repeat" | "list"; /** Serialize runs for this CLI. */
serialize?: boolean;
};
type AgentDefaultsConfig = {
/** Primary model and fallbacks (provider/model). */model?: AgentModelListConfig; /** Optional image-capable model and fallbacks (provider/model). */
imageModel?: AgentModelListConfig; /** Model catalog with optional aliases (full provider/model keys). */
models?: Record<string, AgentModelEntryConfig>; /** Agent working directory (preferred). Used as the default cwd for agent runs. */
workspace?: string; /** Optional repository root for system prompt runtime line (overrides auto-detect). */
repoRoot?: string; /** Skip bootstrap (BOOTSTRAP.md creation, etc.) for pre-configured deployments. */
skipBootstrap?: boolean; /** Max chars for injected bootstrap files before truncation (default: 20000). */
bootstrapMaxChars?: number; /** Optional IANA timezone for the user (used in system prompt; defaults to host timezone). */
userTimezone?: string; /** Time format in system prompt: auto (OS preference), 12-hour, or 24-hour. */
timeFormat?: "auto" | "12" | "24";
/**
* Envelope timestamp timezone: "utc" (default), "local", "user", or an IANA timezone string.
*/
envelopeTimezone?: string;
/**
* Include absolute timestamps in message envelopes ("on" | "off", default: "on").
*/
envelopeTimestamp?: "on" | "off";
/**
* Include elapsed time in message envelopes ("on" | "off", default: "on").
*/
envelopeElapsed?: "on" | "off"; /** Optional context window cap (used for runtime estimates + status %). */
contextTokens?: number; /** Optional CLI backends for text-only fallback (claude-cli, etc.). */
cliBackends?: Record<string, CliBackendConfig>; /** Opt-in: prune old tool results from the LLM context to reduce token usage. */
contextPruning?: AgentContextPruningConfig; /** Compaction tuning and pre-compaction memory flush behavior. */
compaction?: AgentCompactionConfig; /** Vector memory search configuration (per-agent overrides supported). */
memorySearch?: MemorySearchConfig; /** Default thinking level when no /think directive is present. */
thinkingDefault?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh"; /** Default verbose level when no /verbose directive is present. */
verboseDefault?: "off" | "on" | "full"; /** Default elevated level when no /elevated directive is present. */
elevatedDefault?: "off" | "on" | "ask" | "full"; /** Default block streaming level when no override is present. */
blockStreamingDefault?: "off" | "on";
/**
* Block streaming boundary:
* - "text_end": end of each assistant text content block (before tool calls)
* - "message_end": end of the whole assistant message (may include tool blocks)
*/
blockStreamingBreak?: "text_end" | "message_end"; /** Soft block chunking for streamed replies (min/max chars, prefer paragraph/newline). */
blockStreamingChunk?: BlockStreamingChunkConfig;
/**
* Block reply coalescing (merge streamed chunks before send).
* idleMs: wait time before flushing when idle.
*/
blockStreamingCoalesce?: BlockStreamingCoalesceConfig; /** Human-like delay between block replies. */
humanDelay?: HumanDelayConfig;
timeoutSeconds?: number; /** Max inbound media size in MB for agent-visible attachments (text note or future image attach). */
mediaMaxMb?: number;
typingIntervalSeconds?: number; /** Typing indicator start mode (never|instant|thinking|message). */
typingMode?: TypingMode; /** Periodic background heartbeat runs. */
heartbeat?: {
/** Heartbeat interval (duration string, default unit: minutes; default: 30m). */every?: string; /** Optional active-hours window (local time); heartbeats run only inside this window. */
activeHours?: {
/** Start time (24h, HH:MM). Inclusive. */start?: string; /** End time (24h, HH:MM). Exclusive. Use "24:00" for end-of-day. */
end?: string; /** Timezone for the window ("user", "local", or IANA TZ id). Default: "user". */
timezone?: string;
}; /** Heartbeat model override (provider/model). */
model?: string; /** Session key for heartbeat runs ("main" or explicit session key). */
session?: string; /** Delivery target ("last", "none", or a channel id). */
target?: "last" | "none" | ChannelId; /** Optional delivery override (E.164 for WhatsApp, chat id for Telegram). */
to?: string; /** Override the heartbeat prompt body (default: "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."). */
prompt?: string; /** Max chars allowed after HEARTBEAT_OK before delivery (default: 30). */
ackMaxChars?: number;
/**
* When enabled, deliver the model's reasoning payload for heartbeat runs (when available)
* as a separate message prefixed with `Reasoning:` (same as `/reasoning on`).
*
* Default: false (only the final heartbeat payload is delivered).
*/
includeReasoning?: boolean;
}; /** Max concurrent agent runs across all conversations. Default: 1 (sequential). */
maxConcurrent?: number; /** Sub-agent defaults (spawned via sessions_spawn). */
subagents?: {
/** Max concurrent sub-agent runs (global lane: "subagent"). Default: 1. */maxConcurrent?: number; /** Auto-archive sub-agent sessions after N minutes (default: 60). */
archiveAfterMinutes?: number; /** Default model selection for spawned sub-agents (string or {primary,fallbacks}). */
model?: string | {
primary?: string;
fallbacks?: string[];
}; /** Default thinking level for spawned sub-agents (e.g. "off", "low", "medium", "high"). */
thinking?: string;
}; /** Optional sandbox settings for non-main sessions. */
sandbox?: {
/** Enable sandboxing for sessions. */mode?: "off" | "non-main" | "all";
/**
* Agent workspace access inside the sandbox.
* - "none": do not mount the agent workspace into the container; use a sandbox workspace under workspaceRoot
* - "ro": mount the agent workspace read-only; disables write/edit tools
* - "rw": mount the agent workspace read/write; enables write/edit tools
*/
workspaceAccess?: "none" | "ro" | "rw";
/**
* Session tools visibility for sandboxed sessions.
* - "spawned": only allow session tools to target sessions spawned from this session (default)
* - "all": allow session tools to target any session
*/
sessionToolsVisibility?: "spawned" | "all"; /** Container/workspace scope for sandbox isolation. */
scope?: "session" | "agent" | "shared"; /** Legacy alias for scope ("session" when true, "shared" when false). */
perSession?: boolean; /** Root directory for sandbox workspaces. */
workspaceRoot?: string; /** Docker-specific sandbox settings. */
docker?: SandboxDockerSettings; /** Optional sandboxed browser settings. */
browser?: SandboxBrowserSettings; /** Auto-prune sandbox containers. */
prune?: SandboxPruneSettings;
};
};
type AgentCompactionMode = "default" | "safeguard";
type AgentCompactionConfig = {
/** Compaction summarization mode. */mode?: AgentCompactionMode; /** Minimum reserve tokens enforced for Pi compaction (0 disables the floor). */
reserveTokensFloor?: number; /** Max share of context window for history during safeguard pruning (0.1–0.9, default 0.5). */
maxHistoryShare?: number; /** Pre-compaction memory flush (agentic turn). Default: enabled. */
memoryFlush?: AgentCompactionMemoryFlushConfig;
};
type AgentCompactionMemoryFlushConfig = {
/** Enable the pre-compaction memory flush (default: true). */enabled?: boolean; /** Run the memory flush when context is within this many tokens of the compaction threshold. */
softThresholdTokens?: number; /** User prompt used for the memory flush turn (NO_REPLY is enforced if missing). */
prompt?: string; /** System prompt appended for the memory flush turn. */
systemPrompt?: string;
};
//#endregion
//#region src/config/types.queue.d.ts
type QueueMode = "steer" | "followup" | "collect" | "steer-backlog" | "steer+backlog" | "queue" | "interrupt";
type QueueDropPolicy = "old" | "new" | "summarize";
type QueueModeByProvider = {
whatsapp?: QueueMode;
telegram?: QueueMode;
discord?: QueueMode;
googlechat?: QueueMode;
slack?: QueueMode;
signal?: QueueMode;
imessage?: QueueMode;
msteams?: QueueMode;
webchat?: QueueMode;
};
//#endregion
//#region src/config/types.tts.d.ts
type TtsProvider = "elevenlabs" | "openai" | "edge";
type TtsMode = "final" | "all";
type TtsAutoMode = "off" | "always" | "inbound" | "tagged";
type TtsModelOverrideConfig = {
/** Enable model-provided overrides for TTS. */enabled?: boolean; /** Allow model-provided TTS text blocks. */
allowText?: boolean; /** Allow model-provided provider override. */
allowProvider?: boolean; /** Allow model-provided voice/voiceId override. */
allowVoice?: boolean; /** Allow model-provided modelId override. */
allowModelId?: boolean; /** Allow model-provided voice settings override. */
allowVoiceSettings?: boolean; /** Allow model-provided normalization or language overrides. */
allowNormalization?: boolean; /** Allow model-provided seed override. */
allowSeed?: boolean;
};
type TtsConfig = {
/** Auto-TTS mode (preferred). */auto?: TtsAutoMode; /** Legacy: enable auto-TTS when `auto` is not set. */
enabled?: boolean; /** Apply TTS to final replies only or to all replies (tool/block/final). */
mode?: TtsMode; /** Primary TTS provider (fallbacks are automatic). */
provider?: TtsProvider; /** Optional model override for TTS auto-summary (provider/model or alias). */
summaryModel?: string; /** Allow the model to override TTS parameters. */
modelOverrides?: TtsModelOverrideConfig; /** ElevenLabs configuration. */
elevenlabs?: {
apiKey?: string;
baseUrl?: string;
voiceId?: string;
modelId?: string;
seed?: number;
applyTextNormalization?: "auto" | "on" | "off";
languageCode?: string;
voiceSettings?: {
stability?: number;
similarityBoost?: number;
style?: number;
useSpeakerBoost?: boolean;
speed?: number;
};
}; /** OpenAI configuration. */
openai?: {
apiKey?: string;
model?: string;
voice?: string;
}; /** Microsoft Edge (node-edge-tts) configuration. */
edge?: {
/** Explicitly allow Edge TTS usage (no API key required). */enabled?: boolean;
voice?: string;
lang?: string;
outputFormat?: string;
pitch?: string;
rate?: string;
volume?: string;
saveSubtitles?: boolean;
proxy?: string;
timeoutMs?: number;
}; /** Optional path for local TTS user preferences JSON. */
prefsPath?: string; /** Hard cap for text sent to TTS (chars). */
maxTextLength?: number; /** API request timeout (ms). */
timeoutMs?: number;
};
//#endregion
//#region src/config/types.messages.d.ts
type GroupChatConfig = {
mentionPatterns?: string[];
historyLimit?: number;
};
type DmConfig = {
historyLimit?: number;
};
type QueueConfig = {
mode?: QueueMode;
byChannel?: QueueModeByProvider;
debounceMs?: number; /** Per-channel debounce overrides (ms). */
debounceMsByChannel?: InboundDebounceByProvider;
cap?: number;
drop?: QueueDropPolicy;
};
type InboundDebounceByProvider = Record<string, number>;
type InboundDebounceConfig = {
debounceMs?: number;
byChannel?: InboundDebounceByProvider;
};
type BroadcastStrategy = "parallel" | "sequential";
type BroadcastConfig = {
/** Default processing strategy for broadcast peers. */strategy?: BroadcastStrategy;
/**
* Map peer IDs to arrays of agent IDs that should ALL process messages.
*
* Note: the index signature includes `undefined` so `strategy?: ...` remains type-safe.
*/
[peerId: string]: string[] | BroadcastStrategy | undefined;
};
type AudioConfig = {
/** @deprecated Use tools.media.audio.models instead. */transcription?: {
command: string[];
timeoutSeconds?: number;
};
};
type MessagesConfig = {
/** @deprecated Use `whatsapp.messagePrefix` (WhatsApp-only inbound prefix). */messagePrefix?: string;
/**
* Prefix auto-added to all outbound replies.
*
* - string: explicit prefix (may include template variables)
* - special value: `"auto"` derives `[{agents.list[].identity.name}]` for the routed agent (when set)
*
* Supported template variables (case-insensitive):
* - `{model}` - short model name (e.g., `claude-opus-4-5`, `gpt-4o`)
* - `{modelFull}` - full model identifier (e.g., `anthropic/claude-opus-4-5`)
* - `{provider}` - provider name (e.g., `anthropic`, `openai`)
* - `{thinkingLevel}` or `{think}` - current thinking level (`high`, `low`, `off`)
* - `{identity.name}` or `{identityName}` - agent identity name
*
* Example: `"[{model} | think:{thinkingLevel}]"` → `"[claude-opus-4-5 | think:high]"`
*
* Unresolved variables remain as literal text (e.g., `{model}` if context unavailable).
*
* Default: none
*/
responsePrefix?: string;
groupChat?: GroupChatConfig;
queue?: QueueConfig; /** Debounce rapid inbound messages per sender (global + per-channel overrides). */
inbound?: InboundDebounceConfig; /** Emoji reaction used to acknowledge inbound messages (empty disables). */
ackReaction?: string; /** When to send ack reactions. Default: "group-mentions". */
ackReactionScope?: "group-mentions" | "group-all" | "direct" | "all"; /** Remove ack reaction after reply is sent (default: false). */
removeAckAfterReply?: boolean; /** Text-to-speech settings for outbound replies. */
tts?: TtsConfig;
};
type NativeCommandsSetting = boolean | "auto";
type CommandsConfig = {
/** Enable native command registration when supported (default: "auto"). */native?: NativeCommandsSetting; /** Enable native skill command registration when supported (default: "auto"). */
nativeSkills?: NativeCommandsSetting; /** Enable text command parsing (default: true). */
text?: boolean; /** Allow bash chat command (`!`; `/bash` alias) (default: false). */
bash?: boolean; /** How long bash waits before backgrounding (default: 2000; 0 backgrounds immediately). */
bashForegroundMs?: number; /** Allow /config command (default: false). */
config?: boolean; /** Allow /debug command (default: false). */
debug?: boolean; /** Allow restart commands/tools (default: false). */
restart?: boolean; /** Enforce access-group allowlists/policies for commands (default: true). */
useAccessGroups?: boolean;
};
type ProviderCommandsConfig = {
/** Override native command registration for this provider (bool or "auto"). */native?: NativeCommandsSetting; /** Override native skill command registration for this provider (bool or "auto"). */
nativeSkills?: NativeCommandsSetting;
};
//#endregion
//#region src/config/types.agents.d.ts
type AgentModelConfig = string | {
/** Primary model (provider/model). */primary?: string; /** Per-agent model fallbacks (provider/model). */
fallbacks?: string[];
};
type AgentConfig = {
id: string;
default?: boolean;
name?: string;
workspace?: string;
agentDir?: string;
model?: AgentModelConfig; /** Optional allowlist of skills for this agent (omit = all skills; empty = none). */
skills?: string[];
memorySearch?: MemorySearchConfig; /** Human-like delay between block replies for this agent. */
humanDelay?: HumanDelayConfig; /** Optional per-agent heartbeat overrides. */
heartbeat?: AgentDefaultsConfig["heartbeat"];
identity?: IdentityConfig;
groupChat?: GroupChatConfig;
subagents?: {
/** Allow spawning sub-agents under other agent ids. Use "*" to allow any. */allowAgents?: string[]; /** Per-agent default model for spawned sub-agents (string or {primary,fallbacks}). */
model?: string | {
primary?: string;
fallbacks?: string[];
};
};
sandbox?: {
mode?: "off" | "non-main" | "all"; /** Agent workspace access inside the sandbox. */
workspaceAccess?: "none" | "ro" | "rw";
/**
* Session tools visibility for sandboxed sessions.
* - "spawned": only allow session tools to target sessions spawned from this session (default)
* - "all": allow session tools to target any session
*/
sessionToolsVisibility?: "spawned" | "all"; /** Container/workspace scope for sandbox isolation. */
scope?: "session" | "agent" | "shared"; /** Legacy alias for scope ("session" when true, "shared" when false). */
perSession?: boolean;
workspaceRoot?: string; /** Docker-specific sandbox overrides for this agent. */
docker?: SandboxDockerSettings; /** Optional sandboxed browser overrides for this agent. */
browser?: SandboxBrowserSettings; /** Auto-prune overrides for this agent. */
prune?: SandboxPruneSettings;
};
tools?: AgentToolsConfig;
};
type AgentsConfig = {
defaults?: AgentDefaultsConfig;
list?: AgentConfig[];
};
type AgentBinding = {
agentId: string;
match: {
channel: string;
accountId?: string;
peer?: {
kind: "dm" | "group" | "channel";
id: string;
};
guildId?: string;
teamId?: string;
};
};
//#endregion
//#region src/config/types.approvals.d.ts
type ExecApprovalForwardingMode = "session" | "targets" | "both";
type ExecApprovalForwardTarget = {
/** Channel id (e.g. "discord", "slack", or plugin channel id). */channel: string; /** Destination id (channel id, user id, etc. depending on channel). */
to: string; /** Optional account id for multi-account channels. */
accountId?: string; /** Optional thread id to reply inside a thread. */
threadId?: string | number;
};
type ExecApprovalForwardingConfig = {
/** Enable forwarding exec approvals to chat channels. Default: false. */enabled?: boolean; /** Delivery mode (session=origin chat, targets=config targets, both=both). Default: session. */
mode?: ExecApprovalForwardingMode; /** Only forward approvals for these agent IDs. Omit = all agents. */
agentFilter?: string[]; /** Only forward approvals matching these session key patterns (substring or regex). */
sessionFilter?: string[]; /** Explicit delivery targets (used when mode includes targets). */
targets?: ExecApprovalForwardTarget[];
};
type ApprovalsConfig = {
exec?: ExecApprovalForwardingConfig;
};
//#endregion
//#region src/config/types.auth.d.ts
type AuthProfileConfig = {
provider: string;
/**
* Credential type expected in auth-profiles.json for this profile id.
* - api_key: static provider API key
* - oauth: refreshable OAuth crede