@openclaw/signal 2026.8.1-beta.2 → 2026.8.1

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 (31) hide show
  1. package/dist/{accounts-DrStJjiK.js → accounts-DO4HMqaK.js} +2 -2
  2. package/dist/api.js +11 -10
  3. package/dist/{runtime-JPrGlV7h.js → approval-auth-BsYHLTHK.js} +4 -10
  4. package/dist/{approval-handler.runtime-Bi5yNhnj.js → approval-handler.runtime-PqC23XbY.js} +5 -5
  5. package/dist/{approval-reactions-BulCzhdB.js → approval-reactions-DB-86mqK.js} +63 -154
  6. package/dist/{channel-CzDJMDtX.js → channel-C4vwPXz8.js} +28 -23
  7. package/dist/channel-config-api.js +1 -1
  8. package/dist/channel-plugin-api.js +1 -1
  9. package/dist/{channel.runtime-D12iYzJB.js → channel.runtime-CSviXPlC.js} +2 -2
  10. package/dist/{config-schema-CkMIK7zp.js → config-schema-q_QuhDy2.js} +8 -7
  11. package/dist/contract-api.js +3 -2
  12. package/dist/{doctor-contract-api-Dnp5eQQJ.js → doctor-contract-api-C18u1hW0.js} +1 -1
  13. package/dist/doctor-contract-api.js +1 -1
  14. package/dist/{identity-BQ4OzXXk.js → identity-YXPmgFMu.js} +2 -47
  15. package/dist/{monitor-DiN2hKnG.js → monitor-fR9bCL2p.js} +188 -85
  16. package/dist/normalize-l_b99hap.js +51 -0
  17. package/dist/{probe-C_THuQzn.js → probe-D7BV0KJ5.js} +1 -1
  18. package/dist/{question-reactions-ByjH5qST.js → question-reactions-D04hVKsE.js} +2 -2
  19. package/dist/{reaction-runtime-api-Cf6QHcE5.js → reaction-runtime-api-Ch0dk2sh.js} +11 -36
  20. package/dist/reaction-runtime-api.js +1 -1
  21. package/dist/rpc-context-dXSy4NtF.js +15 -0
  22. package/dist/runtime-api.js +9 -10
  23. package/dist/{send-BvGgByMd.js → send-DO3rs413.js} +6 -24
  24. package/dist/{send.runtime-CtTBBrjL.js → send.runtime-FRY_sREg.js} +1 -1
  25. package/dist/{transport-detection-BItHawMq.js → transport-detection-BSXAOmuH.js} +19 -42
  26. package/dist/{transport-detection.runtime-DoiwPcrG.js → transport-detection.runtime-Cnykqcu0.js} +1 -1
  27. package/node_modules/ws/lib/permessage-deflate.js +3 -1
  28. package/node_modules/ws/package.json +1 -1
  29. package/openclaw.plugin.json +0 -6
  30. package/package.json +5 -5
  31. package/dist/rpc-context-CZUoE0-A.js +0 -17
@@ -1,51 +1,6 @@
1
- import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
1
+ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
2
2
  import { normalizeE164 } from "openclaw/plugin-sdk/text-utility-runtime";
3
3
  import { resolveAllowlistMatchByCandidates } from "openclaw/plugin-sdk/allow-from";
4
- //#region extensions/signal/src/normalize.ts
5
- function normalizeSignalMessagingTarget(raw) {
6
- const trimmed = raw.trim();
7
- if (!trimmed) return;
8
- let normalized = trimmed;
9
- if (normalizeLowercaseStringOrEmpty(normalized).startsWith("signal:")) normalized = normalized.slice(7).trim();
10
- if (!normalized) return;
11
- const lower = normalizeLowercaseStringOrEmpty(normalized);
12
- if (lower.startsWith("group:")) {
13
- const id = normalized.slice(6).trim();
14
- return id ? `group:${id}` : void 0;
15
- }
16
- if (lower.startsWith("username:")) {
17
- const id = normalized.slice(9).trim();
18
- return id ? normalizeLowercaseStringOrEmpty(`username:${id}`) : void 0;
19
- }
20
- if (lower.startsWith("u:")) {
21
- const id = normalized.slice(2).trim();
22
- return id ? normalizeLowercaseStringOrEmpty(`username:${id}`) : void 0;
23
- }
24
- if (lower.startsWith("uuid:")) {
25
- const id = normalized.slice(5).trim();
26
- return id ? normalizeLowercaseStringOrEmpty(id) : void 0;
27
- }
28
- return normalizeLowercaseStringOrEmpty(normalized);
29
- }
30
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
31
- const UUID_COMPACT_PATTERN = /^[0-9a-f]{32}$/i;
32
- function looksLikeSignalTargetId(raw, normalized) {
33
- const candidates = normalizeStringEntries([raw, normalized ?? ""]);
34
- for (const candidate of candidates) {
35
- if (/^(signal:)?(group:|username:|u:)/i.test(candidate)) return true;
36
- if (/^(signal:)?uuid:/i.test(candidate)) {
37
- const stripped = candidate.replace(/^signal:/i, "").replace(/^uuid:/i, "").trim();
38
- if (!stripped) continue;
39
- if (UUID_PATTERN.test(stripped) || UUID_COMPACT_PATTERN.test(stripped)) return true;
40
- continue;
41
- }
42
- const withoutSignalPrefix = candidate.replace(/^signal:/i, "").trim();
43
- if (UUID_PATTERN.test(withoutSignalPrefix) || UUID_COMPACT_PATTERN.test(withoutSignalPrefix)) return true;
44
- if (/^\+?\d{3,}$/.test(withoutSignalPrefix)) return true;
45
- }
46
- return false;
47
- }
48
- //#endregion
49
4
  //#region extensions/signal/src/uuid.ts
