@openclaw/signal 2026.7.1-beta.1 → 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,8 +1,5 @@
1
- import { i as resolveSignalAccount } from "./accounts-hOCHbEhX.js";
2
1
  import { detectMime, parseMediaContentLength } from "openclaw/plugin-sdk/media-runtime";
3
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
2
  import { asDateTimestampMs, parseStrictNonNegativeInteger, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
5
- import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
6
3
  import fs from "node:fs/promises";
7
4
  import path from "node:path";
8
5
  import { resolveFetch } from "openclaw/plugin-sdk/fetch-runtime";
@@ -953,127 +950,4 @@ async function streamSignalEvents(params) {
953
950
  });
954
951
  }
955
952
  //#endregion
956
- //#region extensions/signal/src/rpc-context.ts
957
- function resolveSignalRpcContext(opts, accountInfo) {
958
- const hasBaseUrl = Boolean(normalizeOptionalString(opts.baseUrl));
959
- const hasAccount = Boolean(normalizeOptionalString(opts.account));
960
- if ((!hasBaseUrl || !hasAccount) && !accountInfo) throw new Error("Signal account config is required when baseUrl or account is missing");
961
- const resolvedAccount = accountInfo;
962
- const baseUrl = normalizeOptionalString(opts.baseUrl) ?? resolvedAccount?.baseUrl;
963
- if (!baseUrl) throw new Error("Signal base URL is required");
964
- return {
965
- baseUrl,
966
- account: normalizeOptionalString(opts.account) ?? normalizeOptionalString(resolvedAccount?.config.account)
967
- };
968
- }
969
- //#endregion
970
- //#region extensions/signal/src/send-reactions.ts
971
- function normalizeSignalId(raw) {
972
- const trimmed = raw.trim();
973
- if (!trimmed) return "";
974
- return trimmed.replace(/^signal:/i, "").trim();
975
- }
976
- function normalizeSignalUuid(raw) {
977
- const trimmed = normalizeSignalId(raw);
978
- if (!trimmed) return "";
979
- if (normalizeLowercaseStringOrEmpty(trimmed).startsWith("uuid:")) return trimmed.slice(5).trim();
980
- return trimmed;
981
- }
982
- function resolveTargetAuthorParams(params) {
983
- const candidates = [
984
- params.targetAuthor,
985
- params.targetAuthorUuid,
986
- params.fallback
987
- ];
988
- for (const candidate of candidates) {
989
- const raw = candidate?.trim();
990
- if (!raw) continue;
991
- const normalized = normalizeSignalUuid(raw);
992
- if (normalized) return { targetAuthor: normalized };
993
- }
994
- return {};
995
- }
996
- async function sendReactionSignalCore(params) {
997
- const cfg = requireRuntimeConfig(params.opts.cfg, "Signal reactions");
998
- const apiMode = cfg.channels?.signal?.apiMode;
999
- const accountInfo = resolveSignalAccount({
1000
- cfg,
1001
- accountId: params.opts.accountId
1002
- });
1003
- const { baseUrl, account } = resolveSignalRpcContext(params.opts, accountInfo);
1004
- const normalizedRecipient = normalizeSignalUuid(params.recipient);
1005
- const groupId = params.opts.groupId?.trim();
1006
- if (!normalizedRecipient && !groupId) throw new Error(params.errors.missingRecipient);
1007
- if (!Number.isFinite(params.targetTimestamp) || params.targetTimestamp <= 0) throw new Error(params.errors.invalidTargetTimestamp);
1008
- const normalizedEmoji = params.emoji?.trim();
1009
- if (!normalizedEmoji) throw new Error(params.errors.missingEmoji);
1010
- const targetAuthorParams = resolveTargetAuthorParams({
1011
- targetAuthor: params.opts.targetAuthor,
1012
- targetAuthorUuid: params.opts.targetAuthorUuid,
1013
- fallback: normalizedRecipient
1014
- });
1015
- if (groupId && !targetAuthorParams.targetAuthor) throw new Error(params.errors.missingTargetAuthor);
1016
- const requestParams = {
1017
- emoji: normalizedEmoji,
1018
- targetTimestamp: params.targetTimestamp,
1019
- ...params.remove ? { remove: true } : {},
1020
- ...targetAuthorParams
1021
- };
1022
- if (normalizedRecipient) requestParams.recipients = [normalizedRecipient];
1023
- if (groupId) requestParams.groupIds = [groupId];
1024
- if (account) requestParams.account = account;
1025
- return {
1026
- ok: true,
1027
- timestamp: (await signalRpcRequest("sendReaction", requestParams, {
1028
- baseUrl,
1029
- timeoutMs: params.opts.timeoutMs,
1030
- apiMode
1031
- }))?.timestamp
1032
- };
1033
- }
1034
- /**
1035
- * Send a Signal reaction to a message
1036
- * @param recipient - UUID or E.164 phone number of the message author
1037
- * @param targetTimestamp - Message ID (timestamp) to react to
1038
- * @param emoji - Emoji to react with
1039
- * @param opts - Optional account/connection overrides
1040
- */
1041
- async function sendReactionSignal(recipient, targetTimestamp, emoji, opts) {
1042
- return await sendReactionSignalCore({
1043
- recipient,
1044
- targetTimestamp,
1045
- emoji,
1046
- remove: false,
1047
- opts,
1048
- errors: {
1049
- missingRecipient: "Recipient or groupId is required for Signal reaction",
1050
- invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction",
1051
- missingEmoji: "Emoji is required for Signal reaction",
1052
- missingTargetAuthor: "targetAuthor is required for group reactions"
1053
- }
1054
- });
1055
- }
1056
- /**
1057
- * Remove a Signal reaction from a message
1058
- * @param recipient - UUID or E.164 phone number of the message author
1059
- * @param targetTimestamp - Message ID (timestamp) to remove reaction from
1060
- * @param emoji - Emoji to remove
1061
- * @param opts - Optional account/connection overrides
1062
- */
1063
- async function removeReactionSignal(recipient, targetTimestamp, emoji, opts) {
1064
- return await sendReactionSignalCore({
1065
- recipient,
1066
- targetTimestamp,
1067
- emoji,
1068
- remove: true,
1069
- opts,
1070
- errors: {
1071
- missingRecipient: "Recipient or groupId is required for Signal reaction removal",
1072
- invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction removal",
1073
- missingEmoji: "Emoji is required for Signal reaction removal",
1074
- missingTargetAuthor: "targetAuthor is required for group reaction removal"
1075
- }
1076
- });
1077
- }
1078
- //#endregion
1079
- export { signalRpcRequest as a, signalCheck as i, sendReactionSignal as n, streamSignalEvents as o, resolveSignalRpcContext as r, removeReactionSignal as t };
953
+ export { signalRpcRequest as n, streamSignalEvents as r, signalCheck as t };
@@ -1,6 +1,7 @@
1
- import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./accounts-hOCHbEhX.js";
2
- import { d as normalizeSignalMessagingTarget, l as looksLikeUuid } from "./identity-B6O4k8xg.js";
3
- import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BRJCLiqK.js";
1
+ import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./accounts-B7Rz3_xV.js";
2
+ import { d as normalizeSignalMessagingTarget } from "./identity-B6O4k8xg.js";
3
+ import { n as signalApprovalAuth, t as getSignalApprovalApprovers } from "./approval-auth-DPVK9A_l.js";
4
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BkAxQPGs.js";
4
5
  import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
