@openclaw/signal 2026.7.1-beta.1 → 2026.7.1-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,16 @@
1
- import { i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId } from "./accounts-hOCHbEhX.js";
2
- import { c as resolveSignalSender, d as normalizeSignalMessagingTarget, l as looksLikeUuid, o as resolveSignalPeerId, s as resolveSignalRecipient, u as looksLikeSignalTargetId } from "./identity-B6O4k8xg.js";
3
- import { i as markdownToSignalTextChunks, n as resolveSignalReactionLevel, o as shouldSuppressLocalSignalExecApprovalPrompt, s as signalApprovalCapability, t as signalMessageActions } from "./message-actions-BnugYXjB.js";
1
+ import { a as resolveSignalReplyToMode, i as resolveSignalAccount, n as listSignalAccountIds, r as resolveDefaultSignalAccountId } from "./accounts-Bz4HtP0g.js";
2
+ import { c as resolveSignalSender, d as normalizeSignalMessagingTarget, l as looksLikeUuid, o as resolveSignalPeerId, s as resolveSignalRecipient, u as looksLikeSignalTargetId } from "./identity-C8-yk4J9.js";
3
+ import { i as resolveSignalTarget, r as listSignalAliasDirectoryEntries } from "./approval-auth-CsHNcAiy.js";
4
+ import { i as markdownToSignalTextChunks, n as resolveSignalReactionLevel, o as shouldSuppressLocalSignalExecApprovalPrompt, s as signalApprovalCapability, t as signalMessageActions } from "./message-actions-3OLDgjis.js";
4
5
  import { t as SignalChannelConfigSchema } from "./config-schema-BiojLEsX.js";
5
6
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
6
7
  import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
7
8
  import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
