@openclaw/signal 0.0.0 → 2026.6.11-beta.2
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.
- package/README.md +12 -2
- package/dist/accounts-hOCHbEhX.js +40 -0
- package/dist/api.js +16 -0
- package/dist/approval-handler.runtime-CeJI1wow.js +158 -0
- package/dist/approval-resolver-BR0MioAA.js +15 -0
- package/dist/channel-LmY2UpOt.js +619 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-entry.js +18 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.runtime-B0-YpkC4.js +65 -0
- package/dist/config-api-KS-qhQvD.js +2 -0
- package/dist/config-schema-BiojLEsX.js +27 -0
- package/dist/contract-api.js +3 -0
- package/dist/identity-B6O4k8xg.js +130 -0
- package/dist/index.js +18 -0
- package/dist/install-signal-cli-CXgTF3de.js +243 -0
- package/dist/message-actions-Bue0g2Kc.js +548 -0
- package/dist/monitor-CUhIKHJo.js +1404 -0
- package/dist/probe-BQ_Izoya.js +48 -0
- package/dist/reaction-runtime-api-C_PQ45D9.js +1074 -0
- package/dist/reaction-runtime-api.js +2 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/runtime-api.js +18 -0
- package/dist/secret-contract-api.js +5 -0
- package/dist/send-CBlFUkY_.js +599 -0
- package/dist/send.runtime-CfaZd8X4.js +2 -0
- package/dist/setup-entry.js +11 -0
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +22 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/node_modules/ws/lib/receiver.js +760 -0
- package/node_modules/ws/lib/sender.js +607 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +562 -0
- package/node_modules/ws/lib/websocket.js +1407 -0
- package/node_modules/ws/package.json +70 -0
- package/node_modules/ws/wrapper.mjs +21 -0
- package/npm-shrinkwrap.json +36 -0
- package/openclaw.plugin.json +771 -0
- 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 };
|