@openclaw/signal 0.0.0 → 2026.6.11

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 (49) hide show
  1. package/README.md +12 -2
  2. package/dist/accounts-hOCHbEhX.js +40 -0
  3. package/dist/api.js +16 -0
  4. package/dist/approval-handler.runtime-CeJI1wow.js +158 -0
  5. package/dist/approval-resolver-BR0MioAA.js +15 -0
  6. package/dist/channel-LmY2UpOt.js +619 -0
  7. package/dist/channel-config-api.js +2 -0
  8. package/dist/channel-entry.js +18 -0
  9. package/dist/channel-plugin-api.js +2 -0
  10. package/dist/channel.runtime-B0-YpkC4.js +65 -0
  11. package/dist/config-api-KS-qhQvD.js +2 -0
  12. package/dist/config-schema-BiojLEsX.js +27 -0
  13. package/dist/contract-api.js +3 -0
  14. package/dist/identity-B6O4k8xg.js +130 -0
  15. package/dist/index.js +18 -0
  16. package/dist/install-signal-cli-CXgTF3de.js +243 -0
  17. package/dist/message-actions-Bue0g2Kc.js +548 -0
  18. package/dist/monitor-CUhIKHJo.js +1404 -0
  19. package/dist/probe-BQ_Izoya.js +48 -0
  20. package/dist/reaction-runtime-api-C_PQ45D9.js +1074 -0
  21. package/dist/reaction-runtime-api.js +2 -0
  22. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  23. package/dist/runtime-api.js +18 -0
  24. package/dist/secret-contract-api.js +5 -0
  25. package/dist/send-CBlFUkY_.js +599 -0
  26. package/dist/send.runtime-CfaZd8X4.js +2 -0
  27. package/dist/setup-entry.js +11 -0
  28. package/node_modules/ws/LICENSE +20 -0
  29. package/node_modules/ws/README.md +548 -0
  30. package/node_modules/ws/browser.js +8 -0
  31. package/node_modules/ws/index.js +22 -0
  32. package/node_modules/ws/lib/buffer-util.js +131 -0
  33. package/node_modules/ws/lib/constants.js +19 -0
  34. package/node_modules/ws/lib/event-target.js +292 -0
  35. package/node_modules/ws/lib/extension.js +203 -0
  36. package/node_modules/ws/lib/limiter.js +55 -0
  37. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  38. package/node_modules/ws/lib/receiver.js +760 -0
  39. package/node_modules/ws/lib/sender.js +607 -0
  40. package/node_modules/ws/lib/stream.js +161 -0
  41. package/node_modules/ws/lib/subprotocol.js +62 -0
  42. package/node_modules/ws/lib/validation.js +152 -0
  43. package/node_modules/ws/lib/websocket-server.js +562 -0
  44. package/node_modules/ws/lib/websocket.js +1407 -0
  45. package/node_modules/ws/package.json +70 -0
  46. package/node_modules/ws/wrapper.mjs +21 -0
  47. package/npm-shrinkwrap.json +36 -0
  48. package/openclaw.plugin.json +771 -0
  49. package/package.json +77 -7
