@openclaw/signal 2026.7.2-beta.7 → 2026.8.1-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.
Files changed (29) hide show
  1. package/dist/api.js +7 -7
  2. package/dist/{approval-handler.runtime-C-XYgcxE.js → approval-handler.runtime-Bi5yNhnj.js} +2 -2
  3. package/dist/{approval-reactions-DtL47gSK.js → approval-reactions-BulCzhdB.js} +15 -45
  4. package/dist/{channel-CmbWaYlm.js → channel-CzDJMDtX.js} +50 -91
  5. package/dist/channel-config-api.js +1 -1
  6. package/dist/channel-plugin-api.js +1 -1
  7. package/dist/{channel.runtime-BRDaqr5-.js → channel.runtime-D12iYzJB.js} +2 -2
  8. package/dist/{client-BazXTH7O.js → client-CvnR8_XA.js} +31 -34
  9. package/dist/{client-adapter-mXGeEHQJ.js → client-adapter-D9SNPaNx.js} +13 -6
  10. package/dist/{config-schema-Y3dFGwdL.js → config-schema-CkMIK7zp.js} +4 -5
  11. package/dist/contract-api.js +1 -1
  12. package/dist/{doctor-contract-api-Dcte-QYB.js → doctor-contract-api-Dnp5eQQJ.js} +19 -22
  13. package/dist/doctor-contract-api.js +1 -1
  14. package/dist/{install-signal-cli-qJF7iC4h.js → install-signal-cli-Cpiyk0k1.js} +8 -5
  15. package/dist/{monitor-BvIzkrYW.js → monitor-DiN2hKnG.js} +128 -67
  16. package/dist/{probe-Br9abaGp.js → probe-C_THuQzn.js} +23 -7
  17. package/dist/question-reactions-ByjH5qST.js +80 -0
  18. package/dist/{reaction-runtime-api-BjeDyKcz.js → reaction-runtime-api-Cf6QHcE5.js} +8 -19
  19. package/dist/reaction-runtime-api.js +1 -1
  20. package/dist/runtime-api.js +7 -7
  21. package/dist/{send-B_Ks4MZr.js → send-BvGgByMd.js} +12 -3
  22. package/dist/{send.runtime-CbuXycZT.js → send.runtime-CtTBBrjL.js} +1 -1
  23. package/dist/{transport-detection-Da06-LOL.js → transport-detection-BItHawMq.js} +50 -145
  24. package/dist/{transport-detection.runtime-D_190UUL.js → transport-detection.runtime-DoiwPcrG.js} +1 -1
  25. package/dist/{transport-probes.runtime-CV6Qs0Og.js → transport-probes.runtime-BsJdp0oI.js} +1 -1
  26. package/openclaw.plugin.json +4 -1
  27. package/package.json +5 -5
  28. package/dist/approval-resolver-B3m0D2R0.js +0 -16
  29. package/dist/question-reactions-Ds48FtEu.js +0 -109
@@ -2,25 +2,24 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
2
  import { i as resolveSignalAccount, o as resolveSignalReplyToMode } from "./accounts-DrStJjiK.js";
3
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
4
  import { t as getOptionalSignalRuntime } from "./runtime-JPrGlV7h.js";
