@openclaw/signal 2026.7.2-beta.1 → 2026.7.2-beta.3

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,12 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { a as resolveSignalReplyToMode, i as resolveSignalAccount } from "./accounts-Bz4HtP0g.js";
3
- 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 } from "./identity-BF8taj7g.js";
4
- import { a as resolveSignalReactionLevel, c as isSignalNativeApprovalHandlerConfigured, r as materializeSignalPresentationFallback, t as registerSignalReplyContext } from "./reply-authors-osU_SY4J.js";
5
- import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-aEM0kGhC.js";
6
- import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-DifyinAU.js";
7
- import { a as maybeResolveSignalApprovalReaction, l as resolveSignalApprovalConversationKey, s as registerSignalApprovalReactionTargetForDeliveredPayload, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-ChcS2rQN.js";
8
- import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-BWtA6h3J.js";
9
- import { createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
3
+ 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 } from "./identity-BQ4OzXXk.js";
4
+ import { t as getOptionalSignalRuntime } from "./runtime-B900w6lc.js";
5
+ import { a as resolveSignalReactionLevel, c as isSignalNativeApprovalHandlerConfigured, r as materializeSignalPresentationFallback, t as registerSignalReplyContext } from "./reply-authors-DcNqGpX-.js";
6
+ import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-DZD8DCai.js";
7
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-tPkUVRoL.js";
8
+ import { a as maybeResolveSignalApprovalReaction, l as resolveSignalApprovalConversationKey, s as registerSignalApprovalReactionTargetForDeliveredPayload, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-DcpZRS5-.js";
9
+ import { r as registerSignalQuestionReactionTargetForDeliveredPayload, t as maybeResolveSignalQuestionReaction } from "./question-reactions-BFFJNQsu.js";
10
+ import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-f6JLstd2.js";
11
+ import { DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions, createChannelIngressDrain, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
10
12
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
11
13
  import { detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
12
14
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
@@ -20,7 +22,7 @@ import { resolveChannelGroupPolicy, resolveChannelGroupRequireMention } from "op
20
22
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
21
23
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
22
24
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
23
- import { chunkTextWithMode, createReplyDispatcherWithTyping, createReplyReferencePlanner, dispatchInboundMessage, resolveChunkMode, resolveTextChunkLimit, settleReplyDispatcher } from "openclaw/plugin-sdk/reply-runtime";
25
+ import { chunkTextWithMode, createReplyReferencePlanner, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
24
26
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
25
27
  import { computeBackoff, createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, sleep, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
26
28
  import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
@@ -31,14 +33,14 @@ import { createInterface } from "node:readline";
31
33
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
32
34
  import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
33
35
  import { DEFAULT_EMOJIS, DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
34
- import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, runChannelInboundEvent, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
36
+ import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
35
37
  import { isControlCommandMessage } from "openclaw/plugin-sdk/command-detection";
36
- import { recordInboundSession, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
37
38
  import { createInternalHookEvent, fireAndForgetHook, toInternalMessageReceivedContext, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
38
39
  import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
39
40
  import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
40
41
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
41
42
  import { createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
43
+ import { upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
42
44
  import { listChatCommands, maybeResolveTextAlias, normalizeCommandBody } from "openclaw/plugin-sdk/command-auth-native";
43
45
  import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
44
46
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
@@ -412,7 +414,12 @@ function resolveSignalQuoteContext(params) {
412
414
  sourceNumber: params.dataMessage?.quote?.author ?? null,
413
415
  sourceUuid: params.dataMessage?.quote?.authorUuid ?? null
414
416
  });
415
- const quoteSenderAllowed = !params.isGroup || params.effectiveGroupAllow.length === 0 ? true : quoteSender ? isSignalSenderAllowed(quoteSender, params.effectiveGroupAllow) : false;
417
+ const quoteSenderAllowed = resolveInboundSupplementalSenderAllowed({
418
+ isGroup: params.isGroup,
419
+ groupPolicy: params.effectiveGroupAllow.length === 0 ? "open" : "allowlist",
420
+ allowFrom: params.effectiveGroupAllow,
421
+ isSenderAllowed: (allowFrom) => quoteSender ? isSignalSenderAllowed(quoteSender, allowFrom) : false
422
+ });
416
423
  const visibleQuote = filterChannelInboundQuoteContext(contextVisibilityMode, {
417
424
  body: quoteText,
418
425
  sender: quoteSender ? formatSignalSenderDisplay(quoteSender) : void 0,
@@ -661,6 +668,7 @@ function createSignalEventHandler(deps) {
661
668
  },
662
669
  route: {
663
670
  agentId: route.agentId,
671
+ dmScope: route.dmScope,
664
672
  accountId: route.accountId,
665
673
  routeSessionKey: route.sessionKey
666
674
  },
@@ -786,10 +794,12 @@ function createSignalEventHandler(deps) {
786
794
  allowImplicitCurrentMessage: replyToMode !== "off" && replyThreading?.implicitCurrentMessage !== "deny",
787
795
  state: { hasReplied: false }
788
796
  };
789
- const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({
797
+ const dispatcherOptions = {
790
798
  ...replyPipeline,
791
799
  humanDelay: resolveHumanDelayConfig(deps.cfg, route.agentId),
792
- typingCallbacks,
800
+ typingCallbacks
801
+ };
802
+ const delivery = {
793
803
  deliver: async (payload, _info) => {
794
804
  await deps.deliverReplies({
795
805
  cfg: deps.cfg,
@@ -809,7 +819,7 @@ function createSignalEventHandler(deps) {
809
819
  onError: (err, info) => {
810
820
  deps.runtime.error?.(danger(`signal ${info.kind} reply failed: ${String(err)}`));
811
821
  }
812
- });
822
+ };
813
823
  const inboundLastRouteSessionKey = resolveInboundLastRouteSessionKey({
814
824
  route,
815
825
  sessionKey: route.sessionKey
@@ -826,12 +836,14 @@ function createSignalEventHandler(deps) {
826
836
  raw: entry
827
837
  }),
828
838
  resolveTurn: () => ({
839
+ cfg: deps.cfg,
829
840
  channel: "signal",
830
841
  accountId: route.accountId,
831
- routeSessionKey: route.sessionKey,
832
- storePath,
842
+ route: {
843
+ agentId: route.agentId,
844
+ sessionKey: route.sessionKey
845
+ },
833
846
  ctxPayload,
834
- recordInboundSession,
835
847
  record: {
836
848
  updateLastRoute: !entry.isGroup ? {
837
849
  sessionKey: inboundLastRouteSessionKey,
@@ -865,41 +877,31 @@ function createSignalEventHandler(deps) {
865
877
  historyMap: deps.groupHistories,
866
878
  limit: deps.historyLimit
867
879
  },
868
- onPreDispatchFailure: () => settleReplyDispatcher({
869
- dispatcher,
870
- onSettled: () => markDispatchIdle()
871
- }),
872
- runDispatch: async () => {
873
- try {
874
- if (statusReactionController) statusReactionController.setThinking();
875
- return await dispatchInboundMessage({
876
- ctx: ctxPayload,
877
- cfg: deps.cfg,
878
- dispatcher,
879
- replyOptions: {
880
- ...replyOptions,
881
- disableBlockStreaming: typeof deps.blockStreaming === "boolean" ? !deps.blockStreaming : void 0,
882
- ...statusReactionController ? {
883
- allowProgressCallbacksWhenSourceDeliverySuppressed: true,
884
- allowToolLifecycleWhenProgressHidden: true,
885
- onToolStart: async (payload) => {
886
- const toolName = payload.name?.trim();
887
- if (toolName) await statusReactionController.setTool(toolName);
888
- },
889
- onCompactionStart: async () => {
890
- await statusReactionController.setCompacting();
891
- },
892
- onCompactionEnd: async () => {
893
- statusReactionController.cancelPending();
894
- await statusReactionController.setThinking();
895
- }
896
- } : {},
897
- onModelSelected
898
- }
899
- });
900
- } finally {
901
- markDispatchIdle();
902
- }
880
+ afterRecord: () => {
881
+ if (statusReactionController) statusReactionController.setThinking();
882
+ },
883
+ dispatcherOptions,
884
+ delivery,
885
+ sessionInitRetry: { delaysMs: [] },
886
+ replyOptions: {
887
+ ...entry.turnAdoptionLifecycle ? bindIngressLifecycleToReplyOptions(entry.turnAdoptionLifecycle) : {},
888
+ disableBlockStreaming: typeof deps.blockStreaming === "boolean" ? !deps.blockStreaming : void 0,
889
+ ...statusReactionController ? {
890
+ allowProgressCallbacksWhenSourceDeliverySuppressed: true,
891
+ allowToolLifecycleWhenProgressHidden: true,
892
+ onToolStart: async (payload) => {
893
+ const toolName = payload.name?.trim();
894
+ if (toolName) await statusReactionController.setTool(toolName);
895
+ },
896
+ onCompactionStart: async () => {
897
+ await statusReactionController.setCompacting();
898
+ },
899
+ onCompactionEnd: async () => {
900
+ statusReactionController.cancelPending();
901
+ await statusReactionController.setThinking();
902
+ }
903
+ } : {},
904
+ onModelSelected
903
905
  }
904
906
  }),
905
907
  onFinalize: (result) => {
@@ -919,20 +921,64 @@ function createSignalEventHandler(deps) {
919
921
  }
920
922
  });
921
923
  }
924
+ function buildFlushIngressLifecycle(entries) {
925
+ const lifecycles = entries.map((entry) => entry.turnAdoptionLifecycle).filter((lifecycle) => lifecycle !== void 0);
926
+ const [firstLifecycle] = lifecycles;
927
+ if (!firstLifecycle) return {
928
+ lifecycle: void 0,
929
+ settle: async () => {}
930
+ };
931
+ let handedOff = false;
932
+ const adoptAll = async () => {
933
+ for (const lifecycle of lifecycles) await lifecycle.onAdopted();
934
+ };
935
+ return {
936
+ lifecycle: {
937
+ abortSignal: lifecycles.length === 1 ? firstLifecycle.abortSignal : AbortSignal.any(lifecycles.map((lifecycle) => lifecycle.abortSignal)),
938
+ onAdopted: async () => {
939
+ handedOff = true;
940
+ await adoptAll();
941
+ },
942
+ onDeferred: () => {
943
+ handedOff = true;
944
+ for (const lifecycle of lifecycles) lifecycle.onDeferred();
945
+ },
946
+ onAdoptionFinalizing: () => {
947
+ for (const lifecycle of lifecycles) lifecycle.onAdoptionFinalizing();
948
+ },
949
+ onAbandoned: async () => {
950
+ handedOff = true;
951
+ await Promise.all(lifecycles.map((lifecycle) => Promise.resolve(lifecycle.onAbandoned())));
952
+ }
953
+ },
954
+ settle: async () => {
955
+ if (!handedOff) await adoptAll();
956
+ }
957
+ };
958
+ }
922
959
  async function flushSignalInboundEntries(entries) {
923
960
  const last = entries.at(-1);
924
961
  if (!last) return;
962
+ const { lifecycle, settle } = buildFlushIngressLifecycle(entries);
925
963
  if (entries.length === 1) {
926
- await handleSignalInboundMessage(last);
964
+ await handleSignalInboundMessage(lifecycle ? {
965
+ ...last,
966
+ turnAdoptionLifecycle: lifecycle
967
+ } : last);
968
+ await settle();
969
+ return;
970
+ }
971
+ const combinedText = entries.map((entry) => entry.bodyText).filter(Boolean).join("\n");
972
+ const combinedCommandBody = entries.map((entry) => entry.commandBody).filter(Boolean).join("\n");
973
+ if (!combinedText.trim()) {
974
+ await settle();
927
975
  return;
928
976
  }
929
- const combinedText = entries.map((entry) => entry.bodyText).filter(Boolean).join("\\n");
930
- const combinedCommandBody = entries.map((entry) => entry.commandBody).filter(Boolean).join("\\n");
931
- if (!combinedText.trim()) return;
932
977
  await handleSignalInboundMessage({
933
978
  ...last,
934
979
  bodyText: combinedText,
935
980
  commandBody: combinedCommandBody,
981
+ ...lifecycle ? { turnAdoptionLifecycle: lifecycle } : {},
936
982
  isBatched: true,
937
983
  nativeReplyBody: last.nativeReplyBody ?? last.bodyText,
938
984
  mediaPath: void 0,
@@ -940,6 +986,7 @@ function createSignalEventHandler(deps) {
940
986
  mediaPaths: void 0,
941
987
  mediaTypes: void 0
942
988
  });
989
+ await settle();
943
990
  }
944
991
  async function retrySignalInboundFlush(entries, initialError) {
945
992
  let lastError = initialError;
@@ -974,7 +1021,10 @@ function createSignalEventHandler(deps) {
974
1021
  } catch (err) {
975
1022
  if (!isSignalReplySessionInitConflictError(err)) throw err;
976
1023
  if (deps.abortSignal?.aborted) return;
977
- const retryTask = retrySignalInboundFlush(entries, err);
1024
+ const retryTask = retrySignalInboundFlush(entries, err).catch(async (terminalError) => {
1025
+ await Promise.all(entries.map((entry) => Promise.resolve(entry.turnAdoptionLifecycle?.onAbandoned())));
1026
+ throw terminalError;
1027
+ });
978
1028
  deps.runTrackedTask?.(() => retryTask.catch(() => void 0));
979
1029
  await retryTask;
980
1030
  }
@@ -988,13 +1038,11 @@ function createSignalEventHandler(deps) {
988
1038
  cfg: deps.cfg,
989
1039
  channel: "signal",
990
1040
  buildKey: (entry) => resolveSignalInboundDebounceKey(deps.accountId, entry),
991
- shouldDebounce: (entry) => {
992
- return shouldDebounceTextInbound({
993
- text: entry.commandBody,
994
- cfg: deps.cfg,
995
- hasMedia: Boolean(entry.mediaPath || entry.mediaType || entry.mediaPaths?.length)
996
- });
997
- },
1041
+ shouldDebounce: (entry) => shouldDebounceTextInbound({
1042
+ text: entry.commandBody,
1043
+ cfg: deps.cfg,
1044
+ hasMedia: Boolean(entry.mediaPath || entry.mediaType || entry.mediaPaths?.length)
1045
+ }),
998
1046
  onFlush: flushNormalSignalInboundEntries,
999
1047
  onError: reportSignalInboundFlushError,
1000
1048
  onCancel: pendingInboundRegistry.complete
@@ -1034,6 +1082,18 @@ function createSignalEventHandler(deps) {
1034
1082
  logVerbose(`Blocked signal reaction sender ${params.senderDisplay} (${params.accessDecision.reasonCode})`);
1035
1083
  return true;
1036
1084
  }
1085
+ if (conversationKey && await maybeResolveSignalQuestionReaction({
1086
+ cfg: deps.cfg,
1087
+ accountId: deps.accountId,
1088
+ conversationKey,
1089
+ messageId,
1090
+ reactionKey: emojiLabel,
1091
+ isRemove: Boolean(params.reaction.isRemove),
1092
+ actorId: formatSignalSenderId(params.sender),
1093
+ targetAuthor: params.reaction.targetAuthor,
1094
+ targetAuthorUuid: params.reaction.targetAuthorUuid,
1095
+ logDebug: logVerbose
1096
+ })) return true;
1037
1097
  const targets = deps.resolveSignalReactionTargets(params.reaction);
1038
1098
  if (!deps.shouldEmitSignalReactionNotification({
1039
1099
  mode: deps.reactionMode,
@@ -1073,7 +1133,7 @@ function createSignalEventHandler(deps) {
1073
1133
  });
1074
1134
  return true;
1075
1135
  }
1076
- return async (event) => {
1136
+ return async (event, turnAdoptionLifecycle) => {
1077
1137
  if (event.event !== "receive" || !event.data) return;
1078
1138
  let payload;
1079
1139
  try {
@@ -1372,7 +1432,8 @@ function createSignalEventHandler(deps) {
1372
1432
  wasMentioned: effectiveWasMentioned,
1373
1433
  replyToBody: visibleQuoteText || void 0,
1374
1434
  replyToSender: visibleQuoteSender,
1375
- replyToIsQuote: visibleQuoteText ? true : void 0
1435
+ replyToIsQuote: visibleQuoteText ? true : void 0,
1436
+ turnAdoptionLifecycle
1376
1437
  };
1377
1438
  pendingInboundRegistry.cancelPendingOnAbort(entry, debouncer.cancelKey);
1378
1439
  const inboundLane = resolveSignalControlLaneKey(deps.accountId, entry) ? controlDebouncer : debouncer;
@@ -1383,6 +1444,169 @@ function createSignalEventHandler(deps) {
1383
1444
  } finally {
1384
1445
  activeEnqueueEntries.delete(entry);
1385
1446
  }
1447
+ if (turnAdoptionLifecycle) return { kind: "deferred" };
1448
+ };
1449
+ }
1450
+ //#endregion
1451
+ //#region extensions/signal/src/reaction-targets.ts
1452
+ function registerSignalReactionTargetsForDeliveredPayload(params) {
1453
+ registerSignalQuestionReactionTargetForDeliveredPayload(params);
1454
+ registerSignalApprovalReactionTargetForDeliveredPayload(params);
1455
+ }
1456
+ //#endregion
1457
+ //#region extensions/signal/src/signal-ingress.ts
1458
+ const SIGNAL_INGRESS_COMPLETED_TTL_MS = 720 * 60 * 60 * 1e3;
1459
+ const SIGNAL_INGRESS_COMPLETED_MAX_ENTRIES = 1e3;
1460
+ const SIGNAL_INGRESS_FAILED_TTL_MS = 720 * 60 * 60 * 1e3;
1461
+ const SIGNAL_INGRESS_FAILED_MAX_ENTRIES = 1e3;
1462
+ const SIGNAL_INGRESS_DRAIN_INTERVAL_MS = 1e3;
1463
+ var SignalIngressPermanentError = class extends Error {
1464
+ constructor(reason, message, options) {
1465
+ super(message, options);
1466
+ this.reason = reason;
1467
+ this.name = "SignalIngressPermanentError";
1468
+ }
1469
+ };
1470
+ function isRecord(value) {
1471
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1472
+ }
1473
+ function normalizeRawString(value) {
1474
+ return typeof value === "string" && value.trim() ? value.trim() : null;
1475
+ }
1476
+ function normalizeTimestamp(value) {
1477
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1478
+ }
1479
+ function parseReceiveEnvelope(event) {
1480
+ if (event.event !== "receive" || !event.data) return null;
1481
+ let parsed;
1482
+ try {
1483
+ parsed = JSON.parse(event.data);
1484
+ } catch (error) {
1485
+ throw new SignalIngressPermanentError("parse-error", "Signal receive event contains invalid JSON", { cause: error });
1486
+ }
1487
+ if (!isRecord(parsed)) throw new SignalIngressPermanentError("parse-error", "Signal receive event must contain a JSON object");
1488
+ return isRecord(parsed.envelope) ? parsed.envelope : null;
1489
+ }
1490
+ function resolveDataMessage(envelope) {
1491
+ if (isRecord(envelope.dataMessage)) return envelope.dataMessage;
1492
+ return isRecord(envelope.editMessage?.dataMessage) ? envelope.editMessage.dataMessage : null;
1493
+ }
1494
+ function inspectSignalIngressEvent(event) {
1495
+ const envelope = parseReceiveEnvelope(event);
1496
+ if (!envelope || "syncMessage" in envelope) return null;
1497
+ const dataMessage = resolveDataMessage(envelope);
1498
+ const reactionMessage = isRecord(envelope.reactionMessage) ? envelope.reactionMessage : null;
1499
+ if (!dataMessage && !reactionMessage) return null;
1500
+ const senderUuid = normalizeRawString(envelope.sourceUuid);
1501
+ const senderNumber = normalizeRawString(envelope.sourceNumber);
1502
+ const senderKey = senderUuid ? `uuid:${senderUuid}` : senderNumber ? `number:${senderNumber}` : null;
1503
+ if (!senderKey) throw new SignalIngressPermanentError("missing-sender", "Signal dispatchable envelope is missing sourceUuid/sourceNumber");
1504
+ const timestamp = normalizeTimestamp(envelope.timestamp) ?? normalizeTimestamp(dataMessage?.timestamp);
1505
+ if (timestamp === null) throw new SignalIngressPermanentError("missing-timestamp", "Signal dispatchable envelope is missing a stable timestamp");
1506
+ const dataGroup = isRecord(dataMessage?.groupInfo) ? dataMessage.groupInfo : null;
1507
+ const reactionGroup = isRecord(reactionMessage?.groupInfo) ? reactionMessage.groupInfo : null;
1508
+ const groupId = normalizeRawString(dataGroup?.groupId) ?? normalizeRawString(reactionGroup?.groupId);
1509
+ return {
1510
+ eventId: JSON.stringify([senderKey, timestamp]),
1511
+ laneKey: groupId ? `group:${groupId}` : `direct:${senderKey}`
1512
+ };
1513
+ }
1514
+ /** Durable receive chokepoint. Metadata comes from raw fields; payload stays byte-equivalent JSON. */
1515
+ async function enqueueSignalIngressEvent(params) {
1516
+ const facts = inspectSignalIngressEvent(params.event);
1517
+ if (!facts) return { kind: "ignored" };
1518
+ const receivedAt = params.now ?? Date.now();
1519
+ await params.queue.prune({
1520
+ completedTtlMs: SIGNAL_INGRESS_COMPLETED_TTL_MS,
1521
+ completedMaxEntries: SIGNAL_INGRESS_COMPLETED_MAX_ENTRIES,
1522
+ failedTtlMs: SIGNAL_INGRESS_FAILED_TTL_MS,
1523
+ failedMaxEntries: SIGNAL_INGRESS_FAILED_MAX_ENTRIES,
1524
+ ...params.now === void 0 ? {} : { now: params.now }
1525
+ });
1526
+ return await params.queue.enqueue(facts.eventId, {
1527
+ version: 1,
1528
+ receivedAt,
1529
+ event: params.event
1530
+ }, {
1531
+ receivedAt,
1532
+ laneKey: facts.laneKey
1533
+ });
1534
+ }
1535
+ function resolveSignalIngressNonRetryableFailure(error) {
1536
+ return error instanceof SignalIngressPermanentError ? {
1537
+ reason: error.reason,
1538
+ message: error.message
1539
+ } : null;
1540
+ }
1541
+ function createSignalIngressDrain(params) {
1542
+ return createChannelIngressDrain({
1543
+ queue: params.queue,
1544
+ retryPolicy: {
1545
+ maxAttempts: DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
1546
+ deadLetterMinAgeMs: DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS
1547
+ },
1548
+ resolveNonRetryableFailure: resolveSignalIngressNonRetryableFailure,
1549
+ ...params.abortSignal ? { abortSignal: params.abortSignal } : {},
1550
+ ...params.onLog ? { onLog: params.onLog } : {},
1551
+ dispatchClaimedEvent: async (record, lifecycle) => {
1552
+ if (!inspectSignalIngressEvent(record.payload.event)) throw new SignalIngressPermanentError("unsupported-event", "Signal ingress row no longer contains a dispatchable envelope");
1553
+ return await params.dispatch(record.payload.event, lifecycle);
1554
+ }
1555
+ });
1556
+ }
1557
+ /** Open the account queue, recover it, and keep newly appended rows draining. */
1558
+ async function startSignalIngressMonitor(params) {
1559
+ let queue = params.queue;
1560
+ if (!queue) {
1561
+ const pluginRuntime = getOptionalSignalRuntime();
1562
+ if (!pluginRuntime) throw new Error("Signal runtime not initialized for durable ingress");
1563
+ queue = pluginRuntime.state.openChannelIngressQueue({ accountId: params.accountId });
1564
+ }
1565
+ const drain = createSignalIngressDrain({
1566
+ queue,
1567
+ dispatch: params.dispatch,
1568
+ onLog: (message) => params.runtime.log?.(`signal ${message}`)
1569
+ });
1570
+ let drainPass;
1571
+ let drainRequested = false;
1572
+ const requestDrain = () => {
1573
+ drainRequested = true;
1574
+ if (!drainPass) drainPass = (async () => {
1575
+ while (drainRequested) {
1576
+ drainRequested = false;
1577
+ if ((await drain.drainOnce()).started > 0) params.runTrackedTask(async () => {
1578
+ await drain.waitForIdle();
1579
+ await requestDrain();
1580
+ });
1581
+ }
1582
+ })().finally(() => {
1583
+ drainPass = void 0;
1584
+ if (drainRequested) requestDrain().catch((error) => {
1585
+ params.runtime.error?.(`signal ingress drain failed: ${String(error)}`);
1586
+ });
1587
+ });
1588
+ return drainPass;
1589
+ };
1590
+ await requestDrain();
1591
+ const timer = setInterval(() => {
1592
+ requestDrain().catch((error) => {
1593
+ params.runtime.error?.(`signal ingress drain failed: ${String(error)}`);
1594
+ });
1595
+ }, SIGNAL_INGRESS_DRAIN_INTERVAL_MS);
1596
+ timer.unref?.();
1597
+ return {
1598
+ receive: async (event) => {
1599
+ if ((await enqueueSignalIngressEvent({
1600
+ queue,
1601
+ event
1602
+ })).kind !== "ignored") await requestDrain();
1603
+ },
1604
+ stop: async () => {
1605
+ clearInterval(timer);
1606
+ await drainPass?.catch(() => void 0);
1607
+ await drain.waitForIdle();
1608
+ drain.dispose();
1609
+ }
1386
1610
  };
1387
1611
  }
1388
1612
  //#endregion
@@ -1412,9 +1636,9 @@ async function runSignalSseLoop({ baseUrl, account, abortSignal, runtime, onEven
1412
1636
  abortSignal,
1413
1637
  timeoutMs,
1414
1638
  apiMode,
1415
- onEvent: (event) => {
1639
+ onEvent: async (event) => {
1416
1640
  reconnectAttempts = 0;
1417
- onEvent(event);
1641
+ await onEvent(event);
1418
1642
  },
1419
1643
  logger: {
1420
1644
  log: runtime.log,
@@ -1447,15 +1671,15 @@ var monitor_exports = /* @__PURE__ */ __exportAll({
1447
1671
  deliverReplies: () => deliverReplies,
1448
1672
  monitorSignalProvider: () => monitorSignalProvider
1449
1673
  });
1450
- function resolveRuntime(opts) {
1451
- return opts.runtime ?? createNonExitingRuntime();
1452
- }
1453
1674
  function createSignalMonitorTaskRunner(runtime) {
1454
1675
  const inFlight = /* @__PURE__ */ new Set();
1455
1676
  return {
1456
1677
  runTask(task) {
1457
- const trackedTask = Promise.resolve().then(task).catch((err) => runtime.error?.(`signal monitor task failed: ${String(err)}`)).finally(() => inFlight.delete(trackedTask));
1678
+ const trackedTask = Promise.resolve().then(task);
1458
1679
  inFlight.add(trackedTask);
1680
+ trackedTask.catch((err) => runtime.error?.(`signal monitor task failed: ${String(err)}`));
1681
+ trackedTask.finally(() => inFlight.delete(trackedTask)).catch(() => void 0);
1682
+ return trackedTask;
1459
1683
  },
1460
1684
  async waitForIdle() {
1461
1685
  while (inFlight.size > 0) await Promise.allSettled(inFlight);
@@ -1465,13 +1689,16 @@ function createSignalMonitorTaskRunner(runtime) {
1465
1689
  function createSignalDaemonLifecycle(params) {
1466
1690
  let daemonHandle = null;
1467
1691
  let daemonStopRequested = false;
1692
+ let daemonStopPromise;
1468
1693
  let daemonExitError;
1469
1694
  const daemonAbortController = new AbortController();
1470
1695
  const abortSignal = params.abortSignal ? AbortSignal.any([params.abortSignal, daemonAbortController.signal]) : daemonAbortController.signal;
1471
1696
  const stop = () => {
1697
+ if (daemonStopPromise) return daemonStopPromise;
1472
1698
  daemonStopRequested = true;
1473
1699
  if (!daemonAbortController.signal.aborted) daemonAbortController.abort(params.abortSignal?.reason ?? /* @__PURE__ */ new Error("Signal monitor stopped"));
1474
- return daemonHandle?.stop() ?? Promise.resolve();
1700
+ daemonStopPromise = daemonHandle?.stop() ?? Promise.resolve();
1701
+ return daemonStopPromise;
1475
1702
  };
1476
1703
  const attach = (handle) => {
1477
1704
  daemonHandle = handle;
@@ -1489,9 +1716,6 @@ function createSignalDaemonLifecycle(params) {
1489
1716
  abortSignal
1490
1717
  };
1491
1718
  }
1492
- function normalizeAllowList(raw) {
1493
- return normalizeStringEntries(raw);
1494
- }
1495
1719
  function resolveSignalReactionTargets(reaction) {
1496
1720
  const targets = [];
1497
1721
  const uuid = reaction.targetAuthorUuid?.trim();
@@ -1654,7 +1878,7 @@ async function deliverReplies(params) {
1654
1878
  }), visibleText);
1655
1879
  }
1656
1880
  }) !== "empty") {
1657
- registerSignalApprovalReactionTargetForDeliveredPayload({
1881
+ registerSignalReactionTargetsForDeliveredPayload({
1658
1882
  cfg: params.cfg,
1659
1883
  target: {
1660
1884
  channel: "signal",
@@ -1712,7 +1936,7 @@ function createSignalNativeReplyResolver(params) {
1712
1936
  };
1713
1937
  }
1714
1938
  async function monitorSignalProvider(opts = {}) {
1715
- const runtime = resolveRuntime(opts);
1939
+ const runtime = opts.runtime ?? createNonExitingRuntime();
1716
1940
  const cfg = opts.config ?? getRuntimeConfig();
1717
1941
  const accountInfo = resolveSignalAccount({
1718
1942
  cfg,
@@ -1725,8 +1949,8 @@ async function monitorSignalProvider(opts = {}) {
1725
1949
  const baseUrl = normalizeOptionalString(opts.baseUrl) ?? accountInfo.baseUrl;
1726
1950
  const account = normalizeOptionalString(opts.account) ?? normalizeOptionalString(accountInfo.config.account);
1727
1951
  const dmPolicy = accountInfo.config.dmPolicy ?? "pairing";
1728
- const allowFrom = normalizeAllowList(opts.allowFrom ?? accountInfo.config.allowFrom);
1729
- const groupAllowFrom = normalizeAllowList(opts.groupAllowFrom ?? accountInfo.config.groupAllowFrom ?? (accountInfo.config.allowFrom && accountInfo.config.allowFrom.length > 0 ? accountInfo.config.allowFrom : []));
1952
+ const allowFrom = normalizeStringEntries(opts.allowFrom ?? accountInfo.config.allowFrom);
1953
+ const groupAllowFrom = normalizeStringEntries(opts.groupAllowFrom ?? accountInfo.config.groupAllowFrom ?? (accountInfo.config.allowFrom && accountInfo.config.allowFrom.length > 0 ? accountInfo.config.allowFrom : []));
1730
1954
  const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
1731
1955
  const { groupPolicy, providerMissingFallbackApplied } = resolveAllowlistProviderRuntimeGroupPolicy({
1732
1956
  providerConfigPresent: cfg.channels?.signal !== void 0,
@@ -1740,7 +1964,7 @@ async function monitorSignalProvider(opts = {}) {
1740
1964
  log: (message) => runtime.log?.(message)
1741
1965
  });
1742
1966
  const reactionMode = accountInfo.config.reactionNotifications ?? "own";
1743
- const reactionAllowlist = normalizeAllowList(accountInfo.config.reactionAllowlist);
1967
+ const reactionAllowlist = normalizeStringEntries(accountInfo.config.reactionAllowlist);
1744
1968
  const mediaMaxBytes = (opts.mediaMaxMb ?? accountInfo.config.mediaMaxMb ?? 8) * 1024 * 1024;
1745
1969
  const ignoreAttachments = opts.ignoreAttachments ?? accountInfo.config.ignoreAttachments ?? false;
1746
1970
  const sendReadReceipts = Boolean(opts.sendReadReceipts ?? accountInfo.config.sendReadReceipts);
@@ -1752,6 +1976,7 @@ async function monitorSignalProvider(opts = {}) {
1752
1976
  const daemonLifecycle = createSignalDaemonLifecycle({ abortSignal: opts.abortSignal });
1753
1977
  const monitorTaskRunner = createSignalMonitorTaskRunner(runtime);
1754
1978
  let daemonHandle = null;
1979
+ let ingressMonitor;
1755
1980
  if (autoStart && configuredApiMode === "container") throw new Error("channels.signal.autoStart=true is incompatible with channels.signal.apiMode=container");
1756
1981
  if (autoStart) {
1757
1982
  const cliPath = opts.cliPath ?? accountInfo.config.cliPath ?? "signal-cli";
@@ -1772,9 +1997,7 @@ async function monitorSignalProvider(opts = {}) {
1772
1997
  });
1773
1998
  daemonLifecycle.attach(daemonHandle);
1774
1999
  }
1775
- const onAbort = () => {
1776
- daemonLifecycle.stop();
1777
- };
2000
+ const onAbort = () => void daemonLifecycle.stop();
1778
2001
  opts.abortSignal?.addEventListener("abort", onAbort, { once: true });
1779
2002
  try {
1780
2003
  if (daemonHandle) {
@@ -1809,7 +2032,9 @@ async function monitorSignalProvider(opts = {}) {
1809
2032
  const handleEvent = createSignalEventHandler({
1810
2033
  runtime,
1811
2034
  abortSignal: daemonLifecycle.abortSignal,
1812
- runTrackedTask: (task) => monitorTaskRunner.runTask(task),
2035
+ runTrackedTask: (task) => {
2036
+ monitorTaskRunner.runTask(task);
2037
+ },
1813
2038
  cfg,
1814
2039
  baseUrl,
1815
2040
  account,
@@ -1843,6 +2068,14 @@ async function monitorSignalProvider(opts = {}) {
1843
2068
  shouldEmitSignalReactionNotification,
1844
2069
  buildSignalReactionSystemEventText
1845
2070
  });
2071
+ ingressMonitor = await startSignalIngressMonitor({
2072
+ accountId: accountInfo.accountId,
2073
+ dispatch: handleEvent,
2074
+ runtime,
2075
+ runTrackedTask: (task) => {
2076
+ monitorTaskRunner.runTask(task);
2077
+ }
2078
+ });
1846
2079
  await runSignalSseLoop({
1847
2080
  baseUrl,
1848
2081
  account,
@@ -1851,9 +2084,7 @@ async function monitorSignalProvider(opts = {}) {
1851
2084
  timeoutMs: 0,
1852
2085
  apiMode: configuredApiMode,
1853
2086
  policy: opts.reconnectPolicy,
1854
- onEvent: (event) => {
1855
- monitorTaskRunner.runTask(() => handleEvent(event));
1856
- }
2087
+ onEvent: (event) => monitorTaskRunner.runTask(async () => await ingressMonitor?.receive(event))
1857
2088
  });
1858
2089
  const daemonExitError = daemonLifecycle.getExitError();
1859
2090
  if (daemonExitError) throw daemonExitError;
@@ -1862,6 +2093,7 @@ async function monitorSignalProvider(opts = {}) {
1862
2093
  if (opts.abortSignal?.aborted && !daemonExitError) return;
1863
2094
  throw err;
1864
2095
  } finally {
2096
+ await ingressMonitor?.stop();
1865
2097
  await Promise.all([daemonLifecycle.stop(), monitorTaskRunner.waitForIdle()]);
1866
2098
  opts.abortSignal?.removeEventListener("abort", onAbort);
1867
2099
  }
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-aEM0kGhC.js";
2
+ import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-DZD8DCai.js";
3
3
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
4
4
  //#region extensions/signal/src/probe.ts
5
5
  var probe_exports = /* @__PURE__ */ __exportAll({ probeSignal: () => probeSignal });