5
6
  import { resolveReactionLevel } from "openclaw/plugin-sdk/status-helpers";
6
7
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -9,32 +10,8 @@ import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-de
9
10
  import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
10
11
  import { createChannelApproverDmTargetResolver, createChannelNativeOriginTargetResolver, createNativeApprovalChannelRouteGates, createNativeApprovalForwardingFallbackSuppressor, shouldSuppressLocalNativeExecApprovalPrompt } from "openclaw/plugin-sdk/approval-native-runtime";
11
12
  import { buildApprovalReactionPendingContentForRequest } from "openclaw/plugin-sdk/approval-reaction-runtime";
12
- import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
13
- import { normalizeE164 } from "openclaw/plugin-sdk/text-utility-runtime";
14
13
  import { createActionGate, jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
15
14
  import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
16
- //#region extensions/signal/src/approval-auth.ts
17
- function normalizeSignalApproverId(value) {
18
- const normalized = normalizeSignalMessagingTarget(String(value));
19
- if (!normalized || normalized.startsWith("group:") || normalized.startsWith("username:")) return;
20
- if (looksLikeUuid(normalized)) return `uuid:${normalized}`;
21
- const e164 = normalizeE164(normalized);
22
- return e164.length > 1 ? e164 : void 0;
23
- }
24
- function getSignalApprovalApprovers(params) {
25
- const account = resolveSignalAccount(params).config;
26
- return resolveApprovalApprovers({
27
- allowFrom: account.allowFrom,
28
- defaultTo: account.defaultTo,
29
- normalizeApprover: normalizeSignalApproverId
30
- });
31
- }
32
- const signalApprovalAuth = createResolvedApproverActionAuthAdapter({
33
- channelLabel: "Signal",
34
- resolveApprovers: getSignalApprovalApprovers,
35
- normalizeSenderId: (value) => normalizeSignalApproverId(value)
36
- });
37
- //#endregion
38
15
  //#region extensions/signal/src/approval-native.ts
39
16
  const DEFAULT_APPROVAL_FORWARDING_MODE = "session";
40
17
  function isSignalApprovalTransportEnabled(params) {
@@ -233,7 +210,7 @@ const signalApprovalCapability = createChannelApprovalCapability({
233
210
  accountId,
234
211
  request
235
212
  }),
236
- load: async () => (await import("./approval-handler.runtime-C4S7IAvt.js")).signalApprovalNativeRuntime
213
+ load: async () => (await import("./approval-handler.runtime-BZLgs7EM.js")).signalApprovalNativeRuntime
237
214
  })