5
- import { i as materializeSignalPresentationFallback, n as registerSignalReplyContext, o as resolveSignalReactionLevel, s as formatSignalMediaText, u as isSignalNativeApprovalHandlerConfigured } from "./transport-detection-Da06-LOL.js";
6
- import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-mXGeEHQJ.js";
7
- import { n as sendReactionSignal } from "./reaction-runtime-api-BjeDyKcz.js";
8
- import { a as maybeResolveSignalApprovalReaction, l as resolveSignalApprovalConversationKey, s as registerSignalApprovalReactionTargetForDeliveredPayload, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-DtL47gSK.js";
9
- import { r as registerSignalQuestionReactionTargetForDeliveredPayload, t as maybeResolveSignalQuestionReaction } from "./question-reactions-Ds48FtEu.js";
10
- import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-B_Ks4MZr.js";
5
+ import { i as materializeSignalPresentationFallback, n as registerSignalReplyContext, o as resolveSignalReactionLevel, s as formatSignalMediaText, u as isSignalNativeApprovalHandlerConfigured } from "./transport-detection-BItHawMq.js";
6
+ import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-D9SNPaNx.js";
7
+ import { n as sendReactionSignal } from "./reaction-runtime-api-Cf6QHcE5.js";
8
+ import { a as maybeResolveSignalApprovalReaction, l as resolveSignalApprovalConversationKey, s as registerSignalApprovalReactionTargetForDeliveredPayload, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-BulCzhdB.js";
9
+ import { r as registerSignalQuestionReactionTargetForDeliveredPayload, t as maybeResolveSignalQuestionReaction } from "./question-reactions-ByjH5qST.js";
10
+ import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-BvGgByMd.js";
11
11
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
12
12
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
13
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 { normalizeNullableString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
15
+ import { asPositiveSafeInteger, isRecord, 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, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
19
- import nodePath from "node:path";
18
+ import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, readAgentRunTerminalOutcome, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toHistoryMediaEntries, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
20
19
  import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
21
20
  import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
22
- import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
23
21
  import { resolveChannelGroupPolicy, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
22
+ import path from "node:path";
24
23
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
25
24
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
26
25
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
@@ -45,6 +44,7 @@ import { upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-ru
45
44
  import { listChatCommands, maybeResolveTextAlias, normalizeCommandBody } from "openclaw/plugin-sdk/command-auth-native";
46
45
  import { isAbortRequestText } from "openclaw/plugin-sdk/command-primitives-runtime";
47
46
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
47
+ import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
48
48
  //#region extensions/signal/src/daemon.ts
49
49
  const SIGNAL_DAEMON_STOP_KILL_TIMEOUT_MS = 1500;
50
50
  function formatSignalDaemonExit(exit) {
@@ -72,7 +72,7 @@ function bindSignalCliOutput(params) {
72
72
  function resolveSignalCliConfigPath(raw) {
73
73
  const value = raw.trim();
74
74
  if (value === "~") return os.homedir();
75
- if (value.startsWith("~/") || value.startsWith("~\\")) return nodePath.join(os.homedir(), value.slice(2));
75
+ if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(os.homedir(), value.slice(2));
76
76
  return value;
77
77
  }
78
78
  function buildDaemonArgs(opts) {
@@ -180,6 +180,37 @@ function spawnSignalDaemon(opts) {
180
180
  };
181
181
  }
182
182
  //#endregion
183
+ //#region extensions/signal/src/daemon-lifecycle.ts
184
+ function createSignalDaemonLifecycle(params) {
185
+ let daemonHandle = null;
186
+ let daemonStopRequested = false;
187
+ let daemonStopPromise;
188
+ let daemonExitError;
189
+ const daemonAbortController = new AbortController();
190
+ const abortSignal = params.abortSignal ? AbortSignal.any([params.abortSignal, daemonAbortController.signal]) : daemonAbortController.signal;
191
+ const stop = () => {
192
+ if (daemonStopPromise) return daemonStopPromise;
193
+ daemonStopRequested = true;
194
+ if (!daemonAbortController.signal.aborted) daemonAbortController.abort(params.abortSignal?.reason ?? /* @__PURE__ */ new Error("Signal monitor stopped"));
195
+ daemonStopPromise = daemonHandle?.stop() ?? Promise.resolve();
196
+ return daemonStopPromise;
197
+ };
198
+ const attach = (handle) => {
199
+ daemonHandle = handle;
200
+ handle.exited.then((exit) => {
201
+ if (daemonStopRequested || params.abortSignal?.aborted) return;
202
+ daemonExitError = new Error(formatSignalDaemonExit(exit));
203
+ if (!daemonAbortController.signal.aborted) daemonAbortController.abort(daemonExitError);
204
+ });
205
+ };
206
+ return {
207
+ attach,
208
+ stop,
209
+ getExitError: () => daemonExitError,
210
+ abortSignal
211
+ };
212
+ }
213
+ //#endregion
183
214
  //#region extensions/signal/src/monitor/access-policy.ts
184
215
  const SIGNAL_UUID_KIND = "plugin:signal-uuid";
185
216
  const SIGNAL_GROUP_KIND = "plugin:signal-group";
@@ -274,6 +305,7 @@ async function resolveSignalAccessState(params) {
274
305
  kind: isGroup ? "group" : "direct",
275
306
  id: isGroup ? params.groupId ?? "unknown" : params.sender.raw
276
307
  },
308
+ contextBinding: params.contextBinding,
277
309
  ...isGroup ? { event: { mayPair: false } } : {},
278
310
  dmPolicy: params.dmPolicy,
279
311
  groupPolicy: params.groupPolicy,
@@ -606,7 +638,7 @@ function createSignalEventHandler(deps) {
606
638
  });
607
639
  const replyThreading = resolveBatchedReplyThreadingPolicy(replyToMode, entry.isBatched === true);
608
640
  const media = await toInboundMediaFactsWithMetadata(entry.media);
609
- const ctxPayload = buildChannelInboundEventContext({
641
+ const ctxPayload = (deps.channelRuntime?.inbound.buildContext ?? buildChannelInboundEventContext)({
610
642
  channel: "signal",
611
643
  supplemental: { quote: entry.replyToBody ? {
612
644
  body: entry.replyToBody,
@@ -650,6 +682,7 @@ function createSignalEventHandler(deps) {
650
682
  } } : {},
651
683
  commands: { authorized: entry.commandAuthorized }
652
684
  },
685
+ channelIngress: entry.channelIngress,
653
686
  media,
654
687
  extra: {
655
688
  GroupSubject: entry.isGroup ? entry.groupName ?? void 0 : void 0,
@@ -838,13 +871,16 @@ function createSignalEventHandler(deps) {
838
871
  onToolStart: async (payload) => {
839
872
  const toolName = payload.name?.trim();
840
873
  if (toolName) await statusReactionController.setTool(toolName);
874
+ return false;
841
875
  },
842
876
  onCompactionStart: async () => {
843
877
  await statusReactionController.setCompacting();
878
+ return false;
844
879
  },
845
880
  onCompactionEnd: async () => {
846
881
  statusReactionController.cancelPending();
847
882
  await statusReactionController.setThinking();
883
+ return false;
848
884
  }
849
885
  } : {},
850
886
  onModelSelected
@@ -854,9 +890,10 @@ function createSignalEventHandler(deps) {
854
890
  if (!statusReactionController) return;
855
891
  const hasFinalResponse = result.dispatched && hasVisibleInboundReplyDispatch(result.dispatchResult);
856
892
  const hasDeliveryFailure = result.dispatched && hasSignalStatusReplyDeliveryFailure(result.dispatchResult);
893
+ const hasAgentRunFailure = result.dispatched && readAgentRunTerminalOutcome(result.dispatchResult) === "failed";
857
894
  finalizeSignalStatusReaction({
858
895
  controller: statusReactionController,
859
- outcome: hasFinalResponse && !hasDeliveryFailure ? "done" : "error"
896
+ outcome: hasFinalResponse && !hasDeliveryFailure && !hasAgentRunFailure ? "done" : "error"
860
897
  }).catch((err) => {
861
898
  logVerbose(`signal: status reaction finalize failed: ${String(err)}`);
862
899
  });
@@ -867,9 +904,16 @@ function createSignalEventHandler(deps) {
867
904
  async function flushSignalInboundEntries(entries, lifecycle, settle) {
868
905
  const last = entries.at(-1);
869
906
  if (!last) return;
907
+ const channelIngress = await resolveSignalBatchChannelIngress(entries, last);
908
+ if (channelIngress.some((entry) => !entry.senderAccess.allowed)) {
909
+ logVerbose("signal: authorization changed before dispatch");
910
+ await settle();
911
+ return;
912
+ }
870
913
  if (entries.length === 1) {
871
914
  await handleSignalInboundMessage({
872
915
  ...last,
916
+ channelIngress,
873
917
  turnAdoptionLifecycle: lifecycle
874
918
  });
875
919
  await settle();
@@ -888,10 +932,30 @@ function createSignalEventHandler(deps) {
888
932
  turnAdoptionLifecycle: lifecycle,
889
933
  isBatched: true,
890
934
  nativeReplyBody: last.nativeReplyBody ?? last.bodyText,
891
- media: entries.flatMap((entry) => entry.media ?? [])
935
+ media: entries.flatMap((entry) => entry.media ?? []),
936
+ channelIngress
892
937
  });
893
938
  await settle();
894
939
  }
940
+ async function resolveSignalBatchChannelIngress(entries, last) {
941
+ if (last.boundChannelIngress) return last.boundChannelIngress;
942
+ const route = resolveSignalInboundRoute({
943
+ cfg: deps.cfg,
944
+ accountId: deps.accountId,
945
+ isGroup: last.isGroup,
946
+ groupId: last.groupId,
947
+ senderPeerId: last.senderPeerId
948
+ });
949
+ const contextBinding = {
950
+ agentId: route.agentId,
951
+ sessionKey: route.sessionKey,
952
+ ...last.messageId ? { messageId: last.messageId } : {},
953
+ inboundEventKind: "user_request"
954
+ };
955
+ const resolved = await Promise.all(entries.flatMap((entry) => entry.resolveChannelIngress ? [entry.resolveChannelIngress(contextBinding)] : (entry.channelIngress ?? []).map(async (ingress) => ingress)));
956
+ last.boundChannelIngress = resolved;
957
+ return resolved;
958
+ }
895
959
  async function retrySignalInboundFlush(entries, lifecycle, settle, initialError) {
896
960
  let lastError = initialError;
897
961
  for (const [attemptIndex, delayMs] of RETRYABLE_FLUSH_RETRY_DELAYS_MS.entries()) {
@@ -1017,6 +1081,7 @@ function createSignalEventHandler(deps) {
1017
1081
  if (!deps.shouldEmitSignalReactionNotification({
1018
1082
  mode: deps.reactionMode,
1019
1083
  account: deps.account,
1084
+ accountUuid: deps.accountUuid,
1020
1085
  targets,
1021
1086
  sender: params.sender,
1022
1087
  allowlist: deps.reactionAllowlist
@@ -1077,7 +1142,7 @@ function createSignalEventHandler(deps) {
1077
1142
  const isGroup = Boolean(groupId);
1078
1143
  const hasControlCommandInMessage = isControlCommandMessage(messageText, deps.cfg);
1079
1144
  const senderDisplay = formatSignalSenderDisplay(sender);
1080
- const { senderAccess, commandAccess } = await resolveSignalAccessState({
1145
+ const resolveChannelIngress = async (contextBinding) => await resolveSignalAccessState({
1081
1146
  accountId: deps.accountId,
1082
1147
  dmPolicy: deps.dmPolicy,
1083
1148
  groupPolicy: deps.groupPolicy,
@@ -1087,8 +1152,10 @@ function createSignalEventHandler(deps) {
1087
1152
  groupId,
1088
1153
  isGroup,
1089
1154
  cfg: deps.cfg,
1090
- hasControlCommand: hasControlCommandInMessage
1155
+ hasControlCommand: hasControlCommandInMessage,
1156
+ contextBinding
1091
1157
  });
1158
+ const { senderAccess, commandAccess } = await resolveChannelIngress();
1092
1159
  const quoteText = normalizeOptionalString(dataMessage?.quote?.text) ?? "";
1093
1160
  const { contextVisibilityMode, quoteSenderAllowed, visibleQuoteText, visibleQuoteSender } = resolveSignalQuoteContext({
1094
1161
  cfg: deps.cfg,
@@ -1218,8 +1285,8 @@ function createSignalEventHandler(deps) {
1218
1285
  sender: envelope.sourceName ?? senderDisplay,
1219
1286
  body: messageText || visibleQuoteText,
1220
1287
  media: toHistoryMediaEntries(pendingMedia),
1221
- timestamp: envelope.timestamp ?? void 0,
1222
- messageId: typeof envelope.timestamp === "number" ? String(envelope.timestamp) : void 0
1288
+ timestamp: inboundTimestamp,
1289
+ messageId
1223
1290
  }
1224
1291
  });
1225
1292
  await registerSignalReplyContext({
@@ -1349,7 +1416,8 @@ function createSignalEventHandler(deps) {
1349
1416
  replyToBody: visibleQuoteText || void 0,
1350
1417
  replyToSender: visibleQuoteSender,
1351
1418
  replyToIsQuote: visibleQuoteText ? true : void 0,
1352
- turnAdoptionLifecycle
1419
+ turnAdoptionLifecycle,
1420
+ resolveChannelIngress
1353
1421
  };
1354
1422
  pendingInboundRegistry.cancelPendingOnAbort(entry, debouncer.cancelKey);
1355
1423
  const inboundLane = resolveSignalControlLaneKey(deps.accountId, entry) ? controlDebouncer : debouncer;
@@ -1374,7 +1442,7 @@ function registerSignalReactionTargetsForDeliveredPayload(params) {
1374
1442
  const SIGNAL_INGRESS_DRAIN_INTERVAL_MS = 1e3;
1375
1443
  const SignalIngressPermanentError = createChannelIngressError("SignalIngressPermanentError", { withReason: true });
1376
1444
  function normalizeTimestamp(value) {
1377
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
1445
+ return asPositiveSafeInteger(value) ?? null;
1378
1446
  }
1379
1447
  function parseReceiveEnvelope(event) {
1380
1448
  if (event.event !== "receive" || !event.data) return null;
@@ -1384,18 +1452,18 @@ function parseReceiveEnvelope(event) {
1384
1452
  } catch (error) {
1385
1453
  throw new SignalIngressPermanentError("parse-error", "Signal receive event contains invalid JSON", { cause: error });
1386
1454
  }
1387
- if (!isRecord$1(parsed)) throw new SignalIngressPermanentError("parse-error", "Signal receive event must contain a JSON object");
1388
- return isRecord$1(parsed.envelope) ? parsed.envelope : null;
1455
+ if (!isRecord(parsed)) throw new SignalIngressPermanentError("parse-error", "Signal receive event must contain a JSON object");
1456
+ return isRecord(parsed.envelope) ? parsed.envelope : null;
1389
1457
  }
1390
1458
  function resolveDataMessage(envelope) {
1391
- if (isRecord$1(envelope.dataMessage)) return envelope.dataMessage;
1392
- return isRecord$1(envelope.editMessage?.dataMessage) ? envelope.editMessage.dataMessage : null;
1459
+ if (isRecord(envelope.dataMessage)) return envelope.dataMessage;
1460
+ return isRecord(envelope.editMessage?.dataMessage) ? envelope.editMessage.dataMessage : null;
1393
1461
  }
1394
1462
  function inspectSignalIngressEvent(event) {
1395
1463
  const envelope = parseReceiveEnvelope(event);
1396
1464
  if (!envelope || "syncMessage" in envelope) return null;
1397
1465
  const dataMessage = resolveDataMessage(envelope);
1398
- const reactionMessage = isRecord$1(envelope.reactionMessage) ? envelope.reactionMessage : null;
1466
+ const reactionMessage = isRecord(envelope.reactionMessage) ? envelope.reactionMessage : null;
1399
1467
  if (!dataMessage && !reactionMessage) return null;
1400
1468
  const senderUuid = normalizeNullableString(envelope.sourceUuid);
1401
1469
  const senderNumber = normalizeNullableString(envelope.sourceNumber);
@@ -1403,12 +1471,13 @@ function inspectSignalIngressEvent(event) {
1403
1471
  if (!senderKey) throw new SignalIngressPermanentError("missing-sender", "Signal dispatchable envelope is missing sourceUuid/sourceNumber");
1404
1472
  const timestamp = normalizeTimestamp(envelope.timestamp) ?? normalizeTimestamp(dataMessage?.timestamp);
1405
1473
  if (timestamp === null) throw new SignalIngressPermanentError("missing-timestamp", "Signal dispatchable envelope is missing a stable timestamp");
1406
- const dataGroup = isRecord$1(dataMessage?.groupInfo) ? dataMessage.groupInfo : null;
1407
- const reactionGroup = isRecord$1(reactionMessage?.groupInfo) ? reactionMessage.groupInfo : null;
1474
+ const dataGroup = isRecord(dataMessage?.groupInfo) ? dataMessage.groupInfo : null;
1475
+ const reactionGroup = isRecord(reactionMessage?.groupInfo) ? reactionMessage.groupInfo : null;
1408
1476
  const groupId = normalizeNullableString(dataGroup?.groupId) ?? normalizeNullableString(reactionGroup?.groupId);
1409
1477
  return {
1410
1478
  eventId: JSON.stringify([senderKey, timestamp]),
1411
- laneKey: groupId ? `group:${groupId}` : `direct:${senderKey}`
1479
+ laneKey: groupId ? `group:${groupId}` : `direct:${senderKey}`,
1480
+ ...senderUuid && senderNumber ? { numberAliasEventId: JSON.stringify([`number:${senderNumber}`, timestamp]) } : {}
1412
1481
  };
1413
1482
  }
1414
1483
  function resolveSignalIngressNonRetryableFailure(error) {
@@ -1425,8 +1494,9 @@ async function startSignalIngressMonitor(params) {
1425
1494
  if (!pluginRuntime) throw new Error("Signal runtime not initialized for durable ingress");
1426
1495
  queue = pluginRuntime.state.openChannelIngressQueue({ accountId: params.accountId });
1427
1496
  }
1497
+ const ingressQueue = queue;
1428
1498
  const monitor = createChannelIngressMonitor({
1429
- queue,
1499
+ queue: ingressQueue,
1430
1500
  inspect: (event) => inspectSignalIngressEvent(event),
1431
1501
  payload: {
1432
1502
  version: 1,
@@ -1446,10 +1516,15 @@ async function startSignalIngressMonitor(params) {
1446
1516
  createClaimError: (_kind, claim) => new SignalIngressPermanentError("unsupported-event", `Signal ingress row ${claim.id} has an invalid payload`)
1447
1517
  },
1448
1518
  deliver: (event, lifecycle) => params.dispatch(event, lifecycle),
1519
+ onDurableAdmission: async (_event, { facts }) => {
1520
+ const { numberAliasEventId } = facts;
1521
+ if (!numberAliasEventId) return;
1522
+ if (!await ingressQueue.complete(numberAliasEventId)) await ingressQueue.complete(facts.eventId);
1523
+ },
1449
1524
  pollIntervalMs: SIGNAL_INGRESS_DRAIN_INTERVAL_MS,
1450
1525
  retention: {
1451
1526
  pruneIntervalMs: 0,
1452
- completedMaxEntries: 1e3,
1527
+ completedMaxEntries: 2e3,
1453
1528
  failedMaxEntries: 1e3
1454
1529
  },
1455
1530
  appendRetryDelaysMs: [0],
@@ -1477,7 +1552,14 @@ const DEFAULT_RECONNECT_POLICY = {
1477
1552
  factor: 2,
1478
1553
  jitter: .2
1479
1554
  };
1480
- async function runSignalSseLoop({ baseUrl, account, abortSignal, runtime, onEvent, timeoutMs, transportKind, policy }) {
1555
+ function publishSignalRecovering(statusSink, lastError) {
1556
+ statusSink?.({
1557
+ connected: false,
1558
+ lifecycle: "recovering",
1559
+ ...lastError ? { lastError } : {}
1560
+ });
1561
+ }
1562
+ async function runSignalSseLoop({ baseUrl, account, abortSignal, runtime, onEvent, timeoutMs, transportKind, policy, statusSink }) {
1481
1563
  const reconnectPolicy = {
1482
1564
  ...DEFAULT_RECONNECT_POLICY,
1483
1565
  ...policy
@@ -1496,6 +1578,9 @@ async function runSignalSseLoop({ baseUrl, account, abortSignal, runtime, onEven
1496
1578
  abortSignal,
1497
1579
  timeoutMs,
1498
1580
  transportKind,
1581
+ onStreamOpen: () => {
1582
+ statusSink?.(channelReadyPatch());
1583
+ },
1499
1584
  onEvent: async (event) => {
1500
1585
  reconnectAttempts = 0;
1501
1586
  await onEvent(event);
@@ -1506,6 +1591,7 @@ async function runSignalSseLoop({ baseUrl, account, abortSignal, runtime, onEven
1506
1591
  }
1507
1592
  });
1508
1593
  if (abortSignal?.aborted) return;
1594
+ publishSignalRecovering(statusSink);
1509
1595
  reconnectAttempts += 1;
1510
1596
  const delayMs = computeBackoff(reconnectPolicy, reconnectAttempts);
1511
1597
  logReconnectVerbose(`Signal stream ended, reconnecting in ${delayMs / 1e3}s...`);
@@ -1513,6 +1599,7 @@ async function runSignalSseLoop({ baseUrl, account, abortSignal, runtime, onEven
1513
1599
  } catch (err) {
1514
1600
  if (abortSignal?.aborted) return;
1515
1601
  runtime.error?.(`Signal stream error: ${String(err)}`);
1602
+ publishSignalRecovering(statusSink, String(err));
1516
1603
  reconnectAttempts += 1;
1517
1604
  const delayMs = computeBackoff(reconnectPolicy, reconnectAttempts);
1518
1605
  runtime.log?.(`Signal connection lost, reconnecting in ${delayMs / 1e3}s...`);
@@ -1546,36 +1633,6 @@ function createSignalMonitorTaskRunner(runtime) {
1546
1633
  }
1547
1634
  };
1548
1635
  }
1549
- function createSignalDaemonLifecycle(params) {
1550
- let daemonHandle = null;
1551
- let daemonStopRequested = false;
1552
- let daemonStopPromise;
1553
- let daemonExitError;
1554
- const daemonAbortController = new AbortController();
1555
- const abortSignal = params.abortSignal ? AbortSignal.any([params.abortSignal, daemonAbortController.signal]) : daemonAbortController.signal;
1556
- const stop = () => {
1557
- if (daemonStopPromise) return daemonStopPromise;
1558
- daemonStopRequested = true;
1559
- if (!daemonAbortController.signal.aborted) daemonAbortController.abort(params.abortSignal?.reason ?? /* @__PURE__ */ new Error("Signal monitor stopped"));
1560
- daemonStopPromise = daemonHandle?.stop() ?? Promise.resolve();
1561
- return daemonStopPromise;
1562
- };
1563
- const attach = (handle) => {
1564
- daemonHandle = handle;
1565
- handle.exited.then((exit) => {
1566
- if (daemonStopRequested || params.abortSignal?.aborted) return;
1567
- daemonExitError = new Error(formatSignalDaemonExit(exit));
1568
- if (!daemonAbortController.signal.aborted) daemonAbortController.abort(daemonExitError);
1569
- });
1570
- };
1571
- const getExitError = () => daemonExitError;
1572
- return {
1573
- attach,
1574
- stop,
1575
- getExitError,
1576
- abortSignal
1577
- };
1578
- }
1579
1636
  function resolveSignalReactionTargets(reaction) {
1580
1637
  const targets = [];
1581
1638
  const uuid = reaction.targetAuthorUuid?.trim();
@@ -1603,16 +1660,17 @@ function isSignalReactionMessage(reaction) {
1603
1660
  return Boolean(emoji && typeof timestamp === "number" && timestamp > 0 && hasTarget);
1604
1661
  }
1605
1662
  function shouldEmitSignalReactionNotification(params) {
1606
- const { mode, account, targets, sender, allowlist } = params;
1663
+ const { mode, account, accountUuid, targets, sender, allowlist } = params;
1607
1664
  const effectiveMode = mode ?? "own";
1608
1665
  if (effectiveMode === "off") return false;
1609
1666
  if (effectiveMode === "own") {
1610
- const accountId = account?.trim();
1611
- if (!accountId || !targets || targets.length === 0) return false;
1612
- const normalizedAccount = normalizeE164(accountId);
1667
+ const accountId = normalizeOptionalString(account);
1668
+ const normalizedAccountUuid = normalizeOptionalString(accountUuid);
1669
+ if (!accountId && !normalizedAccountUuid || !targets || targets.length === 0) return false;
1670
+ const normalizedAccount = accountId ? normalizeE164(accountId) : void 0;
1613
1671
  return targets.some((target) => {
1614
- if (target.kind === "uuid") return accountId === target.id || accountId === `uuid:${target.id}`;
1615
- return normalizedAccount === target.id;
1672
+ if (target.kind === "uuid") return [accountId, normalizedAccountUuid].some((candidate) => candidate === target.id || candidate === `uuid:${target.id}`);
1673
+ return Boolean(normalizedAccount) && normalizedAccount === target.id;
1616
1674
  });
1617
1675
  }
1618
1676
  if (effectiveMode === "allowlist") {
@@ -1893,6 +1951,7 @@ async function monitorSignalProvider(opts = {}) {
1893
1951
  });
1894
1952
  const handleEvent = createSignalEventHandler({
1895
1953
  runtime,
1954
+ channelRuntime: opts.channelRuntime,
1896
1955
  abortSignal: daemonLifecycle.abortSignal,
1897
1956
  runTrackedTask: (task) => {
1898
1957
  monitorTaskRunner.runTask(task);
@@ -1943,6 +2002,7 @@ async function monitorSignalProvider(opts = {}) {
1943
2002
  timeoutMs: 0,
1944
2003
  transportKind,
1945
2004
  policy: opts.reconnectPolicy,
2005
+ statusSink: opts.statusSink,
1946
2006
  onEvent: (event) => monitorTaskRunner.runTask(async () => await ingressMonitor?.receive(event))
1947
2007
  });
1948
2008
  const daemonExitError = daemonLifecycle.getExitError();
@@ -1950,6 +2010,7 @@ async function monitorSignalProvider(opts = {}) {
1950
2010
  } catch (err) {
1951
2011
  const daemonExitError = daemonLifecycle.getExitError();
1952
2012
  if (opts.abortSignal?.aborted && !daemonExitError) return;
2013
+ if (daemonExitError) publishSignalRecovering(opts.statusSink, daemonExitError.message);
1953
2014
  throw err;
1954
2015
  } finally {
1955
2016
  await ingressMonitor?.stop();
@@ -1,19 +1,26 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { t as detectSignalTransport } from "./transport-detection-Da06-LOL.js";
3
- import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-mXGeEHQJ.js";
2
+ import { t as detectSignalTransport } from "./transport-detection-BItHawMq.js";
3
+ import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-D9SNPaNx.js";
4
4
  import { runChannelProbe } from "openclaw/plugin-sdk/text-utility-runtime";
5
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
6
  //#region extensions/signal/src/probe.ts
7
- var probe_exports = /* @__PURE__ */ __exportAll({ probeSignal: () => probeSignal });
7
+ var probe_exports = /* @__PURE__ */ __exportAll({
8
+ probeSignal: () => probeSignal,
9
+ probeSignalAccount: () => probeSignalAccount
10
+ });
8
11
  function parseSignalVersion(value) {
9
12
  if (typeof value === "string" && value.trim()) return value.trim();
10
13
  if (typeof value === "object" && value !== null) {
11
- const version = value.version;
14
+ const { version, versions } = value;
12
15
  if (typeof version === "string" && version.trim()) return version.trim();
16
+ if (Array.isArray(versions)) {
17
+ const normalizedVersions = versions.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
18
+ if (normalizedVersions.length > 0) return normalizedVersions.join(", ");
19
+ }
13
20
  }
14
21
  return null;
15
22
  }
16
- async function probeSignal(baseUrl, timeoutMs, options = {}) {
23
+ async function probeSignalTransport(baseUrl, timeoutMs, options, account) {
17
24
  return await runChannelProbe(void 0, async () => {
18
25
  const result = {
19
26
  ok: false,
@@ -30,7 +37,10 @@ async function probeSignal(baseUrl, timeoutMs, options = {}) {
30
37
  error: formatErrorMessage(error)
31
38
  };
32
39
  }
33
- const check = await signalCheck(baseUrl, timeoutMs, { transportKind });
40
+ const check = await signalCheck(baseUrl, timeoutMs, {
41
+ transportKind,
42
+ ...account && transportKind === "container" ? { account } : {}
43
+ });
34
44
  if (!check.ok) return {
35
45
  ...result,
36
46
  status: check.status ?? null,
@@ -47,11 +57,17 @@ async function probeSignal(baseUrl, timeoutMs, options = {}) {
47
57
  }
48
58
  return {
49
59
  ...result,
50
- ok: true,
60
+ ok: result.error === null,
51
61
  status: check.status ?? null
52
62
  };
53
63
  });
54
64
  }
65
+ async function probeSignal(baseUrl, timeoutMs, options = {}) {
66
+ return await probeSignalTransport(baseUrl, timeoutMs, options);
67
+ }
68
+ async function probeSignalAccount(params) {
69
+ return await probeSignalTransport(params.baseUrl, params.timeoutMs, { transportKind: params.transportKind }, params.account);
70
+ }
55
71
  async function resolveProbeTransportKind(baseUrl, timeoutMs, options) {
56
72
  if (options.transportKind) return options.transportKind;
57
73
  if (options.apiMode === "container") return "container";
@@ -0,0 +1,80 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { o as resolveSignalTarget } from "./runtime-JPrGlV7h.js";
3
+ import { l as resolveSignalApprovalConversationKey, u as resolveSignalApprovalTargetAuthorKeys } from "./approval-reactions-BulCzhdB.js";
4
+ import { createQuestionReactionTargetStore, questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
5
+ import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
6
+ //#region extensions/signal/src/question-reactions.ts
7
+ var question_reactions_exports = /* @__PURE__ */ __exportAll({
8
+ maybeResolveSignalQuestionReaction: () => maybeResolveSignalQuestionReaction,
9
+ registerSignalQuestionReactionTargetForDeliveredPayload: () => registerSignalQuestionReactionTargetForDeliveredPayload
10
+ });
11
+ function buildKey(identity) {
12
+ const values = [
13
+ identity.accountId,
14
+ identity.conversationKey,
15
+ identity.messageId
16
+ ].map((value) => value.trim());
17
+ return values.every(Boolean) ? values.join(":") : null;
18
+ }
19
+ const questionReactionTargets = createQuestionReactionTargetStore({
20
+ channel: "signal",
21
+ channelDisplayName: "Signal",
22
+ buildKey,
23
+ identityMatches: (stored, incoming) => Boolean(stored && incoming?.some((authorKey) => stored.includes(authorKey))),
24
+ registerChannelDelivery: questionGatewayRuntime.registerChannelDelivery,
25
+ resolveReaction: questionGatewayRuntime.resolveReaction
26
+ });
27
+ function resolveConversationKey(params) {
28
+ try {
29
+ return resolveSignalTarget({
30
+ cfg: params.cfg,
31
+ accountId: params.accountId,
32
+ input: params.to
33
+ })?.to ?? resolveSignalApprovalConversationKey(params.to);
34
+ } catch {
35
+ return resolveSignalApprovalConversationKey(params.to);
36
+ }
37
+ }
38
+ function registerSignalQuestionReactionTargetForDeliveredPayload(params) {
39
+ const binding = questionGatewayRuntime.readReactionBinding(params.payload);
40
+ if (params.target.channel !== "signal" || !binding) return false;
41
+ const conversationKey = resolveConversationKey({
42
+ cfg: params.cfg,
43
+ ...params.target
44
+ });
45
+ const targetAuthorKeys = resolveSignalApprovalTargetAuthorKeys(params);
46
+ if (!conversationKey || targetAuthorKeys.length === 0) return false;
47
+ const accountId = normalizeAccountId(params.target.accountId ?? void 0);
48
+ let registered = false;
49
+ for (const result of params.results) {
50
+ const messageId = result.channel === "signal" ? result.messageId.trim() : "";
51
+ if (!messageId || messageId === "unknown") continue;
52
+ registered = questionReactionTargets.register(binding, {
53
+ accountId,
54
+ conversationKey,
55
+ messageId
56
+ }, targetAuthorKeys) || registered;
57
+ }
58
+ return registered;
59
+ }
60
+ async function maybeResolveSignalQuestionReaction(params) {
61
+ if (params.isRemove) return false;
62
+ const optionIndex = questionGatewayRuntime.resolveReactionIndex(params.reactionKey);
63
+ if (optionIndex === void 0) return false;
64
+ const authorKeys = resolveSignalApprovalTargetAuthorKeys(params);
65
+ return await questionReactionTargets.resolve({
66
+ identities: [{
67
+ accountId: params.accountId,
68
+ conversationKey: params.conversationKey,
69
+ messageId: params.messageId
70
+ }],
71
+ optionIndex,
72
+ cfg: params.cfg,
73
+ senderId: params.actorId,
74
+ gatewayUrl: params.gatewayUrl,
75
+ metadata: authorKeys,
76
+ logDebug: params.logDebug
77
+ });
78
+ }
79
+ //#endregion
80
+ export { question_reactions_exports as n, registerSignalQuestionReactionTargetForDeliveredPayload as r, maybeResolveSignalQuestionReaction as t };
@@ -1,5 +1,5 @@
1
1
  import { i as resolveSignalAccount } from "./accounts-DrStJjiK.js";
2
- import { n as signalRpcRequest } from "./client-adapter-mXGeEHQJ.js";
2
+ import { n as signalRpcRequest } from "./client-adapter-D9SNPaNx.js";
3
3
  import { t as resolveSignalRpcContext } from "./rpc-context-CZUoE0-A.js";
4
4
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
@@ -37,16 +37,17 @@ async function sendReactionSignalCore(params) {
37
37
  const { baseUrl, account } = resolveSignalRpcContext(params.opts, accountInfo);
38
38
  const normalizedRecipient = normalizeSignalUuid(params.recipient);
39
39
  const groupId = params.opts.groupId?.trim();
40
- if (!normalizedRecipient && !groupId) throw new Error(params.errors.missingRecipient);
41
- if (!Number.isFinite(params.targetTimestamp) || params.targetTimestamp <= 0) throw new Error(params.errors.invalidTargetTimestamp);
40
+ const operation = `Signal reaction${params.remove ? " removal" : ""}`;
41
+ if (!normalizedRecipient && !groupId) throw new Error(`Recipient or groupId is required for ${operation}`);
42
+ if (!Number.isFinite(params.targetTimestamp) || params.targetTimestamp <= 0) throw new Error(`Valid targetTimestamp is required for ${operation}`);
42
43
  const normalizedEmoji = params.emoji?.trim();
43
- if (!normalizedEmoji) throw new Error(params.errors.missingEmoji);
44
+ if (!normalizedEmoji) throw new Error(`Emoji is required for ${operation}`);
44
45
  const targetAuthorParams = resolveTargetAuthorParams({
45
46
  targetAuthor: params.opts.targetAuthor,
46
47
  targetAuthorUuid: params.opts.targetAuthorUuid,
47
48
  fallback: normalizedRecipient
48
49
  });
49
- if (groupId && !targetAuthorParams.targetAuthor) throw new Error(params.errors.missingTargetAuthor);
50
+ if (groupId && !targetAuthorParams.targetAuthor) throw new Error(`targetAuthor is required for group reaction${params.remove ? " removal" : "s"}`);
50
51
  const requestParams = {
51
52
  emoji: normalizedEmoji,
52
53
  targetTimestamp: params.targetTimestamp,
@@ -78,13 +79,7 @@ async function sendReactionSignal(recipient, targetTimestamp, emoji, opts) {
78
79
  targetTimestamp,
79
80
  emoji,
80
81
  remove: false,
81
- opts,
82
- errors: {
83
- missingRecipient: "Recipient or groupId is required for Signal reaction",
84
- invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction",
85
- missingEmoji: "Emoji is required for Signal reaction",
86
- missingTargetAuthor: "targetAuthor is required for group reactions"
87
- }
82
+ opts
88
83
  });
89
84
  }
90
85
  /**
@@ -100,13 +95,7 @@ async function removeReactionSignal(recipient, targetTimestamp, emoji, opts) {
100
95
  targetTimestamp,
101
96
  emoji,
102
97
  remove: true,
103
- opts,
104
- errors: {
105
- missingRecipient: "Recipient or groupId is required for Signal reaction removal",
106
- invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction removal",
107
- missingEmoji: "Emoji is required for Signal reaction removal",
108
- missingTargetAuthor: "targetAuthor is required for group reaction removal"
109
- }
98
+ opts
110
99
  });
111
100
  }
112
101
  //#endregion
@@ -1,2 +1,2 @@
1
- import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BjeDyKcz.js";
1
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-Cf6QHcE5.js";
2
2
  export { removeReactionSignal, sendReactionSignal };