50
5
  const UUID_HYPHENATED_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
51
6
  const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
@@ -143,4 +98,4 @@ function isSignalSenderAllowed(sender, allowFrom) {
143
98
  }).allowed;
144
99
  }
145
100
  //#endregion
146
- export { normalizeSignalAllowRecipient as a, resolveSignalSender as c, normalizeSignalMessagingTarget as d, isSignalSenderAllowed as i, looksLikeUuid as l, formatSignalSenderDisplay as n, resolveSignalPeerId as o, formatSignalSenderId as r, resolveSignalRecipient as s, formatSignalPairingIdLine as t, looksLikeSignalTargetId as u };
101
+ export { normalizeSignalAllowRecipient as a, resolveSignalSender as c, isSignalSenderAllowed as i, looksLikeUuid as l, formatSignalSenderDisplay as n, resolveSignalPeerId as o, formatSignalSenderId as r, resolveSignalRecipient as s, formatSignalPairingIdLine as t };
@@ -1,23 +1,24 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { i as resolveSignalAccount, o as resolveSignalReplyToMode } from "./accounts-DrStJjiK.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-BQ4OzXXk.js";
4
- import { t as getOptionalSignalRuntime } from "./runtime-JPrGlV7h.js";
5
- import { i as materializeSignalPresentationFallback, n as registerSignalReplyContext, o as resolveSignalReactionLevel, s as formatSignalMediaText, u as isSignalNativeApprovalHandlerConfigured } from "./transport-detection-BItHawMq.js";
2
+ import { l as normalizeSignalTransportHost } from "./transport-policy-DxvSMHp9.js";
3
+ import { i as resolveSignalAccount, o as resolveSignalReplyToMode } from "./accounts-DO4HMqaK.js";
4
+ import { n as normalizeSignalMessagingTarget } from "./normalize-l_b99hap.js";
5
+ import { a as normalizeSignalAllowRecipient, c as resolveSignalSender, i as isSignalSenderAllowed, l as looksLikeUuid, n as formatSignalSenderDisplay, o as resolveSignalPeerId, r as formatSignalSenderId, s as resolveSignalRecipient, t as formatSignalPairingIdLine } from "./identity-YXPmgFMu.js";
6
+ import { c as resolveSignalReactionLevel, f as isSignalNativeApprovalHandlerConfigured, i as getOptionalSignalRuntime, l as formatSignalMediaText, n as registerSignalReplyContext, o as materializeSignalPresentationFallback } from "./transport-detection-BSXAOmuH.js";
6
7
  import { n as signalRpcRequest, r as streamSignalEvents, t as signalCheck } from "./client-adapter-D9SNPaNx.js";
