@openclaw/signal 2026.7.2-beta.5 → 2026.7.2-beta.7

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.
package/dist/api.js CHANGED
@@ -2,9 +2,9 @@ import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefau
2
2
  import { a as normalizeSignalAllowRecipient, c as resolveSignalSender, d as normalizeSignalMessagingTarget, i as isSignalSenderAllowed, l as looksLikeUuid, n as formatSignalSenderDisplay, o as resolveSignalPeerId, r as formatSignalSenderId, s as resolveSignalRecipient, t as formatSignalPairingIdLine, u as looksLikeSignalTargetId } from "./identity-BQ4OzXXk.js";
3
3
  import { a as signalMessageActions, c as markdownToSignalText, l as markdownToSignalTextChunks, o as resolveSignalReactionLevel, t as detectSignalTransport } from "./transport-detection-Da06-LOL.js";
4
4
  import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BjeDyKcz.js";
5
- import { a as normalizeSignalAccountInput, d as probeSignalTransport, f as writeSignalAccountTransport, l as signalSetupContract, n as createSignalPluginBase, p as resolveSignalOutboundTarget, r as signalSetupWizard, t as signalPlugin, u as prepareSignalManagedNativeTransport } from "./channel-CBIljGCH.js";
5
+ import { a as normalizeSignalAccountInput, d as probeSignalTransport, f as writeSignalAccountTransport, l as signalSetupContract, n as createSignalPluginBase, p as resolveSignalOutboundTarget, r as signalSetupWizard, t as signalPlugin, u as prepareSignalManagedNativeTransport } from "./channel-CmbWaYlm.js";
6
6
  import { i as installSignalCli, o as looksLikeArchive, r as extractSignalCliArchive, s as pickAsset } from "./install-signal-cli-qJF7iC4h.js";
7
- import { t as monitorSignalProvider } from "./monitor-CHBkf7lk.js";
7
+ import { t as monitorSignalProvider } from "./monitor-BvIzkrYW.js";
8
8
  import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-B_Ks4MZr.js";
9
9
  import { t as probeSignal } from "./probe-Br9abaGp.js";
10
10
  //#region extensions/signal/src/channel.setup.ts
