@openclaw/signal 2026.6.11 → 2026.7.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.
@@ -1,16 +1,18 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { i as resolveSignalAccount } from "./accounts-hOCHbEhX.js";
2
+ import { i as resolveSignalAccount } from "./accounts-B7Rz3_xV.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-B6O4k8xg.js";
4
- import { a as isSignalNativeApprovalHandlerConfigured } from "./message-actions-Bue0g2Kc.js";
5
- import { a as signalRpcRequest, i as signalCheck, o as streamSignalEvents } from "./reaction-runtime-api-C_PQ45D9.js";
6
- import { a as maybeResolveSignalApprovalReaction, n as sendReadReceiptSignal, r as sendTypingSignal, s as resolveSignalApprovalConversationKey, t as sendMessageSignal } from "./send-CBlFUkY_.js";
4
+ import { a as isSignalNativeApprovalHandlerConfigured, n as resolveSignalReactionLevel } from "./message-actions-Cs9XckSd.js";
5
+ import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-Dm8-wT2n.js";
6
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BkAxQPGs.js";
7
+ import { i as maybeResolveSignalApprovalReaction, o as registerSignalApprovalReactionTargetForDeliveredPayload, s as resolveSignalApprovalConversationKey, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-5x1kmQEq.js";
8
+ import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-CLzc3RUg.js";
7
9
  import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";
8
10
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
9
11
  import { detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
10
12
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
11
13
  import { normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
12
14
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
13
- import { normalizeE164 } from "openclaw/plugin-sdk/text-utility-runtime";
15
+ import { normalizeE164, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
14
16
  import path from "node:path";
15
17
  import { resolveChannelGroupPolicy, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
16
18
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
@@ -18,14 +20,14 @@ import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "opencla
18
20
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
19
21
  import { chunkTextWithMode, createReplyDispatcherWithTyping, dispatchInboundMessage, resolveChunkMode, resolveTextChunkLimit, settleReplyDispatcher } from "openclaw/plugin-sdk/reply-runtime";
20
22
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
21
- import { computeBackoff, createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
23
+ import { computeBackoff, createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, sleep, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
22
24
  import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
23
25
  import { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runtime";
24
26
  import { spawn } from "node:child_process";
25
27
  import os from "node:os";
26
28
  import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
27
- import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
28
- import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, runChannelInboundEvent, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
29
+ import { DEFAULT_EMOJIS, DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, resolveAckReaction, shouldAckReaction } from "openclaw/plugin-sdk/channel-feedback";
30
+ import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, runChannelInboundEvent, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
29
31
  import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native";
30
32
  import { recordInboundSession, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
31
33
  import { createInternalHookEvent, fireAndForgetHook, toInternalMessageReceivedContext, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
@@ -363,6 +365,42 @@ function resolveSignalInboundRoute(params) {
363
365
  }
364
366
  });
365
367
  }
368
+ function resolveSignalStatusReactionTimestamp(params) {
369
+ if (typeof params.timestamp === "number") return Number.isFinite(params.timestamp) && params.timestamp > 0 ? params.timestamp : null;
370
+ const parsed = Number(params.messageId);
371
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
372
+ }
373
+ function hasSignalStatusReplyDeliveryFailure(result) {
374
+ const failedCounts = result.failedCounts;
375
+ return (failedCounts?.tool ?? 0) > 0 || (failedCounts?.block ?? 0) > 0 || (failedCounts?.final ?? 0) > 0;
376
+ }
377
+ function resolveSignalStatusReactionEmojis(emojis) {
378
+ if (emojis?.stallHard !== void 0) return emojis;
379
+ return {
380
+ ...emojis,
381
+ stallHard: DEFAULT_EMOJIS.stallSoft
382
+ };
383
+ }
384
+ async function finalizeSignalStatusReaction(params) {
385
+ if (params.outcome === "done") {
386
+ await params.controller.setDone();
387
+ if (params.removeAckAfterReply) {
388
+ await sleep(params.timing.doneHoldMs);
389
+ await params.controller.clear();
390
+ } else await params.controller.restoreInitial();
391
+ return;
392
+ }
393
+ await params.controller.setError();
394
+ if (params.hasFinalResponse) {
395
+ if (params.removeAckAfterReply) {
396
+ await sleep(params.timing.errorHoldMs);
397
+ await params.controller.clear();
398
+ } else await params.controller.restoreInitial();
399
+ return;
400
+ }
401
+ if (params.removeAckAfterReply) await sleep(params.timing.errorHoldMs);
402
+ await params.controller.restoreInitial();
403
+ }
366
404
  function createSignalEventHandler(deps) {
367
405
  async function handleSignalInboundMessage(entry) {
368
406
  const fromLabel = formatInboundFromLabel({
@@ -473,9 +511,71 @@ function createSignalEventHandler(deps) {
473
511
  extra: { GroupSubject: entry.isGroup ? entry.groupName ?? void 0 : void 0 }
474
512
  });
475
513
  if (shouldLogVerbose()) {
476
- const preview = body.slice(0, 200).replace(/\\n/g, "\\\\n");
514
+ const preview = truncateUtf16Safe(body, 200).replace(/\\n/g, "\\\\n");
477
515
  logVerbose(`signal inbound: from=${ctxPayload.From} len=${body.length} preview="${preview}"`);
478
516
  }
517
+ const statusReactionTimestamp = resolveSignalStatusReactionTimestamp(entry);
518
+ const statusReactionsConfig = deps.cfg.messages?.statusReactions;
519
+ const signalReactionLevel = resolveSignalReactionLevel({
520
+ cfg: deps.cfg,
521
+ accountId: route.accountId
522
+ });
523
+ const ackReaction = resolveAckReaction(deps.cfg, route.agentId, {
524
+ channel: "signal",
525
+ accountId: route.accountId
526
+ });
527
+ const shouldSendStatusReaction = Boolean(ackReaction && shouldAckReaction({
528
+ scope: deps.cfg.messages?.ackReactionScope,
529
+ isDirect: !entry.isGroup,
530
+ isGroup: entry.isGroup,
531
+ isMentionableGroup: entry.isGroup,
532
+ requireMention: entry.requireMention === true,
533
+ canDetectMention: entry.canDetectMention === true,
534
+ effectiveWasMentioned: entry.wasMentioned === true
535
+ }));
536
+ const statusReactionTarget = `${entry.groupId ?? entry.senderRecipient}/${statusReactionTimestamp ?? "unknown"}`;
537
+ const signalReactionOpts = {
538
+ cfg: deps.cfg,
539
+ ...deps.baseUrl ? { baseUrl: deps.baseUrl } : {},
540
+ ...deps.account ? { account: deps.account } : {},
541
+ ...deps.accountId ? { accountId: deps.accountId } : {},
542
+ ...entry.isGroup && entry.groupId ? {
543
+ groupId: entry.groupId,
544
+ targetAuthor: entry.senderRecipient
545
+ } : {}
546
+ };
547
+ const statusReactionRecipient = entry.isGroup ? "" : entry.senderRecipient;
548
+ let currentStatusReactionEmoji = ackReaction;
549
+ const statusReactionController = statusReactionsConfig?.enabled === true && signalReactionLevel.level !== "off" && shouldSendStatusReaction && statusReactionTimestamp ? createStatusReactionController({
550
+ enabled: true,
551
+ adapter: {
552
+ setReaction: async (emoji) => {
553
+ await sendReactionSignal(statusReactionRecipient, statusReactionTimestamp, emoji, signalReactionOpts);
554
+ currentStatusReactionEmoji = emoji;
555
+ },
556
+ clearReaction: async () => {
557
+ if (!currentStatusReactionEmoji) return;
558
+ await removeReactionSignal(statusReactionRecipient, statusReactionTimestamp, currentStatusReactionEmoji, signalReactionOpts);
559
+ currentStatusReactionEmoji = "";
560
+ }
561
+ },
562
+ initialEmoji: ackReaction,
563
+ emojis: resolveSignalStatusReactionEmojis(statusReactionsConfig.emojis),
564
+ timing: statusReactionsConfig.timing,
565
+ onError: (err) => {
566
+ logAckFailure({
567
+ log: logVerbose,
568
+ channel: "signal",
569
+ target: statusReactionTarget,
570
+ error: err
571
+ });
572
+ }
573
+ }) : null;
574
+ const statusReactionTiming = {
575
+ ...DEFAULT_TIMING,
576
+ ...statusReactionsConfig?.timing
577
+ };
578
+ if (statusReactionController) statusReactionController.setQueued();
479
579
  const { onModelSelected, typingCallbacks, ...replyPipeline } = createChannelMessageReplyPipeline({
480
580
  cfg: deps.cfg,
481
581
  agentId: route.agentId,
@@ -583,6 +683,7 @@ function createSignalEventHandler(deps) {
583
683
  }),
584
684
  runDispatch: async () => {
585
685
  try {
686
+ if (statusReactionController) statusReactionController.setThinking();
586
687
  return await dispatchInboundMessage({
587
688
  ctx: ctxPayload,
588
689
  cfg: deps.cfg,
@@ -590,6 +691,21 @@ function createSignalEventHandler(deps) {
590
691
  replyOptions: {
591
692
  ...replyOptions,
592
693
  disableBlockStreaming: typeof deps.blockStreaming === "boolean" ? !deps.blockStreaming : void 0,
694
+ ...statusReactionController ? {
695
+ allowProgressCallbacksWhenSourceDeliverySuppressed: true,
696
+ allowToolLifecycleWhenProgressHidden: true,
697
+ onToolStart: async (payload) => {
698
+ const toolName = payload.name?.trim();
699
+ if (toolName) await statusReactionController.setTool(toolName);
700
+ },
701
+ onCompactionStart: async () => {
702
+ await statusReactionController.setCompacting();
703
+ },
704
+ onCompactionEnd: async () => {
705
+ statusReactionController.cancelPending();
706
+ await statusReactionController.setThinking();
707
+ }
708
+ } : {},
593
709
  onModelSelected
594
710
  }
595
711
  });
@@ -597,7 +713,21 @@ function createSignalEventHandler(deps) {
597
713
  markDispatchIdle();
598
714
  }
599
715
  }
600
- })
716
+ }),
717
+ onFinalize: (result) => {
718
+ if (!statusReactionController) return;
719
+ const hasFinalResponse = result.dispatched && hasVisibleInboundReplyDispatch(result.dispatchResult);
720
+ const hasDeliveryFailure = result.dispatched && hasSignalStatusReplyDeliveryFailure(result.dispatchResult);
721
+ finalizeSignalStatusReaction({
722
+ controller: statusReactionController,
723
+ outcome: hasFinalResponse && !hasDeliveryFailure ? "done" : "error",
724
+ hasFinalResponse,
725
+ removeAckAfterReply: deps.cfg.messages?.removeAckAfterReply ?? false,
726
+ timing: statusReactionTiming
727
+ }).catch((err) => {
728
+ logVerbose(`signal: status reaction finalize failed: ${String(err)}`);
729
+ });
730
+ }
601
731
  }
602
732
  });
603
733
  }
@@ -930,9 +1060,9 @@ function createSignalEventHandler(deps) {
930
1060
  }
931
1061
  const bodyText = messageText || placeholder || visibleQuoteText || "";
932
1062
  if (!bodyText) return;
933
- const receiptTimestamp = typeof envelope.timestamp === "number" ? envelope.timestamp : typeof dataMessage.timestamp === "number" ? dataMessage.timestamp : void 0;
934
- if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && receiptTimestamp) try {
935
- await sendReadReceiptSignal(`signal:${senderRecipient}`, receiptTimestamp, {
1063
+ const inboundTimestamp = typeof envelope.timestamp === "number" ? envelope.timestamp : typeof dataMessage.timestamp === "number" ? dataMessage.timestamp : void 0;
1064
+ if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && inboundTimestamp) try {
1065
+ await sendReadReceiptSignal(`signal:${senderRecipient}`, inboundTimestamp, {
936
1066
  cfg: deps.cfg,
937
1067
  baseUrl: deps.baseUrl,
938
1068
  account: deps.account,
@@ -941,9 +1071,9 @@ function createSignalEventHandler(deps) {
941
1071
  } catch (err) {
942
1072
  logVerbose(`signal read receipt failed for ${senderDisplay}: ${String(err)}`);
943
1073
  }
944
- else if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && !receiptTimestamp) logVerbose(`signal read receipt skipped (missing timestamp) for ${senderDisplay}`);
1074
+ else if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && !inboundTimestamp) logVerbose(`signal read receipt skipped (missing timestamp) for ${senderDisplay}`);
945
1075
  const senderName = envelope.sourceName ?? senderDisplay;
946
- const messageId = typeof envelope.timestamp === "number" ? String(envelope.timestamp) : void 0;
1076
+ const messageId = typeof inboundTimestamp === "number" ? String(inboundTimestamp) : void 0;
947
1077
  await inboundDebouncer.enqueue({
948
1078
  senderName,
949
1079
  senderDisplay,
@@ -954,13 +1084,15 @@ function createSignalEventHandler(deps) {
954
1084
  isGroup,
955
1085
  bodyText,
956
1086
  commandBody: messageText,
957
- timestamp: envelope.timestamp ?? void 0,
1087
+ timestamp: inboundTimestamp,
958
1088
  messageId,
959
1089
  mediaPath,
960
1090
  mediaType,
961
1091
  mediaPaths: mediaPaths.length > 0 ? mediaPaths : void 0,
962
1092
  mediaTypes: mediaTypes.length > 0 ? mediaTypes : void 0,
963
1093
  commandAuthorized,
1094
+ canDetectMention,
1095
+ requireMention,
964
1096
  wasMentioned: effectiveWasMentioned,
965
1097
  replyToBody: visibleQuoteText || void 0,
966
1098
  replyToSender: visibleQuoteSender,
@@ -1026,7 +1158,10 @@ async function runSignalSseLoop({ baseUrl, account, abortSignal, runtime, onEven
1026
1158
  }
1027
1159
  //#endregion
1028
1160
  //#region extensions/signal/src/monitor.ts
1029
- var monitor_exports = /* @__PURE__ */ __exportAll({ monitorSignalProvider: () => monitorSignalProvider });
1161
+ var monitor_exports = /* @__PURE__ */ __exportAll({
1162
+ deliverReplies: () => deliverReplies,
1163
+ monitorSignalProvider: () => monitorSignalProvider
1164
+ });
1030
1165
  function resolveRuntime(opts) {
1031
1166
  return opts.runtime ?? createNonExitingRuntime();
1032
1167
  }
@@ -1220,30 +1355,63 @@ async function fetchAttachment(params) {
1220
1355
  }
1221
1356
  async function deliverReplies(params) {
1222
1357
  const { replies, target, baseUrl, account, accountId, runtime, maxBytes, textLimit, chunkMode } = params;
1223
- for (const payload of replies) if (await deliverTextOrMediaReply({
1224
- payload,
1225
- text: resolveSendableOutboundReplyParts(payload).text,
1226
- chunkText: (value) => chunkTextWithMode(value, textLimit, chunkMode),
1227
- sendText: async (chunk) => {
1228
- await sendMessageSignal(target, chunk, {
1229
- cfg: params.cfg,
1230
- baseUrl,
1231
- account,
1232
- maxBytes,
1233
- accountId
1358
+ for (const payload of replies) {
1359
+ const deliveryResults = [];
1360
+ const deliveredPayload = addSignalApprovalReactionHintToStructuredPayload({
1361
+ cfg: params.cfg,
1362
+ accountId,
1363
+ to: target,
1364
+ payload,
1365
+ targetAuthor: account
1366
+ }) ?? payload;
1367
+ const reply = resolveSendableOutboundReplyParts(deliveredPayload);
1368
+ const recordDeliveryResult = (result, visibleText) => {
1369
+ const messageId = typeof result?.messageId === "string" && result.messageId.trim() ? result.messageId.trim() : null;
1370
+ if (messageId) deliveryResults.push({
1371
+ channel: "signal",
1372
+ messageId,
1373
+ meta: { signalVisibleText: visibleText }
1234
1374
  });
1235
- },
1236
- sendMedia: async ({ mediaUrl, caption }) => {
1237
- await sendMessageSignal(target, caption ?? "", {
1375
+ };
1376
+ if (await deliverTextOrMediaReply({
1377
+ payload: deliveredPayload,
1378
+ text: reply.text,
1379
+ chunkText: (value) => chunkTextWithMode(value, textLimit, chunkMode),
1380
+ sendText: async (chunk) => {
1381
+ recordDeliveryResult(await sendMessageSignal(target, chunk, {
1382
+ cfg: params.cfg,
1383
+ baseUrl,
1384
+ account,
1385
+ maxBytes,
1386
+ accountId
1387
+ }), chunk);
1388
+ },
1389
+ sendMedia: async ({ mediaUrl, caption }) => {
1390
+ const visibleText = caption ?? "";
1391
+ recordDeliveryResult(await sendMessageSignal(target, visibleText, {
1392
+ cfg: params.cfg,
1393
+ baseUrl,
1394
+ account,
1395
+ mediaUrl,
1396
+ maxBytes,
1397
+ accountId
1398
+ }), visibleText);
1399
+ }
1400
+ }) !== "empty") {
1401
+ registerSignalApprovalReactionTargetForDeliveredPayload({
1238
1402
  cfg: params.cfg,
1239
- baseUrl,
1240
- account,
1241
- mediaUrl,
1242
- maxBytes,
1243
- accountId
1403
+ target: {
1404
+ channel: "signal",
1405
+ to: target,
1406
+ accountId
1407
+ },
1408
+ payload: deliveredPayload,
1409
+ results: deliveryResults,
1410
+ targetAuthor: account
1244
1411
  });
1412
+ runtime.log?.(`delivered reply to ${target}`);
1245
1413
  }
1246
- }) !== "empty") runtime.log?.(`delivered reply to ${target}`);
1414
+ }
1247
1415
  }
1248
1416
  async function monitorSignalProvider(opts = {}) {
1249
1417
  const runtime = resolveRuntime(opts);
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { a as signalRpcRequest, i as signalCheck } from "./reaction-runtime-api-C_PQ45D9.js";
2
+ import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-Dm8-wT2n.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 });
@@ -0,0 +1,115 @@
1
+ import { i as resolveSignalAccount } from "./accounts-B7Rz3_xV.js";
2
+ import { n as signalRpcRequest } from "./client-adapter-Dm8-wT2n.js";
3
+ import { t as resolveSignalRpcContext } from "./rpc-context-DbFMe7am.js";
4
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
5
+ import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
6
+ //#region extensions/signal/src/send-reactions.ts
7
+ function normalizeSignalId(raw) {
8
+ const trimmed = raw.trim();
9
+ if (!trimmed) return "";
10
+ return trimmed.replace(/^signal:/i, "").trim();
11
+ }
12
+ function normalizeSignalUuid(raw) {
13
+ const trimmed = normalizeSignalId(raw);
14
+ if (!trimmed) return "";
15
+ if (normalizeLowercaseStringOrEmpty(trimmed).startsWith("uuid:")) return trimmed.slice(5).trim();
16
+ return trimmed;
17
+ }
18
+ function resolveTargetAuthorParams(params) {
19
+ const candidates = [
20
+ params.targetAuthor,
21
+ params.targetAuthorUuid,
22
+ params.fallback
23
+ ];
24
+ for (const candidate of candidates) {
25
+ const raw = candidate?.trim();
26
+ if (!raw) continue;
27
+ const normalized = normalizeSignalUuid(raw);
28
+ if (normalized) return { targetAuthor: normalized };
29
+ }
30
+ return {};
31
+ }
32
+ async function sendReactionSignalCore(params) {
33
+ const cfg = requireRuntimeConfig(params.opts.cfg, "Signal reactions");
34
+ const apiMode = cfg.channels?.signal?.apiMode;
35
+ const accountInfo = resolveSignalAccount({
36
+ cfg,
37
+ accountId: params.opts.accountId
38
+ });
39
+ const { baseUrl, account } = resolveSignalRpcContext(params.opts, accountInfo);
40
+ const normalizedRecipient = normalizeSignalUuid(params.recipient);
41
+ const groupId = params.opts.groupId?.trim();
42
+ if (!normalizedRecipient && !groupId) throw new Error(params.errors.missingRecipient);
43
+ if (!Number.isFinite(params.targetTimestamp) || params.targetTimestamp <= 0) throw new Error(params.errors.invalidTargetTimestamp);
44
+ const normalizedEmoji = params.emoji?.trim();
45
+ if (!normalizedEmoji) throw new Error(params.errors.missingEmoji);
46
+ const targetAuthorParams = resolveTargetAuthorParams({
47
+ targetAuthor: params.opts.targetAuthor,
48
+ targetAuthorUuid: params.opts.targetAuthorUuid,
49
+ fallback: normalizedRecipient
50
+ });
51
+ if (groupId && !targetAuthorParams.targetAuthor) throw new Error(params.errors.missingTargetAuthor);
52
+ const requestParams = {
53
+ emoji: normalizedEmoji,
54
+ targetTimestamp: params.targetTimestamp,
55
+ ...params.remove ? { remove: true } : {},
56
+ ...targetAuthorParams
57
+ };
58
+ if (normalizedRecipient) requestParams.recipients = [normalizedRecipient];
59
+ if (groupId) requestParams.groupIds = [groupId];
60
+ if (account) requestParams.account = account;
61
+ return {
62
+ ok: true,
63
+ timestamp: (await signalRpcRequest("sendReaction", requestParams, {
64
+ baseUrl,
65
+ timeoutMs: params.opts.timeoutMs,
66
+ apiMode
67
+ }))?.timestamp
68
+ };
69
+ }
70
+ /**
71
+ * Send a Signal reaction to a message
72
+ * @param recipient - UUID or E.164 phone number of the message author
73
+ * @param targetTimestamp - Message ID (timestamp) to react to
74
+ * @param emoji - Emoji to react with
75
+ * @param opts - Optional account/connection overrides
76
+ */
77
+ async function sendReactionSignal(recipient, targetTimestamp, emoji, opts) {
78
+ return await sendReactionSignalCore({
79
+ recipient,
80
+ targetTimestamp,
81
+ emoji,
82
+ remove: false,
83
+ opts,
84
+ errors: {
85
+ missingRecipient: "Recipient or groupId is required for Signal reaction",
86
+ invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction",
87
+ missingEmoji: "Emoji is required for Signal reaction",
88
+ missingTargetAuthor: "targetAuthor is required for group reactions"
89
+ }
90
+ });
91
+ }
92
+ /**
93
+ * Remove a Signal reaction from a message
94
+ * @param recipient - UUID or E.164 phone number of the message author
95
+ * @param targetTimestamp - Message ID (timestamp) to remove reaction from
96
+ * @param emoji - Emoji to remove
97
+ * @param opts - Optional account/connection overrides
98
+ */
99
+ async function removeReactionSignal(recipient, targetTimestamp, emoji, opts) {
100
+ return await sendReactionSignalCore({
101
+ recipient,
102
+ targetTimestamp,
103
+ emoji,
104
+ remove: true,
105
+ opts,
106
+ errors: {
107
+ missingRecipient: "Recipient or groupId is required for Signal reaction removal",
108
+ invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction removal",
109
+ missingEmoji: "Emoji is required for Signal reaction removal",
110
+ missingTargetAuthor: "targetAuthor is required for group reaction removal"
111
+ }
112
+ });
113
+ }
114
+ //#endregion
115
+ export { sendReactionSignal as n, removeReactionSignal as t };
@@ -1,2 +1,2 @@
1
- import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-C_PQ45D9.js";
1
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BkAxQPGs.js";
2
2
  export { removeReactionSignal, sendReactionSignal };
@@ -0,0 +1,17 @@
1
+ import "./accounts-B7Rz3_xV.js";
2
+ import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ //#region extensions/signal/src/rpc-context.ts
4
+ function resolveSignalRpcContext(opts, accountInfo) {
5
+ const hasBaseUrl = Boolean(normalizeOptionalString(opts.baseUrl));
6
+ const hasAccount = Boolean(normalizeOptionalString(opts.account));
7
+ if ((!hasBaseUrl || !hasAccount) && !accountInfo) throw new Error("Signal account config is required when baseUrl or account is missing");
8
+ const resolvedAccount = accountInfo;
9
+ const baseUrl = normalizeOptionalString(opts.baseUrl) ?? resolvedAccount?.baseUrl;
10
+ if (!baseUrl) throw new Error("Signal base URL is required");
11
+ return {
12
+ baseUrl,
13
+ account: normalizeOptionalString(opts.account) ?? normalizeOptionalString(resolvedAccount?.config.account)
14
+ };
15
+ }
16
+ //#endregion
17
+ export { resolveSignalRpcContext as t };
@@ -1,12 +1,13 @@
1
- import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./accounts-hOCHbEhX.js";
1
+ import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./accounts-B7Rz3_xV.js";
2
2
  import { d as normalizeSignalMessagingTarget, u as looksLikeSignalTargetId } from "./identity-B6O4k8xg.js";
3
- import { n as resolveSignalReactionLevel, t as signalMessageActions } from "./message-actions-Bue0g2Kc.js";
4
- import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-C_PQ45D9.js";
3
+ import { n as resolveSignalReactionLevel, t as signalMessageActions } from "./message-actions-Cs9XckSd.js";
4
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BkAxQPGs.js";
5
5
  import { n as buildChannelConfigSchema, t as SignalConfigSchema } from "./config-api-KS-qhQvD.js";
6
- import { r as installSignalCli } from "./install-signal-cli-CXgTF3de.js";
7
- import { t as monitorSignalProvider } from "./monitor-CUhIKHJo.js";
8
- import { t as sendMessageSignal, u as setSignalRuntime } from "./send-CBlFUkY_.js";
9
- import { t as probeSignal } from "./probe-BQ_Izoya.js";
6
+ import { r as installSignalCli } from "./install-signal-cli-TwhJ0DGy.js";
7
+ import { u as setSignalRuntime } from "./approval-reactions-5x1kmQEq.js";
8
+ import { t as monitorSignalProvider } from "./monitor-C9SiyrFt.js";
9
+ import { t as sendMessageSignal } from "./send-CLzc3RUg.js";
10
+ import { t as probeSignal } from "./probe-BL2BqTbG.js";
10
11
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
11
12
  import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";
12
13
  import { buildBaseAccountStatusSnapshot, buildBaseChannelStatusSummary, collectStatusIssuesFromLastError, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";