@openclaw/signal 2026.7.1-beta.2 → 2026.7.1-beta.5

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 (24) hide show
  1. package/dist/{accounts-B7Rz3_xV.js → accounts-Bz4HtP0g.js} +20 -2
  2. package/dist/api.js +9 -9
  3. package/dist/{approval-auth-DPVK9A_l.js → approval-auth-CsHNcAiy.js} +2 -2
  4. package/dist/{approval-handler.runtime-BZLgs7EM.js → approval-handler.runtime-DDQ4ZRyc.js} +5 -5
  5. package/dist/{approval-reactions-5x1kmQEq.js → approval-reactions-UFmUPD0A.js} +113 -28
  6. package/dist/{channel-BfWp-F-r.js → channel-Cnhy1RwG.js} +42 -23
  7. package/dist/channel-plugin-api.js +1 -1
  8. package/dist/{channel.runtime-Ks7GIWM1.js → channel.runtime-CQN0RaFx.js} +3 -3
  9. package/dist/{client-adapter-Dm8-wT2n.js → client-adapter-C9mB2Jz8.js} +41 -11
  10. package/dist/contract-api.js +2 -2
  11. package/dist/{identity-B6O4k8xg.js → identity-C8-yk4J9.js} +12 -8
  12. package/dist/{install-signal-cli-TwhJ0DGy.js → install-signal-cli-ik8VPaGg.js} +7 -7
  13. package/dist/{message-actions-Cs9XckSd.js → message-actions-3OLDgjis.js} +6 -6
  14. package/dist/{monitor-C9SiyrFt.js → monitor-Bee8O5Ay.js} +120 -27
  15. package/dist/{probe-BL2BqTbG.js → probe-Cnht8Ihg.js} +1 -1
  16. package/dist/{reaction-runtime-api-BkAxQPGs.js → reaction-runtime-api-EQ7cc7-Y.js} +3 -3
  17. package/dist/reaction-runtime-api.js +1 -1
  18. package/dist/{rpc-context-DbFMe7am.js → rpc-context-DsHWh2hc.js} +1 -1
  19. package/dist/runtime-api.js +9 -9
  20. package/dist/{send-CLzc3RUg.js → send-CUJy6WPt.js} +84 -11
  21. package/dist/{send.runtime-DgijYwpJ.js → send.runtime-CEi5oyCG.js} +1 -1
  22. package/npm-shrinkwrap.json +2 -2
  23. package/openclaw.plugin.json +132 -0
  24. package/package.json +4 -4
@@ -61,11 +61,14 @@ function stripSignalPrefix(value) {
61
61
  }
