@openclaw/sms 2026.7.1 → 2026.7.2-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-CS-JLPcU.js";
1
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-Cp0f-YPc.js";
2
2
  import { DEFAULT_ACCOUNT_ID, normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id";
3
3
  import { createHybridChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
4
4
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
@@ -6,19 +6,23 @@ import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, w
6
6
  import { createConditionalWarningCollector } from "openclaw/plugin-sdk/channel-policy";
7
7
  import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
8
8
  import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
9
- import { chunkTextForOutbound, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
9
+ import { chunkTextForOutbound, renderMarkdownIRChunksWithinLimit, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
10
10
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, listCombinedAccountIds, resolveAccountEntry, resolveListedDefaultAccountId, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
11
11
  import { parseStrictInteger } from "openclaw/plugin-sdk/number-runtime";
12
12
  import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
13
- import { AllowFromListSchema, DmPolicySchema, buildChannelConfigSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-primitives";
13
+ import { AllowFromListSchema, DmPolicySchema, buildChannelConfigSchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
14
14
  import { requireChannelOpenAllowFrom } from "openclaw/plugin-sdk/extension-shared";
15
15
  import { z } from "zod";
16
- import { createFixedWindowRateLimiter, readRequestBodyWithLimit, registerPluginHttpRoute } from "openclaw/plugin-sdk/webhook-ingress";
16
+ import { createFixedWindowRateLimiter, readRequestBodyWithLimit, registerPluginHttpRoute, resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
17
+ import { runDetachedWebhookWork } from "openclaw/plugin-sdk/webhook-request-guards";
17
18
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
18
19
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
19
- import { createHmac, timingSafeEqual } from "node:crypto";
20
+ import { createHmac } from "node:crypto";
20
21
  import * as querystring from "node:querystring";
22
+ import { readResponseTextPrefix, readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
23
+ import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
21
24
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
25
+ import { performance } from "node:perf_hooks";
22
26
  //#region extensions/sms/src/phone.ts
23
27
  function normalizeSmsPhoneNumber(raw) {
24
28
  const trimmed = raw.trim().replace(/^(?:sms|twilio-sms):/i, "");
@@ -129,7 +133,7 @@ function isSmsAccountConfigured(account) {
129
133
  //#endregion
130
134
  //#region extensions/sms/src/config-schema.ts
131
135
  const SecretInputSchema = buildSecretInputSchema();
132
- const SmsAccountConfigSchema = z.object({
136
+ const SmsChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannelSchema(z.object({
133
137
  name: z.string().optional(),
134
138
  enabled: z.boolean().optional(),
135
139
  accountSid: z.string().optional(),
@@ -143,18 +147,17 @@ const SmsAccountConfigSchema = z.object({
143
147
  dmPolicy: DmPolicySchema.optional().default("pairing"),
144
148
  allowFrom: AllowFromListSchema,
145
149
  textChunkLimit: z.number().int().positive().optional()
146
- }).strict().superRefine((value, ctx) => {
147
- requireChannelOpenAllowFrom({
148
- channel: "sms",
149
- policy: value.dmPolicy,
150
- allowFrom: value.allowFrom,
151
- ctx,
152
- requireOpenAllowFrom
153
- });
154
- });
155
- const SmsChannelConfigSchema = buildChannelConfigSchema(SmsAccountConfigSchema.extend({
156
- accounts: z.record(z.string(), SmsAccountConfigSchema.optional()).optional(),
157
- defaultAccount: z.string().optional()
150
+ }).strict(), {
151
+ optionalAccount: true,
152
+ refine: (value, ctx) => {
153
+ requireChannelOpenAllowFrom({
154
+ channel: "sms",
155
+ policy: value.dmPolicy,
156
+ allowFrom: value.allowFrom,
157
+ ctx,
158
+ requireOpenAllowFrom
159
+ });
160
+ }
158
161
  }), { uiHints: {
159
162
  "": {
160
163
  label: "SMS",
@@ -213,6 +216,7 @@ const TWILIO_API_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
213
216
  const TRUNCATED_RESPONSE_SUFFIX = "... [truncated]";
214
217
  const WEBHOOK_BODY_LIMIT_BYTES = 32 * 1024;
215
218
  const WEBHOOK_BODY_TIMEOUT_MS = 5e3;
219
+ const TWILIO_CHANNEL_ADDRESS_RE = /^([a-z][a-z0-9-]*):(.*)$/i;
216
220
  function firstString(value) {
217
221
  if (Array.isArray(value)) return firstString(value[0]);
218
222
  return typeof value === "string" ? value : "";
@@ -262,17 +266,16 @@ function requestSearch(req) {
262
266
  return "";
263
267
  }
264
268
  }
265
- function configuredUrlHasQuery(url) {
269
+ function stripUrlFragment(url) {
266
270
  const hashIndex = url.indexOf("#");
267
- return (hashIndex === -1 ? url : url.slice(0, hashIndex)).includes("?");
271
+ return hashIndex === -1 ? url : url.slice(0, hashIndex);
268
272
  }
269
273
  function resolveTwilioWebhookSignatureUrl(params) {
270
- if (configuredUrlHasQuery(params.publicWebhookUrl)) return params.publicWebhookUrl;
274
+ const signatureBaseUrl = stripUrlFragment(params.publicWebhookUrl);
275
+ if (signatureBaseUrl.includes("?")) return signatureBaseUrl;
271
276
  const search = requestSearch(params.req);
272
- if (!search) return params.publicWebhookUrl;
273
- const hashIndex = params.publicWebhookUrl.indexOf("#");
274
- if (hashIndex === -1) return `${params.publicWebhookUrl}${search}`;
275
- return `${params.publicWebhookUrl.slice(0, hashIndex)}${search}${params.publicWebhookUrl.slice(hashIndex)}`;
277
+ if (!search) return signatureBaseUrl;
278
+ return `${signatureBaseUrl}${search}`;
276
279
  }
277
280
  var TwilioSmsApiError = class extends Error {
278
281
  constructor(httpStatus, responseText, operation = "send") {
@@ -295,21 +298,26 @@ function computeTwilioSignature(params) {
295
298
  const data = params.url + Object.keys(params.form).toSorted().map((key) => `${key}${params.form[key] ?? ""}`).join("");
296
299
  return createHmac("sha1", params.authToken).update(data).digest("base64");
297
300
  }
298
- function safeEqual(a, b) {
299
- const left = Buffer.from(a);
300
- const right = Buffer.from(b);
301
- return left.length === right.length && timingSafeEqual(left, right);
302
- }
303
301
  function verifyTwilioSignature(params) {
304
302
  if (!params.signature || !params.url || !params.authToken) return false;
305
- return safeEqual(params.signature, computeTwilioSignature({
303
+ return safeEqualSecret(params.signature, computeTwilioSignature({
306
304
  url: params.url,
307
305
  authToken: params.authToken,
308
306
  form: params.form
309
307
  }));
310
308
  }
309
+ function parseTwilioInboundFrom(raw) {
310
+ const trimmed = raw.trim();
311
+ if (!trimmed) return null;
312
+ const channelAddress = trimmed.match(TWILIO_CHANNEL_ADDRESS_RE);
313
+ const kind = channelAddress?.[1]?.toLowerCase();
314
+ if (kind && kind !== "rcs") return null;
315
+ const phoneNumber = normalizeSmsPhoneNumber(channelAddress?.[2] ?? trimmed);
316
+ if (!looksLikeSmsPhoneNumber(phoneNumber)) return null;
317
+ return phoneNumber;
318
+ }
311
319
  function buildTwilioInboundMessage(form) {
312
- const from = firstTrimmedString(form.From);
320
+ const from = parseTwilioInboundFrom(firstTrimmedString(form.From));
313
321
  const to = firstTrimmedString(form.To);
314
322
  const body = firstString(form.Body);
315
323
  const accountSid = firstTrimmedString(form.AccountSid);
@@ -353,37 +361,13 @@ function appendTruncatedResponseSuffix(text) {
353
361
  return `${text.trimEnd()}${TRUNCATED_RESPONSE_SUFFIX}`;
354
362
  }
355
363
  async function readTwilioApiResponseText(response) {
356
- if (!response.body) return "";
357
364
  const maxBytes = response.ok ? TWILIO_API_SUCCESS_BODY_LIMIT_BYTES : TWILIO_API_ERROR_BODY_LIMIT_BYTES;
358
- const truncateOnLimit = !response.ok;
359
- const reader = response.body.getReader();
360
- const decoder = new TextDecoder();
361
- let totalBytes = 0;
362
- let text = "";
363
- try {
364
- while (true) {
365
- const { done, value } = await reader.read();
366
- if (done) return text + decoder.decode();
367
- if (!value?.byteLength) continue;
368
- const remainingBytes = maxBytes - totalBytes;
369
- if (value.byteLength > remainingBytes) {
370
- const clipped = remainingBytes > 0 ? value.slice(0, remainingBytes) : void 0;
371
- if (truncateOnLimit) {
372
- if (clipped) text += decoder.decode(clipped, { stream: true });
373
- await reader.cancel().catch(() => void 0);
374
- return appendTruncatedResponseSuffix(text + decoder.decode());
375
- }
376
- await reader.cancel().catch(() => void 0);
377
- throw new Error(`Twilio SMS API response body too large: ${totalBytes + value.byteLength} bytes (limit: ${maxBytes} bytes)`);
378
- }
379
- text += decoder.decode(value, { stream: true });
380
- totalBytes += value.byteLength;
381
- }
382
- } finally {
383
- try {
384
- reader.releaseLock();
385
- } catch {}
365
+ if (!response.ok) {
366
+ const prefix = await readResponseTextPrefix(response, maxBytes);
367
+ return prefix.truncated ? appendTruncatedResponseSuffix(prefix.text) : prefix.text;
386
368
  }
369
+ const body = await readResponseWithLimit(response, maxBytes, { onOverflow: ({ size, maxBytes: limit }) => /* @__PURE__ */ new Error(`Twilio SMS API response body too large: ${size} bytes (limit: ${limit} bytes)`) });
370
+ return new TextDecoder().decode(body);
387
371
  }
388
372
  function normalizeRequestHeaders(headers) {
389
373
  if (!headers) return {};
@@ -547,17 +531,31 @@ async function sendSmsViaTwilio(params) {
547
531
  }
548
532
  //#endregion
549
533
  //#region extensions/sms/src/send.ts
534
+ const SMS_ASSISTANT_TRANSCRIPT_ROLE_PREFIX = "[assistant-authored transcript] ";
550
535
  function toSmsPlainText(text) {
551
- return stripMarkdown(sanitizeAssistantVisibleText(text).replace(/```[^\n]*\n?([\s\S]*?)```/g, (_match, body) => body.trim()).replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g, (_match, label, url) => {
552
- const cleanLabel = label.trim();
553
- const cleanUrl = url.trim();
554
- return cleanLabel && cleanLabel !== cleanUrl ? `${cleanLabel} (${cleanUrl})` : cleanUrl;
555
- })).replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
536
+ return stripMarkdown(sanitizeAssistantVisibleText(text), {
537
+ assistantTranscriptRoleHeaders: true,
538
+ assistantTranscriptRolePrefix: SMS_ASSISTANT_TRANSCRIPT_ROLE_PREFIX,
539
+ linkStyle: "label-and-url"
540
+ }).replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
541
+ }
542
+ function chunkSmsPlainText(text, limit) {
543
+ return renderMarkdownIRChunksWithinLimit({
544
+ ir: {
545
+ text,
546
+ styles: [],
547
+ links: []
548
+ },
549
+ limit,
550
+ assistantTranscriptRoleMessageBoundaries: true,
551
+ renderChunk: (chunk) => chunk.annotations?.some((annotation) => annotation.type === "assistant_transcript_role") ? `${SMS_ASSISTANT_TRANSCRIPT_ROLE_PREFIX}${chunk.text}` : chunk.text,
552
+ measureRendered: (rendered) => rendered.length
553
+ }).map(({ rendered }) => rendered).filter(Boolean);
556
554
  }
557
555
  async function sendSmsTextChunks(params) {
558
556
  const text = toSmsPlainText(params.text);
559
557
  if (!text) throw new Error("SMS send requires non-empty text.");
560
- const chunks = chunkTextForOutbound(text, params.account.textChunkLimit).filter(Boolean);
558
+ const chunks = chunkSmsPlainText(text, params.account.textChunkLimit);
561
559
  const sendChunks = chunks.length ? chunks : [text];
562
560
  const results = [];
563
561
  for (const textLocal of sendChunks) results.push(await sendSmsViaTwilio({
@@ -729,49 +727,98 @@ async function dispatchSmsInboundEvent(params) {
729
727
  });
730
728
  }
731
729
  //#endregion
730
+ //#region extensions/sms/src/webhook-replay-guard.ts
731
+ const REPLAY_CACHE_TTL_MS = 10 * 6e4;
732
+ const REPLAY_CACHE_MAX_KEYS = 1e4;
733
+ function createSmsWebhookReplayGuard(options = {}) {
734
+ const ttlMs = options.ttlMs ?? REPLAY_CACHE_TTL_MS;
735
+ const maxKeys = options.maxKeys ?? REPLAY_CACHE_MAX_KEYS;
736
+ const now = options.now ?? (() => performance.now());
737
+ const entries = /* @__PURE__ */ new Map();
738
+ const pruneExpired = (nowMs) => {
739
+ for (const [key, expiresAt] of entries) {
740
+ if (expiresAt > nowMs) break;
741
+ entries.delete(key);
742
+ }
743
+ };
744
+ return { remember: (messageSid) => {
745
+ const nowMs = now();
746
+ pruneExpired(nowMs);
747
+ if (entries.has(messageSid)) return { kind: "replayed" };
748
+ if (entries.size >= maxKeys) {
749
+ const oldestExpiresAt = entries.values().next().value ?? nowMs;
750
+ return {
751
+ kind: "saturated",
752
+ retryAfterMs: Math.max(0, oldestExpiresAt - nowMs)
753
+ };
754
+ }
755
+ entries.set(messageSid, nowMs + ttlMs);
756
+ return { kind: "accepted" };
757
+ } };
758
+ }
759
+ //#endregion
732
760
  //#region extensions/sms/src/webhook.ts
733
- const rateLimiter = createFixedWindowRateLimiter({
734
- maxRequests: 30,
761
+ const INVALID_REQUEST_MAX_REQUESTS = 300;
762
+ const CALLBACK_DISPATCH_MAX_REQUESTS = 30;
763
+ const invalidRequestRateLimiter = createFixedWindowRateLimiter({
764
+ maxRequests: INVALID_REQUEST_MAX_REQUESTS,
735
765
  windowMs: 6e4,
736
766
  maxTrackedKeys: 5e3
737
767
  });
738
- const REPLAY_CACHE_TTL_MS = 10 * 6e4;
739
- const REPLAY_CACHE_MAX_KEYS = 1e4;
740
- const replayCache = /* @__PURE__ */ new Map();
768
+ const callbackDispatchRateLimiter = createFixedWindowRateLimiter({
769
+ maxRequests: CALLBACK_DISPATCH_MAX_REQUESTS,
770
+ windowMs: 6e4,
771
+ maxTrackedKeys: 5e3
772
+ });
773
+ const replayGuardsByAccount = /* @__PURE__ */ new Map();
774
+ function resolveSmsWebhookReplayGuard(account) {
775
+ const key = `${account.accountId}\0${account.accountSid}`;
776
+ const existing = replayGuardsByAccount.get(key);
777
+ if (existing) return existing;
778
+ const created = createSmsWebhookReplayGuard();
779
+ replayGuardsByAccount.set(key, created);
780
+ return created;
781
+ }
741
782
  function headerValue(value) {
742
783
  if (Array.isArray(value)) return value[0];
743
784
  return value;
744
785
  }
745
- function rateLimitKey(req) {
746
- return req.socket?.remoteAddress ?? "unknown";
786
+ function resolvedClientAddress(params) {
787
+ return resolveRequestClientIp(params.req, params.cfg.gateway?.trustedProxies, params.cfg.gateway?.allowRealIpFallback === true) ?? params.req.socket?.remoteAddress ?? "unknown";
747
788
  }
748
- function rememberWebhookMessage(params) {
749
- const now = params.now ?? Date.now();
750
- for (const [key, expiresAt] of replayCache) {
751
- if (expiresAt > now && replayCache.size <= REPLAY_CACHE_MAX_KEYS) break;
752
- replayCache.delete(key);
753
- }
754
- const key = `${params.accountId}:${params.messageSid}`;
755
- if ((replayCache.get(key) ?? 0) > now) return false;
756
- replayCache.set(key, now + REPLAY_CACHE_TTL_MS);
789
+ function rateLimitKey(params) {
790
+ return `${params.account.accountId}:${params.account.webhookPath}:${params.clientAddress}`;
791
+ }
792
+ function rejectInvalidRequestRateLimit(params) {
793
+ params.log?.warn?.(`SMS webhook invalid-request rate limit exceeded for ${params.key}`);
794
+ respondTwiml(params.res, 429, "Rate limit exceeded");
757
795
  return true;
758
796
  }
759
797
  function createSmsWebhookHandler(params) {
798
+ const webhookReplayGuard = resolveSmsWebhookReplayGuard(params.account);
760
799
  return async (req, res) => {
761
800
  if (req.method !== "POST") {
762
801
  respondTwiml(res, 405, "Method not allowed");
763
802
  return true;
764
803
  }
765
- const key = rateLimitKey(req);
766
- if (rateLimiter.isRateLimited(key)) {
767
- params.log?.warn?.(`SMS webhook rate limit exceeded for ${key}`);
768
- respondTwiml(res, 429, "Rate limit exceeded");
769
- return true;
770
- }
804
+ const clientAddress = resolvedClientAddress({
805
+ cfg: params.cfg,
806
+ req
807
+ });
808
+ const key = rateLimitKey({
809
+ account: params.account,
810
+ clientAddress
811
+ });
812
+ const invalidRequestRateLimited = invalidRequestRateLimiter.isRateLimited(key);
771
813
  let form;
772
814
  try {
773
815
  form = await readTwilioWebhookForm(req);
774
816
  } catch {
817
+ if (invalidRequestRateLimited) return rejectInvalidRequestRateLimit({
818
+ key,
819
+ log: params.log,
820
+ res
821
+ });
775
822
  respondTwiml(res, 400, "Invalid request body");
776
823
  return true;
777
824
  }
@@ -785,6 +832,11 @@ function createSmsWebhookHandler(params) {
785
832
  authToken: params.account.authToken,
786
833
  form
787
834
  })) {
835
+ if (invalidRequestRateLimited) return rejectInvalidRequestRateLimit({
836
+ key,
837
+ log: params.log,
838
+ res
839
+ });
788
840
  params.log?.warn?.("SMS webhook rejected invalid Twilio signature");
789
841
  respondTwiml(res, 403, "Invalid signature");
790
842
  return true;
@@ -792,29 +844,54 @@ function createSmsWebhookHandler(params) {
792
844
  }
793
845
  const msg = buildTwilioInboundMessage(form);
794
846
  if (!msg) {
847
+ if (invalidRequestRateLimited) return rejectInvalidRequestRateLimit({
848
+ key,
849
+ log: params.log,
850
+ res
851
+ });
795
852
  respondTwiml(res, 400, "Missing SMS payload");
796
853
  return true;
797
854
  }
798
855
  if (msg.accountSid && msg.accountSid !== params.account.accountSid) {
856
+ if (invalidRequestRateLimited) return rejectInvalidRequestRateLimit({
857
+ key,
858
+ log: params.log,
859
+ res
860
+ });
799
861
  params.log?.warn?.("SMS webhook rejected mismatched Twilio AccountSid");
800
862
  respondTwiml(res, 403, "Invalid account");
801
863
  return true;
802
864
  }
803
- if (!rememberWebhookMessage({
804
- accountId: params.account.accountId,
805
- messageSid: msg.messageSid
806
- })) {
865
+ if (invalidRequestRateLimited && params.account.dangerouslyDisableSignatureValidation) return rejectInvalidRequestRateLimit({
866
+ key,
867
+ log: params.log,
868
+ res
869
+ });
870
+ if (callbackDispatchRateLimiter.isRateLimited(key)) {
871
+ params.log?.warn?.(`SMS webhook rate limit exceeded for ${key}`);
872
+ respondTwiml(res, 429, "Rate limit exceeded");
873
+ return true;
874
+ }
875
+ const replayDecision = webhookReplayGuard.remember(msg.messageSid);
876
+ if (replayDecision.kind === "replayed") {
807
877
  params.log?.warn?.(`SMS webhook ignored replayed message ${msg.messageSid}`);
808
878
  respondTwiml(res, 200);
809
879
  return true;
810
880
  }
811
- dispatchSmsInboundEvent({
881
+ if (replayDecision.kind === "saturated") {
882
+ const retryAfterSeconds = Math.max(1, Math.ceil(replayDecision.retryAfterMs / 1e3));
883
+ params.log?.warn?.("SMS webhook replay cache is full of unexpired message SIDs");
884
+ res.setHeader("Retry-After", String(retryAfterSeconds));
885
+ respondTwiml(res, 429, "Replay cache saturated");
886
+ return true;
887
+ }
888
+ runDetachedWebhookWork(() => dispatchSmsInboundEvent({
812
889
  cfg: params.cfg,
813
890
  account: params.account,
814
891
  msg,
815
892
  channelRuntime: params.channelRuntime,
816
893
  log: params.log
817
- }).catch((err) => {
894
+ })).catch((err) => {
818
895
  params.log?.error?.(`SMS webhook dispatch failed: ${err instanceof Error ? err.message : String(err)}`);
819
896
  });
820
897
  respondTwiml(res, 200);
@@ -1,27 +1,11 @@
1
- import { collectConditionalChannelFieldAssignments, getChannelSurface, hasOwnProperty } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
1
+ import { collectConditionalChannelFieldAssignments, createChannelSecretTargetRegistryEntries, getChannelSurface, hasOwnProperty } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
2
2
  //#region extensions/sms/src/secret-contract.ts
3
3
  const DEFAULT_ACCOUNT_ID = "default";
4
- const secretTargetRegistryEntries = [{
5
- id: "channels.sms.accounts.*.authToken",
6
- targetType: "channels.sms.accounts.*.authToken",
7
- configFile: "openclaw.json",
8
- pathPattern: "channels.sms.accounts.*.authToken",
9
- secretShape: "secret_input",
10
- expectedResolvedValue: "string",
11
- includeInPlan: true,
12
- includeInConfigure: true,
13
- includeInAudit: true
14
- }, {
15
- id: "channels.sms.authToken",
16
- targetType: "channels.sms.authToken",
17
- configFile: "openclaw.json",
18
- pathPattern: "channels.sms.authToken",
19
- secretShape: "secret_input",
20
- expectedResolvedValue: "string",
21
- includeInPlan: true,
22
- includeInConfigure: true,
23
- includeInAudit: true
24
- }];
4
+ const secretTargetRegistryEntries = createChannelSecretTargetRegistryEntries({
5
+ channelKey: "sms",
6
+ account: ["authToken"],
7
+ channel: ["authToken"]
8
+ });
25
9
  function hasTopLevelSmsAccount(channel) {
26
10
  for (const field of [
27
11
  "accountSid",
@@ -1,2 +1,2 @@
1
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-CS-JLPcU.js";
1
+ import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-Cp0f-YPc.js";
2
2
  export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/sms",
3
- "version": "2026.7.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/sms",
9
- "version": "2026.7.1",
9
+ "version": "2026.7.2-beta.2",
10
10
  "dependencies": {
11
11
  "zod": "4.4.3"
12
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/sms",
3
- "version": "2026.7.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "description": "OpenClaw SMS channel plugin for Twilio text messages.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,10 +26,10 @@
26
26
  "quickstartAllowFrom": true
27
27
  },
28
28
  "compat": {
29
- "pluginApi": ">=2026.7.1"
29
+ "pluginApi": ">=2026.7.2-beta.2"
30
30
  },
31
31
  "build": {
32
- "openclawVersion": "2026.7.1",
32
+ "openclawVersion": "2026.7.2-beta.2",
33
33
  "bundledDist": false
34
34
  },
35
35
  "install": {
@@ -54,7 +54,7 @@
54
54
  "README.md"
55
55
  ],
56
56
  "peerDependencies": {
57
- "openclaw": ">=2026.7.1"
57
+ "openclaw": ">=2026.7.2-beta.2"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "openclaw": {