@@ -0,0 +1,548 @@
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-C_PQ45D9.js";
4
+ import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
5
+ import { resolveReactionLevel } from "openclaw/plugin-sdk/status-helpers";
6
+ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
7
+ import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
8
+ import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
9
+ import { createChannelApproverDmTargetResolver, createChannelNativeOriginTargetResolver, createNativeApprovalChannelRouteGates, createNativeApprovalForwardingFallbackSuppressor, shouldSuppressLocalNativeExecApprovalPrompt } from "openclaw/plugin-sdk/approval-native-runtime";
10
+ import { buildApprovalReactionPendingContentForRequest } from "openclaw/plugin-sdk/approval-reaction-runtime";
11
+ import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
12
+ import { normalizeE164 } from "openclaw/plugin-sdk/text-utility-runtime";
13
+ import { markdownToIR, renderMarkdownIRChunksWithinLimit } from "openclaw/plugin-sdk/text-chunking";
14
+ import { createActionGate, jsonResult, readStringParam, resolveReactionMessageId } from "openclaw/plugin-sdk/channel-actions";
15
+ 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
+ //#region extensions/signal/src/approval-native.ts
39
+ const DEFAULT_APPROVAL_FORWARDING_MODE = "session";
40
+ function isSignalApprovalTransportEnabled(params) {
41
+ return resolveSignalAccount({
42
+ cfg: params.cfg,
43
+ accountId: params.accountId
44
+ }).enabled;
45
+ }
46
+ function normalizeSignalForwardTarget(target) {
47
+ if (normalizeLowercaseStringOrEmpty(target.channel) !== "signal") return null;
48
+ const to = normalizeSignalMessagingTarget(target.to);
49
+ if (!to) return null;
50
+ return {
51
+ to,
52
+ accountId: normalizeOptionalString(target.accountId),
53
+ threadId: target.threadId ?? null
54
+ };
55
+ }
56
+ function resolveTurnSourceSignalOriginTarget(request) {
57
+ if (normalizeLowercaseStringOrEmpty(request.request.turnSourceChannel) !== "signal") return null;
58
+ const to = normalizeSignalMessagingTarget(request.request.turnSourceTo ?? "");
59
+ if (!to) return null;
60
+ return {
61
+ to,
62
+ accountId: normalizeOptionalString(request.request.turnSourceAccountId)
63
+ };
64
+ }
65
+ function resolveSessionSignalOriginTarget(sessionTarget) {
66
+ const to = normalizeSignalMessagingTarget(sessionTarget.to);
67
+ return to ? {
68
+ to,
69
+ accountId: normalizeOptionalString(sessionTarget.accountId)
70
+ } : null;
71
+ }
72
+ const { canApprovalPotentiallyRouteToChannel: canApprovalPotentiallyRouteToSignal, canAnyApprovalPotentiallyRouteToChannel: canAnyApprovalPotentiallyRouteToSignal, isNativeApprovalHandlerConfigured: isSignalNativeApprovalHandlerConfiguredBase, isSessionApprovalEligible: isSignalSessionApprovalEligible, shouldHandleApprovalRequest: shouldHandleSignalApprovalRequest } = createNativeApprovalChannelRouteGates({
73
+ channel: "signal",
74
+ defaultForwardingMode: DEFAULT_APPROVAL_FORWARDING_MODE,
75
+ isTransportEnabled: isSignalApprovalTransportEnabled,
76
+ listAccountIds: listSignalAccountIds,
77
+ resolveDefaultAccountId: resolveDefaultSignalAccountId,
78
+ normalizeForwardTarget: normalizeSignalForwardTarget,
79
+ resolveTurnSourceTarget: resolveTurnSourceSignalOriginTarget
80
+ });
81
+ function isSignalNativeApprovalHandlerConfigured(params) {
82
+ return isSignalNativeApprovalHandlerConfiguredBase(params);
83
+ }
84
+ function resolveSignalSessionTargetFromSessionKey(sessionKey) {
85
+ const rest = parseAgentSessionKey(sessionKey)?.rest ?? normalizeOptionalString(sessionKey);
86
+ if (!rest || !normalizeLowercaseStringOrEmpty(rest).startsWith("signal:")) return null;
87
+ return normalizeSignalMessagingTarget(rest.slice(7)) ?? null;
88
+ }
89
+ function shouldSuppressLocalSignalExecApprovalPrompt(params) {
90
+ return shouldSuppressLocalNativeExecApprovalPrompt({
91
+ ...params,
92
+ isTransportEnabled: isSignalApprovalTransportEnabled,
93
+ isSessionRouteEligible: ({ cfg, accountId, metadata }) => {
94
+ if (getSignalApprovalApprovers({
95
+ cfg,
96
+ accountId
97
+ }).length > 0) return true;
98
+ const sessionTarget = resolveSignalSessionTargetFromSessionKey(metadata.sessionKey);
99
+ return Boolean(sessionTarget && !isSignalGroupTarget(sessionTarget));
100
+ }
101
+ });
102
+ }
103
+ const resolveSignalOriginTargetBase = createChannelNativeOriginTargetResolver({
104
+ channel: "signal",
105
+ shouldHandleRequest: shouldHandleSignalApprovalRequest,
106
+ resolveTurnSourceTarget: resolveTurnSourceSignalOriginTarget,
107
+ resolveSessionTarget: resolveSessionSignalOriginTarget,
108
+ normalizeTarget: (target) => {
109
+ const to = normalizeSignalMessagingTarget(target.to);
110
+ return to ? {
111
+ ...target,
112
+ to
113
+ } : null;
114
+ }
115
+ });
116
+ function isSignalGroupTarget(to) {
117
+ return normalizeLowercaseStringOrEmpty(to).startsWith("group:");
118
+ }
119
+ function resolveSignalOriginTarget(params) {
120
+ const target = resolveSignalOriginTargetBase(params);
121
+ if (!target) return null;
122
+ if (isSignalGroupTarget(target.to) && getSignalApprovalApprovers({
123
+ cfg: params.cfg,
124
+ accountId: params.accountId
125
+ }).length === 0) return null;
126
+ return target;
127
+ }
128
+ const resolveSignalApproverDmTargets = createChannelApproverDmTargetResolver({
129
+ shouldHandleRequest: shouldHandleSignalApprovalRequest,
130
+ resolveApprovers: getSignalApprovalApprovers,
131
+ mapApprover: (approver, params) => {
132
+ const to = normalizeSignalMessagingTarget(approver);
133
+ if (!to) return null;
134
+ return {
135
+ to,
136
+ accountId: normalizeOptionalString(params.accountId)
137
+ };
138
+ }
139
+ });
140
+ const shouldSuppressSignalForwardingFallback = createNativeApprovalForwardingFallbackSuppressor({
141
+ channel: "signal",
142
+ normalizeForwardTarget: normalizeSignalForwardTarget,
143
+ resolveAccountId: ({ forwardingTarget, request }) => forwardingTarget.accountId ?? normalizeOptionalString(request.request.turnSourceAccountId),
144
+ resolveForwardingTargetForMatch: ({ forwardingTarget, accountId }) => ({
145
+ ...forwardingTarget,
146
+ accountId
147
+ }),
148
+ isSessionRouteEligible: isSignalSessionApprovalEligible,
149
+ resolveOriginTarget: resolveSignalOriginTarget,
150
+ resolveApproverDmTargets: resolveSignalApproverDmTargets
151
+ });
152
+ function buildSignalExecPendingPayload(params) {
153
+ return buildApprovalReactionPendingContentForRequest(params).manualFallbackPayload;
154
+ }
155
+ function buildSignalPluginPendingPayload(params) {
156
+ return buildApprovalReactionPendingContentForRequest(params).manualFallbackPayload;
157
+ }
158
+ const signalApprovalCapability = createChannelApprovalCapability({
159
+ ...signalApprovalAuth,
160
+ getActionAvailabilityState: ({ cfg, accountId, approvalKind }) => (approvalKind ? canApprovalPotentiallyRouteToSignal({
161
+ cfg,
162
+ accountId,
163
+ approvalKind
164
+ }) : canAnyApprovalPotentiallyRouteToSignal({
165
+ cfg,
166
+ accountId
167
+ })) ? { kind: "enabled" } : { kind: "disabled" },
168
+ getExecInitiatingSurfaceState: ({ cfg, accountId }) => canApprovalPotentiallyRouteToSignal({
169
+ cfg,
170
+ accountId,
171
+ approvalKind: "exec"
172
+ }) ? { kind: "enabled" } : { kind: "disabled" },
173
+ describeExecApprovalSetup: ({ accountId }) => {
174
+ return `Signal supports native exec approvals for this account when \`approvals.exec.enabled\` is true and the route allows Signal. Link Signal and keep the gateway running; configure \`${accountId && accountId !== "default" ? `channels.signal.accounts.${accountId}` : "channels.signal"}.allowFrom\` to restrict approvers.`;
175
+ },
176
+ delivery: {
177
+ hasConfiguredDmRoute: ({ cfg }) => listSignalAccountIds(cfg).some((accountId) => {
178
+ if (!canAnyApprovalPotentiallyRouteToSignal({
179
+ cfg,
180
+ accountId,
181
+ nativeSessionOnly: true
182
+ })) return false;
183
+ return getSignalApprovalApprovers({
184
+ cfg,
185
+ accountId
186
+ }).length > 0;
187
+ }),
188
+ shouldSuppressForwardingFallback: shouldSuppressSignalForwardingFallback
189
+ },
190
+ render: {
191
+ exec: { buildPendingPayload: ({ request, nowMs }) => buildSignalExecPendingPayload({
192
+ request,
193
+ nowMs
194
+ }) },
195
+ plugin: { buildPendingPayload: ({ request, nowMs }) => buildSignalPluginPendingPayload({
196
+ request,
197
+ nowMs
198
+ }) }
199
+ },
200
+ native: {
201
+ describeDeliveryCapabilities: ({ cfg, accountId, approvalKind, request }) => {
202
+ const originTarget = resolveSignalOriginTarget({
203
+ cfg,
204
+ accountId,
205
+ approvalKind,
206
+ request
207
+ });
208
+ const approverTargets = resolveSignalApproverDmTargets({
209
+ cfg,
210
+ accountId,
211
+ approvalKind,
212
+ request
213
+ });
214
+ return {
215
+ enabled: Boolean(originTarget) || approverTargets.length > 0,
216
+ preferredSurface: originTarget ? "origin" : "approver-dm",
217
+ supportsOriginSurface: Boolean(originTarget),
218
+ supportsApproverDmSurface: approverTargets.length > 0,
219
+ notifyOriginWhenDmOnly: true
220
+ };
221
+ },
222
+ resolveOriginTarget: resolveSignalOriginTarget,
223
+ resolveApproverDmTargets: resolveSignalApproverDmTargets
224
+ },
225
+ nativeRuntime: createLazyChannelApprovalNativeRuntimeAdapter({
226
+ eventKinds: ["exec", "plugin"],
227
+ isConfigured: ({ cfg, accountId, context }) => Boolean(context) && isSignalNativeApprovalHandlerConfigured({
228
+ cfg,
229
+ accountId
230
+ }),
231
+ shouldHandle: ({ cfg, accountId, context, request }) => Boolean(context) && shouldHandleSignalApprovalRequest({
232
+ cfg,
233
+ accountId,
234
+ request
235
+ }),
236
+ load: async () => (await import("./approval-handler.runtime-CeJI1wow.js")).signalApprovalNativeRuntime
237
+ })
238
+ });
239
+ //#endregion
240
+ //#region extensions/signal/src/format.ts
241
+ function normalizeUrlForComparison(url) {
242
+ let normalized = normalizeLowercaseStringOrEmpty(url);
243
+ normalized = normalized.replace(/^https?:\/\//, "");
244
+ normalized = normalized.replace(/^www\./, "");
245
+ normalized = normalized.replace(/\/+$/, "");
246
+ return normalized;
247
+ }
248
+ function mapStyle(style) {
249
+ switch (style) {
250
+ case "bold": return "BOLD";
251
+ case "italic": return "ITALIC";
252
+ case "strikethrough": return "STRIKETHROUGH";
253
+ case "code":
254
+ case "code_block": return "MONOSPACE";
255
+ case "spoiler": return "SPOILER";
256
+ default: return null;
257
+ }
258
+ }
259
+ function mergeStyles(styles) {
260
+ const sorted = [...styles].toSorted((a, b) => {
261
+ if (a.start !== b.start) return a.start - b.start;
262
+ if (a.length !== b.length) return a.length - b.length;
263
+ return a.style.localeCompare(b.style);
264
+ });
265
+ const merged = [];
266
+ for (const style of sorted) {
267
+ const prev = merged[merged.length - 1];
268
+ if (prev && prev.style === style.style && style.start <= prev.start + prev.length) {
269
+ const prevEnd = prev.start + prev.length;
270
+ prev.length = Math.max(prevEnd, style.start + style.length) - prev.start;
271
+ continue;
272
+ }
273
+ merged.push({ ...style });
274
+ }
275
+ return merged;
276
+ }
277
+ function clampStyles(styles, maxLength) {
278
+ const clamped = [];
279
+ for (const style of styles) {
280
+ const start = Math.max(0, Math.min(style.start, maxLength));
281
+ const length = Math.min(style.start + style.length, maxLength) - start;
282
+ if (length > 0) clamped.push({
283
+ start,
284
+ length,
285
+ style: style.style
286
+ });
287
+ }
288
+ return clamped;
289
+ }
290
+ function applyInsertionsToStyles(spans, insertions) {
291
+ if (insertions.length === 0) return spans;
292
+ const sortedInsertions = [...insertions].toSorted((a, b) => a.pos - b.pos);
293
+ let updated = spans;
294
+ let cumulativeShift = 0;
295
+ for (const insertion of sortedInsertions) {
296
+ const insertionPos = insertion.pos + cumulativeShift;
297
+ const next = [];
298
+ for (const span of updated) {
299
+ if (span.end <= insertionPos) {
300
+ next.push(span);
301
+ continue;
302
+ }
303
+ if (span.start >= insertionPos) {
304
+ next.push({
305
+ start: span.start + insertion.length,
306
+ end: span.end + insertion.length,
307
+ style: span.style
308
+ });
309
+ continue;
310
+ }
311
+ if (span.start < insertionPos && span.end > insertionPos) {
312
+ if (insertionPos > span.start) next.push({
313
+ start: span.start,
314
+ end: insertionPos,
315
+ style: span.style
316
+ });
317
+ const shiftedStart = insertionPos + insertion.length;
318
+ const shiftedEnd = span.end + insertion.length;
319
+ if (shiftedEnd > shiftedStart) next.push({
320
+ start: shiftedStart,
321
+ end: shiftedEnd,
322
+ style: span.style
323
+ });
324
+ }
325
+ }
326
+ updated = next;
327
+ cumulativeShift += insertion.length;
328
+ }
329
+ return updated;
330
+ }
331
+ function renderSignalText(ir) {
332
+ const text = ir.text ?? "";
333
+ if (!text) return {
334
+ text: "",
335
+ styles: []
336
+ };
337
+ const sortedLinks = [...ir.links].toSorted((a, b) => a.start - b.start);
338
+ let out = "";
339
+ let cursor = 0;
340
+ const insertions = [];
341
+ for (const link of sortedLinks) {
342
+ if (link.start < cursor) continue;
343
+ out += text.slice(cursor, link.end);
344
+ const href = link.href.trim();
345
+ const trimmedLabel = text.slice(link.start, link.end).trim();
346
+ if (href) if (!trimmedLabel) {
347
+ out += href;
348
+ insertions.push({
349
+ pos: link.end,
350
+ length: href.length
351
+ });
352
+ } else {
353
+ const normalizedLabel = normalizeUrlForComparison(trimmedLabel);
354
+ let comparableHref = href;
355
+ if (href.startsWith("mailto:")) comparableHref = href.slice(7);
356
+ if (normalizedLabel !== normalizeUrlForComparison(comparableHref)) {
357
+ const addition = ` (${href})`;
358
+ out += addition;
359
+ insertions.push({
360
+ pos: link.end,
361
+ length: addition.length
362
+ });
363
+ }
364
+ }
365
+ cursor = link.end;
366
+ }
367
+ out += text.slice(cursor);
368
+ const adjusted = applyInsertionsToStyles(ir.styles.map((span) => {
369
+ const mapped = mapStyle(span.style);
370
+ if (!mapped) return null;
371
+ return {
372
+ start: span.start,
373
+ end: span.end,
374
+ style: mapped
375
+ };
376
+ }).filter((span) => span !== null), insertions);
377
+ const trimmedText = out.trimEnd();
378
+ const trimmedLength = trimmedText.length;
379
+ return {
380
+ text: trimmedText,
381
+ styles: mergeStyles(clampStyles(adjusted.map((span) => ({
382
+ start: span.start,
383
+ length: span.end - span.start,
384
+ style: span.style
385
+ })), trimmedLength))
386
+ };
387
+ }
388
+ function markdownToSignalText(markdown, options = {}) {
389
+ return renderSignalText(markdownToIR(markdown ?? "", {
390
+ linkify: true,
391
+ enableSpoilers: true,
392
+ headingStyle: "bold",
393
+ blockquotePrefix: "> ",
394
+ tableMode: options.tableMode
395
+ }));
396
+ }
397
+ function markdownToSignalTextChunks(markdown, limit, options = {}) {
398
+ return renderMarkdownIRChunksWithinLimit({
399
+ ir: markdownToIR(markdown ?? "", {
400
+ linkify: true,
401
+ enableSpoilers: true,
402
+ headingStyle: "bold",
403
+ blockquotePrefix: "> ",
404
+ tableMode: options.tableMode
405
+ }),
406
+ limit,
407
+ renderChunk: renderSignalText,
408
+ measureRendered: (rendered) => rendered.text.length
409
+ }).map(({ rendered }) => rendered);
410
+ }
411
+ //#endregion
412
+ //#region extensions/signal/src/reaction-level.ts
413
+ /**
414
+ * Resolve the effective reaction level and its implications for Signal.
415
+ *
416
+ * Levels:
417
+ * - "off": No reactions at all
418
+ * - "ack": Only automatic ack reactions (👀 when processing), no agent reactions
419
+ * - "minimal": Agent can react, but sparingly (default)
420
+ * - "extensive": Agent can react liberally
421
+ */
422
+ function resolveSignalReactionLevel(params) {
423
+ return resolveReactionLevel({
424
+ value: resolveSignalAccount({
425
+ cfg: params.cfg,
426
+ accountId: params.accountId
427
+ }).config.reactionLevel,
428
+ defaultLevel: "minimal",
429
+ invalidFallback: "minimal"
430
+ });
431
+ }
432
+ //#endregion
433
+ //#region extensions/signal/src/message-actions.ts
434
+ const providerId = "signal";
435
+ const GROUP_PREFIX = "group:";
436
+ function normalizeSignalReactionRecipient(raw) {
437
+ const trimmed = raw.trim();
438
+ if (!trimmed) return trimmed;
439
+ const withoutSignal = trimmed.replace(/^signal:/i, "").trim();
440
+ if (!withoutSignal) return withoutSignal;
441
+ if (normalizeLowercaseStringOrEmpty(withoutSignal).startsWith("uuid:")) return withoutSignal.slice(5).trim();
442
+ return withoutSignal;
443
+ }
444
+ function resolveSignalReactionTarget(raw) {
445
+ const trimmed = raw.trim();
446
+ if (!trimmed) return {};
447
+ const withoutSignal = trimmed.replace(/^signal:/i, "").trim();
448
+ if (!withoutSignal) return {};
449
+ if (normalizeLowercaseStringOrEmpty(withoutSignal).startsWith(GROUP_PREFIX)) {
450
+ const groupId = withoutSignal.slice(6).trim();
451
+ return groupId ? { groupId } : {};
452
+ }
453
+ return { recipient: normalizeSignalReactionRecipient(withoutSignal) };
454
+ }
455
+ async function mutateSignalReaction(params) {
456
+ const options = {
457
+ cfg: params.cfg,
458
+ accountId: params.accountId,
459
+ groupId: params.target.groupId,
460
+ targetAuthor: params.targetAuthor,
461
+ targetAuthorUuid: params.targetAuthorUuid
462
+ };
463
+ if (params.remove) {
464
+ await removeReactionSignal(params.target.recipient ?? "", params.timestamp, params.emoji, options);
465
+ return jsonResult({
466
+ ok: true,
467
+ removed: params.emoji
468
+ });
469
+ }
470
+ await sendReactionSignal(params.target.recipient ?? "", params.timestamp, params.emoji, options);
471
+ return jsonResult({
472
+ ok: true,
473
+ added: params.emoji
474
+ });
475
+ }
476
+ const signalMessageActions = {
477
+ describeMessageTool: ({ cfg, accountId }) => {
478
+ const configuredAccounts = accountId ? [resolveSignalAccount({
479
+ cfg,
480
+ accountId
481
+ })].filter((account) => account.enabled && account.configured) : listEnabledSignalAccounts(cfg).filter((account) => account.configured);
482
+ if (configuredAccounts.length === 0) return null;
483
+ const actions = new Set(["send"]);
484
+ if (configuredAccounts.some((account) => createActionGate(account.config.actions)("reactions"))) actions.add("react");
485
+ return { actions: Array.from(actions) };
486
+ },
487
+ supportsAction: ({ action }) => action !== "send",
488
+ handleAction: async ({ action, params, cfg, accountId, toolContext }) => {
489
+ if (action === "send") throw new Error("Send should be handled by outbound, not actions handler.");
490
+ if (action === "react") {
491
+ const reactionLevelInfo = resolveSignalReactionLevel({
492
+ cfg,
493
+ accountId: accountId ?? void 0
494
+ });
495
+ if (!reactionLevelInfo.agentReactionsEnabled) throw new Error(`Signal agent reactions disabled (reactionLevel="${reactionLevelInfo.level}"). Set channels.signal.reactionLevel to "minimal" or "extensive" to enable.`);
496
+ const actionConfig = resolveSignalAccount({
497
+ cfg,
498
+ accountId
499
+ }).config.actions;
500
+ if (!createActionGate(actionConfig)("reactions")) throw new Error("Signal reactions are disabled via actions.reactions.");
501
+ const target = resolveSignalReactionTarget(readStringParam(params, "recipient") ?? readStringParam(params, "to", {
502
+ required: true,
503
+ label: "recipient (UUID, phone number, or group)"
504
+ }));
505
+ if (!target.recipient && !target.groupId) throw new Error("recipient or group required");
506
+ const messageIdRaw = resolveReactionMessageId({
507
+ args: params,
508
+ toolContext
509
+ });
510
+ const messageId = messageIdRaw != null ? String(messageIdRaw) : void 0;
511
+ if (!messageId) throw new Error("messageId (timestamp) required. Provide messageId explicitly or react to the current inbound message.");
512
+ const targetAuthor = readStringParam(params, "targetAuthor");
513
+ const targetAuthorUuid = readStringParam(params, "targetAuthorUuid");
514
+ if (target.groupId && !targetAuthor && !targetAuthorUuid) throw new Error("targetAuthor or targetAuthorUuid required for group reactions.");
515
+ const emoji = readStringParam(params, "emoji", { allowEmpty: true });
516
+ const remove = typeof params.remove === "boolean" ? params.remove : void 0;
517
+ const timestamp = parseStrictNonNegativeInteger(messageId);
518
+ if (timestamp === void 0) throw new Error(`Invalid messageId: ${messageId}. Expected numeric timestamp.`);
519
+ if (remove) {
520
+ if (!emoji) throw new Error("Emoji required to remove reaction.");
521
+ return await mutateSignalReaction({
522
+ cfg,
523
+ accountId: accountId ?? void 0,
524
+ target,
525
+ timestamp,
526
+ emoji,
527
+ remove: true,
528
+ targetAuthor,
529
+ targetAuthorUuid
530
+ });
531
+ }
532
+ if (!emoji) throw new Error("Emoji required to add reaction.");
533
+ return await mutateSignalReaction({
534
+ cfg,
535
+ accountId: accountId ?? void 0,
536
+ target,
537
+ timestamp,
538
+ emoji,
539
+ remove: false,
540
+ targetAuthor,
541
+ targetAuthorUuid
542
+ });
543
+ }
544
+ throw new Error(`Action ${action} not supported for ${providerId}.`);
545
+ }
546
+ };
547
+ //#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 };