@@ -3,8 +3,8 @@ import { a as resolveSignalAccountConfig, i as resolveSignalAccount, n as listSi
3
3
  import { c as resolveSignalSender, d as normalizeSignalMessagingTarget, l as looksLikeUuid, o as resolveSignalPeerId, s as resolveSignalRecipient, u as looksLikeSignalTargetId } from "./identity-BQ4OzXXk.js";
4
4
  import { a as listSignalAliasDirectoryEntries, o as resolveSignalTarget } from "./runtime-JPrGlV7h.js";
5
5
  import { a as signalMessageActions, d as shouldSuppressLocalSignalExecApprovalPrompt, f as signalApprovalCapability, i as materializeSignalPresentationFallback, l as markdownToSignalTextChunks, o as resolveSignalReactionLevel, r as resolveSignalReplyContextWithPersistence, s as formatSignalMediaText, t as detectSignalTransport } from "./transport-detection-Da06-LOL.js";
6
- import { i as migrateLegacySignalTransportConfig, n as normalizeCompatibilityConfig, r as clearLegacySignalTransportFieldsForAccount } from "./doctor-contract-api-DbUCknbC.js";
7
- import { t as SignalChannelConfigSchema } from "./config-schema-DLvpQPN5.js";
6
+ import { i as migrateLegacySignalTransportConfig, n as normalizeCompatibilityConfig, r as clearLegacySignalTransportFieldsForAccount } from "./doctor-contract-api-Dcte-QYB.js";
7
+ import { t as SignalChannelConfigSchema } from "./config-schema-Y3dFGwdL.js";
8
8
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
9
9
  import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
10
10
  import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
@@ -579,7 +579,7 @@ const signalDoctor = {
579
579
  //#region extensions/signal/src/shared.ts
580
580
  const SIGNAL_CHANNEL = "signal";
581
581
  async function loadSignalChannelRuntime() {
582
- return await import("./channel.runtime-B8GXmuol.js");
582
+ return await import("./channel.runtime-BRDaqr5-.js");
583
583
  }
584
584
  const signalSetupWizard = createSignalSetupWizardProxy(async () => (await loadSignalChannelRuntime()).signalSetupWizard);
585
585
  const signalConfigAdapter = {
@@ -663,7 +663,7 @@ function createSignalPluginBase(params) {
663
663
  }
664
664
  //#endregion
665
665
  //#region extensions/signal/src/channel.ts
666
- const loadSignalMonitorModule = createLazyRuntimeModule(() => import("./monitor-CHBkf7lk.js").then((n) => n.n));
666
+ const loadSignalMonitorModule = createLazyRuntimeModule(() => import("./monitor-BvIzkrYW.js").then((n) => n.n));
667
667
  const loadSignalProbeModule = createLazyRuntimeModule(() => import("./probe-Br9abaGp.js").then((n) => n.n));
668
668
  const loadSignalSendRuntime = createLazyRuntimeModule(() => import("./send.runtime-CbuXycZT.js"));
669
669
  const loadSignalApprovalReactionsModule = createLazyRuntimeModule(() => import("./approval-reactions-DtL47gSK.js").then((n) => n.r));
@@ -1,2 +1,2 @@
1
- import { t as SignalChannelConfigSchema } from "./config-schema-DLvpQPN5.js";
1
+ import { t as SignalChannelConfigSchema } from "./config-schema-Y3dFGwdL.js";
2
2
  export { SignalChannelConfigSchema };
@@ -1,2 +1,2 @@
1
- import { t as signalPlugin } from "./channel-CBIljGCH.js";
1
+ import { t as signalPlugin } from "./channel-CmbWaYlm.js";
2
2
  export { signalPlugin };
@@ -1,5 +1,5 @@
1
1
  import { i as resolveSignalAccount, n as listSignalAccountIds } from "./accounts-DrStJjiK.js";
2
- import { c as signalNumberTextInput, i as createSignalCliPathTextInput, o as signalCompletionNote, s as signalDmPolicy } from "./channel-CBIljGCH.js";
2
+ import { c as signalNumberTextInput, i as createSignalCliPathTextInput, o as signalCompletionNote, s as signalDmPolicy } from "./channel-CmbWaYlm.js";
3
3
  import { i as installSignalCli } from "./install-signal-cli-qJF7iC4h.js";
4
4
  import { detectBinary } from "openclaw/plugin-sdk/setup-tools";
5
5
  import { createDetectedBinaryStatus, createSetupTranslator, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
@@ -1,6 +1,7 @@
1
1
  import { createChannelConfigUiHints } from "openclaw/plugin-sdk/channel-core";
2
2
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId, resolveAccountEntry } from "openclaw/plugin-sdk/account-resolution";
4
+ import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
4
5
  import { ChannelDeliveryStreamingConfigSchema, ChannelSendReadReceiptsSchema, ExecutableTokenSchema, ReplyToModeSchema, buildChannelConfigSchema, buildChannelReactionShape, buildCommonChannelAccountShape, buildGroupEntrySchema, requireAllowlistAllowFrom, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
5
6
  import { z } from "zod";
6
7
  //#region extensions/signal/src/config-ui-hints.ts
@@ -60,9 +61,6 @@ const SIGNAL_RETIRED_TRANSPORT_KEYS = [
60
61
  "ignoreStories"
61
62
  ];
62
63
  const SignalTransportUrlSchema = z.string().url().regex(/^[Hh][Tt][Tt][Pp][Ss]?:\/\/(?![^/?#]*@)/, "Expected http:// or https:// URL without embedded credentials");
63
- function isRecord$1(value) {
64
- return typeof value === "object" && value !== null && !Array.isArray(value);
65
- }
66
64
  function projectSignalConfigForUpdateValidation(value) {
67
65
  if (process.env.OPENCLAW_UPDATE_IN_PROGRESS !== "1" || !isRecord$1(value)) return value;
68
66
  const next = { ...value };
@@ -1,5 +1,6 @@
1
1
  import { a as isSignalManagedNativeConnectionUrlForBind, c as buildSignalTransportHttpUrl, i as assignSignalManagedNativePort, o as isValidSignalManagedNativePort, r as allocateSignalManagedNativePort, s as resolveLocalSignalTransportPort, u as normalizeSignalTransportUrl } from "./transport-policy-DxvSMHp9.js";
2
2
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-resolution";
3
+ import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
3
4
  import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor";
4
5
  //#region extensions/signal/src/config-compat.ts
5
6
  const LEGACY_TRANSPORT_FIELDS = [
@@ -18,11 +19,8 @@ const PENDING_LEGACY_INVALID_URL_WARNING = "- channels.signal: legacy httpUrl is
18
19
  const PENDING_LEGACY_INVALID_HOST_WARNING = "- channels.signal: legacy httpHost is invalid; keep the current config, correct httpHost, then run openclaw doctor --fix.";
19
20
  const PENDING_LEGACY_INVALID_PORT_WARNING = "- channels.signal: legacy httpPort must be an integer between 1 and 65535; correct httpPort, then run openclaw doctor --fix.";
20
21
  const PENDING_LEGACY_CONTAINER_ACCOUNT_WARNING = "- channels.signal: legacy container transport requires an account number; add channels.signal.account (or the relevant channels.signal.accounts.*.account) and rerun openclaw doctor --fix.";
21
- function isRecord$1(value) {
22
- return typeof value === "object" && value !== null && !Array.isArray(value);
23
- }
24
22
  function isSignalTransportConfig(value) {
25
- if (!isRecord$1(value)) return false;
23
+ if (!isRecord(value)) return false;
26
24
  if (value.kind === "managed-native") {
27
25
  if (value.httpPort !== void 0 && !isValidSignalManagedNativePort(value.httpPort)) return false;
28
26
  if (value.url === void 0) return true;
@@ -175,8 +173,8 @@ function hasRootSignalAccount(entries) {
175
173
  return entries.length === 1 || Boolean(optionalString(root?.account)) || isSignalTransportConfig(root?.transport);
176
174
  }
177
175
  function signalAccountIds(entries) {
178
- const accounts = isRecord$1(entries[0]?.accounts) ? entries[0].accounts : {};
179
- return Object.entries(accounts).filter(([, entry]) => isRecord$1(entry)).map(([accountId]) => accountId);
176
+ const accounts = isRecord(entries[0]?.accounts) ? entries[0].accounts : {};
177
+ return Object.entries(accounts).filter(([, entry]) => isRecord(entry)).map(([accountId]) => accountId);
180
178
  }
181
179
  function isDefaultSignalAccountId(accountId) {
182
180
  return Boolean(accountId?.trim()) && normalizeAccountId(accountId) === DEFAULT_ACCOUNT_ID;
@@ -186,10 +184,10 @@ function resolveSignalAccountKey(accounts, accountId) {
186
184
  return Object.keys(accounts).find((key) => Boolean(key.trim()) && normalizeAccountId(key) === normalizedAccountId);
187
185
  }
188
186
  function nestedDefaultOwnsEffectiveTransport(entries) {
189
- const accounts = isRecord$1(entries[0]?.accounts) ? entries[0].accounts : {};
187
+ const accounts = isRecord(entries[0]?.accounts) ? entries[0].accounts : {};
190
188
  const nestedDefaultKey = resolveSignalAccountKey(accounts, DEFAULT_ACCOUNT_ID);
191
189
  const nestedDefault = nestedDefaultKey ? accounts[nestedDefaultKey] : void 0;
192
- return isRecord$1(nestedDefault) && (isSignalTransportConfig(nestedDefault.transport) || hasLegacyFields(nestedDefault));
190
+ return isRecord(nestedDefault) && (isSignalTransportConfig(nestedDefault.transport) || hasLegacyFields(nestedDefault));
193
191
  }
194
192
  function isDiscardedTransportEntry(entries, index) {
195
193
  if (index === 0) return !hasRootSignalAccount(entries) || nestedDefaultOwnsEffectiveTransport(entries);
@@ -204,23 +202,23 @@ function shouldMaterializeTransport(entries, index) {
204
202
  function clearLegacySignalTransportFieldsForAccount(params) {
205
203
  const next = structuredClone(params.cfg);
206
204
  const signal = next.channels?.signal;
207
- if (!isRecord$1(signal)) return next;
205
+ if (!isRecord(signal)) return next;
208
206
  if (isDefaultSignalAccountId(params.accountId)) {
209
207
  clearLegacyTransportFields(signal);
210
208
  delete signal.apiMode;
211
- const accounts = isRecord$1(signal.accounts) ? signal.accounts : void 0;
209
+ const accounts = isRecord(signal.accounts) ? signal.accounts : void 0;
212
210
  const nestedDefaultKey = accounts ? resolveSignalAccountKey(accounts, DEFAULT_ACCOUNT_ID) : void 0;
213
211
  const nestedDefault = nestedDefaultKey ? accounts?.[nestedDefaultKey] : void 0;
214
- if (isRecord$1(nestedDefault)) {
212
+ if (isRecord(nestedDefault)) {
215
213
  clearLegacyTransportFields(nestedDefault);
216
214
  delete nestedDefault.transport;
217
215
  }
218
216
  return next;
219
217
  }
220
- const accounts = isRecord$1(signal.accounts) ? signal.accounts : void 0;
218
+ const accounts = isRecord(signal.accounts) ? signal.accounts : void 0;
221
219
  const accountKey = accounts ? resolveSignalAccountKey(accounts, params.accountId) : void 0;
222
220
  const account = accountKey ? accounts?.[accountKey] : void 0;
223
- if (isRecord$1(account)) clearLegacyTransportFields(account);
221
+ if (isRecord(account)) clearLegacyTransportFields(account);
224
222
  return next;
225
223
  }
226
224
  function allocateMigratedManagedPorts(params) {
@@ -239,12 +237,12 @@ function allocateMigratedManagedPorts(params) {
239
237
  const localConnectionPort = resolveLocalSignalTransportPort(transport.url);
240
238
  if (localConnectionPort !== void 0) reservedPorts.add(localConnectionPort);
241
239
  }
242
- if (index === canonicalDefaultIndex || isRecord$1(params.entries[index]?.transport)) reservedPorts.add(transport.httpPort ?? 8080);
240
+ if (index === canonicalDefaultIndex || isRecord(params.entries[index]?.transport)) reservedPorts.add(transport.httpPort ?? 8080);
243
241
  }
244
242
  return params.transports.map((transport, index) => {
245
243
  if (!transport || index === 0 && canonicalDefaultIndex !== 0) return transport;
246
244
  if (transport.kind !== "managed-native") return transport;
247
- if (isRecord$1(params.entries[index]?.transport) || index === canonicalDefaultIndex) return transport;
245
+ if (isRecord(params.entries[index]?.transport) || index === canonicalDefaultIndex) return transport;
248
246
  const rawPreferredPort = params.entries[index]?.httpPort;
249
247
  const preferredPort = typeof rawPreferredPort === "number" ? rawPreferredPort : transport.httpPort;
250
248
  const httpPort = allocateSignalManagedNativePort({
@@ -258,10 +256,10 @@ function allocateMigratedManagedPorts(params) {
258
256
  function applyMigratedSignalTransports(params) {
259
257
  const next = structuredClone(params.cfg);
260
258
  const nextSignal = next.channels?.signal;
261
- if (!isRecord$1(nextSignal)) return;
259
+ if (!isRecord(nextSignal)) return;
262
260
  const accountIds = signalAccountIds(params.entries);
263
- const nextAccounts = isRecord$1(nextSignal.accounts) ? nextSignal.accounts : {};
264
- const nextEntries = [nextSignal, ...Object.values(nextAccounts).filter(isRecord$1)];
261
+ const nextAccounts = isRecord(nextSignal.accounts) ? nextSignal.accounts : {};
262
+ const nextEntries = [nextSignal, ...Object.values(nextAccounts).filter(isRecord)];
265
263
  const rootIsAccount = hasRootSignalAccount(params.entries);
266
264
  const canonicalRootTransport = isSignalTransportConfig(params.entries[0]?.transport) ? params.entries[0].transport : void 0;
267
265
  for (const [index, entry] of nextEntries.entries()) {
@@ -280,17 +278,17 @@ function applyMigratedSignalTransports(params) {
280
278
  }
281
279
  function hasContainerTransportWithoutEffectiveAccount(cfg) {
282
280
  const signal = cfg.channels?.signal;
283
- if (!isRecord$1(signal)) return false;
284
- const accounts = isRecord$1(signal.accounts) ? signal.accounts : {};
281
+ if (!isRecord(signal)) return false;
282
+ const accounts = isRecord(signal.accounts) ? signal.accounts : {};
285
283
  const rootTransport = isSignalTransportConfig(signal.transport) ? signal.transport : void 0;
286
284
  const defaultKey = resolveSignalAccountKey(accounts, DEFAULT_ACCOUNT_ID);
287
285
  const defaultEntry = defaultKey ? accounts[defaultKey] : void 0;
288
- const defaultAccount = isRecord$1(defaultEntry) && defaultEntry.account !== void 0 ? optionalString(defaultEntry.account) : optionalString(signal.account);
286
+ const defaultAccount = isRecord(defaultEntry) && defaultEntry.account !== void 0 ? optionalString(defaultEntry.account) : optionalString(signal.account);
289
287
  const channelEnabled = signal.enabled !== false;
290
- const defaultEnabled = !isRecord$1(defaultEntry) || defaultEntry.enabled !== false;
288
+ const defaultEnabled = !isRecord(defaultEntry) || defaultEntry.enabled !== false;
291
289
  if (rootTransport?.kind === "container" && channelEnabled && defaultEnabled && !defaultAccount) return true;
292
290
  for (const [accountId, entry] of Object.entries(accounts)) {
293
- if (!isRecord$1(entry)) continue;
291
+ if (!isRecord(entry)) continue;
294
292
  if (!channelEnabled || entry.enabled === false) continue;
295
293
  const isDefaultAccount = isDefaultSignalAccountId(accountId);
296
294
  if ((isDefaultAccount && rootTransport ? rootTransport : isSignalTransportConfig(entry.transport) ? entry.transport : void 0)?.kind !== "container" || isDefaultAccount && rootTransport) continue;
@@ -300,17 +298,17 @@ function hasContainerTransportWithoutEffectiveAccount(cfg) {
300
298
  }
301
299
  async function migrateLegacySignalTransportConfig(params) {
302
300
  const signal = params.cfg.channels?.signal;
303
- if (!isRecord$1(signal)) return {
301
+ if (!isRecord(signal)) return {
304
302
  config: params.cfg,
305
303
  changes: []
306
304
  };
307
- const accounts = isRecord$1(signal.accounts) ? signal.accounts : {};
308
- if (!(Object.hasOwn(signal, "apiMode") || hasLegacyFields(signal) || Object.values(accounts).some((entry) => isRecord$1(entry) && hasLegacyFields(entry)))) return {
305
+ const accounts = isRecord(signal.accounts) ? signal.accounts : {};
306
+ if (!(Object.hasOwn(signal, "apiMode") || hasLegacyFields(signal) || Object.values(accounts).some((entry) => isRecord(entry) && hasLegacyFields(entry)))) return {
309
307
  config: params.cfg,
310
308
  changes: []
311
309
  };
312
310
  const apiMode = signal.apiMode;
313
- const entries = [signal, ...Object.values(accounts).filter(isRecord$1)];
311
+ const entries = [signal, ...Object.values(accounts).filter(isRecord)];
314
312
  const legacyResolutionEntries = entries.filter((_, index) => shouldMaterializeTransport(entries, index)).filter((entry) => !isSignalTransportConfig(entry.transport));
315
313
  const invalidDerivedEndpoint = findInvalidLegacyDerivedEndpoint(legacyResolutionEntries, signal);
316
314
  if (invalidDerivedEndpoint) return {
@@ -369,16 +367,16 @@ async function migrateLegacySignalTransportConfig(params) {
369
367
  }
370
368
  function migrateLegacySignalTransportConfigSync(cfg) {
371
369
  const signal = cfg.channels?.signal;
372
- if (!isRecord$1(signal)) return {
370
+ if (!isRecord(signal)) return {
373
371
  config: cfg,
374
372
  changes: []
375
373
  };
376
- const accounts = isRecord$1(signal.accounts) ? signal.accounts : {};
377
- if (!(Object.hasOwn(signal, "apiMode") || hasLegacyFields(signal) || Object.values(accounts).some((entry) => isRecord$1(entry) && hasLegacyFields(entry)))) return {
374
+ const accounts = isRecord(signal.accounts) ? signal.accounts : {};
375
+ if (!(Object.hasOwn(signal, "apiMode") || hasLegacyFields(signal) || Object.values(accounts).some((entry) => isRecord(entry) && hasLegacyFields(entry)))) return {
378
376
  config: cfg,
379
377
  changes: []
380
378
  };
381
- const entries = [signal, ...Object.values(accounts).filter(isRecord$1)];
379
+ const entries = [signal, ...Object.values(accounts).filter(isRecord)];
382
380
  const legacyResolutionEntries = entries.filter((_, index) => shouldMaterializeTransport(entries, index)).filter((entry) => !isSignalTransportConfig(entry.transport));
383
381
  const invalidDerivedEndpoint = findInvalidLegacyDerivedEndpoint(legacyResolutionEntries, signal);
384
382
  if (invalidDerivedEndpoint) return {
@@ -445,9 +443,6 @@ const RETIRED_SIGNAL_ACCOUNT_TRANSPORT_FIELDS = [
445
443
  "receiveMode",
446
444
  "ignoreStories"
447
445
  ];
448
- function isRecord(value) {
449
- return typeof value === "object" && value !== null && !Array.isArray(value);
450
- }
451
446
  function hasRetiredSignalAccountTransportFields(value) {
452
447
  return isRecord(value) && RETIRED_SIGNAL_ACCOUNT_TRANSPORT_FIELDS.some((field) => Object.hasOwn(value, field));
453
448
  }
@@ -1,2 +1,2 @@
1
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-api-DbUCknbC.js";
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-api-Dcte-QYB.js";
2
2
  export { legacyConfigRules, normalizeCompatibilityConfig };
@@ -8,17 +8,18 @@ import { n as sendReactionSignal } from "./reaction-runtime-api-BjeDyKcz.js";
8
8
  import { a as maybeResolveSignalApprovalReaction, l as resolveSignalApprovalConversationKey, s as registerSignalApprovalReactionTargetForDeliveredPayload, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-DtL47gSK.js";
9
9
  import { r as registerSignalQuestionReactionTargetForDeliveredPayload, t as maybeResolveSignalQuestionReaction } from "./question-reactions-Ds48FtEu.js";
10
10
  import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-B_Ks4MZr.js";
11
- import { bindIngressLifecycleToReplyOptions, createChannelIngressMonitor, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
11
+ import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
12
12
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
13
- import { detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
13
+ import { canonicalizeBase64, detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
14
14
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
15
- import { normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
15
+ import { normalizeNullableString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
16
16
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
17
17
  import { normalizeE164, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
18
- import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toHistoryMediaEntries, toInboundMediaFacts } from "openclaw/plugin-sdk/channel-inbound";
18
+ import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toHistoryMediaEntries, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
19
19
  import nodePath from "node:path";
20
20
  import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
21
21
  import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
22
+ import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
22
23
  import { resolveChannelGroupPolicy, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
23
24
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
24
25
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
@@ -387,17 +388,9 @@ function createSignalPendingInboundRegistry(accountId) {
387
388
  if (!conversationKey) return;
388
389
  for (const inboundKey of countsByConversation.get(conversationKey)?.keys() ?? []) cancelKey(inboundKey);
389
390
  };
390
- const completeAfter = (flush) => async (entries) => {
391
- try {
392
- await flush(entries);
393
- } finally {
394
- complete(entries);
395
- }
396
- };
397
391
  return {
398
392
  track,
399
393
  complete,
400
- completeAfter,
401
394
  cancelPendingOnAbort
402
395
  };
403
396
  }
@@ -612,7 +605,7 @@ function createSignalEventHandler(deps) {
612
605
  chatType: entry.isGroup ? "group" : "direct"
613
606
  });
614
607
  const replyThreading = resolveBatchedReplyThreadingPolicy(replyToMode, entry.isBatched === true);
615
- const media = toInboundMediaFacts(entry.media);
608
+ const media = await toInboundMediaFactsWithMetadata(entry.media);
616
609
  const ctxPayload = buildChannelInboundEventContext({
617
610
  channel: "signal",
618
611
  supplemental: { quote: entry.replyToBody ? {
@@ -664,7 +657,7 @@ function createSignalEventHandler(deps) {
664
657
  }
665
658
  });
666
659
  if (shouldLogVerbose()) {
667
- const preview = truncateUtf16Safe(body, 200).replace(/\\n/g, "\\\\n");
660
+ const preview = truncateUtf16Safe(body, 200).replace(/\r/g, "\\r").replace(/\n/g, "\\n");
668
661
  logVerbose(`signal inbound: from=${ctxPayload.From} len=${body.length} preview="${preview}"`);
669
662
  }
670
663
  const statusReactionTimestamp = resolveSignalStatusReactionTimestamp(entry);
@@ -682,7 +675,6 @@ function createSignalEventHandler(deps) {
682
675
  isDirect: !entry.isGroup,
683
676
  isGroup: entry.isGroup,
684
677
  isMentionableGroup: entry.isGroup,
685
- requireMention: entry.requireMention === true,
686
678
  canDetectMention: entry.canDetectMention === true,
687
679
  effectiveWasMentioned: entry.wasMentioned === true
688
680
  }));
@@ -872,15 +864,14 @@ function createSignalEventHandler(deps) {
872
864
  }
873
865
  });
874
866
  }
875
- async function flushSignalInboundEntries(entries) {
867
+ async function flushSignalInboundEntries(entries, lifecycle, settle) {
876
868
  const last = entries.at(-1);
877
869
  if (!last) return;
878
- const { lifecycle, settle } = fanInChannelIngressLifecycles(entries.map((entry) => entry.turnAdoptionLifecycle));
879
870
  if (entries.length === 1) {
880
- await handleSignalInboundMessage(lifecycle ? {
871
+ await handleSignalInboundMessage({
881
872
  ...last,
882
873
  turnAdoptionLifecycle: lifecycle
883
- } : last);
874
+ });
884
875
  await settle();
885
876
  return;
886
877
  }
@@ -894,14 +885,14 @@ function createSignalEventHandler(deps) {
894
885
  ...last,
895
886
  bodyText: combinedText,
896
887
  commandBody: combinedCommandBody,
897
- ...lifecycle ? { turnAdoptionLifecycle: lifecycle } : {},
888
+ turnAdoptionLifecycle: lifecycle,
898
889
  isBatched: true,
899
890
  nativeReplyBody: last.nativeReplyBody ?? last.bodyText,
900
891
  media: entries.flatMap((entry) => entry.media ?? [])
901
892
  });
902
893
  await settle();
903
894
  }
904
- async function retrySignalInboundFlush(entries, initialError) {
895
+ async function retrySignalInboundFlush(entries, lifecycle, settle, initialError) {
905
896
  let lastError = initialError;
906
897
  for (const [attemptIndex, delayMs] of RETRYABLE_FLUSH_RETRY_DELAYS_MS.entries()) {
907
898
  const attempt = attemptIndex + 1;
@@ -917,7 +908,7 @@ function createSignalEventHandler(deps) {
917
908
  }
918
909
  if (deps.abortSignal?.aborted) return;
919
910
  try {
920
- await flushSignalInboundEntries(entries);
911
+ await flushSignalInboundEntries(entries, lifecycle, settle);
921
912
  return;
922
913
  } catch (err) {
923
914
  if (deps.abortSignal?.aborted) return;
@@ -927,26 +918,41 @@ function createSignalEventHandler(deps) {
927
918
  }
928
919
  throw lastError;
929
920
  }
930
- const flushDebouncedSignalInboundEntries = async (entries) => {
931
- if (!entries.some((entry) => activeEnqueueEntries.has(entry)) && deps.abortSignal?.aborted) return;
932
- try {
933
- await flushSignalInboundEntries(entries);
934
- } catch (err) {
935
- if (!isSignalReplySessionInitConflictError(err)) throw err;
936
- if (deps.abortSignal?.aborted) return;
937
- const retryTask = retrySignalInboundFlush(entries, err).catch(async (terminalError) => {
938
- await Promise.all(entries.map((entry) => Promise.resolve(entry.turnAdoptionLifecycle?.onAbandoned())));
939
- throw terminalError;
940
- });
941
- deps.runTrackedTask?.(() => retryTask.catch(() => void 0));
942
- await retryTask;
943
- }
921
+ const flushDebouncedSignalInboundEntries = (entries, createFlush) => {
922
+ const { lifecycle, settle } = fanInChannelIngressLifecycles(entries.map((entry) => entry.turnAdoptionLifecycle));
923
+ return createFlush({
924
+ lifecycle,
925
+ dispatch: async (admissionLifecycle) => {
926
+ if (!entries.some((entry) => activeEnqueueEntries.has(entry)) && deps.abortSignal?.aborted) return;
927
+ try {
928
+ await flushSignalInboundEntries(entries, admissionLifecycle, settle);
929
+ } catch (err) {
930
+ if (!isSignalReplySessionInitConflictError(err)) throw err;
931
+ if (deps.abortSignal?.aborted) return;
932
+ await retrySignalInboundFlush(entries, admissionLifecycle, settle, err).catch(async (terminalError) => {
933
+ await Promise.all(entries.map((entry) => Promise.resolve(entry.turnAdoptionLifecycle?.onAbandoned())));
934
+ throw terminalError;
935
+ });
936
+ }
937
+ }
938
+ });
944
939
  };
945
940
  const reportSignalInboundFlushError = (err) => {
946
941
  deps.runtime.error?.(`signal debounce flush failed: ${String(err)}`);
947
942
  };
948
943
  const pendingInboundRegistry = createSignalPendingInboundRegistry(deps.accountId);
949
- const flushNormalSignalInboundEntries = pendingInboundRegistry.completeAfter(flushDebouncedSignalInboundEntries);
944
+ const trackSignalInboundFlush = (flush) => {
945
+ deps.runTrackedTask?.(() => flush.completion.catch(() => void 0));
946
+ return flush;
947
+ };
948
+ const flushNormalSignalInboundEntries = (entries, createFlush) => {
949
+ const flush = flushDebouncedSignalInboundEntries(entries, createFlush);
950
+ const completion = flush.completion.finally(() => pendingInboundRegistry.complete(entries));
951
+ return trackSignalInboundFlush({
952
+ admission: flush.admission,
953
+ completion
954
+ });
955
+ };
950
956
  const { debouncer } = createChannelInboundDebouncer({
951
957
  cfg: deps.cfg,
952
958
  channel: "signal",
@@ -966,7 +972,7 @@ function createSignalEventHandler(deps) {
966
972
  serializeImmediate: true,
967
973
  buildKey: (entry) => resolveSignalControlLaneKey(deps.accountId, entry),
968
974
  shouldDebounce: () => false,
969
- onFlush: flushDebouncedSignalInboundEntries,
975
+ onFlush: (entries, createFlush) => trackSignalInboundFlush(flushDebouncedSignalInboundEntries(entries, createFlush)),
970
976
  onError: reportSignalInboundFlushError
971
977
  });
972
978
  async function handleReactionOnlyInbound(params) {
@@ -1365,24 +1371,8 @@ function registerSignalReactionTargetsForDeliveredPayload(params) {
1365
1371
  }
1366
1372
  //#endregion
1367
1373
  //#region extensions/signal/src/signal-ingress.ts
1368
- const SIGNAL_INGRESS_COMPLETED_TTL_MS = 720 * 60 * 60 * 1e3;
1369
- const SIGNAL_INGRESS_COMPLETED_MAX_ENTRIES = 1e3;
1370
- const SIGNAL_INGRESS_FAILED_TTL_MS = 720 * 60 * 60 * 1e3;
1371
- const SIGNAL_INGRESS_FAILED_MAX_ENTRIES = 1e3;
1372
1374
  const SIGNAL_INGRESS_DRAIN_INTERVAL_MS = 1e3;
1373
- var SignalIngressPermanentError = class extends Error {
1374
- constructor(reason, message, options) {
1375
- super(message, options);
1376
- this.reason = reason;
1377
- this.name = "SignalIngressPermanentError";
1378
- }
1379
- };
1380
- function isRecord$1(value) {
1381
- return typeof value === "object" && value !== null && !Array.isArray(value);
1382
- }
1383
- function normalizeRawString(value) {
1384
- return typeof value === "string" && value.trim() ? value.trim() : null;
1385
- }
1375
+ const SignalIngressPermanentError = createChannelIngressError("SignalIngressPermanentError", { withReason: true });
1386
1376
  function normalizeTimestamp(value) {
1387
1377
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1388
1378
  }
@@ -1407,15 +1397,15 @@ function inspectSignalIngressEvent(event) {
1407
1397
  const dataMessage = resolveDataMessage(envelope);
1408
1398
  const reactionMessage = isRecord$1(envelope.reactionMessage) ? envelope.reactionMessage : null;
1409
1399
  if (!dataMessage && !reactionMessage) return null;
1410
- const senderUuid = normalizeRawString(envelope.sourceUuid);
1411
- const senderNumber = normalizeRawString(envelope.sourceNumber);
1400
+ const senderUuid = normalizeNullableString(envelope.sourceUuid);
1401
+ const senderNumber = normalizeNullableString(envelope.sourceNumber);
1412
1402
  const senderKey = senderUuid ? `uuid:${senderUuid}` : senderNumber ? `number:${senderNumber}` : null;
1413
1403
  if (!senderKey) throw new SignalIngressPermanentError("missing-sender", "Signal dispatchable envelope is missing sourceUuid/sourceNumber");
1414
1404
  const timestamp = normalizeTimestamp(envelope.timestamp) ?? normalizeTimestamp(dataMessage?.timestamp);
1415
1405
  if (timestamp === null) throw new SignalIngressPermanentError("missing-timestamp", "Signal dispatchable envelope is missing a stable timestamp");
1416
1406
  const dataGroup = isRecord$1(dataMessage?.groupInfo) ? dataMessage.groupInfo : null;
1417
1407
  const reactionGroup = isRecord$1(reactionMessage?.groupInfo) ? reactionMessage.groupInfo : null;
1418
- const groupId = normalizeRawString(dataGroup?.groupId) ?? normalizeRawString(reactionGroup?.groupId);
1408
+ const groupId = normalizeNullableString(dataGroup?.groupId) ?? normalizeNullableString(reactionGroup?.groupId);
1419
1409
  return {
1420
1410
  eventId: JSON.stringify([senderKey, timestamp]),
1421
1411
  laneKey: groupId ? `group:${groupId}` : `direct:${senderKey}`
@@ -1459,10 +1449,8 @@ async function startSignalIngressMonitor(params) {
1459
1449
  pollIntervalMs: SIGNAL_INGRESS_DRAIN_INTERVAL_MS,
1460
1450
  retention: {
1461
1451
  pruneIntervalMs: 0,
1462
- completedTtlMs: SIGNAL_INGRESS_COMPLETED_TTL_MS,
1463
- completedMaxEntries: SIGNAL_INGRESS_COMPLETED_MAX_ENTRIES,
1464
- failedTtlMs: SIGNAL_INGRESS_FAILED_TTL_MS,
1465
- failedMaxEntries: SIGNAL_INGRESS_FAILED_MAX_ENTRIES
1452
+ completedMaxEntries: 1e3,
1453
+ failedMaxEntries: 1e3
1466
1454
  },
1467
1455
  appendRetryDelaysMs: [0],
1468
1456
  drain: {
@@ -1680,7 +1668,9 @@ async function fetchAttachment(params) {
1680
1668
  });
1681
1669
  if (!result?.data) return null;
1682
1670
  if (estimateBase64DecodedBytes(result.data) > params.maxBytes) throw new Error(`Signal attachment ${attachment.id} exceeds ${(params.maxBytes / (1024 * 1024)).toFixed(0)}MB limit`);
1683
- const buffer = Buffer.from(result.data, "base64");
1671
+ const canonicalData = canonicalizeBase64(result.data);
1672
+ if (!canonicalData) throw new Error(`Signal attachment ${attachment.id} returned malformed base64 data`);
1673
+ const buffer = Buffer.from(canonicalData, "base64");
1684
1674
  const originalFilename = normalizeOptionalString(attachment.filename ?? void 0);
1685
1675
  const saved = await saveMediaBuffer(buffer, normalizeOptionalString(attachment.contentType ?? void 0) ?? await detectMime({
1686
1676
  buffer,
@@ -3,9 +3,9 @@ import { d as normalizeSignalMessagingTarget, u as looksLikeSignalTargetId } fro
3
3
  import { n as setSignalRuntime } from "./runtime-JPrGlV7h.js";
4
4
  import { a as signalMessageActions, o as resolveSignalReactionLevel } from "./transport-detection-Da06-LOL.js";
5
5
  import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BjeDyKcz.js";
6
- import { n as SignalConfigSchema } from "./config-schema-DLvpQPN5.js";
6
+ import { n as SignalConfigSchema } from "./config-schema-Y3dFGwdL.js";
7
7
  import { i as installSignalCli } from "./install-signal-cli-qJF7iC4h.js";
8
- import { t as monitorSignalProvider } from "./monitor-CHBkf7lk.js";
8
+ import { t as monitorSignalProvider } from "./monitor-BvIzkrYW.js";
9
9
  import { t as sendMessageSignal } from "./send-B_Ks4MZr.js";
10
10
  import { t as probeSignal } from "./probe-Br9abaGp.js";
11
11
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/signal",
3
- "version": "2026.7.2-beta.5",
3
+ "version": "2026.7.2-beta.7",
4
4
  "description": "OpenClaw Signal channel plugin",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -93,10 +93,10 @@
93
93
  "allowInvalidConfigRecovery": true
94
94
  },
95
95
  "compat": {
96
- "pluginApi": ">=2026.7.2-beta.5"
96
+ "pluginApi": ">=2026.7.2-beta.7"
97
97
  },
98
98
  "build": {
99
- "openclawVersion": "2026.7.2-beta.5",
99
+ "openclawVersion": "2026.7.2-beta.7",
100
100
  "bundledDist": false
101
101
  },
102
102
  "release": {
@@ -118,7 +118,7 @@
118
118
  "README.md"
119
119
  ],
120
120
  "peerDependencies": {
121
- "openclaw": ">=2026.7.2-beta.5"
121
+ "openclaw": ">=2026.7.2-beta.7"
122
122
  },
123
123
  "peerDependenciesMeta": {
124
124
  "openclaw": {