import { v as parseStrictInteger } from "../../number-coercion-CJQ8TR--.js";
import { l as normalizeStringEntries } from "../../string-normalization-WNUDCpXX.js";
import { s as hasConfiguredSecretInput, u as normalizeResolvedSecretInputString } from "../../types.secrets-_0JOMGE5.js";
import { r as normalizeOptionalAccountId, t as DEFAULT_ACCOUNT_ID } from "../../account-id-Df9e41E6.js";
import { At as boolean, Nn as record, Rn as string, Tn as object, wn as number } from "../../schemas-6cH6bZ7o.js";
import { o as DmPolicySchema, z as requireOpenAllowFrom } from "../../zod-schema.core-DVJoUqmJ.js";
import { r as buildChannelConfigSchema, t as AllowFromListSchema } from "../../config-schema-CksfghjU.js";
import { r as fetchWithSsrFGuard } from "../../fetch-guard-CQ_cMV3y.js";
import { t as resolveAccountEntry } from "../../account-lookup-DL1YTqjF.js";
import { o as resolveStableChannelMessageIngress } from "../../runtime-CoNC7Xrb.js";
import { t as createMessageReceiptFromOutboundResults } from "../../receipt-BdiTEyRN.js";
import { i as createHybridChannelConfigAdapter, l as createScopedDmSecurityResolver } from "../../channel-config-helpers-CArQsBke.js";
import "../../number-runtime-DBLVDypr.js";
import "../../string-coerce-runtime-CEGJWkQ_.js";
import { n as stripMarkdown } from "../../chunk-items-HPRB2OIa.js";
import { t as chunkTextForOutbound } from "../../text-chunking-D45TeeEs.js";
import { a as listCombinedAccountIds, c as resolveMergedAccountConfig, s as resolveListedDefaultAccountId } from "../../account-helpers-DkK7P7YF.js";
import { i as createChatChannelPlugin } from "../../core-FbtVsADa.js";
import "../../channel-core-Bhi3OVCA.js";
import { r as buildSecretInputSchema } from "../../secret-input-pjGhJN46.js";
import { a as waitUntilAbort } from "../../channel-lifecycle.core-CDt0hRZR.js";
import { b as createConditionalWarningCollector } from "../../channel-policy-DZN9UFBL.js";
import { n as createEmptyChannelDirectoryAdapter } from "../../directory-runtime-C7pcflZE.js";
import { w as defineChannelMessageAdapter } from "../../channel-outbound-Ccv8B-AA.js";
import { s as readRequestBodyWithLimit } from "../../http-body-DJhknSPN.js";
import "../../ssrf-runtime-DCwTmrya.js";
import { t as registerPluginHttpRoute } from "../../http-registry-C-uN7McO.js";
import "../../account-resolution-DBB0e3KP.js";
import { f as requireChannelOpenAllowFrom } from "../../extension-shared-ldTWHIea.js";
import "../../channel-config-primitives-CaSvPEZE.js";
import "../../channel-ingress-runtime-mXYYiFY4.js";
import { t as createChannelPairingChallengeIssuer } from "../../channel-pairing-B7DlhBLM.js";
import { a as createFixedWindowRateLimiter } from "../../webhook-ingress-D2y3cVI3.js";
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "../../secret-contract-GlYzl__a.js";
import { createHmac, timingSafeEqual } from "node:crypto";
import * as querystring from "node:querystring";
//#region extensions/sms/src/phone.ts
function normalizeSmsPhoneNumber(raw) {
	const trimmed = raw.trim().replace(/^(?:sms|twilio-sms):/i, "");
	if (!trimmed) return "";
	return (trimmed.startsWith("+") ? trimmed : `+${trimmed}`).replace(/[^\d+]/g, "");
}
function looksLikeSmsPhoneNumber(raw) {
	const normalized = normalizeSmsPhoneNumber(raw);
	return /^\+[1-9]\d{6,14}$/.test(normalized);
}
function normalizeSmsAllowFrom(raw) {
	if (raw.trim() === "*") return "*";
	return normalizeSmsPhoneNumber(raw).toLowerCase();
}
//#endregion
//#region extensions/sms/src/accounts.ts
const CHANNEL_ID$3 = "sms";
const DEFAULT_WEBHOOK_PATH = "/webhooks/sms";
const DEFAULT_TEXT_CHUNK_LIMIT = 1500;
function getChannelConfig(cfg) {
	return cfg?.channels?.[CHANNEL_ID$3];
}
function parseList(raw) {
	if (!raw) return [];
	return (Array.isArray(raw) ? raw : typeof raw === "string" ? normalizeStringEntries(raw.split(",")) : [raw]).map((entry) => normalizeSmsAllowFrom(String(entry))).filter(Boolean);
}
function parseTextChunkLimit(raw) {
	if (typeof raw === "number" && Number.isSafeInteger(raw) && raw > 0) return raw;
	if (typeof raw === "string" && /^\d+$/.test(raw.trim())) return parseStrictInteger(raw.trim()) ?? DEFAULT_TEXT_CHUNK_LIMIT;
	return DEFAULT_TEXT_CHUNK_LIMIT;
}
function firstNonBlankEnv(...values) {
	return values.find((value) => value?.trim());
}
function hasBaseAccount(channelCfg) {
	return Boolean(channelCfg?.accountSid || hasConfiguredSecretInput(channelCfg?.authToken) || channelCfg?.fromNumber || channelCfg?.messagingServiceSid || process.env.TWILIO_ACCOUNT_SID || process.env.TWILIO_AUTH_TOKEN || process.env.TWILIO_PHONE_NUMBER || process.env.TWILIO_SMS_FROM || process.env.TWILIO_MESSAGING_SERVICE_SID);
}
function listSmsAccountIds(cfg) {
	const channelCfg = getChannelConfig(cfg);
	return listCombinedAccountIds({
		configuredAccountIds: Object.keys(channelCfg?.accounts ?? {}),
		implicitAccountId: hasBaseAccount(channelCfg) ? DEFAULT_ACCOUNT_ID : void 0
	});
}
function resolveDefaultSmsAccountId(cfg) {
	const channelCfg = getChannelConfig(cfg);
	return resolveListedDefaultAccountId({
		accountIds: listSmsAccountIds(cfg),
		configuredDefaultAccountId: normalizeOptionalAccountId(channelCfg?.defaultAccount)
	});
}
function resolveSmsAccount(cfg, accountId) {
	const channelCfg = getChannelConfig(cfg) ?? {};
	const id = normalizeOptionalAccountId(accountId) ?? resolveDefaultSmsAccountId(cfg);
	const accountConfig = resolveAccountEntry(channelCfg.accounts, id);
	const merged = resolveMergedAccountConfig({
		channelConfig: { ...channelCfg },
		accounts: channelCfg.accounts ? Object.fromEntries(Object.entries(channelCfg.accounts).map(([accountKey, account]) => [accountKey, { ...account }])) : void 0,
		accountId: id,
		omitKeys: ["defaultAccount"]
	});
	const useEnvFallbacks = id === DEFAULT_ACCOUNT_ID;
	const envAccountSid = useEnvFallbacks ? process.env.TWILIO_ACCOUNT_SID : void 0;
	const envAuthToken = useEnvFallbacks ? process.env.TWILIO_AUTH_TOKEN : void 0;
	const envFromNumber = useEnvFallbacks ? firstNonBlankEnv(process.env.TWILIO_PHONE_NUMBER, process.env.TWILIO_SMS_FROM) : void 0;
	const envMessagingServiceSid = useEnvFallbacks ? process.env.TWILIO_MESSAGING_SERVICE_SID : void 0;
	const envWebhookPath = useEnvFallbacks ? process.env.SMS_WEBHOOK_PATH : void 0;
	const envPublicWebhookUrl = useEnvFallbacks ? process.env.SMS_PUBLIC_WEBHOOK_URL : void 0;
	const envAllowFrom = useEnvFallbacks ? process.env.SMS_ALLOWED_USERS : void 0;
	const envTextChunkLimit = useEnvFallbacks ? process.env.SMS_TEXT_CHUNK_LIMIT : void 0;
	const envDisableSignatureValidation = useEnvFallbacks ? process.env.SMS_DANGEROUSLY_DISABLE_SIGNATURE_VALIDATION : void 0;
	const webhookPath = (merged.webhookPath ?? envWebhookPath ?? DEFAULT_WEBHOOK_PATH).trim();
	const publicWebhookUrl = (merged.publicWebhookUrl ?? envPublicWebhookUrl ?? "").trim();
	const authToken = normalizeResolvedSecretInputString({
		value: merged.authToken ?? envAuthToken,
		path: id === "default" ? "channels.sms.authToken" : `channels.sms.accounts.${id}.authToken`
	}) ?? "";
	return {
		accountId: id,
		enabled: channelCfg.enabled !== false && accountConfig?.enabled !== false,
		accountSid: (merged.accountSid ?? envAccountSid ?? "").trim(),
		authToken,
		fromNumber: normalizeSmsPhoneNumber(merged.fromNumber ?? envFromNumber ?? ""),
		messagingServiceSid: (merged.messagingServiceSid ?? envMessagingServiceSid ?? "").trim(),
		defaultTo: normalizeSmsPhoneNumber(merged.defaultTo ?? ""),
		webhookPath: webhookPath || DEFAULT_WEBHOOK_PATH,
		publicWebhookUrl,
		dangerouslyDisableSignatureValidation: merged.dangerouslyDisableSignatureValidation === true || envDisableSignatureValidation === "true",
		dmPolicy: merged.dmPolicy ?? "pairing",
		allowFrom: parseList(merged.allowFrom ?? envAllowFrom),
		textChunkLimit: parseTextChunkLimit(merged.textChunkLimit ?? envTextChunkLimit)
	};
}
function inspectSmsAccount(cfg, accountId) {
	const account = resolveSmsAccount(cfg, accountId);
	const configured = isSmsAccountConfigured(account);
	return {
		enabled: account.enabled,
		configured,
		tokenStatus: account.authToken ? "available" : "missing",
		webhookPath: account.webhookPath,
		signatureValidation: account.dangerouslyDisableSignatureValidation || account.publicWebhookUrl ? "configured" : "missing-public-url"
	};
}
function isSmsAccountConfigured(account) {
	return Boolean(account.accountSid && account.authToken && (account.fromNumber || account.messagingServiceSid));
}
//#endregion
//#region extensions/sms/src/config-schema.ts
const SecretInputSchema = buildSecretInputSchema();
const SmsAccountConfigSchema = object({
	name: string().optional(),
	enabled: boolean().optional(),
	accountSid: string().optional(),
	authToken: SecretInputSchema.optional(),
	fromNumber: string().optional(),
	messagingServiceSid: string().optional(),
	defaultTo: string().optional(),
	webhookPath: string().optional(),
	publicWebhookUrl: string().optional(),
	dangerouslyDisableSignatureValidation: boolean().optional(),
	dmPolicy: DmPolicySchema.optional().default("pairing"),
	allowFrom: AllowFromListSchema,
	textChunkLimit: number().int().positive().optional()
}).strict().superRefine((value, ctx) => {
	requireChannelOpenAllowFrom({
		channel: "sms",
		policy: value.dmPolicy,
		allowFrom: value.allowFrom,
		ctx,
		requireOpenAllowFrom
	});
});
const SmsChannelConfigSchema = buildChannelConfigSchema(SmsAccountConfigSchema.extend({
	accounts: record(string(), SmsAccountConfigSchema.optional()).optional(),
	defaultAccount: string().optional()
}), { uiHints: {
	"": {
		label: "SMS",
		help: "Twilio SMS channel configuration for inbound webhooks and outbound text replies."
	},
	accountSid: {
		label: "Twilio Account SID",
		help: "Twilio Account SID used for SMS outbound API calls."
	},
	authToken: {
		label: "Twilio Auth Token",
		help: "Twilio Auth Token used to sign webhook validation and SMS outbound API calls."
	},
	fromNumber: {
		label: "SMS From Number",
		help: "Twilio SMS-capable phone number in E.164 format, for example +15551234567."
	},
	messagingServiceSid: {
		label: "Twilio Messaging Service SID",
		help: "Twilio Messaging Service SID to use instead of a dedicated fromNumber."
	},
	defaultTo: {
		label: "SMS Default To Number",
		help: "Optional default outbound phone number used when a send flow omits an explicit SMS target."
	},
	publicWebhookUrl: {
		label: "SMS Public Webhook URL",
		help: "Public URL configured in Twilio for incoming messages. Must match Twilio's signed URL exactly."
	},
	webhookPath: {
		label: "SMS Webhook Path",
		help: "Gateway HTTP path that receives Twilio incoming-message webhooks. Use a distinct path per account."
	},
	dmPolicy: {
		label: "SMS DM Policy",
		help: "Direct SMS access control (\"pairing\" recommended). \"open\" requires channels.sms.allowFrom=[\"*\"]."
	},
	allowFrom: {
		label: "SMS Allow From",
		help: "Allowed sender phone numbers in E.164 format, or * when dmPolicy is open."
	},
	textChunkLimit: {
		label: "SMS Text Chunk Limit",
		help: "Maximum characters per outbound SMS chunk before OpenClaw splits long replies."
	}
} });
//#endregion
//#region extensions/sms/src/twilio.ts
const TWILIO_ACCOUNTS_URL = "https://api.twilio.com/2010-04-01/Accounts";
const TWILIO_MESSAGING_URL = "https://messaging.twilio.com/v1";
const TWILIO_API_HOSTNAME = "api.twilio.com";
const TWILIO_MESSAGING_HOSTNAME = "messaging.twilio.com";
const TWILIO_API_TIMEOUT_MS = 3e4;
const WEBHOOK_BODY_LIMIT_BYTES = 32 * 1024;
const WEBHOOK_BODY_TIMEOUT_MS = 5e3;
function firstString(value) {
	if (Array.isArray(value)) return firstString(value[0]);
	return typeof value === "string" ? value : "";
}
function firstTrimmedString(value) {
	return firstString(value).trim();
}
function firstStringish(value) {
	const first = Array.isArray(value) ? value[0] : value;
	if (typeof first === "string") return first;
	return typeof first === "number" ? String(first) : "";
}
function parseTwilioApiError(text) {
	try {
		const parsed = JSON.parse(text);
		if (!parsed || typeof parsed !== "object") return {};
		const record = parsed;
		return {
			code: typeof record.code === "number" ? record.code : void 0,
			message: typeof record.message === "string" ? record.message : void 0
		};
	} catch {
		return {};
	}
}
function parseTwilioSuccessPayload(text) {
	if (!text.trim()) return {};
	try {
		const parsed = JSON.parse(text);
		if (!parsed || typeof parsed !== "object") throw new Error("Twilio SMS send returned malformed JSON.");
		const record = parsed;
		return {
			sid: typeof record.sid === "string" ? record.sid : void 0,
			to: typeof record.to === "string" ? record.to : void 0,
			from: typeof record.from === "string" ? record.from : void 0,
			status: typeof record.status === "string" ? record.status : void 0
		};
	} catch (cause) {
		if (cause instanceof Error && cause.message === "Twilio SMS send returned malformed JSON.") throw cause;
		throw new Error("Twilio SMS send returned malformed JSON.", { cause });
	}
}
function requestSearch(req) {
	try {
		return new URL(req.url ?? "/", "http://localhost").search;
	} catch {
		return "";
	}
}
function configuredUrlHasQuery(url) {
	const hashIndex = url.indexOf("#");
	return (hashIndex === -1 ? url : url.slice(0, hashIndex)).includes("?");
}
function resolveTwilioWebhookSignatureUrl(params) {
	if (configuredUrlHasQuery(params.publicWebhookUrl)) return params.publicWebhookUrl;
	const search = requestSearch(params.req);
	if (!search) return params.publicWebhookUrl;
	const hashIndex = params.publicWebhookUrl.indexOf("#");
	if (hashIndex === -1) return `${params.publicWebhookUrl}${search}`;
	return `${params.publicWebhookUrl.slice(0, hashIndex)}${search}${params.publicWebhookUrl.slice(hashIndex)}`;
}
var TwilioSmsApiError = class extends Error {
	constructor(httpStatus, responseText, operation = "send") {
		const parsed = parseTwilioApiError(responseText);
		const detail = parsed.message ?? (responseText || "unknown");
		super(`Twilio SMS ${operation} failed (${httpStatus}): ${detail}`);
		this.name = "TwilioSmsApiError";
		this.httpStatus = httpStatus;
		this.responseText = responseText;
		this.twilioCode = parsed.code;
	}
};
function parseTwilioFormBody(body) {
	const parsed = querystring.parse(body);
	const out = {};
	for (const [key, value] of Object.entries(parsed)) out[key] = firstString(value);
	return out;
}
function computeTwilioSignature(params) {
	const data = params.url + Object.keys(params.form).toSorted().map((key) => `${key}${params.form[key] ?? ""}`).join("");
	return createHmac("sha1", params.authToken).update(data).digest("base64");
}
function safeEqual(a, b) {
	const left = Buffer.from(a);
	const right = Buffer.from(b);
	return left.length === right.length && timingSafeEqual(left, right);
}
function verifyTwilioSignature(params) {
	if (!params.signature || !params.url || !params.authToken) return false;
	return safeEqual(params.signature, computeTwilioSignature({
		url: params.url,
		authToken: params.authToken,
		form: params.form
	}));
}
function buildTwilioInboundMessage(form) {
	const from = firstTrimmedString(form.From);
	const to = firstTrimmedString(form.To);
	const body = firstString(form.Body);
	const accountSid = firstTrimmedString(form.AccountSid);
	const messageSid = firstTrimmedString(form.MessageSid) || firstTrimmedString(form.SmsSid) || firstTrimmedString(form.SmsMessageSid);
	if (!from || !to || !body || !messageSid) return null;
	return {
		accountSid,
		from,
		to,
		body,
		messageSid
	};
}
async function readTwilioWebhookForm(req) {
	return parseTwilioFormBody(await readRequestBodyWithLimit(req, {
		maxBytes: WEBHOOK_BODY_LIMIT_BYTES,
		timeoutMs: WEBHOOK_BODY_TIMEOUT_MS
	}));
}
function respondTwiml(res, statusCode, body = "") {
	res.statusCode = statusCode;
	res.setHeader("content-type", "text/xml; charset=utf-8");
	res.end(body || "<Response></Response>");
}
function twilioApiUrl(accountSid, path, query) {
	const normalizedPath = path.startsWith("/") ? path : `/${path}`;
	const url = new URL(`${TWILIO_ACCOUNTS_URL}/${encodeURIComponent(accountSid)}${normalizedPath}`);
	if (query) url.search = query.toString();
	return url.toString();
}
function twilioMessagingUrl(path, query) {
	const normalizedPath = path.startsWith("/") ? path : `/${path}`;
	const url = new URL(`${TWILIO_MESSAGING_URL}${normalizedPath}`);
	if (query) url.search = query.toString();
	return url.toString();
}
function basicAuthHeader(account) {
	return `Basic ${Buffer.from(`${account.accountSid}:${account.authToken}`).toString("base64")}`;
}
function normalizeRequestHeaders(headers) {
	if (!headers) return {};
	if (headers instanceof Headers) return Object.fromEntries(headers.entries());
	if (Array.isArray(headers)) return Object.fromEntries(headers.map(([key, value]) => [key, value]));
	return Object.fromEntries(Object.entries(headers));
}
async function requestTwilioApi(params) {
	const init = {
		...params.init,
		headers: {
			...normalizeRequestHeaders(params.init?.headers),
			authorization: basicAuthHeader(params.account)
		}
	};
	if (params.fetchImpl) {
		const response = await params.fetchImpl(params.url, init);
		return {
			ok: response.ok,
			status: response.status,
			text: await response.text()
		};
	}
	const guarded = await fetchWithSsrFGuard({
		url: params.url,
		init,
		auditContext: "sms-twilio-api",
		policy: { allowedHostnames: [params.allowedHostname] },
		requireHttps: true,
		timeoutMs: params.timeoutMs ?? TWILIO_API_TIMEOUT_MS
	});
	try {
		return {
			ok: guarded.response.ok,
			status: guarded.response.status,
			text: await guarded.response.text()
		};
	} finally {
		await guarded.release();
	}
}
function parseTwilioIncomingPhoneNumber(record) {
	return {
		sid: firstTrimmedString(record.sid),
		phoneNumber: firstTrimmedString(record.phone_number ?? record.phoneNumber),
		smsUrl: firstTrimmedString(record.sms_url ?? record.smsUrl),
		smsMethod: firstTrimmedString(record.sms_method ?? record.smsMethod),
		voiceUrl: firstTrimmedString(record.voice_url ?? record.voiceUrl)
	};
}
function parseTwilioMessageLogEntry(record) {
	return {
		sid: firstTrimmedString(record.sid),
		direction: firstTrimmedString(record.direction),
		status: firstTrimmedString(record.status),
		to: firstTrimmedString(record.to),
		from: firstTrimmedString(record.from),
		errorCode: firstStringish(record.error_code ?? record.errorCode).trim(),
		body: firstString(record.body),
		dateCreated: firstTrimmedString(record.date_created ?? record.dateCreated),
		dateSent: firstTrimmedString(record.date_sent ?? record.dateSent)
	};
}
function parseTwilioMessagingService(record) {
	return {
		sid: firstTrimmedString(record.sid),
		inboundRequestUrl: firstTrimmedString(record.inbound_request_url ?? record.inboundRequestUrl),
		inboundMethod: firstTrimmedString(record.inbound_method ?? record.inboundMethod),
		useInboundWebhookOnNumber: Boolean(record.use_inbound_webhook_on_number ?? record.useInboundWebhookOnNumber)
	};
}
function parseTwilioListPayload(text, key, parseEntry) {
	if (!text.trim()) return [];
	const parsed = JSON.parse(text);
	if (!parsed || typeof parsed !== "object") return [];
	const items = parsed[key];
	if (!Array.isArray(items)) return [];
	return items.filter((item) => Boolean(item && typeof item === "object" && !Array.isArray(item))).map(parseEntry);
}
async function listTwilioIncomingPhoneNumbers(params) {
	const query = new URLSearchParams();
	if (params.phoneNumber) query.set("PhoneNumber", params.phoneNumber);
	const response = await requestTwilioApi({
		account: params.account,
		url: twilioApiUrl(params.account.accountSid, "/IncomingPhoneNumbers.json", query),
		allowedHostname: TWILIO_API_HOSTNAME,
		fetchImpl: params.fetchImpl,
		timeoutMs: params.timeoutMs
	});
	if (!response.ok) throw new TwilioSmsApiError(response.status, response.text, "phone-number lookup");
	return parseTwilioListPayload(response.text, "incoming_phone_numbers", parseTwilioIncomingPhoneNumber);
}
async function retrieveTwilioMessagingService(params) {
	const response = await requestTwilioApi({
		account: params.account,
		url: twilioMessagingUrl(`/Services/${encodeURIComponent(params.serviceSid)}`),
		allowedHostname: TWILIO_MESSAGING_HOSTNAME,
		fetchImpl: params.fetchImpl,
		timeoutMs: params.timeoutMs
	});
	if (!response.ok) throw new TwilioSmsApiError(response.status, response.text, "messaging-service lookup");
	const parsed = JSON.parse(response.text);
	if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Twilio Messaging Service lookup returned malformed JSON.");
	return parseTwilioMessagingService(parsed);
}
async function listTwilioMessages(params) {
	const query = new URLSearchParams();
	if (params.to) query.set("To", params.to);
	if (params.from) query.set("From", params.from);
	query.set("PageSize", String(params.pageSize ?? 5));
	const response = await requestTwilioApi({
		account: params.account,
		url: twilioApiUrl(params.account.accountSid, "/Messages.json", query),
		allowedHostname: TWILIO_API_HOSTNAME,
		fetchImpl: params.fetchImpl,
		timeoutMs: params.timeoutMs
	});
	if (!response.ok) throw new TwilioSmsApiError(response.status, response.text, "message lookup");
	return parseTwilioListPayload(response.text, "messages", parseTwilioMessageLogEntry);
}
async function sendSmsViaTwilio(params) {
	if (!params.account.fromNumber && !params.account.messagingServiceSid) throw new Error("Twilio SMS send requires fromNumber or messagingServiceSid.");
	const body = new URLSearchParams({
		To: params.to,
		Body: params.text
	});
	if (params.account.fromNumber) body.set("From", params.account.fromNumber);
	else body.set("MessagingServiceSid", params.account.messagingServiceSid);
	const init = {
		method: "POST",
		headers: { "content-type": "application/x-www-form-urlencoded" },
		body
	};
	const response = await requestTwilioApi({
		account: params.account,
		url: twilioApiUrl(params.account.accountSid, "/Messages.json"),
		allowedHostname: TWILIO_API_HOSTNAME,
		init,
		fetchImpl: params.fetchImpl
	});
	if (!response.ok) throw new TwilioSmsApiError(response.status, response.text);
	const payload = parseTwilioSuccessPayload(response.text);
	const sid = payload.sid?.trim();
	if (!sid) throw new Error("Twilio SMS send response did not include a Message SID.");
	return {
		sid,
		to: payload.to?.trim() || params.to,
		...payload.from?.trim() ? { from: payload.from.trim() } : {},
		...payload.status?.trim() ? { status: payload.status.trim() } : {}
	};
}
//#endregion
//#region extensions/sms/src/send.ts
function toSmsPlainText(text) {
	return stripMarkdown(text.replace(/```[^\n]*\n?([\s\S]*?)```/g, (_match, body) => body.trim()).replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, (_match, label, url) => {
		const cleanLabel = label.trim();
		const cleanUrl = url.trim();
		return cleanLabel && cleanLabel !== cleanUrl ? `${cleanLabel} (${cleanUrl})` : cleanUrl;
	})).replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
}
async function sendSmsTextChunks(params) {
	const text = toSmsPlainText(params.text);
	if (!text) throw new Error("SMS send requires non-empty text.");
	const chunks = chunkTextForOutbound(text, params.account.textChunkLimit).filter(Boolean);
	const sendChunks = chunks.length ? chunks : [text];
	const results = [];
	for (const textLocal of sendChunks) results.push(await sendSmsViaTwilio({
		account: params.account,
		to: params.to,
		text: textLocal
	}));
	return results;
}
//#endregion
//#region extensions/sms/src/inbound.ts
const CHANNEL_ID$2 = "sms";
async function authorizeSmsSender(params) {
	return await resolveStableChannelMessageIngress({
		channelId: CHANNEL_ID$2,
		accountId: params.account.accountId,
		cfg: params.cfg,
		identity: {
			key: "phone",
			entryIdPrefix: "sms-entry"
		},
		readStoreAllowFrom: async () => await params.channelRuntime.pairing.readAllowFromStore({
			channel: CHANNEL_ID$2,
			accountId: params.account.accountId
		}),
		subject: { stableId: params.from },
		conversation: {
			kind: "direct",
			id: "direct"
		},
		event: { mayPair: true },
		dmPolicy: params.account.dmPolicy,
		allowFrom: params.account.allowFrom
	});
}
async function issueSmsPairingChallenge(params) {
	await createChannelPairingChallengeIssuer({
		channel: CHANNEL_ID$2,
		upsertPairingRequest: async (input) => await params.channelRuntime.pairing.upsertPairingRequest({
			channel: CHANNEL_ID$2,
			accountId: params.account.accountId,
			...input
		})
	})({
		senderId: params.from,
		senderIdLine: `Your SMS phone number: ${params.from}`,
		sendPairingReply: async (text) => {
			await sendSmsTextChunks({
				account: params.account,
				to: params.from,
				text
			});
		},
		onCreated: () => {
			params.log?.info?.(`SMS pairing request created for ${params.from}`);
		},
		onReplyError: (err) => {
			params.log?.warn?.(`SMS pairing reply failed for ${params.from}: ${String(err)}`);
		}
	});
}
async function dispatchSmsInboundEvent(params) {
	const from = normalizeSmsPhoneNumber(params.msg.from);
	const auth = await authorizeSmsSender({
		cfg: params.cfg,
		account: params.account,
		channelRuntime: params.channelRuntime,
		from
	});
	if (!auth.senderAccess.allowed) {
		if (auth.senderAccess.decision === "pairing") {
			await issueSmsPairingChallenge({
				account: params.account,
				channelRuntime: params.channelRuntime,
				from,
				log: params.log
			});
			return;
		}
		params.log?.warn?.(`SMS sender ${from} is not authorized`);
		return;
	}
	const route = params.channelRuntime.routing.resolveAgentRoute({
		cfg: params.cfg,
		channel: CHANNEL_ID$2,
		accountId: params.account.accountId,
		peer: {
			kind: "direct",
			id: from
		}
	});
	const sessionKey = route.sessionKey;
	await params.channelRuntime.inbound.run({
		channel: CHANNEL_ID$2,
		accountId: params.account.accountId,
		raw: params.msg,
		adapter: {
			ingest: (msg) => ({
				id: msg.messageSid,
				timestamp: Date.now(),
				rawText: msg.body,
				textForAgent: msg.body,
				textForCommands: msg.body,
				raw: msg
			}),
			resolveTurn: async (input) => {
				const ctxPayload = params.channelRuntime.inbound.buildContext({
					channel: CHANNEL_ID$2,
					accountId: params.account.accountId,
					timestamp: input.timestamp,
					from: `sms:${from}`,
					sender: {
						id: from,
						name: from
					},
					conversation: {
						kind: "direct",
						id: from,
						label: from
					},
					route: {
						agentId: route.agentId,
						accountId: params.account.accountId,
						routeSessionKey: sessionKey,
						dispatchSessionKey: sessionKey
					},
					reply: { to: `sms:${from}` },
					message: {
						rawBody: input.rawText,
						commandBody: input.textForCommands,
						bodyForAgent: input.textForAgent
					},
					extra: {
						MessageSid: params.msg.messageSid,
						To: params.msg.to
					}
				});
				const storePath = params.channelRuntime.session.resolveStorePath(params.cfg.session?.store, { agentId: route.agentId });
				return {
					cfg: params.cfg,
					channel: CHANNEL_ID$2,
					accountId: params.account.accountId,
					agentId: route.agentId,
					routeSessionKey: sessionKey,
					storePath,
					ctxPayload,
					recordInboundSession: params.channelRuntime.session.recordInboundSession,
					dispatchReplyWithBufferedBlockDispatcher: params.channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher,
					delivery: {
						durable: () => ({ to: from }),
						deliver: async (payload) => {
							const text = payload.text;
							if (!text) return { visibleReplySent: false };
							await sendSmsTextChunks({
								account: params.account,
								to: from,
								text
							});
							return { visibleReplySent: true };
						}
					},
					dispatcherOptions: { onReplyStart: () => {
						params.log?.info?.(`SMS reply started for ${from}`);
					} }
				};
			}
		}
	});
}
//#endregion
//#region extensions/sms/src/webhook.ts
const rateLimiter = createFixedWindowRateLimiter({
	maxRequests: 30,
	windowMs: 6e4,
	maxTrackedKeys: 5e3
});
const REPLAY_CACHE_TTL_MS = 10 * 6e4;
const REPLAY_CACHE_MAX_KEYS = 1e4;
const replayCache = /* @__PURE__ */ new Map();
function headerValue(value) {
	if (Array.isArray(value)) return value[0];
	return value;
}
function rateLimitKey(req) {
	return req.socket?.remoteAddress ?? "unknown";
}
function rememberWebhookMessage(params) {
	const now = params.now ?? Date.now();
	for (const [key, expiresAt] of replayCache) {
		if (expiresAt > now && replayCache.size <= REPLAY_CACHE_MAX_KEYS) break;
		replayCache.delete(key);
	}
	const key = `${params.accountId}:${params.messageSid}`;
	if ((replayCache.get(key) ?? 0) > now) return false;
	replayCache.set(key, now + REPLAY_CACHE_TTL_MS);
	return true;
}
function createSmsWebhookHandler(params) {
	return async (req, res) => {
		if (req.method !== "POST") {
			respondTwiml(res, 405, "Method not allowed");
			return true;
		}
		const key = rateLimitKey(req);
		if (rateLimiter.isRateLimited(key)) {
			params.log?.warn?.(`SMS webhook rate limit exceeded for ${key}`);
			respondTwiml(res, 429, "Rate limit exceeded");
			return true;
		}
		let form;
		try {
			form = await readTwilioWebhookForm(req);
		} catch {
			respondTwiml(res, 400, "Invalid request body");
			return true;
		}
		if (!params.account.dangerouslyDisableSignatureValidation) {
			if (!verifyTwilioSignature({
				signature: headerValue(req.headers["x-twilio-signature"]),
				url: resolveTwilioWebhookSignatureUrl({
					req,
					publicWebhookUrl: params.account.publicWebhookUrl
				}),
				authToken: params.account.authToken,
				form
			})) {
				params.log?.warn?.("SMS webhook rejected invalid Twilio signature");
				respondTwiml(res, 403, "Invalid signature");
				return true;
			}
		}
		const msg = buildTwilioInboundMessage(form);
		if (!msg) {
			respondTwiml(res, 400, "Missing SMS payload");
			return true;
		}
		if (msg.accountSid && msg.accountSid !== params.account.accountSid) {
			params.log?.warn?.("SMS webhook rejected mismatched Twilio AccountSid");
			respondTwiml(res, 403, "Invalid account");
			return true;
		}
		if (!rememberWebhookMessage({
			accountId: params.account.accountId,
			messageSid: msg.messageSid
		})) {
			params.log?.warn?.(`SMS webhook ignored replayed message ${msg.messageSid}`);
			respondTwiml(res, 200);
			return true;
		}
		dispatchSmsInboundEvent({
			cfg: params.cfg,
			account: params.account,
			msg,
			channelRuntime: params.channelRuntime,
			log: params.log
		}).catch((err) => {
			params.log?.error?.(`SMS webhook dispatch failed: ${err instanceof Error ? err.message : String(err)}`);
		});
		respondTwiml(res, 200);
		return true;
	};
}
//#endregion
//#region extensions/sms/src/gateway.ts
const CHANNEL_ID$1 = "sms";
const activeRoutes = /* @__PURE__ */ new Map();
const activeRoutePaths = /* @__PURE__ */ new Map();
function routeKey(account) {
	return `${account.accountId}:${normalizeWebhookPath(account.webhookPath)}`;
}
function normalizeWebhookPath(path) {
	const trimmed = path.trim();
	return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}