62
62
  function resolveSignalSender(params) {
63
63
  const sourceNumber = params.sourceNumber?.trim();
64
- if (sourceNumber) return {
65
- kind: "phone",
66
- raw: sourceNumber,
67
- e164: normalizeE164(sourceNumber)
68
- };
64
+ if (sourceNumber) {
65
+ const e164 = normalizeE164(sourceNumber);
66
+ if (e164) return {
67
+ kind: "phone",
68
+ raw: sourceNumber,
69
+ e164
70
+ };
71
+ }
69
72
  const sourceUuid = params.sourceUuid?.trim();
70
73
  if (sourceUuid) return {
71
74
  kind: "uuid",
@@ -106,10 +109,11 @@ function parseSignalAllowEntry(entry) {
106
109
  kind: "uuid",
107
110
  raw: stripped
108
111
  };
109
- return {
112
+ const e164 = normalizeE164(stripped);
113
+ return e164 ? {
110
114
  kind: "phone",
111
- e164: normalizeE164(stripped)
112
- };
115
+ e164
116
+ } : null;
113
117
  }
114
118
  function normalizeSignalAllowRecipient(entry) {
115
119
  const parsed = parseSignalAllowEntry(entry);
@@ -1,11 +1,11 @@
1
1
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
2
2
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
3
- import fs from "node:fs/promises";
4
- import path from "node:path";
3
+ import nodePath from "node:path";
5
4
  import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
6
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
7
6
  import { CONFIG_DIR, extractArchive, resolveBrewExecutable } from "openclaw/plugin-sdk/setup-tools";
8
7
  import { createWriteStream } from "node:fs";
8
+ import fs from "node:fs/promises";
9
9
  import { Readable, Transform } from "node:stream";
10
10
  import { pipeline } from "node:stream/promises";
11
11
  import { runPluginCommandWithTimeout } from "openclaw/plugin-sdk/run-command";
@@ -97,7 +97,7 @@ async function findSignalCliBinary(root) {
97
97
  if (depth > 3) return;
98
98
  const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
99
99
  for (const entry of entries) {
100
- const full = path.join(dir, entry.name);
100
+ const full = nodePath.join(dir, entry.name);
101
101
  if (entry.isDirectory()) await enqueue(full, depth + 1);
102
102
  else if (entry.isFile() && entry.name === "signal-cli") candidates.push(full);
103
103
  }
@@ -117,7 +117,7 @@ async function resolveBrewSignalCliPath(brewExe) {
117
117
  });
118
118
  if (result.code === 0 && result.stdout.trim()) {
119
119
  const prefix = result.stdout.trim();
120
- const candidate = path.join(prefix, "bin", "signal-cli");
120
+ const candidate = nodePath.join(prefix, "bin", "signal-cli");
121
121
  try {
122
122
  await fs.access(candidate);
123
123
  return candidate;
@@ -202,11 +202,11 @@ async function installSignalCliFromRelease(runtime) {
202
202
  ok: false,
203
203
  error: "No compatible release asset found for this platform."
204
204
  };
205
- const tmpDir = await fs.mkdtemp(path.join(resolvePreferredOpenClawTmpDir(), "openclaw-signal-"));
206
- const archivePath = path.join(tmpDir, asset.name);
205
+ const tmpDir = await fs.mkdtemp(nodePath.join(resolvePreferredOpenClawTmpDir(), "openclaw-signal-"));
206
+ const archivePath = nodePath.join(tmpDir, asset.name);
207
207
  runtime.log(`Downloading signal-cli ${version} (${asset.name})…`);
208
208
  await downloadToFile(asset.browser_download_url, archivePath);
209
- const installRoot = path.join(CONFIG_DIR, "tools", "signal-cli", version);
209
+ const installRoot = nodePath.join(CONFIG_DIR, "tools", "signal-cli", version);
210
210
  await fs.mkdir(installRoot, { recursive: true });
211
211
  if (!looksLikeArchive(normalizeLowercaseStringOrEmpty(asset.name))) return {
212
212
  ok: false,
@@ -1,7 +1,7 @@
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";
1
+ import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./accounts-Bz4HtP0g.js";
2
+ import { d as normalizeSignalMessagingTarget } from "./identity-C8-yk4J9.js";
3
+ import { n as signalApprovalAuth, t as getSignalApprovalApprovers } from "./approval-auth-CsHNcAiy.js";
4
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-EQ7cc7-Y.js";
5
5
  import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
6
6
  import { resolveReactionLevel } from "openclaw/plugin-sdk/status-helpers";
7
7
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -210,7 +210,7 @@ const signalApprovalCapability = createChannelApprovalCapability({
210
210
  accountId,
211
211
  request
212
212
  }),
213
- load: async () => (await import("./approval-handler.runtime-BZLgs7EM.js")).signalApprovalNativeRuntime
213
+ load: async () => (await import("./approval-handler.runtime-DDQ4ZRyc.js")).signalApprovalNativeRuntime
214
214
  })
215
215
  });
216
216
  //#endregion
@@ -457,7 +457,7 @@ const signalMessageActions = {
457
457
  accountId
458
458
  })].filter((account) => account.enabled && account.configured) : listEnabledSignalAccounts(cfg).filter((account) => account.configured);
459
459
  if (configuredAccounts.length === 0) return null;
460
- const actions = new Set(["send"]);
460
+ const actions = /* @__PURE__ */ new Set(["send"]);
461
461
  if (configuredAccounts.some((account) => createActionGate(account.config.actions)("reactions"))) actions.add("react");
462
462
  return { actions: Array.from(actions) };
463
463
  },