7
- import { n as sendReactionSignal } from "./reaction-runtime-api-Cf6QHcE5.js";
8
- import { a as maybeResolveSignalApprovalReaction, l as resolveSignalApprovalConversationKey, s as registerSignalApprovalReactionTargetForDeliveredPayload, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-BulCzhdB.js";
9
- import { r as registerSignalQuestionReactionTargetForDeliveredPayload, t as maybeResolveSignalQuestionReaction } from "./question-reactions-ByjH5qST.js";
10
- import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-BvGgByMd.js";
8
+ import { n as sendReactionSignal } from "./reaction-runtime-api-Ch0dk2sh.js";
9
+ import { i as maybeResolveSignalApprovalReaction, o as registerSignalApprovalReactionTargetForDeliveredPayload, s as resolveSignalApprovalConversationKey, t as addSignalApprovalReactionHintToStructuredPayload } from "./approval-reactions-DB-86mqK.js";
10
+ import { r as registerSignalQuestionReactionTargetForDeliveredPayload, t as maybeResolveSignalQuestionReaction } from "./question-reactions-D04hVKsE.js";
11
+ import { n as sendReadReceiptSignal, r as sendTypingSignal, t as sendMessageSignal } from "./send-DO3rs413.js";
11
12
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
12
13
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
13
- import { canonicalizeBase64, detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
14
14
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
15
15
  import { asPositiveSafeInteger, isRecord, normalizeNullableString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
16
16
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
17
17
  import { normalizeE164, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
18
18
  import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, hasVisibleInboundReplyDispatch, logInboundDrop, matchesMentionPatterns, readAgentRunTerminalOutcome, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toHistoryMediaEntries, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
19
+ import { canonicalizeBase64, detectMime, estimateBase64DecodedBytes, kindFromMime, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
19
20
  import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
20
- import { resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
21
+ import { ensurePortAvailable, extractErrorCode, formatErrorMessage as formatErrorMessage$1, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
21
22
  import { resolveChannelGroupPolicy, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
22
23
  import path from "node:path";
23
24
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
@@ -50,6 +51,47 @@ const SIGNAL_DAEMON_STOP_KILL_TIMEOUT_MS = 1500;
50
51
  function formatSignalDaemonExit(exit) {
51
52
  return `signal daemon exited (source=${exit.source} code=${exit.code ?? "null"} signal=${exit.signal ?? "null"})`;
52
53
  }
54
+ function formatSignalDaemonEndpoint(httpHost, httpPort) {
55
+ return `${httpHost.includes(":") ? `[${httpHost}]` : httpHost}:${httpPort}`;
56
+ }
57
+ async function assertSignalDaemonEndpointAvailable(params) {
58
+ try {
59
+ await ensurePortAvailable(params.httpPort, params.httpHost, params.abortSignal);
60
+ } catch (error) {
61
+ if (params.abortSignal?.aborted) throw params.abortSignal.reason;
62
+ if (!(extractErrorCode(error) === "EADDRINUSE" || error instanceof Error && error.name === "PortInUseError")) return;
63
+ const endpoint = formatSignalDaemonEndpoint(params.httpHost, params.httpPort);
64
+ throw new Error(`Signal managed native endpoint ${endpoint} is unavailable: ${formatErrorMessage$1(error)} Stop the conflicting service, configure this Signal account with a different transport.httpPort, or use external-native for an intentionally operator-managed daemon.`, { cause: error });
65
+ }
66
+ }
67
+ async function waitForSignalDaemonReady(params) {
68
+ await (params.waitForTransportReadyFn ?? waitForTransportReady)({
69
+ label: "signal daemon",
70
+ timeoutMs: Math.max(0, params.startupDeadlineMs - Date.now()),
71
+ logAfterMs: params.logAfterMs,
72
+ logIntervalMs: params.logIntervalMs,
73
+ pollIntervalMs: 150,
74
+ abortSignal: params.abortSignal,
75
+ runtime: params.runtime,
76
+ check: async () => {
77
+ const remainingMs = params.startupDeadlineMs - Date.now();
78
+ if (remainingMs <= 0) return {
79
+ ok: false,
80
+ error: "startup deadline exceeded"
81
+ };
82
+ const res = await signalCheck(params.baseUrl, Math.min(1e3, remainingMs));
83
+ if (Date.now() >= params.startupDeadlineMs) return {
84
+ ok: false,
85
+ error: "startup deadline exceeded"
86
+ };
87
+ if (res.ok) return { ok: true };
88
+ return {
89
+ ok: false,
90
+ error: res.error ?? (res.status ? `HTTP ${res.status}` : "unreachable")
91
+ };
92
+ }
93
+ });
94
+ }
53
95
  function isRecoverableSignalCliReceiveException(line) {
54
96
  return /\breceive exception:\s+.*\binvalid PreKey message:\s+decryption failed\b/i.test(line);
55
97
  }
@@ -211,6 +253,68 @@ function createSignalDaemonLifecycle(params) {
211
253
  };
212
254
  }
213
255
  //#endregion
256
+ //#region extensions/signal/src/native-reply.ts
257
+ function resolveSignalNativeReplyId(params) {
258
+ if (params.payload.replyToCurrent === false) return;
259
+ const payloadReplyToId = normalizeOptionalString(params.payload.replyToId);
260
+ const isExplicitCurrentReply = params.payload.replyToTag === true || params.payload.replyToCurrent === true;
261
+ if (!payloadReplyToId && !isExplicitCurrentReply && params.replyContext?.allowImplicitCurrentMessage === false) return;
262
+ const contextReplyToId = normalizeOptionalString(params.replyContext?.replyToId);
263
+ if (!contextReplyToId || payloadReplyToId && payloadReplyToId !== contextReplyToId) return;
264
+ return payloadReplyToId ?? contextReplyToId;
265
+ }
266
+ function isSignalStatusNoticePayload(payload) {
267
+ return Boolean(payload.isCompactionNotice || payload.isFallbackNotice || payload.isStatusNotice);
268
+ }
269
+ function createSignalNativeReplyIdPlan(params) {
270
+ const replyToId = resolveSignalNativeReplyId(params);
271
+ if (!replyToId) return {
272
+ peek: () => void 0,
273
+ use: () => void 0,
274
+ markSent: () => void 0
275
+ };
276
+ const isExplicitReply = params.payload.replyToTag === true || params.payload.replyToCurrent === true;
277
+ if (isSignalStatusNoticePayload(params.payload)) {
278
+ const resolve = params.replyToMode === "off" ? () => void 0 : () => replyToId;
279
+ return {
280
+ peek: resolve,
281
+ use: resolve,
282
+ markSent: () => void 0
283
+ };
284
+ }
285
+ if (isExplicitReply) {
286
+ const resolve = () => replyToId;
287
+ return {
288
+ peek: resolve,
289
+ use: resolve,
290
+ markSent: () => void 0
291
+ };
292
+ }
293
+ const planner = createReplyReferencePlanner({
294
+ replyToMode: params.replyToMode,
295
+ existingId: replyToId,
296
+ hasReplied: params.replyContext?.state?.hasReplied
297
+ });
298
+ const syncState = () => {
299
+ if (params.replyContext?.state) params.replyContext.state.hasReplied = planner.hasReplied();
300
+ };
301
+ return {
302
+ peek: () => planner.peek(),
303
+ use: () => {
304
+ const nextReplyToId = planner.use();
305
+ syncState();
306
+ return nextReplyToId;
307
+ },
308
+ markSent: () => {
309
+ planner.markSent();
310
+ syncState();
311
+ }
312
+ };
313
+ }
314
+ function createSignalNativeReplyIdResolver(params) {
315
+ return createSignalNativeReplyIdPlan(params).use;
316
+ }
317
+ //#endregion
214
318
  //#region extensions/signal/src/monitor/access-policy.ts
215
319
  const SIGNAL_UUID_KIND = "plugin:signal-uuid";
216
320
  const SIGNAL_GROUP_KIND = "plugin:signal-group";
@@ -556,8 +660,7 @@ function resolveSignalStatusReactionTimestamp(params) {
556
660
  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
557
661
  }
558
662
  function hasSignalStatusReplyDeliveryFailure(result) {
559
- const failedCounts = result.failedCounts;
560
- return (failedCounts?.tool ?? 0) > 0 || (failedCounts?.block ?? 0) > 0 || (failedCounts?.final ?? 0) > 0;
663
+ return Object.values(result.settledReceipt?.counts ?? {}).some((counts) => counts.failedBeforeSend > 0 || counts.failedAfterSend > 0);
561
664
  }
562
665
  function resolveSignalStatusReactionEmojis(emojis) {
563
666
  if (emojis?.stallHard !== void 0) return emojis;
@@ -775,6 +878,7 @@ function createSignalEventHandler(deps) {
775
878
  };
776
879
  const dispatcherOptions = {
777
880
  ...replyPipeline,
881
+ propagateRetryableNoSendFailure: true,
778
882
  humanDelay: resolveHumanDelayConfig(deps.cfg, route.agentId),
779
883
  typingCallbacks
780
884
  };
@@ -795,6 +899,32 @@ function createSignalEventHandler(deps) {
795
899
  chatType: entry.isGroup ? "group" : "direct"
796
900
  });
797
901
  },
902
+ durable: (payload, info) => {
903
+ if (info.kind !== "final") return false;
904
+ const replyPlan = createSignalNativeReplyIdPlan({
905
+ payload,
906
+ replyContext: nativeReplyContext,
907
+ replyToMode
908
+ });
909
+ const send = async (to, text, options) => {
910
+ entry.turnAdoptionLifecycle?.abortSignal.throwIfAborted();
911
+ deps.abortSignal?.throwIfAborted();
912
+ const result = await sendMessageSignal(to, text, {
913
+ ...options,
914
+ baseUrl: deps.baseUrl,
915
+ account: deps.account,
916
+ maxBytes: deps.mediaMaxBytes,
917
+ accountId: deps.accountId
918
+ });
919
+ replyPlan.markSent();
920
+ return result;
921
+ };
922
+ return {
923
+ deps: { signal: send },
924
+ replyToId: replyPlan.peek() ?? null,
925
+ replyToMode
926
+ };
927
+ },
798
928
  onError: (err, info) => {
799
929
  deps.runtime.error?.(danger(`signal ${info.kind} reply failed: ${String(err)}`));
800
930
  }
@@ -823,6 +953,7 @@ function createSignalEventHandler(deps) {
823
953
  sessionKey: route.sessionKey
824
954
  },
825
955
  ctxPayload,
956
+ dispatchReplyFromConfig: deps.channelRuntime?.reply?.dispatchReplyFromConfig,
826
957
  record: {
827
958
  updateLastRoute: !entry.isGroup ? {
828
959
  sessionKey: inboundLastRouteSessionKey,
@@ -994,7 +1125,7 @@ function createSignalEventHandler(deps) {
994
1125
  if (!isSignalReplySessionInitConflictError(err)) throw err;
995
1126
  if (deps.abortSignal?.aborted) return;
996
1127
  await retrySignalInboundFlush(entries, admissionLifecycle, settle, err).catch(async (terminalError) => {
997
- await Promise.all(entries.map((entry) => Promise.resolve(entry.turnAdoptionLifecycle?.onAbandoned())));
1128
+ await lifecycle?.onAbandoned();
998
1129
  throw terminalError;
999
1130
  });
1000
1131
  }
@@ -1117,10 +1248,10 @@ function createSignalEventHandler(deps) {
1117
1248
  });
1118
1249
  return true;
1119
1250
  }
1120
- return async (event, turnAdoptionLifecycle) => {
1251
+ return async (event, turnAdoptionLifecycle, preparedPayload) => {
1121
1252
  if (event.event !== "receive" || !event.data) return;
1122
- let payload;
1123
- try {
1253
+ let payload = preparedPayload ?? null;
1254
+ if (!preparedPayload) try {
1124
1255
  payload = JSON.parse(event.data);
1125
1256
  } catch (err) {
1126
1257
  deps.runtime.error?.(`failed to parse event: ${String(err)}`);
@@ -1444,7 +1575,7 @@ const SignalIngressPermanentError = createChannelIngressError("SignalIngressPerm
1444
1575
  function normalizeTimestamp(value) {
1445
1576
  return asPositiveSafeInteger(value) ?? null;
1446
1577
  }
1447
- function parseReceiveEnvelope(event) {
1578
+ function parseReceivePayload(event) {
1448
1579
  if (event.event !== "receive" || !event.data) return null;
1449
1580
  let parsed;
1450
1581
  try {
@@ -1453,14 +1584,15 @@ function parseReceiveEnvelope(event) {
1453
1584
  throw new SignalIngressPermanentError("parse-error", "Signal receive event contains invalid JSON", { cause: error });
1454
1585
  }
1455
1586
  if (!isRecord(parsed)) throw new SignalIngressPermanentError("parse-error", "Signal receive event must contain a JSON object");
1456
- return isRecord(parsed.envelope) ? parsed.envelope : null;
1587
+ return parsed;
1457
1588
  }
1458
1589
  function resolveDataMessage(envelope) {
1459
1590
  if (isRecord(envelope.dataMessage)) return envelope.dataMessage;
1460
1591
  return isRecord(envelope.editMessage?.dataMessage) ? envelope.editMessage.dataMessage : null;
1461
1592
  }
1462
- function inspectSignalIngressEvent(event) {
1463
- const envelope = parseReceiveEnvelope(event);
1593
+ function inspectSignalIngressEvent(prepared) {
1594
+ const payload = prepared[1] ??= parseReceivePayload(prepared[0]);
1595
+ const envelope = isRecord(payload?.envelope) ? payload.envelope : null;
1464
1596
  if (!envelope || "syncMessage" in envelope) return null;
1465
1597
  const dataMessage = resolveDataMessage(envelope);
1466
1598
  const reactionMessage = isRecord(envelope.reactionMessage) ? envelope.reactionMessage : null;
@@ -1497,14 +1629,14 @@ async function startSignalIngressMonitor(params) {
1497
1629
  const ingressQueue = queue;
1498
1630
  const monitor = createChannelIngressMonitor({
1499
1631
  queue: ingressQueue,
1500
- inspect: (event) => inspectSignalIngressEvent(event),
1632
+ inspect: (prepared) => inspectSignalIngressEvent(prepared),
1501
1633
  payload: {
1502
1634
  version: 1,
1503
- serialize: (event, { receivedAt }) => ({
1635
+ serialize: ([event], { receivedAt }) => ({
1504
1636
  receivedAt,
1505
1637
  event
1506
1638
  }),
1507
- deserialize: (body) => body.event,
1639
+ deserialize: (body) => [body.event, void 0],
1508
1640
  encode: ({ body }) => ({
1509
1641
  version: 1,
1510
1642
  ...body
@@ -1515,11 +1647,11 @@ async function startSignalIngressMonitor(params) {
1515
1647
  }),
1516
1648
  createClaimError: (_kind, claim) => new SignalIngressPermanentError("unsupported-event", `Signal ingress row ${claim.id} has an invalid payload`)
1517
1649
  },
1518
- deliver: (event, lifecycle) => params.dispatch(event, lifecycle),
1519
- onDurableAdmission: async (_event, { facts }) => {
1650
+ deliver: ([event, parsedPayload], lifecycle) => parsedPayload ? params.dispatch(event, lifecycle, parsedPayload) : void 0,
1651
+ onDurableAdmission: async (_event, { facts, isNew }) => {
1520
1652
  const { numberAliasEventId } = facts;
1521
1653
  if (!numberAliasEventId) return;
1522
- if (!await ingressQueue.complete(numberAliasEventId)) await ingressQueue.complete(facts.eventId);
1654
+ if (!await ingressQueue.complete(numberAliasEventId) && isNew) await ingressQueue.complete(facts.eventId);
1523
1655
  },
1524
1656
  pollIntervalMs: SIGNAL_INGRESS_DRAIN_INTERVAL_MS,
1525
1657
  retention: {
@@ -1537,7 +1669,7 @@ async function startSignalIngressMonitor(params) {
1537
1669
  monitor.start();
1538
1670
  return {
1539
1671
  receive: async (event) => {
1540
- await monitor.admit(event);
1672
+ await monitor.admit([event, void 0]);
1541
1673
  await monitor.waitForPumpIdle();
1542
1674
  },
1543
1675
  stop: monitor.stop,
@@ -1684,25 +1816,6 @@ function buildSignalReactionSystemEventText(params) {
1684
1816
  const withTarget = params.targetLabel ? `${base} from ${params.targetLabel}` : base;
1685
1817
  return params.groupLabel ? `${withTarget} in ${params.groupLabel}` : withTarget;
1686
1818
  }
1687
- async function waitForSignalDaemonReady(params) {
1688
- await (params.waitForTransportReadyFn ?? waitForTransportReady)({
1689
- label: "signal daemon",
1690
- timeoutMs: params.timeoutMs,
1691
- logAfterMs: params.logAfterMs,
1692
- logIntervalMs: params.logIntervalMs,
1693
- pollIntervalMs: 150,
1694
- abortSignal: params.abortSignal,
1695
- runtime: params.runtime,
1696
- check: async () => {
1697
- const res = await signalCheck(params.baseUrl, 1e3);
1698
- if (res.ok) return { ok: true };
1699
- return {
1700
- ok: false,
1701
- error: res.error ?? (res.status ? `HTTP ${res.status}` : "unreachable")
1702
- };
1703
- }
1704
- });
1705
- }
1706
1819
  const SIGNAL_ATTACHMENT_RPC_RESPONSE_HEADROOM_BYTES = 64 * 1024;
1707
1820
  const SIGNAL_BASE64_OVERHEAD_NUMERATOR = 4;
1708
1821
  const SIGNAL_BASE64_OVERHEAD_DENOMINATOR = 3;
@@ -1814,45 +1927,19 @@ async function deliverReplies(params) {
1814
1927
  }
1815
1928
  }
1816
1929
  }
1817
- function resolveSignalNativeReplyOptions(params) {
1818
- if (params.payload.replyToCurrent === false) return {};
1819
- const payloadReplyToId = normalizeOptionalString(params.payload.replyToId);
1820
- const isExplicitCurrentReply = params.payload.replyToTag === true || params.payload.replyToCurrent === true;
1821
- if (!payloadReplyToId && !isExplicitCurrentReply && params.replyContext?.allowImplicitCurrentMessage === false) return {};
1822
- const contextReplyToId = normalizeOptionalString(params.replyContext?.replyToId);
1823
- if (!contextReplyToId || payloadReplyToId && payloadReplyToId !== contextReplyToId) return {};
1824
- const replyToId = payloadReplyToId ?? contextReplyToId;
1825
- const replyToAuthor = normalizeOptionalString(params.replyContext?.author);
1826
- if (!replyToAuthor) return { replyToId };
1827
- return {
1828
- replyToId,
1829
- replyToAuthor,
1830
- replyToBody: params.replyContext?.body ?? ""
1831
- };
1832
- }
1833
- function isSignalStatusNoticePayload(payload) {
1834
- return Boolean(payload.isCompactionNotice || payload.isFallbackNotice || payload.isStatusNotice);
1835
- }
1836
1930
  function createSignalNativeReplyResolver(params) {
1837
- const nativeReply = resolveSignalNativeReplyOptions(params);
1838
- if (!nativeReply.replyToId) return () => ({});
1839
- const isExplicitReply = params.payload.replyToTag === true || params.payload.replyToCurrent === true;
1840
- const isStatusNotice = isSignalStatusNoticePayload(params.payload);
1841
- if (isStatusNotice && params.replyToMode === "off") return () => ({});
1842
- if (isExplicitReply) return () => nativeReply;
1843
- if (isStatusNotice) return () => nativeReply;
1844
- const planner = createReplyReferencePlanner({
1845
- replyToMode: params.replyToMode,
1846
- existingId: nativeReply.replyToId,
1847
- hasReplied: params.replyContext?.state?.hasReplied
1848
- });
1931
+ const nextReplyToId = createSignalNativeReplyIdResolver(params);
1849
1932
  return () => {
1850
- const replyToId = planner.use();
1851
- if (params.replyContext?.state && !isStatusNotice) params.replyContext.state.hasReplied = planner.hasReplied();
1852
- return replyToId ? {
1853
- ...nativeReply,
1854
- replyToId
1855
- } : {};
1933
+ const replyToId = nextReplyToId();
1934
+ if (!replyToId) return {};
1935
+ const replyToAuthor = normalizeOptionalString(params.replyContext?.author);
1936
+ return {
1937
+ replyToId,
1938
+ ...replyToAuthor ? {
1939
+ replyToAuthor,
1940
+ replyToBody: params.replyContext?.body ?? ""
1941
+ } : {}
1942
+ };
1856
1943
  };
1857
1944
  }
1858
1945
  async function monitorSignalProvider(opts = {}) {
@@ -1898,11 +1985,27 @@ async function monitorSignalProvider(opts = {}) {
1898
1985
  const monitorTaskRunner = createSignalMonitorTaskRunner(runtime);
1899
1986
  let daemonHandle = null;
1900
1987
  let ingressMonitor;
1988
+ const startupDeadline = Date.now() + startupTimeoutMs;
1901
1989
  if (autoStart) {
1902
1990
  const cliPath = opts.cliPath ?? managedTransport?.cliPath ?? "signal-cli";
1903
1991
  const configPath = normalizeOptionalString(opts.configPath) ?? normalizeOptionalString(managedTransport?.configPath);
1904
- const httpHost = opts.httpHost ?? managedTransport?.httpHost ?? "127.0.0.1";
1992
+ const httpHost = normalizeSignalTransportHost(opts.httpHost ?? managedTransport?.httpHost ?? "127.0.0.1");
1905
1993
  const httpPort = opts.httpPort ?? managedTransport?.httpPort ?? 8080;
1994
+ const startupTimeoutSignal = AbortSignal.timeout(startupTimeoutMs);
1995
+ const endpointProbeSignal = opts.abortSignal ? AbortSignal.any([opts.abortSignal, startupTimeoutSignal]) : startupTimeoutSignal;
1996
+ try {
1997
+ await assertSignalDaemonEndpointAvailable({
1998
+ httpHost,
1999
+ httpPort,
2000
+ abortSignal: endpointProbeSignal
2001
+ });
2002
+ } catch (error) {
2003
+ if (opts.abortSignal?.aborted) return;
2004
+ if (startupTimeoutSignal.aborted || Date.now() >= startupDeadline) throw new Error(`signal daemon startup timed out after ${startupTimeoutMs}ms while checking its endpoint`, { cause: error });
2005
+ throw error;
2006
+ }
2007
+ if (opts.abortSignal?.aborted) return;
2008
+ if (Date.now() >= startupDeadline) throw new Error(`signal daemon startup timed out after ${startupTimeoutMs}ms before starting`);
1906
2009
  daemonHandle = spawnSignalDaemon({
1907
2010
  cliPath,
1908
2011
  ...configPath ? { configPath } : {},
@@ -1924,7 +2027,7 @@ async function monitorSignalProvider(opts = {}) {
1924
2027
  await waitForSignalDaemonReady({
1925
2028
  baseUrl,
1926
2029
  abortSignal: daemonLifecycle.abortSignal,
1927
- timeoutMs: startupTimeoutMs,
2030
+ startupDeadlineMs: startupDeadline,
1928
2031
  logAfterMs: 1e4,
1929
2032
  logIntervalMs: 1e4,
1930
2033
  runtime,
@@ -0,0 +1,51 @@
1
+ import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
2
+ //#region extensions/signal/src/normalize.ts
3
+ function normalizeSignalReactionRecipient(raw) {
4
+ const withoutSignal = raw.trim().replace(/^signal:/i, "").trim();
5
+ return /^uuid:/i.test(withoutSignal) ? withoutSignal.slice(5).trim() : withoutSignal;
6
+ }
7
+ function normalizeSignalMessagingTarget(raw) {
8
+ const trimmed = raw.trim();
9
+ if (!trimmed) return;
10
+ let normalized = trimmed;
11
+ if (normalizeLowercaseStringOrEmpty(normalized).startsWith("signal:")) normalized = normalized.slice(7).trim();
12
+ if (!normalized) return;
13
+ const lower = normalizeLowercaseStringOrEmpty(normalized);
14
+ if (lower.startsWith("group:")) {
15
+ const id = normalized.slice(6).trim();
16
+ return id ? `group:${id}` : void 0;
17
+ }
18
+ if (lower.startsWith("username:")) {
19
+ const id = normalized.slice(9).trim();
20
+ return id ? normalizeLowercaseStringOrEmpty(`username:${id}`) : void 0;
21
+ }
22
+ if (lower.startsWith("u:")) {
23
+ const id = normalized.slice(2).trim();
24
+ return id ? normalizeLowercaseStringOrEmpty(`username:${id}`) : void 0;
25
+ }
26
+ if (lower.startsWith("uuid:")) {
27
+ const id = normalized.slice(5).trim();
28
+ return id ? normalizeLowercaseStringOrEmpty(id) : void 0;
29
+ }
30
+ return normalizeLowercaseStringOrEmpty(normalized);
31
+ }
32
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
33
+ const UUID_COMPACT_PATTERN = /^[0-9a-f]{32}$/i;
34
+ function looksLikeSignalTargetId(raw, normalized) {
35
+ const candidates = normalizeStringEntries([raw, normalized ?? ""]);
36
+ for (const candidate of candidates) {
37
+ if (/^(signal:)?(group:|username:|u:)/i.test(candidate)) return true;
38
+ if (/^(signal:)?uuid:/i.test(candidate)) {
39
+ const stripped = candidate.replace(/^signal:/i, "").replace(/^uuid:/i, "").trim();
40
+ if (!stripped) continue;
41
+ if (UUID_PATTERN.test(stripped) || UUID_COMPACT_PATTERN.test(stripped)) return true;
42
+ continue;
43
+ }
44
+ const withoutSignalPrefix = candidate.replace(/^signal:/i, "").trim();
45
+ if (UUID_PATTERN.test(withoutSignalPrefix) || UUID_COMPACT_PATTERN.test(withoutSignalPrefix)) return true;
46
+ if (/^\+?\d{3,}$/.test(withoutSignalPrefix)) return true;
47
+ }
48
+ return false;
49
+ }
50
+ //#endregion
51
+ export { normalizeSignalMessagingTarget as n, normalizeSignalReactionRecipient as r, looksLikeSignalTargetId as t };
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { t as detectSignalTransport } from "./transport-detection-BItHawMq.js";
2
+ import { t as detectSignalTransport } from "./transport-detection-BSXAOmuH.js";
3
3
  import { n as signalRpcRequest, t as signalCheck } from "./client-adapter-D9SNPaNx.js";
4
4
  import { runChannelProbe } from "openclaw/plugin-sdk/text-utility-runtime";
5
5
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { o as resolveSignalTarget } from "./runtime-JPrGlV7h.js";
3
- import { l as resolveSignalApprovalConversationKey, u as resolveSignalApprovalTargetAuthorKeys } from "./approval-reactions-BulCzhdB.js";
2
+ import { i as resolveSignalTarget } from "./approval-auth-BsYHLTHK.js";
3
+ import { c as resolveSignalApprovalTargetAuthorKeys, s as resolveSignalApprovalConversationKey } from "./approval-reactions-DB-86mqK.js";
4
4
  import { createQuestionReactionTargetStore, questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
5
5
  import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
6
6
  //#region extensions/signal/src/question-reactions.ts
@@ -1,58 +1,33 @@
1
- import { i as resolveSignalAccount } from "./accounts-DrStJjiK.js";
1
+ import { i as resolveSignalAccount } from "./accounts-DO4HMqaK.js";
2
+ import { r as normalizeSignalReactionRecipient } from "./normalize-l_b99hap.js";
2
3
  import { n as signalRpcRequest } from "./client-adapter-D9SNPaNx.js";
3
- import { t as resolveSignalRpcContext } from "./rpc-context-CZUoE0-A.js";
4
- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
4
+ import { t as resolveSignalRpcContext } from "./rpc-context-dXSy4NtF.js";
5
5
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
6
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
7
  async function sendReactionSignalCore(params) {
33
8
  const accountInfo = resolveSignalAccount({
34
9
  cfg: requireRuntimeConfig(params.opts.cfg, "Signal reactions"),
35
10
  accountId: params.opts.accountId
36
11
  });
37
12
  const { baseUrl, account } = resolveSignalRpcContext(params.opts, accountInfo);
38
- const normalizedRecipient = normalizeSignalUuid(params.recipient);
13
+ const normalizedRecipient = normalizeSignalReactionRecipient(params.recipient);
39
14
  const groupId = params.opts.groupId?.trim();
40
15
  const operation = `Signal reaction${params.remove ? " removal" : ""}`;
41
16
  if (!normalizedRecipient && !groupId) throw new Error(`Recipient or groupId is required for ${operation}`);
42
17
  if (!Number.isFinite(params.targetTimestamp) || params.targetTimestamp <= 0) throw new Error(`Valid targetTimestamp is required for ${operation}`);
43
18
  const normalizedEmoji = params.emoji?.trim();
44
19
  if (!normalizedEmoji) throw new Error(`Emoji is required for ${operation}`);
45
- const targetAuthorParams = resolveTargetAuthorParams({
46
- targetAuthor: params.opts.targetAuthor,
47
- targetAuthorUuid: params.opts.targetAuthorUuid,
48
- fallback: normalizedRecipient
49
- });
50
- if (groupId && !targetAuthorParams.targetAuthor) throw new Error(`targetAuthor is required for group reaction${params.remove ? " removal" : "s"}`);
20
+ const targetAuthor = [
21
+ params.opts.targetAuthor,
22
+ params.opts.targetAuthorUuid,
23
+ normalizedRecipient
24
+ ].map((candidate) => normalizeSignalReactionRecipient(candidate ?? "")).find(Boolean);
25
+ if (groupId && !targetAuthor) throw new Error(`targetAuthor is required for group reaction${params.remove ? " removal" : "s"}`);
51
26
  const requestParams = {
52
27
  emoji: normalizedEmoji,
53
28
  targetTimestamp: params.targetTimestamp,
54
29
  ...params.remove ? { remove: true } : {},
55
- ...targetAuthorParams
30
+ ...targetAuthor ? { targetAuthor } : {}
56
31
  };
57
32
  if (normalizedRecipient) requestParams.recipients = [normalizedRecipient];
58
33
  if (groupId) requestParams.groupIds = [groupId];
@@ -1,2 +1,2 @@
1
- import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-Cf6QHcE5.js";
1
+ import { n as sendReactionSignal, t as removeReactionSignal } from "./reaction-runtime-api-Ch0dk2sh.js";
2
2
  export { removeReactionSignal, sendReactionSignal };
@@ -0,0 +1,15 @@
1
+ import "./accounts-DO4HMqaK.js";
2
+ //#region extensions/signal/src/rpc-context.ts
3
+ function resolveSignalRpcContext(opts, accountInfo) {
4
+ const baseUrlOverride = opts.baseUrl?.trim();
5
+ const accountOverride = opts.account?.trim();
6
+ if ((!baseUrlOverride || !accountOverride) && !accountInfo) throw new Error("Signal account config is required when baseUrl or account is missing");
7
+ const baseUrl = baseUrlOverride || accountInfo?.baseUrl;
8
+ if (!baseUrl) throw new Error("Signal base URL is required");
9
+ return {
10
+ baseUrl,
11
+ account: accountOverride || accountInfo?.config.account?.trim() || void 0
12
+ };
13
+ }
14
+ //#endregion
15
+ export { resolveSignalRpcContext as t };