@openclaw/signal 2026.7.1 → 2026.7.2-beta.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 (28) hide show
  1. package/dist/api.js +8 -8
  2. package/dist/{approval-handler.runtime-DDQ4ZRyc.js → approval-handler.runtime-I1SMFwwR.js} +4 -4
  3. package/dist/{approval-reactions-UFmUPD0A.js → approval-reactions-ChcS2rQN.js} +34 -25
  4. package/dist/{approval-resolver-BR0MioAA.js → approval-resolver-B3m0D2R0.js} +2 -1
  5. package/dist/{channel-Cnhy1RwG.js → channel-DK8XZHPe.js} +132 -42
  6. package/dist/channel-config-api.js +1 -1
  7. package/dist/channel-plugin-api.js +1 -1
  8. package/dist/{channel.runtime-CQN0RaFx.js → channel.runtime-BbDh5UIr.js} +3 -2
  9. package/dist/{client-adapter-C9mB2Jz8.js → client-adapter-aEM0kGhC.js} +52 -18
  10. package/dist/config-api-DIg1lInT.js +3 -0
  11. package/dist/{config-schema-BiojLEsX.js → config-schema-CfVNH0DB.js} +7 -9
  12. package/dist/contract-api.js +3 -3
  13. package/dist/doctor-contract-api.js +16 -0
  14. package/dist/{identity-C8-yk4J9.js → identity-BF8taj7g.js} +7 -4
  15. package/dist/{install-signal-cli-ik8VPaGg.js → install-signal-cli-CtusY3tN.js} +43 -32
  16. package/dist/{monitor-Bee8O5Ay.js → monitor-BdzH3U64.js} +331 -126
  17. package/dist/{probe-Cnht8Ihg.js → probe-CbWkI24J.js} +1 -1
  18. package/dist/{reaction-runtime-api-EQ7cc7-Y.js → reaction-runtime-api-DifyinAU.js} +1 -1
  19. package/dist/reaction-runtime-api.js +1 -1
  20. package/dist/{message-actions-3OLDgjis.js → reply-authors-osU_SY4J.js} +212 -9
  21. package/dist/{approval-auth-CsHNcAiy.js → runtime-BZ9hnS3a.js} +34 -24
  22. package/dist/runtime-api.js +9 -9
  23. package/dist/{send-CUJy6WPt.js → send-BWtA6h3J.js} +14 -9
  24. package/dist/{send.runtime-CEi5oyCG.js → send.runtime-D4WBNNgn.js} +1 -1
  25. package/npm-shrinkwrap.json +2 -2
  26. package/openclaw.plugin.json +72 -48
  27. package/package.json +4 -4
  28. package/dist/config-api-KS-qhQvD.js +0 -2