function collectSmsStartupWarnings(account) {
	const warnings = [];
	if (!account.accountSid || !account.authToken || !account.fromNumber && !account.messagingServiceSid) warnings.push("- SMS: accountSid, authToken, and fromNumber or messagingServiceSid are required.");
	if (!account.publicWebhookUrl && !account.dangerouslyDisableSignatureValidation) warnings.push("- SMS: publicWebhookUrl is required for Twilio signature validation. Set dangerouslyDisableSignatureValidation=true only for local testing.");
	if (account.dmPolicy === "allowlist" && account.allowFrom.length === 0) warnings.push("- SMS: dmPolicy=allowlist with empty allowFrom rejects every sender.");
	if (account.dmPolicy === "open" && !account.allowFrom.includes("*")) warnings.push("- SMS: dmPolicy=open should set allowFrom=[\"*\"] or explicit sender numbers.");
	return warnings;
}
function registerSmsWebhookRoute(params) {
	const key = routeKey(params.account);
	const webhookPath = normalizeWebhookPath(params.account.webhookPath);
	const currentPathOwner = activeRoutePaths.get(webhookPath);
	if (currentPathOwner && currentPathOwner !== params.account.accountId) throw new Error(`SMS webhook path ${webhookPath} is already registered by account ${currentPathOwner}; configure a distinct webhookPath for account ${params.account.accountId}.`);
	activeRoutes.get(key)?.();
	activeRoutePaths.delete(webhookPath);
	const unregister = registerPluginHttpRoute({
		path: webhookPath,
		auth: "plugin",
		pluginId: CHANNEL_ID$1,
		accountId: params.account.accountId,
		log: (msg) => params.log?.info?.(msg),
		handler: createSmsWebhookHandler(params)
	});
	activeRoutes.set(key, unregister);
	activeRoutePaths.set(webhookPath, params.account.accountId);
	return () => {
		unregister();
		activeRoutes.delete(key);
		if (activeRoutePaths.get(webhookPath) === params.account.accountId) activeRoutePaths.delete(webhookPath);
	};
}
async function startSmsGatewayAccount(params) {
	if (!params.account.enabled) {
		params.log?.info?.(`SMS account ${params.account.accountId} is disabled`);
		return waitUntilAbort(params.abortSignal);
	}
	const warnings = collectSmsStartupWarnings(params.account);
	if (warnings.some((warning) => warning.includes("required"))) {
		for (const warning of warnings) params.log?.warn?.(warning);
		return waitUntilAbort(params.abortSignal);
	}
	for (const warning of warnings) params.log?.warn?.(warning);
	const unregister = registerSmsWebhookRoute(params);
	params.log?.info?.(`Registered SMS webhook route ${params.account.webhookPath} for account ${params.account.accountId}`);
	return waitUntilAbort(params.abortSignal, unregister);
}
//#endregion
//#region extensions/sms/src/status.ts
const TWILIO_ERROR_WEBHOOK_REACHABILITY = "11200";
function addTailscaleHint(account, hints) {
	let host;
	try {
		host = new URL(account.publicWebhookUrl).hostname;
	} catch {
		return;
	}
	if (!host.endsWith(".ts.net")) return;
	hints.push(`Tailscale Funnel must expose the exact SMS path: tailscale funnel --bg --set-path ${account.webhookPath} http://127.0.0.1:<gateway-port>${account.webhookPath}`);
}
function compareTwilioWebhook(account, phoneNumber) {
	if (!account.fromNumber) return {
		status: "skipped",
		reason: "Messaging Service senders do not have one phone-number SMS webhook to inspect."
	};
	if (!phoneNumber) return {
		status: "number-not-found",
		expectedNumber: account.fromNumber
	};
	const configuredMethod = phoneNumber.smsMethod.toUpperCase();
	if (!phoneNumber.smsUrl) return {
		status: "missing",
		phoneNumber: phoneNumber.phoneNumber || account.fromNumber,
		expectedUrl: account.publicWebhookUrl,
		configuredMethod
	};
	if (configuredMethod && configuredMethod !== "POST") return {
		status: "method-mismatch",
		phoneNumber: phoneNumber.phoneNumber || account.fromNumber,
		expectedUrl: account.publicWebhookUrl,
		configuredUrl: phoneNumber.smsUrl,
		configuredMethod
	};
	if (phoneNumber.smsUrl !== account.publicWebhookUrl) return {
		status: "url-mismatch",
		phoneNumber: phoneNumber.phoneNumber || account.fromNumber,
		expectedUrl: account.publicWebhookUrl,
		configuredUrl: phoneNumber.smsUrl,
		configuredMethod
	};
	return {
		status: "matches",
		phoneNumber: phoneNumber.phoneNumber || account.fromNumber,
		expectedUrl: account.publicWebhookUrl,
		configuredUrl: phoneNumber.smsUrl,
		configuredMethod,
		voiceUrl: phoneNumber.voiceUrl
	};
}
function compareTwilioMessagingService(account, service) {
	if (service.useInboundWebhookOnNumber) return {
		status: "unavailable",
		reason: "Twilio Messaging Service defers inbound webhooks to sender phone numbers; configure fromNumber or disable defer-to-sender before probing."
	};
	const configuredMethod = service.inboundMethod.toUpperCase();
	if (!service.inboundRequestUrl) return {
		status: "messaging-service-missing",
		serviceSid: service.sid || account.messagingServiceSid,
		expectedUrl: account.publicWebhookUrl,
		configuredMethod
	};
	if (configuredMethod && configuredMethod !== "POST") return {
		status: "messaging-service-method-mismatch",
		serviceSid: service.sid || account.messagingServiceSid,
		expectedUrl: account.publicWebhookUrl,
		configuredUrl: service.inboundRequestUrl,
		configuredMethod
	};
	if (service.inboundRequestUrl !== account.publicWebhookUrl) return {
		status: "messaging-service-url-mismatch",
		serviceSid: service.sid || account.messagingServiceSid,
		expectedUrl: account.publicWebhookUrl,
		configuredUrl: service.inboundRequestUrl,
		configuredMethod
	};
	return {
		status: "messaging-service-matches",
		serviceSid: service.sid || account.messagingServiceSid,
		expectedUrl: account.publicWebhookUrl,
		configuredUrl: service.inboundRequestUrl,
		configuredMethod
	};
}
function recentInboundSummary(messages) {
	const message = messages[0];
	if (!message) return;
	return {
		sid: message.sid,
		direction: message.direction,
		status: message.status,
		errorCode: message.errorCode,
		dateCreated: message.dateCreated,
		dateSent: message.dateSent
	};
}
function webhookError(probe) {
	switch (probe.status) {
		case "matches":
		case "skipped": return;
		case "unavailable": return probe.reason;
		case "number-not-found": return `Twilio account does not list ${probe.expectedNumber} as an incoming phone number.`;
		case "missing": return `Twilio number ${probe.phoneNumber} has no SMS webhook URL configured.`;
		case "method-mismatch": return `Twilio number ${probe.phoneNumber} uses ${probe.configuredMethod || "an unknown method"} for SMS webhooks; use POST.`;
		case "url-mismatch": return `Twilio number ${probe.phoneNumber} points SMS webhooks at ${probe.configuredUrl}; expected ${probe.expectedUrl}.`;
		case "messaging-service-missing": return `Twilio Messaging Service ${probe.serviceSid} has no inbound request URL configured.`;
		case "messaging-service-method-mismatch": return `Twilio Messaging Service ${probe.serviceSid} uses ${probe.configuredMethod || "an unknown method"} for inbound webhooks; use POST.`;
		case "messaging-service-url-mismatch": return `Twilio Messaging Service ${probe.serviceSid} points inbound webhooks at ${probe.configuredUrl}; expected ${probe.expectedUrl}.`;
		case "messaging-service-matches": return;
	}
}
async function probeSmsAccount(params) {
	const hints = [];
	addTailscaleHint(params.account, hints);
	const webhook = params.account.fromNumber ? compareTwilioWebhook(params.account, (await listTwilioIncomingPhoneNumbers({
		account: params.account,
		phoneNumber: params.account.fromNumber,
		fetchImpl: params.options?.fetchImpl,
		timeoutMs: params.timeoutMs
	}))[0]) : params.account.messagingServiceSid ? compareTwilioMessagingService(params.account, await retrieveTwilioMessagingService({
		account: params.account,
		serviceSid: params.account.messagingServiceSid,
		fetchImpl: params.options?.fetchImpl,
		timeoutMs: params.timeoutMs
	})) : {
		status: "unavailable",
		reason: "Twilio SMS probe requires fromNumber or messagingServiceSid."
	};
	const recentInbound = recentInboundSummary(params.account.fromNumber ? await listTwilioMessages({
		account: params.account,
		to: params.account.fromNumber,
		pageSize: 3,
		fetchImpl: params.options?.fetchImpl,
		timeoutMs: params.timeoutMs
	}) : []);
	if (recentInbound?.errorCode === TWILIO_ERROR_WEBHOOK_REACHABILITY) hints.push("Twilio error 11200 means Twilio could not reach the SMS webhook. Check the public URL, tunnel/Funnel route, and Twilio Messaging webhook method.");
	const error = webhookError(webhook) ?? (recentInbound?.errorCode === TWILIO_ERROR_WEBHOOK_REACHABILITY ? `Recent inbound SMS ${recentInbound.sid} has Twilio error 11200.` : void 0);
	return {
		ok: !error,
		...error ? { error } : {},
		webhook,
		...recentInbound ? { recentInbound } : {},
		hints
	};
}
function formatSmsProbeLines(probe) {
	if (!probe || typeof probe !== "object") return [];
	const smsProbe = probe;
	const lines = [];
	if (smsProbe.ok === true) lines.push({
		text: "Probe: ok",
		tone: "success"
	});
	else if (smsProbe.ok === false) lines.push({
		text: `Probe: failed${smsProbe.error ? ` (${smsProbe.error})` : ""}`,
		tone: "error"
	});
	if (smsProbe.webhook?.status === "matches" || smsProbe.webhook?.status === "messaging-service-matches") lines.push({ text: `Twilio SMS webhook: ${smsProbe.webhook.configuredUrl}` });
	else if (smsProbe.webhook?.status && smsProbe.webhook.status !== "skipped") lines.push({
		text: `Twilio SMS webhook: ${smsProbe.webhook.status}`,
		tone: "warn"
	});
	if (smsProbe.recentInbound?.sid) {
		const error = smsProbe.recentInbound.errorCode ? ` error=${smsProbe.recentInbound.errorCode}` : "";
		lines.push({
			text: `Recent inbound: ${smsProbe.recentInbound.status || "unknown"}${error}`,
			tone: smsProbe.recentInbound.errorCode ? "warn" : "muted"
		});
	}
	for (const hint of smsProbe.hints ?? []) lines.push({
		text: hint,
		tone: "warn"
	});
	return lines;
}
//#endregion
//#region extensions/sms/src/channel.ts
const CHANNEL_ID = "sms";
const smsConfigAdapter = createHybridChannelConfigAdapter({
	sectionKey: CHANNEL_ID,
	listAccountIds: listSmsAccountIds,
	resolveAccount: resolveSmsAccount,
	defaultAccountId: resolveDefaultSmsAccountId,
	clearBaseFields: [
		"accountSid",
		"authToken",
		"fromNumber",
		"messagingServiceSid",
		"defaultTo",
		"webhookPath",
		"publicWebhookUrl",
		"dangerouslyDisableSignatureValidation",
		"dmPolicy",
		"allowFrom",
		"textChunkLimit"
	],
	resolveAllowFrom: (account) => account.allowFrom,
	formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom.map((entry) => normalizeSmsAllowFrom(String(entry)))),
	resolveDefaultTo: (account) => account.defaultTo
});
const resolveSmsDmPolicy = createScopedDmSecurityResolver({
	channelKey: CHANNEL_ID,
	resolvePolicy: (account) => account.dmPolicy,
	resolveAllowFrom: (account) => account.allowFrom,
	policyPathSuffix: "dmPolicy",
	defaultPolicy: "pairing",
	approveHint: "openclaw pairing approve sms <code>",
	normalizeEntry: normalizeSmsAllowFrom
});
const collectSmsSecurityWarnings = createConditionalWarningCollector((account) => account.dangerouslyDisableSignatureValidation && "- SMS: Twilio signature validation is disabled. Only use this for local testing.", (account) => account.dmPolicy === "open" && account.allowFrom.includes("*") && "- SMS: dmPolicy=\"open\" allows any phone number to message the bot.");
function smsSetupPatch(input) {
	const patch = {};
	for (const key of [
		"accountSid",
		"authToken",
		"fromNumber",
		"messagingServiceSid",
		"defaultTo",
		"webhookPath",
		"publicWebhookUrl",
		"dmPolicy",
		"allowFrom"
	]) if (input[key] !== void 0) patch[key] = input[key];
	return patch;
}
function applySmsAccountConfig(params) {
	const patch = smsSetupPatch(params.input);
	const channels = { ...params.cfg.channels };
	const current = { ...channels[CHANNEL_ID] };
	if (params.accountId === "default") {
		channels[CHANNEL_ID] = {
			...current,
			...patch
		};
		return {
			...params.cfg,
			channels
		};
	}
	const accounts = { ...current.accounts };
	accounts[params.accountId] = {
		...accounts[params.accountId],
		...patch
	};
	channels[CHANNEL_ID] = {
		...current,
		accounts
	};
	return {
		...params.cfg,
		channels
	};
}
function createSmsReceipt(params) {
	const first = params.results[0];
	if (!first) throw new Error("SMS send did not return a Twilio Message SID.");
	return {
		channel: CHANNEL_ID,
		messageId: first.sid,
		chatId: first.to,
		receipt: createMessageReceiptFromOutboundResults({
			results: params.results.map((result) => ({
				channel: CHANNEL_ID,
				messageId: result.sid,
				chatId: result.to,
				toJid: result.to,
				conversationId: result.to,
				meta: {
					...result.from ? { from: result.from } : {},
					...result.status ? { status: result.status } : {}
				}
			})),
			threadId: first.to,
			kind: params.kind
		})
	};
}
function resolveSmsTextChunkLimit(params) {
	return resolveSmsAccount(params.cfg, params.accountId).textChunkLimit || params.fallbackLimit || 1500;
}
async function sendSmsText(ctx) {
	const account = resolveSmsAccount(ctx.cfg, ctx.accountId);
	const to = normalizeSmsPhoneNumber(ctx.to) || account.defaultTo;
	if (!looksLikeSmsPhoneNumber(to)) throw new Error(`Invalid SMS target: ${ctx.to}`);
	return createSmsReceipt({
		results: await sendSmsTextChunks({
			account,
			to,
			text: ctx.text
		}),
		kind: "text"
	});
}
const smsMessageAdapter = defineChannelMessageAdapter({
	id: CHANNEL_ID,
	durableFinal: { capabilities: {
		text: true,
		media: false,
		messageSendingHooks: true
	} },
	send: { text: async (ctx) => await sendSmsText(ctx) }
});
const smsPlugin = createChatChannelPlugin({
	base: {
		id: CHANNEL_ID,
		meta: {
			id: CHANNEL_ID,
			label: "SMS",
			selectionLabel: "SMS (Twilio)",
			detailLabel: "Twilio SMS",
			docsPath: "/channels/sms",
			docsLabel: "sms",
			blurb: "Twilio-backed SMS with inbound webhooks and outbound replies.",
			order: 88
		},
		capabilities: {
			chatTypes: ["direct"],
			media: false,
			threads: false,
			reactions: false,
			edit: false,
			unsend: false,
			reply: false,
			effects: false,
			blockStreaming: false
		},
		reload: { configPrefixes: [`channels.${CHANNEL_ID}`] },
		configSchema: SmsChannelConfigSchema,
		setup: { applyAccountConfig: applySmsAccountConfig },
		config: {
			...smsConfigAdapter,
			inspectAccount: inspectSmsAccount,
			isConfigured: isSmsAccountConfigured,
			unconfiguredReason: () => "SMS requires accountSid, authToken, and fromNumber or messagingServiceSid.",
			describeAccount: (account) => ({
				accountId: account.accountId,
				name: account.fromNumber || account.messagingServiceSid || "SMS",
				configured: isSmsAccountConfigured(account),
				enabled: account.enabled
			})
		},
		messaging: {
			targetPrefixes: ["twilio-sms"],
			normalizeTarget: (target) => normalizeSmsPhoneNumber(target),
			targetResolver: {
				looksLikeId: looksLikeSmsPhoneNumber,
				hint: "<+15551234567>"
			}
		},
		directory: createEmptyChannelDirectoryAdapter(),
		gateway: { startAccount: async (ctx) => {
			if (!ctx.channelRuntime) {
				ctx.log?.warn?.("SMS channel runtime is not available; webhook route not started");
				return;
			}
			return await startSmsGatewayAccount({
				cfg: ctx.cfg,
				account: ctx.account,
				channelRuntime: ctx.channelRuntime,
				abortSignal: ctx.abortSignal,
				log: ctx.log
			});
		} },
		status: {
			buildAccountSnapshot: ({ account }) => {
				const configured = isSmsAccountConfigured(account);
				return {
					accountId: account.accountId,
					name: account.fromNumber || account.messagingServiceSid || "SMS",
					enabled: account.enabled,
					configured,
					statusState: !account.enabled ? "disabled" : configured ? "configured" : "unconfigured"
				};
			},
			probeAccount: async ({ account, timeoutMs }) => await probeSmsAccount({
				account,
				timeoutMs
			}),
			formatCapabilitiesProbe: ({ probe }) => formatSmsProbeLines(probe),
			buildCapabilitiesDiagnostics: async ({ account }) => ({ lines: collectSmsStartupWarnings(account).map((text) => ({
				text,
				tone: "warn"
			})) })
		},
		secrets: {
			secretTargetRegistryEntries,
			collectRuntimeConfigAssignments
		},
		agentPrompt: { messageToolHints: () => [
			"",
			"### SMS Formatting",
			"SMS is plain text only. Keep replies brief, avoid markdown tables, and split long details into short messages."
		] },
		message: smsMessageAdapter
	},
	pairing: { text: {
		idLabel: "phoneNumber",
		message: "OpenClaw: your SMS access has been approved.",
		normalizeAllowEntry: normalizeSmsAllowFrom,
		notify: async ({ cfg, id, message, accountId }) => {
			await sendSmsTextChunks({
				account: resolveSmsAccount(cfg, accountId),
				to: normalizeSmsPhoneNumber(id),
				text: message
			});
		}
	} },
	security: {
		resolveDmPolicy: resolveSmsDmPolicy,
		collectWarnings: ({ account }) => collectSmsSecurityWarnings(account)
	},
	outbound: {
		deliveryMode: "gateway",
		chunker: chunkTextForOutbound,
		chunkerMode: "text",
		textChunkLimit: 1500,
		resolveEffectiveTextChunkLimit: resolveSmsTextChunkLimit,
		resolveTarget: ({ cfg, to, accountId }) => {
			const explicit = normalizeSmsPhoneNumber(to ?? "");
			if (explicit) return {
				ok: true,
				to: explicit
			};
			if (cfg) {
				const account = resolveSmsAccount(cfg, accountId);
				if (account.defaultTo) return {
					ok: true,
					to: account.defaultTo
				};
			}
			return {
				ok: false,
				error: /* @__PURE__ */ new Error("SMS target must be an E.164 phone number.")
			};
		},
		sanitizeText: ({ text }) => toSmsPlainText(text),
		sendText: sendSmsText
	}
});
//#endregion
export { smsPlugin };