@@ -1,11 +1,11 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { i as resolveSignalAccount } from "./accounts-B7Rz3_xV.js";
3
- import { a as normalizeSignalAllowRecipient, c as resolveSignalSender, d as normalizeSignalMessagingTarget, i as isSignalSenderAllowed, l as looksLikeUuid, n as formatSignalSenderDisplay, o as resolveSignalPeerId, r as formatSignalSenderId, s as resolveSignalRecipient, t as formatSignalPairingIdLine } from "./identity-B6O4k8xg.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";
2
+ import { a as resolveSignalReplyToMode, i as resolveSignalAccount } from "./accounts-Bz4HtP0g.js";
3
+ import { a as normalizeSignalAllowRecipient, c as resolveSignalSender, d as normalizeSignalMessagingTarget, i as isSignalSenderAllowed, l as looksLikeUuid, n as formatSignalSenderDisplay, o as resolveSignalPeerId, r as formatSignalSenderId, s as resolveSignalRecipient, t as formatSignalPairingIdLine } from "./identity-C8-yk4J9.js";
4
+ import { a as isSignalNativeApprovalHandlerConfigured, n as resolveSignalReactionLevel } from "./message-actions-3OLDgjis.js";
5
+ import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-C9mB2Jz8.js";
6
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-EQ7cc7-Y.js";
7
+ import { a as maybeResolveSignalApprovalReaction, l as resolveSignalApprovalConversationKey, s as registerSignalApprovalReactionTargetForDeliveredPayload, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-UFmUPD0A.js";
8
+ import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-CUJy6WPt.js";
9
9
  import { createChannelMessageReplyPipeline } from "openclaw/plugin-sdk/channel-outbound";
10
10
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
11
11
  import { detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
@@ -13,12 +13,13 @@ import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/p
13
13
  import { normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
14
14
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
15
15
  import { normalizeE164, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
16
- import path from "node:path";
16
+ import nodePath from "node:path";
17
+ import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
17
18
  import { resolveChannelGroupPolicy, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
18
19
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
19
20
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
20
21
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
21
- import { chunkTextWithMode, createReplyDispatcherWithTyping, dispatchInboundMessage, resolveChunkMode, resolveTextChunkLimit, settleReplyDispatcher } from "openclaw/plugin-sdk/reply-runtime";
22
+ import { chunkTextWithMode, createReplyDispatcherWithTyping, createReplyReferencePlanner, dispatchInboundMessage, resolveChunkMode, resolveTextChunkLimit, settleReplyDispatcher } from "openclaw/plugin-sdk/reply-runtime";
22
23
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
23
24
  import { computeBackoff, createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, sleep, sleepWithAbort } from "openclaw/plugin-sdk/runtime-env";
24
25
  import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
@@ -27,11 +28,11 @@ import { spawn } from "node:child_process";
27
28
  import os from "node:os";
28
29
  import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
29
30
  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";
31
+ import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, runChannelInboundEvent, shouldDebounceTextInbound } from "openclaw/plugin-sdk/channel-inbound";
31
32
  import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native";
32
33
  import { recordInboundSession, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
33
34
  import { createInternalHookEvent, fireAndForgetHook, toInternalMessageReceivedContext, triggerInternalHook } from "openclaw/plugin-sdk/hook-runtime";
34
- import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
35
+ import { resolveBatchedReplyThreadingPolicy } from "openclaw/plugin-sdk/reply-reference";
35
36
  import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
36
37
  import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
37
38
  import { createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
@@ -63,7 +64,7 @@ function bindSignalCliOutput(params) {
63
64
  function resolveSignalCliConfigPath(raw) {
64
65
  const value = raw.trim();
65
66
  if (value === "~") return os.homedir();
66
- if (value.startsWith("~/") || value.startsWith("~\\")) return path.join(os.homedir(), value.slice(2));
67
+ if (value.startsWith("~/") || value.startsWith("~\\")) return nodePath.join(os.homedir(), value.slice(2));
67
68
  return value;
68
69
  }
69
70
  function buildDaemonArgs(opts) {
@@ -258,6 +259,7 @@ async function handleSignalDirectMessageAccess(params) {
258
259
  }
259
260
  if (params.dmPolicy === "pairing") await createChannelPairingChallengeIssuer({
260
261
  channel: "signal",
262
+ accountId: params.accountId,
261
263
  upsertPairingRequest: async ({ id, meta }) => await upsertChannelPairingRequest({
262
264
  channel: "signal",
263
265
  id,
@@ -459,6 +461,11 @@ function createSignalEventHandler(deps) {
459
461
  historyKey,
460
462
  limit: deps.historyLimit
461
463
  }) : void 0;
464
+ const replyThreading = resolveBatchedReplyThreadingPolicy(resolveSignalReplyToMode({
465
+ cfg: deps.cfg,
466
+ accountId: deps.accountId,
467
+ chatType: entry.isGroup ? "group" : "direct"
468
+ }), entry.isBatched === true);
462
469
  const media = entry.mediaPaths && entry.mediaPaths.length > 0 ? entry.mediaPaths.map((path, index) => ({
463
470
  path,
464
471
  url: path,
@@ -492,12 +499,15 @@ function createSignalEventHandler(deps) {
492
499
  accountId: route.accountId,
493
500
  routeSessionKey: route.sessionKey
494
501
  },
495
- reply: { to: signalTo },
502
+ reply: {
503
+ to: signalTo,
504
+ replyToId: entry.replyToId ?? entry.messageId
505
+ },
496
506
  message: {
497
507
  body: combinedBody,
498
508
  bodyForAgent: entry.bodyText,
499
509
  inboundHistory,
500
- rawBody: entry.bodyText,
510
+ rawBody: entry.commandBody,
501
511
  commandBody: entry.commandBody
502
512
  },
503
513
  access: {
@@ -508,7 +518,10 @@ function createSignalEventHandler(deps) {
508
518
  commands: { authorized: entry.commandAuthorized }
509
519
  },
510
520
  media,
511
- extra: { GroupSubject: entry.isGroup ? entry.groupName ?? void 0 : void 0 }
521
+ extra: {
522
+ GroupSubject: entry.isGroup ? entry.groupName ?? void 0 : void 0,
523
+ ReplyThreading: replyThreading
524
+ }
512
525
  });
513
526
  if (shouldLogVerbose()) {
514
527
  const preview = truncateUtf16Safe(body, 200).replace(/\\n/g, "\\\\n");
@@ -601,6 +614,12 @@ function createSignalEventHandler(deps) {
601
614
  }
602
615
  }
603
616
  });
617
+ const nativeReplyContext = {
618
+ replyToId: ctxPayload.ReplyToId,
619
+ author: entry.senderRecipient,
620
+ body: entry.nativeReplyBody ?? entry.bodyText,
621
+ state: { hasReplied: false }
622
+ };
604
623
  const { dispatcher, replyOptions, markDispatchIdle } = createReplyDispatcherWithTyping({
605
624
  ...replyPipeline,
606
625
  humanDelay: resolveHumanDelayConfig(deps.cfg, route.agentId),
@@ -612,10 +631,13 @@ function createSignalEventHandler(deps) {
612
631
  target: ctxPayload.To,
613
632
  baseUrl: deps.baseUrl,
614
633
  account: deps.account,
634
+ accountUuid: deps.accountUuid,
615
635
  accountId: deps.accountId,
616
636
  runtime: deps.runtime,
617
637
  maxBytes: deps.mediaMaxBytes,
618
- textLimit: deps.textLimit
638
+ textLimit: deps.textLimit,
639
+ replyContext: nativeReplyContext,
640
+ chatType: entry.isGroup ? "group" : "direct"
619
641
  });
620
642
  },
621
643
  onError: (err, info) => {
@@ -634,7 +656,7 @@ function createSignalEventHandler(deps) {
634
656
  ingest: () => ({
635
657
  id: entry.messageId ?? `${entry.timestamp ?? Date.now()}`,
636
658
  timestamp: entry.timestamp,
637
- rawText: entry.bodyText,
659
+ rawText: entry.commandBody,
638
660
  raw: entry
639
661
  }),
640
662
  resolveTurn: () => ({
@@ -741,7 +763,7 @@ function createSignalEventHandler(deps) {
741
763
  },
742
764
  shouldDebounce: (entry) => {
743
765
  return shouldDebounceTextInbound({
744
- text: entry.bodyText,
766
+ text: entry.commandBody,
745
767
  cfg: deps.cfg,
746
768
  hasMedia: Boolean(entry.mediaPath || entry.mediaType || entry.mediaPaths?.length)
747
769
  });
@@ -754,10 +776,14 @@ function createSignalEventHandler(deps) {
754
776
  return;
755
777
  }
756
778
  const combinedText = entries.map((entry) => entry.bodyText).filter(Boolean).join("\\n");
779
+ const combinedCommandBody = entries.map((entry) => entry.commandBody).filter(Boolean).join("\\n");
757
780
  if (!combinedText.trim()) return;
758
781
  await handleSignalInboundMessage({
759
782
  ...last,
760
783
  bodyText: combinedText,
784
+ commandBody: combinedCommandBody,
785
+ isBatched: true,
786
+ nativeReplyBody: last.nativeReplyBody ?? last.bodyText,
761
787
  mediaPath: void 0,
762
788
  mediaType: void 0,
763
789
  mediaPaths: void 0,
@@ -1029,8 +1055,12 @@ function createSignalEventHandler(deps) {
1029
1055
  const mediaTypes = [];
1030
1056
  let placeholder = "";
1031
1057
  const attachments = dataMessage.attachments ?? [];
1058
+ let unavailableAttachmentCount = deps.ignoreAttachments ? attachments.length : 0;
1032
1059
  if (!deps.ignoreAttachments) for (const attachment of attachments) {
1033
- if (!attachment?.id) continue;
1060
+ if (!attachment?.id) {
1061
+ unavailableAttachmentCount += 1;
1062
+ continue;
1063
+ }
1034
1064
  try {
1035
1065
  const fetched = await deps.fetchAttachment({
1036
1066
  baseUrl: deps.baseUrl,
@@ -1047,8 +1077,9 @@ function createSignalEventHandler(deps) {
1047
1077
  mediaPath = fetched.path;
1048
1078
  mediaType = fetched.contentType ?? attachment.contentType ?? void 0;
1049
1079
  }
1050
- }
1080
+ } else unavailableAttachmentCount += 1;
1051
1081
  } catch (err) {
1082
+ unavailableAttachmentCount += 1;
1052
1083
  deps.runtime.error?.(danger(`attachment fetch failed: ${String(err)}`));
1053
1084
  }
1054
1085
  }
@@ -1056,11 +1087,19 @@ function createSignalEventHandler(deps) {
1056
1087
  else {
1057
1088
  const kind = kindFromMime(mediaType ?? void 0);
1058
1089
  if (kind) placeholder = `<media:${kind}>`;
1059
- else if (attachments.length) placeholder = "<media:attachment>";
1090
+ else if (mediaPaths.length > 0) placeholder = "<media:attachment>";
1091
+ }
1092
+ let bodyText = messageText || placeholder || visibleQuoteText || "";
1093
+ if (unavailableAttachmentCount > 0) {
1094
+ const attachmentLabel = unavailableAttachmentCount === 1 ? "attachment" : "attachments";
1095
+ bodyText = formatInboundMediaUnavailableText({
1096
+ body: bodyText,
1097
+ notice: `[signal ${unavailableAttachmentCount > 1 ? `${unavailableAttachmentCount} ` : ""}${attachmentLabel} unavailable]`
1098
+ });
1060
1099
  }
1061
- const bodyText = messageText || placeholder || visibleQuoteText || "";
1062
1100
  if (!bodyText) return;
1063
1101
  const inboundTimestamp = typeof envelope.timestamp === "number" ? envelope.timestamp : typeof dataMessage.timestamp === "number" ? dataMessage.timestamp : void 0;
1102
+ const nativeReplyTargetTimestamp = typeof envelope.editMessage?.targetSentTimestamp === "number" ? envelope.editMessage.targetSentTimestamp : inboundTimestamp;
1064
1103
  if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && inboundTimestamp) try {
1065
1104
  await sendReadReceiptSignal(`signal:${senderRecipient}`, inboundTimestamp, {
1066
1105
  cfg: deps.cfg,
@@ -1074,6 +1113,7 @@ function createSignalEventHandler(deps) {
1074
1113
  else if (deps.sendReadReceipts && !deps.readReceiptsViaDaemon && !isGroup && !inboundTimestamp) logVerbose(`signal read receipt skipped (missing timestamp) for ${senderDisplay}`);
1075
1114
  const senderName = envelope.sourceName ?? senderDisplay;
1076
1115
  const messageId = typeof inboundTimestamp === "number" ? String(inboundTimestamp) : void 0;
1116
+ const replyToId = typeof nativeReplyTargetTimestamp === "number" ? String(nativeReplyTargetTimestamp) : void 0;
1077
1117
  await inboundDebouncer.enqueue({
1078
1118
  senderName,
1079
1119
  senderDisplay,
@@ -1086,6 +1126,7 @@ function createSignalEventHandler(deps) {
1086
1126
  commandBody: messageText,
1087
1127
  timestamp: inboundTimestamp,
1088
1128
  messageId,
1129
+ replyToId,
1089
1130
  mediaPath,
1090
1131
  mediaType,
1091
1132
  mediaPaths: mediaPaths.length > 0 ? mediaPaths : void 0,
@@ -1354,7 +1395,12 @@ async function fetchAttachment(params) {
1354
1395
  };
1355
1396
  }
1356
1397
  async function deliverReplies(params) {
1357
- const { replies, target, baseUrl, account, accountId, runtime, maxBytes, textLimit, chunkMode } = params;
1398
+ const { replies, target, baseUrl, account, accountUuid, accountId, runtime, maxBytes, textLimit, chunkMode } = params;
1399
+ const replyToMode = resolveSignalReplyToMode({
1400
+ cfg: params.cfg,
1401
+ accountId,
1402
+ chatType: params.chatType
1403
+ });
1358
1404
  for (const payload of replies) {
1359
1405
  const deliveryResults = [];
1360
1406
  const deliveredPayload = addSignalApprovalReactionHintToStructuredPayload({
@@ -1362,9 +1408,15 @@ async function deliverReplies(params) {
1362
1408
  accountId,
1363
1409
  to: target,
1364
1410
  payload,
1365
- targetAuthor: account
1411
+ targetAuthor: account,
1412
+ targetAuthorUuid: accountUuid
1366
1413
  }) ?? payload;
1367
1414
  const reply = resolveSendableOutboundReplyParts(deliveredPayload);
1415
+ const nextNativeReply = createSignalNativeReplyResolver({
1416
+ payload: deliveredPayload,
1417
+ replyContext: params.replyContext,
1418
+ replyToMode
1419
+ });
1368
1420
  const recordDeliveryResult = (result, visibleText) => {
1369
1421
  const messageId = typeof result?.messageId === "string" && result.messageId.trim() ? result.messageId.trim() : null;
1370
1422
  if (messageId) deliveryResults.push({
@@ -1383,7 +1435,8 @@ async function deliverReplies(params) {
1383
1435
  baseUrl,
1384
1436
  account,
1385
1437
  maxBytes,
1386
- accountId
1438
+ accountId,
1439
+ ...nextNativeReply()
1387
1440
  }), chunk);
1388
1441
  },
1389
1442
  sendMedia: async ({ mediaUrl, caption }) => {
@@ -1394,7 +1447,8 @@ async function deliverReplies(params) {
1394
1447
  account,
1395
1448
  mediaUrl,
1396
1449
  maxBytes,
1397
- accountId
1450
+ accountId,
1451
+ ...nextNativeReply()
1398
1452
  }), visibleText);
1399
1453
  }
1400
1454
  }) !== "empty") {
@@ -1407,12 +1461,51 @@ async function deliverReplies(params) {
1407
1461
  },
1408
1462
  payload: deliveredPayload,
1409
1463
  results: deliveryResults,
1410
- targetAuthor: account
1464
+ targetAuthor: account,
1465
+ targetAuthorUuid: accountUuid
1411
1466
  });
1412
1467
  runtime.log?.(`delivered reply to ${target}`);
1413
1468
  }
1414
1469
  }
1415
1470
  }
1471
+ function resolveSignalNativeReplyOptions(params) {
1472
+ if (params.payload.replyToCurrent === false) return {};
1473
+ const payloadReplyToId = normalizeOptionalString(params.payload.replyToId);
1474
+ const contextReplyToId = normalizeOptionalString(params.replyContext?.replyToId);
1475
+ if (!payloadReplyToId || !contextReplyToId || payloadReplyToId !== contextReplyToId) return {};
1476
+ const replyToAuthor = normalizeOptionalString(params.replyContext?.author);
1477
+ if (!replyToAuthor) return { replyToId: payloadReplyToId };
1478
+ return {
1479
+ replyToId: payloadReplyToId,
1480
+ replyToAuthor,
1481
+ replyToBody: params.replyContext?.body ?? ""
1482
+ };
1483
+ }
1484
+ function isSignalStatusNoticePayload(payload) {
1485
+ return Boolean(payload.isCompactionNotice || payload.isFallbackNotice || payload.isStatusNotice);
1486
+ }
1487
+ function createSignalNativeReplyResolver(params) {
1488
+ const nativeReply = resolveSignalNativeReplyOptions(params);
1489
+ if (!nativeReply.replyToId) return () => ({});
1490
+ const isExplicitReply = params.payload.replyToTag === true || params.payload.replyToCurrent === true;
1491
+ const isStatusNotice = isSignalStatusNoticePayload(params.payload);
1492
+ if (isStatusNotice && params.replyToMode === "off") return () => ({});
1493
+ if (isExplicitReply) return () => nativeReply;
1494
+ if (isStatusNotice) return () => nativeReply;
1495
+ const planner = createReplyReferencePlanner({
1496
+ replyToMode: params.replyToMode,
1497
+ existingId: nativeReply.replyToId,
1498
+ hasReplied: params.replyContext?.state?.hasReplied
1499
+ });
1500
+ return () => {
1501
+ const replyToId = planner.use();
1502
+ if (params.replyContext?.state && !isStatusNotice) params.replyContext.state.hasReplied = planner.hasReplied();
1503
+ return replyToId ? {
1504
+ ...nativeReply,
1505
+ replyToId
1506
+ } : {};
1507
+ };
1508
+ }
1416
1509
  async function monitorSignalProvider(opts = {}) {
1417
1510
  const runtime = resolveRuntime(opts);
1418
1511
  const cfg = opts.config ?? getRuntimeConfig();
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-Dm8-wT2n.js";
2
+ import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-C9mB2Jz8.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 });
@@ -1,6 +1,6 @@
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";
1
+ import { i as resolveSignalAccount } from "./accounts-Bz4HtP0g.js";
2
+ import { n as signalRpcRequest } from "./client-adapter-C9mB2Jz8.js";
3
+ import { t as resolveSignalRpcContext } from "./rpc-context-DsHWh2hc.js";
4
4
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
6
6
  //#region extensions/signal/src/send-reactions.ts
@@ -1,2 +1,2 @@
1
- import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-BkAxQPGs.js";
1
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-EQ7cc7-Y.js";
2
2
  export { removeReactionSignal, sendReactionSignal };
@@ -1,4 +1,4 @@
1
- import "./accounts-B7Rz3_xV.js";
1
+ import "./accounts-Bz4HtP0g.js";
2
2
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  //#region extensions/signal/src/rpc-context.ts
4
4
  function resolveSignalRpcContext(opts, accountInfo) {
@@ -1,13 +1,13 @@
1
- import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./accounts-B7Rz3_xV.js";
2
- import { d as normalizeSignalMessagingTarget, u as looksLikeSignalTargetId } from "./identity-B6O4k8xg.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";
1
+ import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId, t as listEnabledSignalAccounts } from "./accounts-Bz4HtP0g.js";
2
+ import { d as normalizeSignalMessagingTarget, u as looksLikeSignalTargetId } from "./identity-C8-yk4J9.js";
3
+ import { n as resolveSignalReactionLevel, t as signalMessageActions } from "./message-actions-3OLDgjis.js";
4
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-EQ7cc7-Y.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-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";
6
+ import { r as installSignalCli } from "./install-signal-cli-ik8VPaGg.js";
7
+ import { f as setSignalRuntime } from "./approval-reactions-UFmUPD0A.js";
8
+ import { t as monitorSignalProvider } from "./monitor-Bee8O5Ay.js";
9
+ import { t as sendMessageSignal } from "./send-CUJy6WPt.js";
10
+ import { t as probeSignal } from "./probe-Cnht8Ihg.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,11 +1,12 @@
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";
1
+ import { i as resolveSignalAccount } from "./accounts-Bz4HtP0g.js";
2
+ import { r as markdownToSignalText } from "./message-actions-3OLDgjis.js";
3
+ import { n as signalRpcRequest } from "./client-adapter-C9mB2Jz8.js";
4
+ import { t as resolveSignalRpcContext } from "./rpc-context-DsHWh2hc.js";
5
+ import { c as registerSignalApprovalReactionTargetForOutboundMessage, n as appendSignalApprovalReactionHintForOutboundMessage } from "./approval-reactions-UFmUPD0A.js";
5
6
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
6
7
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
7
8
  import { kindFromMime, resolveOutboundAttachmentFromUrl } from "openclaw/plugin-sdk/media-runtime";
8
- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
9
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
9
10
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
10
11
  //#region extensions/signal/src/send.ts
11
12
  async function resolveSignalRpcAccountInfo(opts) {
@@ -58,7 +59,13 @@ function createSignalSendReceipt(params) {
58
59
  const results = messageId && messageId !== "unknown" ? [{
59
60
  channel: "signal",
60
61
  messageId,
61
- meta: { targetType: params.target.type }
62
+ meta: {
63
+ targetType: params.target.type,
64
+ ...params.replyToId ? {
65
+ replyToId: params.replyToId,
66
+ nativeReplyStatus: params.nativeReplyStatus ?? "sent"
67
+ } : {}
68
+ }
62
69
  }] : [];
63
70
  if (results[0]) {
64
71
  if (params.timestamp != null) results[0].timestamp = params.timestamp;
@@ -68,9 +75,35 @@ function createSignalSendReceipt(params) {
68
75
  }
69
76
  return createMessageReceiptFromOutboundResults({
70
77
  results,
71
- kind: params.kind
78
+ kind: params.kind,
79
+ ...params.replyToId ? { replyToId: params.replyToId } : {}
72
80
  });
73
81
  }
82
+ function parseSignalReplyTimestamp(raw) {
83
+ const value = normalizeOptionalString(raw);
84
+ if (!value || !/^\d+$/.test(value)) return;
85
+ const timestamp = Number(value);
86
+ if (!Number.isSafeInteger(timestamp) || timestamp <= 0) return;
87
+ return timestamp;
88
+ }
89
+ function resolveSignalQuoteParams(opts) {
90
+ const timestamp = parseSignalReplyTimestamp(opts.replyToId);
91
+ const author = normalizeOptionalString(opts.replyToAuthor);
92
+ if (timestamp === void 0 || !author) return;
93
+ return {
94
+ replyToId: String(timestamp),
95
+ params: {
96
+ quoteTimestamp: timestamp,
97
+ quoteAuthor: author,
98
+ quoteMessage: opts.replyToBody ?? ""
99
+ }
100
+ };
101
+ }
102
+ function isSignalQuoteMetadataRejection(error) {
103
+ const normalized = normalizeLowercaseStringOrEmpty(error instanceof Error ? error.message : String(error));
104
+ if (!normalized.includes("quote")) return false;
105
+ return normalized.includes("reject") || normalized.includes("invalid") || normalized.includes("unrecognized") || normalized.includes("unsupported") || normalized.includes("not found") || normalized.includes("no such") || normalized.includes("unknown");
106
+ }
74
107
  async function sendMessageSignal(to, text, opts) {
75
108
  const cfg = requireRuntimeConfig(opts.cfg, "Signal send");
76
109
  const apiMode = cfg.channels?.signal?.apiMode;
@@ -80,7 +113,17 @@ async function sendMessageSignal(to, text, opts) {
80
113
  });
81
114
  const { baseUrl, account } = resolveSignalRpcContext(opts, accountInfo);
82
115
  const target = parseTarget(to);
83
- let message = text ?? "";
116
+ const targetAuthor = normalizeOptionalString(account);
117
+ const targetAuthorUuid = normalizeOptionalString(accountInfo.config.accountUuid);
118
+ const outboundText = appendSignalApprovalReactionHintForOutboundMessage({
119
+ cfg,
120
+ accountId: accountInfo.accountId,
121
+ to,
122
+ text: text ?? "",
123
+ targetAuthor,
124
+ targetAuthorUuid
125
+ });
126
+ let message = outboundText;
84
127
  let messageFromPlaceholder = false;
85
128
  let textStyles = [];
86
129
  const textMode = opts.textMode ?? "markdown";
@@ -127,12 +170,38 @@ async function sendMessageSignal(to, text, opts) {
127
170
  });
128
171
  if (!targetParams) throw new Error("Signal recipient is required");
129
172
  Object.assign(params, targetParams);
130
- const timestamp = (await signalRpcRequest("send", params, {
173
+ const quote = resolveSignalQuoteParams(opts);
174
+ const sendOpts = {
131
175
  baseUrl,
132
176
  timeoutMs: opts.timeoutMs,
133
- apiMode
134
- }))?.timestamp;
177
+ apiMode,
178
+ maxAttachmentBytes: maxBytes
179
+ };
180
+ let nativeReplyStatus;
181
+ let result;
182
+ if (quote) try {
183
+ result = await signalRpcRequest("send", {
184
+ ...params,
185
+ ...quote.params
186
+ }, sendOpts);
187
+ nativeReplyStatus = "sent";
188
+ } catch (error) {
189
+ if (!isSignalQuoteMetadataRejection(error)) throw error;
190
+ result = await signalRpcRequest("send", params, sendOpts);
191
+ nativeReplyStatus = "fallback";
192
+ }
193
+ else result = await signalRpcRequest("send", params, sendOpts);
194
+ const timestamp = result?.timestamp;
135
195
  const messageId = timestamp ? String(timestamp) : "unknown";
196
+ registerSignalApprovalReactionTargetForOutboundMessage({
197
+ cfg,
198
+ accountId: accountInfo.accountId,
199
+ to,
200
+ messageId,
201
+ text: outboundText,
202
+ targetAuthor,
203
+ targetAuthorUuid
204
+ });
136
205
  return {
137
206
  messageId,
138
207
  timestamp,
@@ -140,6 +209,10 @@ async function sendMessageSignal(to, text, opts) {
140
209
  messageId,
141
210
  target,
142
211
  kind: attachments && attachments.length > 0 ? "media" : "text",
212
+ ...quote ? {
213
+ replyToId: quote.replyToId,
214
+ nativeReplyStatus
215
+ } : {},
143
216
  ...timestamp != null ? { timestamp } : {}
144
217
  })
145
218
  };
@@ -1,2 +1,2 @@
1
- import { r as sendTypingSignal, t as sendMessageSignal } from "./send-CLzc3RUg.js";
1
+ import { r as sendTypingSignal, t as sendMessageSignal } from "./send-CUJy6WPt.js";
2
2
  export { sendMessageSignal, sendTypingSignal };