8
9
  import { defineChannelMessageAdapter, resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";
9
10
  import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
10
- import { attachChannelToResult, attachChannelToResults } from "openclaw/plugin-sdk/channel-send-result";
11
+ import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
11
12
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
13
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
12
14
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
13
15
  import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";
14
16
  import { chunkText, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
@@ -72,7 +74,10 @@ const INVALID_SIGNAL_ACCOUNT_ERROR = "Invalid E.164 phone number (must start wit
72
74
  function normalizeSignalAccountInput(value) {
73
75
  const trimmed = normalizeOptionalString(value);
74
76
  if (!trimmed) return null;
75
- const digits = normalizeE164(trimmed).slice(1);
77
+ const phoneInput = trimmed.replace(/^signal:/i, "").trim();
78
+ const plusCount = phoneInput.match(/\+/g)?.length ?? 0;
79
+ if (plusCount > 1 || plusCount === 1 && !phoneInput.startsWith("+")) return null;
80
+ const digits = normalizeE164(phoneInput).slice(1);
76
81
  if (!DIGITS_ONLY.test(digits)) return null;
77
82
  if (digits.length < MIN_E164_DIGITS || digits.length > MAX_E164_DIGITS) return null;
78
83
  return `+${digits}`;
@@ -235,27 +240,45 @@ function createSignalSetupWizardProxy(loadWizard) {
235
240
  //#region extensions/signal/src/shared.ts
236
241
  const SIGNAL_CHANNEL = "signal";
237
242
  async function loadSignalChannelRuntime() {
238
- return await import("./channel.runtime-C5skg4Q0.js");
243
+ return await import("./channel.runtime-CQN0RaFx.js");
239
244
  }
240
245
  const signalSetupWizard = createSignalSetupWizardProxy(async () => (await loadSignalChannelRuntime()).signalSetupWizard);
241
- const signalConfigAdapter = createScopedChannelConfigAdapter({
242
- sectionKey: SIGNAL_CHANNEL,
243
- listAccountIds: (cfg) => listSignalAccountIds(cfg),
244
- resolveAccount: adaptScopedAccountAccessor((params) => resolveSignalAccount(params)),
245
- defaultAccountId: (cfg) => resolveDefaultSignalAccountId(cfg),
246
- clearBaseFields: [
247
- "account",
248
- "configPath",
249
- "httpUrl",
250
- "httpHost",
251
- "httpPort",
252
- "cliPath",
253
- "name"
254
- ],
255
- resolveAllowFrom: (account) => account.config.allowFrom,
256
- formatAllowFrom: (allowFrom) => normalizeStringifiedEntries(allowFrom).map((entry) => entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))).filter(Boolean),
257
- resolveDefaultTo: (account) => account.config.defaultTo
258
- });
246
+ const signalConfigAdapter = {
247
+ ...createScopedChannelConfigAdapter({
248
+ sectionKey: SIGNAL_CHANNEL,
249
+ listAccountIds: (cfg) => listSignalAccountIds(cfg),
250
+ resolveAccount: adaptScopedAccountAccessor((params) => resolveSignalAccount(params)),
251
+ defaultAccountId: (cfg) => resolveDefaultSignalAccountId(cfg),
252
+ clearBaseFields: [
253
+ "account",
254
+ "configPath",
255
+ "httpUrl",
256
+ "httpHost",
257
+ "httpPort",
258
+ "cliPath",
259
+ "name"
260
+ ],
261
+ resolveAllowFrom: (account) => account.config.allowFrom,
262
+ formatAllowFrom: (allowFrom) => normalizeStringifiedEntries(allowFrom).map((entry) => entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))).filter(Boolean),
263
+ resolveDefaultTo: (account) => account.config.defaultTo
264
+ }),
265
+ resolveDefaultTo({ cfg, accountId }) {
266
+ const raw = resolveSignalAccount({
267
+ cfg,
268
+ accountId
269
+ }).config.defaultTo;
270
+ if (typeof raw !== "string" || !raw.trim()) return;
271
+ try {
272
+ return resolveSignalTarget({
273
+ cfg,
274
+ accountId,
275
+ input: raw
276
+ })?.to ?? raw.trim();
277
+ } catch {
278
+ return raw.trim();
279
+ }
280
+ }
281
+ };
259
282
  const signalSecurityAdapter = createRestrictSendersChannelSecurity({
260
283
  channelKey: SIGNAL_CHANNEL,
261
284
  resolveDmPolicy: (account) => account.config.dmPolicy,
@@ -303,26 +326,10 @@ function createSignalPluginBase(params) {
303
326
  }
304
327
  //#endregion
305
328
  //#region extensions/signal/src/channel.ts
306
- let signalMonitorModulePromise = null;
307
- let signalProbeModulePromise = null;
308
- let signalSendRuntimePromise = null;
309
- let signalApprovalReactionsModulePromise = null;
310
- async function loadSignalMonitorModule() {
311
- signalMonitorModulePromise ??= import("./monitor-C1gdgT7W.js").then((n) => n.n);
312
- return await signalMonitorModulePromise;
313
- }
314
- async function loadSignalProbeModule() {
315
- signalProbeModulePromise ??= import("./probe-BvFJLOBi.js").then((n) => n.n);
316
- return await signalProbeModulePromise;
317
- }
318
- async function loadSignalSendRuntime() {
319
- signalSendRuntimePromise ??= import("./send.runtime-BnjSh7QP.js");
320
- return await signalSendRuntimePromise;
321
- }
322
- async function loadSignalApprovalReactionsModule() {
323
- signalApprovalReactionsModulePromise ??= import("./approval-reactions-H-RPGBK7.js").then((n) => n.n);
324
- return await signalApprovalReactionsModulePromise;
325
- }
329
+ const loadSignalMonitorModule = createLazyRuntimeModule(() => import("./monitor-Bee8O5Ay.js").then((n) => n.n));
330
+ const loadSignalProbeModule = createLazyRuntimeModule(() => import("./probe-Cnht8Ihg.js").then((n) => n.n));
331
+ const loadSignalSendRuntime = createLazyRuntimeModule(() => import("./send.runtime-CEi5oyCG.js"));
332
+ const loadSignalApprovalReactionsModule = createLazyRuntimeModule(() => import("./approval-reactions-UFmUPD0A.js").then((n) => n.r));
326
333
  async function resolveSignalSendContext(params) {
327
334
  return {
328
335
  send: resolveOutboundSendDep(params.deps, "signal") ?? (await loadSignalSendRuntime()).sendMessageSignal,
@@ -333,9 +340,16 @@ async function resolveSignalSendContext(params) {
333
340
  })
334
341
  };
335
342
  }
343
+ function resolveSignalSendTarget(params) {
344
+ return resolveSignalTarget({
345
+ cfg: params.cfg,
346
+ accountId: params.accountId,
347
+ input: params.to
348
+ })?.to ?? params.to.trim();
349
+ }
336
350
  async function sendSignalOutbound(params) {
337
351
  const { send, maxBytes } = await resolveSignalSendContext(params);
338
- return await send(params.to, params.text, {
352
+ return await send(resolveSignalSendTarget(params), params.text, {
339
353
  cfg: params.cfg,
340
354
  ...params.mediaUrl ? { mediaUrl: params.mediaUrl } : {},
341
355
  ...params.mediaLocalRoots?.length ? { mediaLocalRoots: params.mediaLocalRoots } : {},
@@ -397,8 +411,11 @@ function buildSignalBaseSessionKey(params) {
397
411
  });
398
412
  }
399
413
  function resolveSignalOutboundSessionRoute(params) {
400
- const resolved = resolveSignalOutboundTarget(params.target);
414
+ const target = params.resolvedTarget?.to ?? params.target;
415
+ const resolved = resolveSignalOutboundTarget(target);
401
416
  if (!resolved) return null;
417
+ const normalizedTarget = target.replace(/^signal:/i, "").trim();
418
+ const recipientSessionExact = resolved.chatType === "group" || /^\+?\d{3,15}$/.test(normalizedTarget) ? true : "direct-alias";
402
419
  const baseSessionKey = buildSignalBaseSessionKey({
403
420
  cfg: params.cfg,
404
421
  agentId: params.agentId,
@@ -408,6 +425,7 @@ function resolveSignalOutboundSessionRoute(params) {
408
425
  return {
409
426
  sessionKey: baseSessionKey,
410
427
  baseSessionKey,
428
+ recipientSessionExact,
411
429
  ...resolved
412
430
  };
413
431
  }
@@ -418,6 +436,11 @@ async function sendFormattedSignalText(ctx) {
418
436
  deps: ctx.deps
419
437
  });
420
438
  const limit = resolveTextChunkLimit(ctx.cfg, "signal", ctx.accountId ?? void 0, { fallbackLimit: 4e3 });
439
+ const to = resolveSignalSendTarget({
440
+ cfg: ctx.cfg,
441
+ accountId: ctx.accountId ?? void 0,
442
+ to: ctx.to
443
+ });
421
444
  const tableMode = resolveMarkdownTableMode({
422
445
  cfg: ctx.cfg,
423
446
  channel: "signal",
@@ -431,16 +454,17 @@ async function sendFormattedSignalText(ctx) {
431
454
  const results = [];
432
455
  for (const chunk of chunks) {
433
456
  ctx.abortSignal?.throwIfAborted();
434
- const result = await send(ctx.to, chunk.text, {
457
+ const deliveryResult = attachChannelToResult("signal", attachSignalVisibleText(await send(to, chunk.text, {
435
458
  cfg: ctx.cfg,
436
459
  maxBytes,
437
460
  accountId: ctx.accountId ?? void 0,
438
461
  textMode: "plain",
439
462
  textStyles: chunk.styles
440
- });
441
- results.push(attachSignalVisibleText(result, chunk.text));
463
+ }), chunk.text));
464
+ results.push(deliveryResult);
465
+ await ctx.onDeliveryResult?.(deliveryResult);
442
466
  }
443
- return attachChannelToResults("signal", results);
467
+ return results;
444
468
  }
445
469
  async function sendFormattedSignalMedia(ctx) {
446
470
  ctx.abortSignal?.throwIfAborted();
@@ -449,6 +473,11 @@ async function sendFormattedSignalMedia(ctx) {
449
473
  accountId: ctx.accountId ?? void 0,
450
474
  deps: ctx.deps
451
475
  });
476
+ const to = resolveSignalSendTarget({
477
+ cfg: ctx.cfg,
478
+ accountId: ctx.accountId ?? void 0,
479
+ to: ctx.to
480
+ });
452
481
  const tableMode = resolveMarkdownTableMode({
453
482
  cfg: ctx.cfg,
454
483
  channel: "signal",
@@ -458,7 +487,7 @@ async function sendFormattedSignalMedia(ctx) {
458
487
  text: ctx.text,
459
488
  styles: []
460
489
  };
461
- return attachChannelToResult("signal", attachSignalVisibleText(await send(ctx.to, formatted.text, {
490
+ return attachChannelToResult("signal", attachSignalVisibleText(await send(to, formatted.text, {
462
491
  cfg: ctx.cfg,
463
492
  mediaUrl: ctx.mediaUrl,
464
493
  mediaLocalRoots: ctx.mediaLocalRoots,
@@ -474,14 +503,20 @@ async function registerDeliveredSignalApprovalPayloadForReactions(params) {
474
503
  cfg: params.cfg,
475
504
  accountId: params.target.accountId ?? void 0
476
505
  });
477
- if (!account.config.account) return;
506
+ const targetAuthor = normalizeOptionalString(account.config.account);
507
+ const targetAuthorUuid = normalizeOptionalString(account.config.accountUuid);
508
+ if (!targetAuthor && !targetAuthorUuid) return;
478
509
  const { registerSignalApprovalReactionTargetForDeliveredPayload } = await loadSignalApprovalReactionsModule();
479
510
  registerSignalApprovalReactionTargetForDeliveredPayload({
480
511
  cfg: params.cfg,
481
- target: params.target,
512
+ target: {
513
+ ...params.target,
514
+ accountId: account.accountId
515
+ },
482
516
  payload: params.payload,
483
517
  results: params.results,
484
- targetAuthor: account.config.account
518
+ targetAuthor,
519
+ targetAuthorUuid
485
520
  });
486
521
  }
487
522
  async function renderSignalApprovalPayloadForReactions(params) {
@@ -489,14 +524,17 @@ async function renderSignalApprovalPayloadForReactions(params) {
489
524
  cfg: params.ctx.cfg,
490
525
  accountId: params.ctx.accountId ?? void 0
491
526
  });
492
- if (!account.config.account) return null;
527
+ const targetAuthor = normalizeOptionalString(account.config.account);
528
+ const targetAuthorUuid = normalizeOptionalString(account.config.accountUuid);
529
+ if (!targetAuthor && !targetAuthorUuid) return null;
493
530
  const { addSignalApprovalReactionHintToStructuredPayload } = await loadSignalApprovalReactionsModule();
494
531
  return addSignalApprovalReactionHintToStructuredPayload({
495
532
  cfg: params.ctx.cfg,
496
533
  accountId: params.ctx.accountId ?? void 0,
497
534
  to: params.ctx.to,
498
535
  payload: params.payload,
499
- targetAuthor: account.config.account
536
+ targetAuthor,
537
+ targetAuthorUuid
500
538
  });
501
539
  }
502
540
  const signalPlugin = createChatChannelPlugin({
@@ -537,9 +575,44 @@ const signalPlugin = createChatChannelPlugin({
537
575
  resolveOutboundSessionRoute: (params) => resolveSignalOutboundSessionRoute(params),
538
576
  targetResolver: {
539
577
  looksLikeId: looksLikeSignalTargetId,
540
- hint: "<E.164|uuid:ID|group:ID|signal:group:ID|signal:+E.164>"
578
+ hint: "<E.164|uuid:ID|group:ID|signal:group:ID|signal:+E.164>",
579
+ resolveTarget: async ({ cfg, accountId, input }) => {
580
+ let target;
581
+ try {
582
+ target = resolveSignalTarget({
583
+ cfg,
584
+ accountId,
585
+ input
586
+ });
587
+ } catch {
588
+ return null;
589
+ }
590
+ if (!target) return null;
591
+ return {
592
+ to: target.to,
593
+ kind: target.kind,
594
+ display: target.source === "alias" ? target.alias : void 0,
595
+ source: target.source === "alias" ? "directory" : "normalized"
596
+ };
597
+ }
541
598
  }
542
599
  },
600
+ directory: {
601
+ listPeers: async ({ cfg, accountId, query, limit }) => listSignalAliasDirectoryEntries({
602
+ cfg,
603
+ accountId,
604
+ query,
605
+ limit,
606
+ kind: "user"
607
+ }),
608
+ listGroups: async ({ cfg, accountId, query, limit }) => listSignalAliasDirectoryEntries({
609
+ cfg,
610
+ accountId,
611
+ query,
612
+ limit,
613
+ kind: "group"
614
+ })
615
+ },
543
616
  heartbeat: {
544
617
  sendTyping: async ({ cfg, to, accountId }) => {
545
618
  await (await loadSignalSendRuntime()).sendTypingSignal(to, {
@@ -605,9 +678,38 @@ const signalPlugin = createChatChannelPlugin({
605
678
  }
606
679
  } },
607
680
  security: signalSecurityAdapter,
681
+ threading: { resolveReplyToMode: (params) => resolveSignalReplyToMode(params) },
608
682
  outbound: {
609
683
  base: {
610
684
  deliveryMode: "direct",
685
+ resolveTarget: ({ cfg, to, accountId }) => {
686
+ const raw = to?.trim();
687
+ if (!raw) return {
688
+ ok: false,
689
+ error: /* @__PURE__ */ new Error("Signal target is required")
690
+ };
691
+ let target;
692
+ try {
693
+ target = resolveSignalTarget({
694
+ cfg: cfg ?? {},
695
+ accountId,
696
+ input: raw
697
+ });
698
+ } catch (error) {
699
+ return {
700
+ ok: false,
701
+ error: error instanceof Error ? error : new Error(String(error))
702
+ };
703
+ }
704
+ if (!target) return {
705
+ ok: false,
706
+ error: /* @__PURE__ */ new Error(`Unknown Signal alias or target "${raw}". Configure channels.signal.aliases.${raw.replace(/^signal:/i, "")} or use E.164, uuid:<id>, username:<name>, or group:<id>.`)
707
+ };
708
+ return {
709
+ ok: true,
710
+ to: target.to
711
+ };
712
+ },
611
713
  chunker: chunkText,
612
714
  chunkerMode: "text",
613
715
  textChunkLimit: 4e3,
@@ -620,13 +722,14 @@ const signalPlugin = createChatChannelPlugin({
620
722
  }),
621
723
  afterDeliverPayload: async (params) => await registerDeliveredSignalApprovalPayloadForReactions(params),
622
724
  renderPresentation: async (params) => await renderSignalApprovalPayloadForReactions(params),
623
- sendFormattedText: async ({ cfg, to, text, accountId, deps, abortSignal }) => await sendFormattedSignalText({
725
+ sendFormattedText: async ({ cfg, to, text, accountId, deps, abortSignal, onDeliveryResult }) => await sendFormattedSignalText({
624
726
  cfg,
625
727
  to,
626
728
  text,
627
729
  accountId,
628
730
  deps,
629
- abortSignal
731
+ abortSignal,
732
+ onDeliveryResult
630
733
  }),
631
734
  sendFormattedMedia: async ({ cfg, to, text, mediaUrl, mediaLocalRoots, mediaReadFile, accountId, deps, abortSignal }) => await sendFormattedSignalMedia({
632
735
  cfg,
@@ -1,2 +1,2 @@
1
- import { t as signalPlugin } from "./channel-aQhurb0Z.js";
1
+ import { t as signalPlugin } from "./channel-Cnhy1RwG.js";
2
2
  export { signalPlugin };
@@ -1,6 +1,6 @@
1
- import { i as resolveSignalAccount, n as listSignalAccountIds } from "./accounts-hOCHbEhX.js";
2
- import { c as signalNumberTextInput, i as createSignalCliPathTextInput, o as signalCompletionNote, s as signalDmPolicy } from "./channel-aQhurb0Z.js";
3
- import { r as installSignalCli } from "./install-signal-cli-TwhJ0DGy.js";
1
+ import { i as resolveSignalAccount, n as listSignalAccountIds } from "./accounts-Bz4HtP0g.js";
2
+ import { c as signalNumberTextInput, i as createSignalCliPathTextInput, o as signalCompletionNote, s as signalDmPolicy } from "./channel-Cnhy1RwG.js";
3
+ import { r as installSignalCli } from "./install-signal-cli-ik8VPaGg.js";
4
4
  import { detectBinary } from "openclaw/plugin-sdk/setup-tools";
5
5
  import { createDetectedBinaryStatus, createSetupTranslator, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
6
6
  //#region extensions/signal/src/setup-surface.ts
@@ -1,13 +1,10 @@
1
- import { i as resolveSignalAccount } from "./accounts-hOCHbEhX.js";
2
1
  import { detectMime, parseMediaContentLength } from "openclaw/plugin-sdk/media-runtime";
3
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
2
  import { asDateTimestampMs, parseStrictNonNegativeInteger, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
5
- import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
6
- import fs from "node:fs/promises";
7
- import path from "node:path";
3
+ import nodePath from "node:path";
8
4
  import { resolveFetch } from "openclaw/plugin-sdk/fetch-runtime";
9
5
  import { readProviderTextResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
10
6
  import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
7
+ import { readRegularFile } from "openclaw/plugin-sdk/security-runtime";
11
8
  import WebSocket from "ws";
12
9
  import { Buffer as Buffer$1 } from "node:buffer";
13
10
  import http from "node:http";
@@ -24,6 +21,8 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
24
21
  */
25
22
  const DEFAULT_TIMEOUT_MS$2 = 1e4;
26
23
  const DEFAULT_ATTACHMENT_RESPONSE_MAX_BYTES = 1048576;
24
+ const SIGNAL_CONTAINER_WS_MAX_PAYLOAD_BYTES = 1024 * 1024;
25
+ const DEFAULT_SIGNAL_CONTAINER_MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024;
27
26
  const CONTAINER_TEXT_STYLE_MARKERS = {
28
27
  BOLD: "**",
29
28
  ITALIC: "*",
@@ -123,7 +122,7 @@ function containerReceiveCheck(normalizedBaseUrl, account, timeoutMs) {
123
122
  resolve(result);
124
123
  };
125
124
  try {
126
- ws = new WebSocket(wsUrl);
125
+ ws = new WebSocket(wsUrl, { maxPayload: SIGNAL_CONTAINER_WS_MAX_PAYLOAD_BYTES });
127
126
  } catch (err) {
128
127
  settle({
129
128
  ok: false,
@@ -156,10 +155,11 @@ function containerReceiveCheck(normalizedBaseUrl, account, timeoutMs) {
156
155
  });
157
156
  });
158
157
  ws.once("close", (code, reason) => {
158
+ const reasonText = reason.length > 0 ? `: ${reason.toString("utf8")}` : "";
159
159
  settle({
160
160
  ok: false,
161
161
  status: null,
162
- error: `Signal container receive WebSocket closed before open (${code}${reason.length > 0 ? `: ${reason.toString("utf8")}` : ""})`
162
+ error: `Signal container receive WebSocket closed before open (${code}${reasonText})`
163
163
  });
164
164
  });
165
165
  });
@@ -227,7 +227,7 @@ async function streamContainerEvents(params) {
227
227
  }
228
228
  };
229
229
  try {
230
- ws = new WebSocket(wsUrl);
230
+ ws = new WebSocket(wsUrl, { maxPayload: SIGNAL_CONTAINER_WS_MAX_PAYLOAD_BYTES });
231
231
  } catch (err) {
232
232
  logError(`[signal-ws] failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`);
233
233
  reject(toLintErrorObject$1(err, "Non-Error rejection"));
@@ -249,7 +249,8 @@ async function streamContainerEvents(params) {
249
249
  logError(`[signal-ws] error: ${err instanceof Error ? err.message : String(err)}`);
250
250
  });
251
251
  ws.on("close", (code, reason) => {
252
- log(`[signal-ws] closed (code=${code}, reason=${reason?.toString() || "no reason"})`);
252
+ const reasonStr = reason?.toString() || "no reason";
253
+ log(`[signal-ws] closed (code=${code}, reason=${reasonStr})`);
253
254
  cleanup();
254
255
  resolve();
255
256
  });
@@ -274,15 +275,20 @@ async function streamContainerEvents(params) {
274
275
  * Convert local file paths to base64 data URIs for the container REST API.
275
276
  * The bbernhard container /v2/send only accepts `base64_attachments` (not file paths).
276
277
  */
277
- async function filesToBase64DataUris(filePaths) {
278
+ async function filesToBase64DataUris(filePaths, maxAttachmentBytes) {
278
279
  const results = [];
280
+ let remainingBytes = maxAttachmentBytes;
279
281
  for (const filePath of filePaths) {
280
- const buffer = await fs.readFile(filePath);
282
+ const { buffer } = await readRegularFile({
283
+ filePath,
284
+ maxBytes: remainingBytes
285
+ });
286
+ remainingBytes -= buffer.byteLength;
281
287
  const mime = await detectMime({
282
288
  buffer,
283
289
  filePath
284
290
  }) ?? "application/octet-stream";
285
- const filename = path.basename(filePath);
291
+ const filename = nodePath.basename(filePath);
286
292
  const b64 = buffer.toString("base64");
287
293
  results.push(`data:${mime};filename=${filename};base64,${b64}`);
288
294
  }
@@ -305,7 +311,7 @@ function renderContainerStyledText(text, styles) {
305
311
  };
306
312
  }).filter((span) => span !== null);
307
313
  if (spans.length === 0) return text;
308
- const positions = [...new Set([
314
+ const positions = [.../* @__PURE__ */ new Set([
309
315
  0,
310
316
  text.length,
311
317
  ...spans.flatMap((span) => [span.start, span.end])
@@ -326,6 +332,12 @@ function parseContainerSendTimestamp(raw) {
326
332
  if (timestamp === void 0) throw new Error("Signal REST send returned invalid timestamp");
327
333
  return timestamp;
328
334
  }
335
+ function normalizeContainerQuoteTimestamp(raw) {
336
+ return parseStrictNonNegativeInteger(raw) ?? void 0;
337
+ }
338
+ function normalizeContainerQuoteText(raw) {
339
+ return typeof raw === "string" ? raw : void 0;
340
+ }
329
341
  /**
330
342
  * Send message via bbernhard container REST API.
331
343
  */
@@ -339,7 +351,16 @@ async function containerSendMessage(params) {
339
351
  payload.message = renderContainerStyledText(params.message, params.textStyles);
340
352
  payload["text_mode"] = "styled";
341
353
  }
342
- if (params.attachments && params.attachments.length > 0) payload.base64_attachments = await filesToBase64DataUris(params.attachments);
354
+ if (params.attachments && params.attachments.length > 0) {
355
+ const configuredMaxBytes = params.maxAttachmentBytes;
356
+ const maxAttachmentBytes = typeof configuredMaxBytes === "number" && Number.isFinite(configuredMaxBytes) && configuredMaxBytes >= 0 ? Math.floor(configuredMaxBytes) : DEFAULT_SIGNAL_CONTAINER_MAX_ATTACHMENT_BYTES;
357
+ payload.base64_attachments = await filesToBase64DataUris(params.attachments, maxAttachmentBytes);
358
+ }
359
+ if (params.quoteTimestamp !== void 0 && params.quoteAuthor) {
360
+ payload.quote_timestamp = params.quoteTimestamp;
361
+ payload.quote_author = params.quoteAuthor;
362
+ payload.quote_message = params.quoteMessage ?? "";
363
+ }
343
364
  const timestamp = parseContainerSendTimestamp((await containerRestRequest("/v2/send", {
344
365
  baseUrl: params.baseUrl,
345
366
  timeoutMs: params.timeoutMs
@@ -440,6 +461,8 @@ async function containerRpcRequest(method, params, opts) {
440
461
  style
441
462
  };
442
463
  });
464
+ const quoteTimestamp = normalizeContainerQuoteTimestamp(p.quoteTimestamp ?? p["quote-timestamp"]);
465
+ const quoteAuthor = normalizeContainerQuoteText(p.quoteAuthor ?? p["quote-author"]);
443
466
  return await containerSendMessage({
444
467
  baseUrl: opts.baseUrl,
445
468
  account: p.account ?? "",
@@ -447,6 +470,10 @@ async function containerRpcRequest(method, params, opts) {
447
470
  message: p.message ?? "",
448
471
  textStyles,
449
472
  attachments: p.attachments,
473
+ maxAttachmentBytes: opts.maxAttachmentBytes,
474
+ quoteTimestamp,
475
+ quoteAuthor: quoteAuthor ? stripUuidPrefix(quoteAuthor) : void 0,
476
+ quoteMessage: normalizeContainerQuoteText(p.quoteMessage ?? p["quote-message"]),
450
477
  timeoutMs: opts.timeoutMs
451
478
  });
452
479
  }
@@ -953,127 +980,4 @@ async function streamSignalEvents(params) {
953
980
  });
954
981
  }
955
982
  //#endregion
956
- //#region extensions/signal/src/rpc-context.ts
957
- function resolveSignalRpcContext(opts, accountInfo) {
958
- const hasBaseUrl = Boolean(normalizeOptionalString(opts.baseUrl));
959
- const hasAccount = Boolean(normalizeOptionalString(opts.account));
960
- if ((!hasBaseUrl || !hasAccount) && !accountInfo) throw new Error("Signal account config is required when baseUrl or account is missing");
961
- const resolvedAccount = accountInfo;
962
- const baseUrl = normalizeOptionalString(opts.baseUrl) ?? resolvedAccount?.baseUrl;
963
- if (!baseUrl) throw new Error("Signal base URL is required");
964
- return {
965
- baseUrl,
966
- account: normalizeOptionalString(opts.account) ?? normalizeOptionalString(resolvedAccount?.config.account)
967
- };
968
- }
969
- //#endregion
970
- //#region extensions/signal/src/send-reactions.ts
971
- function normalizeSignalId(raw) {
972
- const trimmed = raw.trim();
973
- if (!trimmed) return "";
974
- return trimmed.replace(/^signal:/i, "").trim();
975
- }
976
- function normalizeSignalUuid(raw) {
977
- const trimmed = normalizeSignalId(raw);
978
- if (!trimmed) return "";
979
- if (normalizeLowercaseStringOrEmpty(trimmed).startsWith("uuid:")) return trimmed.slice(5).trim();
980
- return trimmed;
981
- }
982
- function resolveTargetAuthorParams(params) {
983
- const candidates = [
984
- params.targetAuthor,
985
- params.targetAuthorUuid,
986
- params.fallback
987
- ];
988
- for (const candidate of candidates) {
989
- const raw = candidate?.trim();
990
- if (!raw) continue;
991
- const normalized = normalizeSignalUuid(raw);
992
- if (normalized) return { targetAuthor: normalized };
993
- }
994
- return {};
995
- }
996
- async function sendReactionSignalCore(params) {
997
- const cfg = requireRuntimeConfig(params.opts.cfg, "Signal reactions");
998
- const apiMode = cfg.channels?.signal?.apiMode;
999
- const accountInfo = resolveSignalAccount({
1000
- cfg,
1001
- accountId: params.opts.accountId
1002
- });
1003
- const { baseUrl, account } = resolveSignalRpcContext(params.opts, accountInfo);
1004
- const normalizedRecipient = normalizeSignalUuid(params.recipient);
1005
- const groupId = params.opts.groupId?.trim();
1006
- if (!normalizedRecipient && !groupId) throw new Error(params.errors.missingRecipient);
1007
- if (!Number.isFinite(params.targetTimestamp) || params.targetTimestamp <= 0) throw new Error(params.errors.invalidTargetTimestamp);
1008
- const normalizedEmoji = params.emoji?.trim();
1009
- if (!normalizedEmoji) throw new Error(params.errors.missingEmoji);
1010
- const targetAuthorParams = resolveTargetAuthorParams({
1011
- targetAuthor: params.opts.targetAuthor,
1012
- targetAuthorUuid: params.opts.targetAuthorUuid,
1013
- fallback: normalizedRecipient
1014
- });
1015
- if (groupId && !targetAuthorParams.targetAuthor) throw new Error(params.errors.missingTargetAuthor);
1016
- const requestParams = {
1017
- emoji: normalizedEmoji,
1018
- targetTimestamp: params.targetTimestamp,
1019
- ...params.remove ? { remove: true } : {},
1020
- ...targetAuthorParams
1021
- };
1022
- if (normalizedRecipient) requestParams.recipients = [normalizedRecipient];
1023
- if (groupId) requestParams.groupIds = [groupId];
1024
- if (account) requestParams.account = account;
1025
- return {
1026
- ok: true,
1027
- timestamp: (await signalRpcRequest("sendReaction", requestParams, {
1028
- baseUrl,
1029
- timeoutMs: params.opts.timeoutMs,
1030
- apiMode
1031
- }))?.timestamp
1032
- };
1033
- }
1034
- /**
1035
- * Send a Signal reaction to a message
1036
- * @param recipient - UUID or E.164 phone number of the message author
1037
- * @param targetTimestamp - Message ID (timestamp) to react to
1038
- * @param emoji - Emoji to react with
1039
- * @param opts - Optional account/connection overrides
1040
- */
1041
- async function sendReactionSignal(recipient, targetTimestamp, emoji, opts) {
1042
- return await sendReactionSignalCore({
1043
- recipient,
1044
- targetTimestamp,
1045
- emoji,
1046
- remove: false,
1047
- opts,
1048
- errors: {
1049
- missingRecipient: "Recipient or groupId is required for Signal reaction",
1050
- invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction",
1051
- missingEmoji: "Emoji is required for Signal reaction",
1052
- missingTargetAuthor: "targetAuthor is required for group reactions"
1053
- }
1054
- });
1055
- }
1056
- /**
1057
- * Remove a Signal reaction from a message
1058
- * @param recipient - UUID or E.164 phone number of the message author
1059
- * @param targetTimestamp - Message ID (timestamp) to remove reaction from
1060
- * @param emoji - Emoji to remove
1061
- * @param opts - Optional account/connection overrides
1062
- */
1063
- async function removeReactionSignal(recipient, targetTimestamp, emoji, opts) {
1064
- return await sendReactionSignalCore({
1065
- recipient,
1066
- targetTimestamp,
1067
- emoji,
1068
- remove: true,
1069
- opts,
1070
- errors: {
1071
- missingRecipient: "Recipient or groupId is required for Signal reaction removal",
1072
- invalidTargetTimestamp: "Valid targetTimestamp is required for Signal reaction removal",
1073
- missingEmoji: "Emoji is required for Signal reaction removal",
1074
- missingTargetAuthor: "targetAuthor is required for group reaction removal"
1075
- }
1076
- });
1077
- }
1078
- //#endregion
1079
- export { signalRpcRequest as a, signalCheck as i, sendReactionSignal as n, streamSignalEvents as o, resolveSignalRpcContext as r, removeReactionSignal as t };
983
+ export { signalRpcRequest as n, streamSignalEvents as r, signalCheck as t };
@@ -1,3 +1,3 @@
1
- import { d as normalizeSignalMessagingTarget, i as isSignalSenderAllowed, u as looksLikeSignalTargetId } from "./identity-B6O4k8xg.js";
2
- import { a as looksLikeArchive, i as installSignalCliFromRelease, n as extractSignalCliArchive, o as pickAsset, r as installSignalCli, t as downloadToFile } from "./install-signal-cli-TwhJ0DGy.js";
1
+ import { d as normalizeSignalMessagingTarget, i as isSignalSenderAllowed, u as looksLikeSignalTargetId } from "./identity-C8-yk4J9.js";
2
+ import { a as looksLikeArchive, i as installSignalCliFromRelease, n as extractSignalCliArchive, o as pickAsset, r as installSignalCli, t as downloadToFile } from "./install-signal-cli-ik8VPaGg.js";
3
3
  export { downloadToFile, extractSignalCliArchive, installSignalCli, installSignalCliFromRelease, isSignalSenderAllowed, looksLikeArchive, looksLikeSignalTargetId, normalizeSignalMessagingTarget, pickAsset };