@@ -2,8 +2,7 @@ import { detectMime, parseMediaContentLength } from "openclaw/plugin-sdk/media-r
2
2
  import { asDateTimestampMs, parseStrictNonNegativeInteger, resolveExpiresAtMsFromDurationMs, resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
3
3
  import nodePath from "node:path";
4
4
  import { resolveFetch } from "openclaw/plugin-sdk/fetch-runtime";
5
- import { readProviderTextResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
6
- import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
5
+ import { readResponseTextPrefix, readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
7
6
  import { readRegularFile } from "openclaw/plugin-sdk/security-runtime";
8
7
  import WebSocket from "ws";
9
8
  import { Buffer as Buffer$1 } from "node:buffer";
@@ -21,7 +20,10 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
21
20
  */
22
21
  const DEFAULT_TIMEOUT_MS$2 = 1e4;
23
22
  const DEFAULT_ATTACHMENT_RESPONSE_MAX_BYTES = 1048576;
24
- const SIGNAL_CONTAINER_WS_MAX_PAYLOAD_BYTES = 1024 * 1024;
23
+ const SIGNAL_REST_ERROR_RESPONSE_MAX_BYTES = 16 * 1024;
24
+ const SIGNAL_REST_SUCCESS_RESPONSE_MAX_BYTES = 16 * 1024 * 1024;
25
+ const WS_MAX_PAYLOAD = 1024 * 1024;
26
+ const WS_HANDSHAKE_MS = 3e4;
25
27
  const DEFAULT_SIGNAL_CONTAINER_MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024;
26
28
  const CONTAINER_TEXT_STYLE_MARKERS = {
27
29
  BOLD: "**",
@@ -62,10 +64,34 @@ function normalizeMaxResponseBytes(value) {
62
64
  function readContentLength(res) {
63
65
  return parseMediaContentLength(res.headers?.get("content-length") ?? null) ?? void 0;
64
66
  }
65
- async function readCappedResponseBuffer(res, maxResponseBytes) {
67
+ function signalRestIdleTimeoutError({ chunkTimeoutMs }) {
68
+ return /* @__PURE__ */ new Error(`Signal REST response body stalled after ${chunkTimeoutMs}ms`);
69
+ }
70
+ function signalAttachmentIdleTimeoutError({ chunkTimeoutMs }) {
71
+ return /* @__PURE__ */ new Error(`Signal REST attachment response body stalled after ${chunkTimeoutMs}ms`);
72
+ }
73
+ async function readSignalRestText(res, bodyIdleTimeoutMs) {
74
+ const bytes = await readResponseWithLimit(res, SIGNAL_REST_SUCCESS_RESPONSE_MAX_BYTES, {
75
+ chunkTimeoutMs: bodyIdleTimeoutMs,
76
+ onIdleTimeout: signalRestIdleTimeoutError,
77
+ onOverflow: ({ maxBytes }) => /* @__PURE__ */ new Error(`Signal REST: text response exceeds ${maxBytes} bytes`)
78
+ });
79
+ return new TextDecoder().decode(bytes);
80
+ }
81
+ async function readSignalRestErrorText(res, bodyIdleTimeoutMs) {
82
+ return (await readResponseTextPrefix(res, SIGNAL_REST_ERROR_RESPONSE_MAX_BYTES, {
83
+ chunkTimeoutMs: bodyIdleTimeoutMs,
84
+ onIdleTimeout: signalRestIdleTimeoutError
85
+ })).text;
86
+ }
87
+ async function readCappedResponseBuffer(res, maxResponseBytes, bodyIdleTimeoutMs) {
66
88
  const contentLength = readContentLength(res);
67
89
  if (contentLength !== void 0 && contentLength > maxResponseBytes) throw new Error("Signal REST attachment exceeded size limit");
68
- return await readResponseWithLimit(res, maxResponseBytes, { onOverflow: () => /* @__PURE__ */ new Error("Signal REST attachment exceeded size limit") });
90
+ return await readResponseWithLimit(res, maxResponseBytes, {
91
+ chunkTimeoutMs: bodyIdleTimeoutMs,
92
+ onIdleTimeout: signalAttachmentIdleTimeoutError,
93
+ onOverflow: () => /* @__PURE__ */ new Error("Signal REST attachment exceeded size limit")
94
+ });
69
95
  }
70
96
  async function releaseUnreadResponseBody(res) {
71
97
  if (res?.bodyUsed !== true) await res?.body?.cancel().catch(() => void 0);
@@ -122,7 +148,7 @@ function containerReceiveCheck(normalizedBaseUrl, account, timeoutMs) {
122
148
  resolve(result);
123
149
  };
124
150
  try {
125
- ws = new WebSocket(wsUrl, { maxPayload: SIGNAL_CONTAINER_WS_MAX_PAYLOAD_BYTES });
151
+ ws = new WebSocket(wsUrl, { maxPayload: WS_MAX_PAYLOAD });
126
152
  } catch (err) {
127
153
  settle({
128
154
  ok: false,
@@ -174,13 +200,15 @@ async function containerRestRequest(endpoint, opts, method = "GET", body) {
174
200
  headers: { "Content-Type": "application/json" }
175
201
  };
176
202
  if (body) init.body = JSON.stringify(body);
177
- const res = await fetchWithTimeout(url, init, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS$2);
203
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS$2;
204
+ const bodyIdleTimeoutMs = resolveTimerTimeoutMs(timeoutMs, DEFAULT_TIMEOUT_MS$2);
205
+ const res = await fetchWithTimeout(url, init, timeoutMs);
178
206
  if (res.status === 204) return;
179
207
  if (!res.ok) {
180
- const errorText = await readResponseTextLimited(res).catch(() => "");
208
+ const errorText = await readSignalRestErrorText(res, bodyIdleTimeoutMs).catch(() => "");
181
209
  throw new Error(`Signal REST ${res.status}: ${errorText || res.statusText}`);
182
210
  }
183
- const text = await readProviderTextResponse(res, "Signal REST");
211
+ const text = await readSignalRestText(res, bodyIdleTimeoutMs);
184
212
  if (!text) return;
185
213
  try {
186
214
  return JSON.parse(text);
@@ -195,9 +223,11 @@ async function containerFetchAttachment(attachmentId, opts) {
195
223
  const url = `${normalizeBaseUrl$1(opts.baseUrl)}/v1/attachments/${encodeURIComponent(attachmentId)}`;
196
224
  let res;
197
225
  try {
198
- res = await fetchWithTimeout(url, { method: "GET" }, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS$2);
226
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS$2;
227
+ const bodyIdleTimeoutMs = resolveTimerTimeoutMs(timeoutMs, DEFAULT_TIMEOUT_MS$2);
228
+ res = await fetchWithTimeout(url, { method: "GET" }, timeoutMs);
199
229
  if (!res.ok) return null;
200
- return await readCappedResponseBuffer(res, normalizeMaxResponseBytes(opts.maxResponseBytes));
230
+ return await readCappedResponseBuffer(res, normalizeMaxResponseBytes(opts.maxResponseBytes), bodyIdleTimeoutMs);
201
231
  } finally {
202
232
  await releaseUnreadResponseBody(res);
203
233
  }
@@ -227,7 +257,10 @@ async function streamContainerEvents(params) {
227
257
  }
228
258
  };
229
259
  try {
230
- ws = new WebSocket(wsUrl, { maxPayload: SIGNAL_CONTAINER_WS_MAX_PAYLOAD_BYTES });
260
+ ws = new WebSocket(wsUrl, {
261
+ maxPayload: WS_MAX_PAYLOAD,
262
+ handshakeTimeout: WS_HANDSHAKE_MS
263
+ });
231
264
  } catch (err) {
232
265
  logError(`[signal-ws] failed to create WebSocket: ${err instanceof Error ? err.message : String(err)}`);
233
266
  reject(toLintErrorObject$1(err, "Non-Error rejection"));
@@ -317,11 +350,10 @@ function renderContainerStyledText(text, styles) {
317
350
  ...spans.flatMap((span) => [span.start, span.end])
318
351
  ])].toSorted((a, b) => a - b);
319
352
  let rendered = "";
320
- for (let i = 0; i < positions.length; i += 1) {
321
- const pos = positions[i];
353
+ for (const [index, pos] of positions.entries()) {
322
354
  for (const span of spans.filter((candidate) => candidate.end === pos).toSorted((a, b) => b.start - a.start)) rendered += span.marker;
323
355
  for (const span of spans.filter((candidate) => candidate.start === pos).toSorted((a, b) => b.end - a.end)) rendered += span.marker;
324
- const next = positions[i + 1];
356
+ const next = positions[index + 1];
325
357
  if (next !== void 0 && next > pos) rendered += escapeContainerStyledText(text.slice(pos, next));
326
358
  }
327
359
  return rendered;
@@ -453,13 +485,14 @@ async function containerRpcRequest(method, params, opts) {
453
485
  const groupId = p.groupId;
454
486
  const formattedGroupId = groupId ? formatGroupIdForContainer(groupId) : void 0;
455
487
  const finalRecipients = recipients.length > 0 ? recipients : usernames.length > 0 ? usernames : formattedGroupId ? [formattedGroupId] : [];
456
- const textStyles = p["text-style"]?.map((s) => {
488
+ const textStyles = p["text-style"]?.flatMap((s) => {
457
489
  const [start, length, style] = s.split(":");
458
- return {
490
+ if (start === void 0 || length === void 0 || style === void 0) return [];
491
+ return [{
459
492
  start: Number(start),
460
493
  length: Number(length),
461
494
  style
462
- };
495
+ }];
463
496
  });
464
497
  const quoteTimestamp = normalizeContainerQuoteTimestamp(p.quoteTimestamp ?? p["quote-timestamp"]);
465
498
  const quoteAuthor = normalizeContainerQuoteText(p.quoteAuthor ?? p["quote-author"]);
@@ -789,6 +822,7 @@ async function streamSignalEvents$1(params) {
789
822
  }
790
823
  if (line.startsWith(":")) return;
791
824
  const [rawField, ...rest] = line.split(":");
825
+ if (rawField === void 0) return;
792
826
  const field = rawField.trim();
793
827
  const rawValue = rest.join(":");
794
828
  const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
@@ -0,0 +1,3 @@
1
+ import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
2
+ import { SignalConfigSchema } from "openclaw/plugin-sdk/bundled-channel-config-schema";
3
+ export { buildChannelConfigSchema as n, SignalConfigSchema as t };
@@ -1,4 +1,5 @@
1
- import { n as buildChannelConfigSchema, t as SignalConfigSchema } from "./config-api-KS-qhQvD.js";
1
+ import { n as buildChannelConfigSchema, t as SignalConfigSchema } from "./config-api-DIg1lInT.js";
2
+ import { createChannelConfigUiHints } from "openclaw/plugin-sdk/channel-core";
2
3
  //#endregion
3
4
  //#region extensions/signal/src/config-schema.ts
4
5
  const SignalChannelConfigSchema = buildChannelConfigSchema(SignalConfigSchema, { uiHints: {
@@ -6,14 +7,11 @@ const SignalChannelConfigSchema = buildChannelConfigSchema(SignalConfigSchema, {
6
7
  label: "Signal",
7
8
  help: "Signal channel provider configuration including account identity and DM policy behavior. Keep account mapping explicit so routing remains stable across multi-device setups."
8
9
  },
9
- dmPolicy: {
10
- label: "Signal DM Policy",
11
- help: "Direct message access control (\"pairing\" recommended). \"open\" requires channels.signal.allowFrom=[\"*\"]."
12
- },
13
- configWrites: {
14
- label: "Signal Config Writes",
15
- help: "Allow Signal to write config in response to channel events/commands (default: true)."
16
- },
10
+ ...createChannelConfigUiHints({
11
+ channelLabel: "Signal",
12
+ dmPolicy: { channelKey: "signal" },
13
+ configWrites: true
14
+ }),
17
15
  account: {
18
16
  label: "Signal Account",
19
17
  help: "Signal account identifier (phone/number handle) used to bind this channel config to a specific Signal identity. Keep this aligned with your linked device/session state."
@@ -1,3 +1,3 @@
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
- export { downloadToFile, extractSignalCliArchive, installSignalCli, installSignalCliFromRelease, isSignalSenderAllowed, looksLikeArchive, looksLikeSignalTargetId, normalizeSignalMessagingTarget, pickAsset };
1
+ import { d as normalizeSignalMessagingTarget, i as isSignalSenderAllowed, u as looksLikeSignalTargetId } from "./identity-BF8taj7g.js";
2
+ import { a as installSignalCliFromRelease, i as installSignalCli, n as downloadToFile, o as looksLikeArchive, r as extractSignalCliArchive, s as pickAsset, t as MAX_SIGNAL_CLI_EXTRACTED_BYTES } from "./install-signal-cli-CtusY3tN.js";
3
+ export { MAX_SIGNAL_CLI_EXTRACTED_BYTES, downloadToFile, extractSignalCliArchive, installSignalCli, installSignalCliFromRelease, isSignalSenderAllowed, looksLikeArchive, looksLikeSignalTargetId, normalizeSignalMessagingTarget, pickAsset };
@@ -0,0 +1,16 @@
1
+ import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor";
2
+ //#region extensions/signal/doctor-contract-api.ts
3
+ const streamingAliasMigration = defineChannelAliasMigration({
4
+ channelId: "signal",
5
+ streaming: {
6
+ defaultMode: "partial",
7
+ deliveryOnly: true
8
+ },
9
+ accountStreamingReplacesRoot: true
10
+ });
11
+ const legacyConfigRules = streamingAliasMigration.legacyConfigRules;
12
+ function normalizeCompatibilityConfig({ cfg }) {
13
+ return streamingAliasMigration.normalizeChannelConfig({ cfg });
14
+ }
15
+ //#endregion
16
+ export { legacyConfigRules, normalizeCompatibilityConfig };
@@ -61,15 +61,16 @@ function stripSignalPrefix(value) {
61
61
  }
62
62
  function resolveSignalSender(params) {
63
63
  const sourceNumber = params.sourceNumber?.trim();
64
+ const sourceUuid = params.sourceUuid?.trim();
64
65
  if (sourceNumber) {
65
66
  const e164 = normalizeE164(sourceNumber);
66
67
  if (e164) return {
67
68
  kind: "phone",
68
69
  raw: sourceNumber,
69
- e164
70
+ e164,
71
+ ...sourceUuid ? { aliases: { uuid: sourceUuid } } : {}
70
72
  };
71
73
  }
72
- const sourceUuid = params.sourceUuid?.trim();
73
74
  if (sourceUuid) return {
74
75
  kind: "uuid",
75
76
  raw: sourceUuid
@@ -124,9 +125,11 @@ function isSignalSenderAllowed(sender, allowFrom) {
124
125
  if (allowFrom.length === 0) return false;
125
126
  const parsed = allowFrom.map(parseSignalAllowEntry).filter((entry) => entry !== null);
126
127
  if (parsed.some((entry) => entry.kind === "any")) return true;
128
+ const senderE164 = sender.kind === "phone" ? sender.e164 : sender.aliases?.e164;
129
+ const senderUuid = sender.kind === "uuid" ? sender.raw : sender.aliases?.uuid;
127
130
  return parsed.some((entry) => {
128
- if (entry.kind === "phone" && sender.kind === "phone") return entry.e164 === sender.e164;
129
- if (entry.kind === "uuid" && sender.kind === "uuid") return entry.raw === sender.raw;
131
+ if (entry.kind === "phone") return senderE164 !== void 0 && entry.e164 === senderE164;
132
+ if (entry.kind === "uuid") return senderUuid !== void 0 && entry.raw === senderUuid;
130
133
  return false;
131
134
  });
132
135
  }
@@ -1,18 +1,20 @@
1
1
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
2
2
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
3
3
  import nodePath from "node:path";
4
- import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
5
4
  import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
6
5
  import { CONFIG_DIR, extractArchive, resolveBrewExecutable } from "openclaw/plugin-sdk/setup-tools";
7
6
  import { createWriteStream } from "node:fs";
8
7
  import fs from "node:fs/promises";
9
8
  import { Readable, Transform } from "node:stream";
10
9
  import { pipeline } from "node:stream/promises";
10
+ import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
11
11
  import { runPluginCommandWithTimeout } from "openclaw/plugin-sdk/run-command";
12
12
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
13
- import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
13
+ import { withTempDownloadPath } from "openclaw/plugin-sdk/temp-path";
14
14
  //#region extensions/signal/src/install-signal-cli.ts
15
15
  const MAX_SIGNAL_CLI_ARCHIVE_BYTES = 256 * 1024 * 1024;
16
+ /** @internal Exported for testing. */
17
+ const MAX_SIGNAL_CLI_EXTRACTED_BYTES = 384 * 1024 * 1024;
16
18
  const SIGNAL_CLI_DOWNLOAD_TIMEOUT_MS = 5 * 6e4;
17
19
  const SIGNAL_CLI_RELEASE_INFO_TIMEOUT_MS = 3e4;
18
20
  const CONTENT_LENGTH_RE = /^\d+$/;
@@ -21,7 +23,13 @@ async function extractSignalCliArchive(archivePath, installRoot, timeoutMs) {
21
23
  await extractArchive({
22
24
  archivePath,
23
25
  destDir: installRoot,
24
- timeoutMs
26
+ timeoutMs,
27
+ limits: {
28
+ maxArchiveBytes: MAX_SIGNAL_CLI_ARCHIVE_BYTES,
29
+ maxEntries: 32,
30
+ maxEntryBytes: MAX_SIGNAL_CLI_EXTRACTED_BYTES,
31
+ maxExtractedBytes: MAX_SIGNAL_CLI_EXTRACTED_BYTES
32
+ }
25
33
  });
26
34
  }
27
35
  /** @internal Exported for testing. */
@@ -202,36 +210,39 @@ async function installSignalCliFromRelease(runtime) {
202
210
  ok: false,
203
211
  error: "No compatible release asset found for this platform."
204
212
  };
205
- const tmpDir = await fs.mkdtemp(nodePath.join(resolvePreferredOpenClawTmpDir(), "openclaw-signal-"));
206
- const archivePath = nodePath.join(tmpDir, asset.name);
207
- runtime.log(`Downloading signal-cli ${version} (${asset.name})…`);
208
- await downloadToFile(asset.browser_download_url, archivePath);
209
- const installRoot = nodePath.join(CONFIG_DIR, "tools", "signal-cli", version);
210
- await fs.mkdir(installRoot, { recursive: true });
211
- if (!looksLikeArchive(normalizeLowercaseStringOrEmpty(asset.name))) return {
212
- ok: false,
213
- error: `Unsupported archive type: ${asset.name}`
214
- };
215
- try {
216
- await extractSignalCliArchive(archivePath, installRoot, 6e4);
217
- } catch (err) {
218
- const message = formatErrorMessage(err);
219
- return {
213
+ return await withTempDownloadPath({
214
+ prefix: "openclaw-signal",
215
+ fileName: asset.name
216
+ }, async (archivePath) => {
217
+ runtime.log(`Downloading signal-cli ${version} (${asset.name})…`);
218
+ await downloadToFile(asset.browser_download_url, archivePath);
219
+ const installRoot = nodePath.join(CONFIG_DIR, "tools", "signal-cli", version);
220
+ await fs.mkdir(installRoot, { recursive: true });
221
+ if (!looksLikeArchive(normalizeLowercaseStringOrEmpty(asset.name))) return {
220
222
  ok: false,
221
- error: `Failed to extract ${asset.name}: ${message}`
223
+ error: `Unsupported archive type: ${asset.name}`
222
224
  };
223
- }
224
- const cliPath = await findSignalCliBinary(installRoot);
225
- if (!cliPath) return {
226
- ok: false,
227
- error: `signal-cli binary not found after extracting ${asset.name}`
228
- };
229
- await fs.chmod(cliPath, 493).catch(() => {});
230
- return {
231
- ok: true,
232
- cliPath,
233
- version
234
- };
225
+ try {
226
+ await extractSignalCliArchive(archivePath, installRoot, 6e4);
227
+ } catch (err) {
228
+ const message = formatErrorMessage(err);
229
+ return {
230
+ ok: false,
231
+ error: `Failed to extract ${asset.name}: ${message}`
232
+ };
233
+ }
234
+ const cliPath = await findSignalCliBinary(installRoot);
235
+ if (!cliPath) return {
236
+ ok: false,
237
+ error: `signal-cli binary not found after extracting ${asset.name}`
238
+ };
239
+ await fs.chmod(cliPath, 493).catch(() => {});
240
+ return {
241
+ ok: true,
242
+ cliPath,
243
+ version
244
+ };
245
+ });
235
246
  }
236
247
  async function installSignalCli(runtime) {
237
248
  if (process.platform === "win32") return {
@@ -242,4 +253,4 @@ async function installSignalCli(runtime) {
242
253
  return installSignalCliViaBrew(runtime);
243
254
  }
244
255
  //#endregion
245
- export { looksLikeArchive as a, installSignalCliFromRelease as i, extractSignalCliArchive as n, pickAsset as o, installSignalCli as r, downloadToFile as t };
256
+ export { installSignalCliFromRelease as a, installSignalCli as i, downloadToFile as n, looksLikeArchive as o, extractSignalCliArchive as r, pickAsset as s, MAX_SIGNAL_CLI_EXTRACTED_BYTES as t };