238
215
  });
239
216
  //#endregion
@@ -545,4 +522,4 @@ const signalMessageActions = {
545
522
  }
546
523
  };
547
524
  //#endregion
548
- export { isSignalNativeApprovalHandlerConfigured as a, getSignalApprovalApprovers as c, markdownToSignalTextChunks as i, signalApprovalAuth as l, resolveSignalReactionLevel as n, shouldSuppressLocalSignalExecApprovalPrompt as o, markdownToSignalText as r, signalApprovalCapability as s, signalMessageActions as t };
525
+ export { isSignalNativeApprovalHandlerConfigured as a, markdownToSignalTextChunks as i, resolveSignalReactionLevel as n, shouldSuppressLocalSignalExecApprovalPrompt as o, markdownToSignalText as r, signalApprovalCapability as s, signalMessageActions as t };
@@ -1,10 +1,11 @@
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-BnugYXjB.js";
5
- import { a as signalRpcRequest, i as signalCheck, o as streamSignalEvents } from "./reaction-runtime-api-BRJCLiqK.js";
6
- import { i as maybeResolveSignalApprovalReaction, o as registerSignalApprovalReactionTargetForDeliveredPayload, s as resolveSignalApprovalConversationKey, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-H-RPGBK7.js";
7
- import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-De6LIOLq.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";
8
9
  import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";
9
10
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
10
11
  import { detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
@@ -19,14 +20,14 @@ import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "opencla
19
20
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
20
21
  import { chunkTextWithMode, createReplyDispatcherWithTyping, dispatchInboundMessage, resolveChunkMode, resolveTextChunkLimit, settleReplyDispatcher } from "openclaw/plugin-sdk/reply-runtime";
21
22
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
22
- 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";
23
24
  import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
24
25
  import { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runtime";
25
26
  import { spawn } from "node:child_process";
26
27
  import os from "node:os";
27
28
  import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
28
- import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
29
- 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";
30
31
  import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native";
31
32
  import { recordInboundSession, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
32
33
  import { createInternalHookEvent, fireAndForgetHook, toInternalMessageReceivedContext, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
@@ -364,6 +365,42 @@ function resolveSignalInboundRoute(params) {
364
365
  }
365
366
  });
366
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
+ }
367
404
  function createSignalEventHandler(deps) {
368
405
  async function handleSignalInboundMessage(entry) {
369
406
  const fromLabel = formatInboundFromLabel({
@@ -477,6 +514,68 @@ function createSignalEventHandler(deps) {
477
514
  const preview = truncateUtf16Safe(body, 200).replace(/\\n/g, "\\\\n");
478
515
  logVerbose(`signal inbound: from=${ctxPayload.From} len=${body.length} preview="${preview}"`);
479
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();
480
579
  const { onModelSelected, typingCallbacks, ...replyPipeline } = createChannelMessageReplyPipeline({
481
580
  cfg: deps.cfg,
482
581
  agentId: route.agentId,
@@ -584,6 +683,7 @@ function createSignalEventHandler(deps) {
584
683
  }),
585
684
  runDispatch: async () => {
586
685
  try {
686
+ if (statusReactionController) statusReactionController.setThinking();
587
687
  return await dispatchInboundMessage({
588
688
  ctx: ctxPayload,
589
689
  cfg: deps.cfg,
@@ -591,6 +691,21 @@ function createSignalEventHandler(deps) {
591
691
  replyOptions: {
592
692
  ...replyOptions,
593
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
+ } : {},
594
709
  onModelSelected
595
710
  }
596
711
  });
@@ -598,7 +713,21 @@ function createSignalEventHandler(deps) {
598
713
  markDispatchIdle();
599
714
  }
600
715
  }
601
- })
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
+ }
602
731
  }
603
732
  });
604
733
  }
@@ -931,9 +1060,9 @@ function createSignalEventHandler(deps) {
931
1060
  }
932
1061
  const bodyText = messageText || placeholder || visibleQuoteText || "";
933
1062
  if (!bodyText) return;
934
- const receiptTimestamp = typeof envelope.timestamp === "number" ? envelope.timestamp : typeof dataMessage.timestamp === "number" ? dataMessage.timestamp : void 0;
935
- if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && receiptTimestamp) try {
936
- 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, {
937
1066
  cfg: deps.cfg,
938
1067
  baseUrl: deps.baseUrl,
939
1068
  account: deps.account,
@@ -942,9 +1071,9 @@ function createSignalEventHandler(deps) {
942
1071
  } catch (err) {
943
1072
  logVerbose(`signal read receipt failed for ${senderDisplay}: ${String(err)}`);
944
1073
  }
945
- 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}`);
946
1075
  const senderName = envelope.sourceName ?? senderDisplay;
947
- const messageId = typeof envelope.timestamp === "number" ? String(envelope.timestamp) : void 0;
1076
+ const messageId = typeof inboundTimestamp === "number" ? String(inboundTimestamp) : void 0;
948
1077
  await inboundDebouncer.enqueue({
949
1078
  senderName,
950
1079
  senderDisplay,
@@ -955,13 +1084,15 @@ function createSignalEventHandler(deps) {
955
1084
  isGroup,
956
1085
  bodyText,
957
1086
  commandBody: messageText,
958
- timestamp: envelope.timestamp ?? void 0,
1087
+ timestamp: inboundTimestamp,
959
1088
  messageId,
960
1089
  mediaPath,
961
1090
  mediaType,
962
1091
  mediaPaths: mediaPaths.length > 0 ? mediaPaths : void 0,
963
1092
  mediaTypes: mediaTypes.length > 0 ? mediaTypes : void 0,
964
1093
  commandAuthorized,
1094
+ canDetectMention,
1095
+ requireMention,
965
1096
  wasMentioned: effectiveWasMentioned,
966
1097
  replyToBody: visibleQuoteText || void 0,
967
1098
  replyToSender: visibleQuoteSender,
@@ -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-BRJCLiqK.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-BRJCLiqK.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,13 +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-BnugYXjB.js";
4
- import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BRJCLiqK.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
6
  import { r as installSignalCli } from "./install-signal-cli-TwhJ0DGy.js";
7
- import { u as setSignalRuntime } from "./approval-reactions-H-RPGBK7.js";
8
- import { t as monitorSignalProvider } from "./monitor-C1gdgT7W.js";
9
- import { t as sendMessageSignal } from "./send-De6LIOLq.js";
10
- import { t as probeSignal } from "./probe-BvFJLOBi.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";
11
11
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
12
12
  import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";
13
13
  import { buildBaseAccountStatusSnapshot, buildBaseChannelStatusSummary, collectStatusIssuesFromLastError, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
@@ -1,6 +1,7 @@
1
- import { i as resolveSignalAccount } from "./accounts-hOCHbEhX.js";
2
- import { r as markdownToSignalText } from "./message-actions-BnugYXjB.js";
3
- import { a as signalRpcRequest, r as resolveSignalRpcContext } from "./reaction-runtime-api-BRJCLiqK.js";
1
+ import { i as resolveSignalAccount } from "./accounts-B7Rz3_xV.js";
2
+ import { r as markdownToSignalText } from "./message-actions-Cs9XckSd.js";
3
+ import { n as signalRpcRequest } from "./client-adapter-Dm8-wT2n.js";
4
+ import { t as resolveSignalRpcContext } from "./rpc-context-DbFMe7am.js";
4
5
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
5
6
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
6
7
  import { kindFromMime, resolveOutboundAttachmentFromUrl } from "openclaw/plugin-sdk/media-runtime";
@@ -1,2 +1,2 @@
1
- import { r as sendTypingSignal, t as sendMessageSignal } from "./send-De6LIOLq.js";
1
+ import { r as sendTypingSignal, t as sendMessageSignal } from "./send-CLzc3RUg.js";
2
2
  export { sendMessageSignal, sendTypingSignal };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/signal",
3
- "version": "2026.7.1-beta.1",
3
+ "version": "2026.7.1-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/signal",
9
- "version": "2026.7.1-beta.1",
9
+ "version": "2026.7.1-beta.2",
10
10
  "dependencies": {
11
11
  "ws": "8.21.0"
12
12
  }