@openclaw/irc 2026.8.1-beta.2 → 2026.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +1 -1
- package/dist/{channel-Bfg9kJRw.js → channel-BxbQEBnS.js} +320 -137
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-runtime-kWMtRD4g.js → channel-runtime-DZcQfOqp.js} +16 -84
- package/dist/{config-schema-DdB_h5mL.js → config-schema-CpB6AaUh.js} +2 -2
- package/dist/doctor-contract-api.js +34 -4
- package/openclaw.plugin.json +40 -12
- package/package.json +4 -4
package/dist/api.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as listIrcAccountIds, g as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin, v as resolveDefaultIrcAccountId, y as resolveIrcAccount } from "./channel-BxbQEBnS.js";
|
|
2
2
|
import { n as setIrcRuntime } from "./runtime-xy-FcjJC.js";
|
|
3
3
|
export { ircPlugin, ircSetupAdapter, ircSetupWizard, listEnabledIrcAccounts, listIrcAccountIds, resolveDefaultIrcAccountId, resolveIrcAccount, setIrcRuntime };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { t as IrcChannelConfigSchema } from "./config-schema-
|
|
1
|
+
import { t as IrcChannelConfigSchema } from "./config-schema-CpB6AaUh.js";
|
|
2
2
|
import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
|
|
3
3
|
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-D_DC3YWj.js";
|
|
4
4
|
import { createAccountListHelpers, describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
5
5
|
import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
|
|
6
6
|
import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
7
7
|
import { buildChannelOutboundSessionRoute, createChatChannelPlugin, parseOptionalDelimitedEntries } from "openclaw/plugin-sdk/channel-core";
|
|
8
|
-
import {
|
|
8
|
+
import { defineStableChannelIngressIdentity, identityEntryAuthenticationClassifier } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
9
|
+
import { buildMutableAllowEntryDetector, collectStandardAllowlistLists, createAllowlistProviderOpenWarningCollector, createConditionalWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, resolveScopeKeyCaseInsensitive, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
|
|
9
10
|
import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister } from "openclaw/plugin-sdk/directory-runtime";
|
|
10
11
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
11
12
|
import { buildBaseChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
|
|
@@ -14,17 +15,19 @@ import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normali
|
|
|
14
15
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
15
16
|
import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
|
16
17
|
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
17
|
-
import {
|
|
18
|
+
import { resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
18
19
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringifiedOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
19
|
-
import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
|
|
20
|
+
import { createAccountStatusSink, createMessageReceiptFromOutboundResults, createReplyToFanout, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
|
|
20
21
|
import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
|
|
21
22
|
import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
|
|
23
|
+
import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
|
|
22
24
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
23
25
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
24
26
|
import net from "node:net";
|
|
25
27
|
import tls from "node:tls";
|
|
26
28
|
import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
27
29
|
import { randomUUID } from "node:crypto";
|
|
30
|
+
import { assertSecretOwnerAvailable } from "openclaw/plugin-sdk/channel-secret-owner-runtime";
|
|
28
31
|
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
29
32
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
|
|
30
33
|
import { applyAccountNameToChannelSection, createAllowFromSection, createPromptParsedAllowFromForAccount, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicySetter, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
@@ -42,6 +45,16 @@ const { listAccountIds: listIrcAccountIds, resolveDefaultAccountId: resolveDefau
|
|
|
42
45
|
hasImplicitDefaultAccount: (cfg) => Boolean((cfg.channels?.irc?.host?.trim() || process.env.IRC_HOST?.trim()) && (cfg.channels?.irc?.nick?.trim() || process.env.IRC_NICK?.trim()))
|
|
43
46
|
});
|
|
44
47
|
function resolvePassword(accountId, merged) {
|
|
48
|
+
const configPassword = resolveSecretInputString({
|
|
49
|
+
value: merged.password,
|
|
50
|
+
path: `channels.irc.accounts.${accountId}.password`,
|
|
51
|
+
mode: "inspect"
|
|
52
|
+
});
|
|
53
|
+
if (configPassword.status === "configured_unavailable") return {
|
|
54
|
+
password: "",
|
|
55
|
+
source: "config",
|
|
56
|
+
unavailable: true
|
|
57
|
+
};
|
|
45
58
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
46
59
|
const envPassword = process.env.IRC_PASSWORD?.trim();
|
|
47
60
|
if (envPassword) return {
|
|
@@ -51,7 +64,7 @@ function resolvePassword(accountId, merged) {
|
|
|
51
64
|
}
|
|
52
65
|
if (merged.passwordFile?.trim()) {
|
|
53
66
|
let diagnostic;
|
|
54
|
-
const
|
|
67
|
+
const password = tryReadSecretFileSync(merged.passwordFile, "IRC password file", {
|
|
55
68
|
rejectSymlink: true,
|
|
56
69
|
credentialDiagnostic: {
|
|
57
70
|
configPath: `channels.irc.accounts.${accountId}.passwordFile`,
|
|
@@ -60,8 +73,8 @@ function resolvePassword(accountId, merged) {
|
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
75
|
});
|
|
63
|
-
if (
|
|
64
|
-
password
|
|
76
|
+
if (password) return {
|
|
77
|
+
password,
|
|
65
78
|
source: "passwordFile"
|
|
66
79
|
};
|
|
67
80
|
return {
|
|
@@ -70,12 +83,8 @@ function resolvePassword(accountId, merged) {
|
|
|
70
83
|
diagnostic
|
|
71
84
|
};
|
|
72
85
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
path: `channels.irc.accounts.${accountId}.password`
|
|
76
|
-
});
|
|
77
|
-
if (configPassword) return {
|
|
78
|
-
password: configPassword,
|
|
86
|
+
if (configPassword.status === "available") return {
|
|
87
|
+
password: configPassword.value,
|
|
79
88
|
source: "config"
|
|
80
89
|
};
|
|
81
90
|
return {
|
|
@@ -85,23 +94,29 @@ function resolvePassword(accountId, merged) {
|
|
|
85
94
|
}
|
|
86
95
|
function resolveNickServConfig(accountId, nickserv) {
|
|
87
96
|
const base = nickserv ?? {};
|
|
97
|
+
const configPassword = resolveSecretInputString({
|
|
98
|
+
value: base.password,
|
|
99
|
+
path: `channels.irc.accounts.${accountId}.nickserv.password`,
|
|
100
|
+
mode: "inspect"
|
|
101
|
+
});
|
|
102
|
+
const unavailable = Boolean(configPassword.ref);
|
|
88
103
|
const envPassword = accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICKSERV_PASSWORD?.trim() : void 0;
|
|
89
104
|
const envRegisterEmail = accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICKSERV_REGISTER_EMAIL?.trim() : void 0;
|
|
90
105
|
const passwordFile = base.passwordFile?.trim();
|
|
91
|
-
let resolvedPassword
|
|
92
|
-
value: base.password,
|
|
93
|
-
path: `channels.irc.accounts.${accountId}.nickserv.password`
|
|
94
|
-
}) || envPassword || "";
|
|
106
|
+
let resolvedPassword;
|
|
95
107
|
let diagnostic;
|
|
96
|
-
if (!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
if (!unavailable) {
|
|
109
|
+
resolvedPassword = configPassword.value || envPassword;
|
|
110
|
+
if (!resolvedPassword && passwordFile) resolvedPassword = tryReadSecretFileSync(passwordFile, "IRC NickServ password file", {
|
|
111
|
+
rejectSymlink: true,
|
|
112
|
+
credentialDiagnostic: {
|
|
113
|
+
configPath: `channels.irc.accounts.${accountId}.nickserv.passwordFile`,
|
|
114
|
+
report: (value) => {
|
|
115
|
+
diagnostic = value;
|
|
116
|
+
}
|
|
102
117
|
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
105
120
|
return {
|
|
106
121
|
config: {
|
|
107
122
|
...base,
|
|
@@ -110,7 +125,8 @@ function resolveNickServConfig(accountId, nickserv) {
|
|
|
110
125
|
password: resolvedPassword || void 0,
|
|
111
126
|
registerEmail: base.registerEmail?.trim() || envRegisterEmail || void 0
|
|
112
127
|
},
|
|
113
|
-
diagnostic
|
|
128
|
+
diagnostic,
|
|
129
|
+
unavailable: base.enabled !== false && unavailable
|
|
114
130
|
};
|
|
115
131
|
}
|
|
116
132
|
function resolveIrcAccount(params) {
|
|
@@ -154,7 +170,7 @@ function resolveIrcAccount(params) {
|
|
|
154
170
|
realname,
|
|
155
171
|
password: passwordResolution.password,
|
|
156
172
|
passwordSource: passwordResolution.source,
|
|
157
|
-
tokenStatus: diagnostics.length > 0 ? "configured_unavailable" : passwordResolution.password || nickservResolution.config.password ? "available" : "missing",
|
|
173
|
+
tokenStatus: diagnostics.length > 0 || passwordResolution.unavailable || nickservResolution.unavailable ? "configured_unavailable" : passwordResolution.password || nickservResolution.config.password ? "available" : "missing",
|
|
158
174
|
...diagnostics.length > 0 ? { credentialDiagnostics: diagnostics } : {},
|
|
159
175
|
config
|
|
160
176
|
};
|
|
@@ -183,7 +199,7 @@ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAll
|
|
|
183
199
|
});
|
|
184
200
|
//#endregion
|
|
185
201
|
//#region extensions/irc/src/gateway.ts
|
|
186
|
-
const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-
|
|
202
|
+
const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-DZcQfOqp.js"));
|
|
187
203
|
async function startIrcGatewayAccount(ctx) {
|
|
188
204
|
const account = ctx.account;
|
|
189
205
|
const statusSink = createAccountStatusSink({
|
|
@@ -222,8 +238,158 @@ function stripIrcControlChars(value) {
|
|
|
222
238
|
return out;
|
|
223
239
|
}
|
|
224
240
|
//#endregion
|
|
225
|
-
//#region extensions/irc/src/
|
|
241
|
+
//#region extensions/irc/src/normalize.ts
|
|
226
242
|
const IRC_TARGET_PATTERN$1 = /^[^\s:]+$/u;
|
|
243
|
+
function isChannelTarget(target) {
|
|
244
|
+
return target.startsWith("#") || target.startsWith("&");
|
|
245
|
+
}
|
|
246
|
+
function normalizeIrcMessagingTarget(raw) {
|
|
247
|
+
const trimmed = raw.trim();
|
|
248
|
+
if (!trimmed) return;
|
|
249
|
+
let target = trimmed;
|
|
250
|
+
if (normalizeLowercaseStringOrEmpty(target).startsWith("irc:")) target = target.slice(4).trim();
|
|
251
|
+
if (normalizeLowercaseStringOrEmpty(target).startsWith("channel:")) {
|
|
252
|
+
target = target.slice(8).trim();
|
|
253
|
+
if (!target.startsWith("#") && !target.startsWith("&")) target = `#${target}`;
|
|
254
|
+
}
|
|
255
|
+
if (normalizeLowercaseStringOrEmpty(target).startsWith("user:")) target = target.slice(5).trim();
|
|
256
|
+
if (!target || !looksLikeIrcTargetId(target)) return;
|
|
257
|
+
return target;
|
|
258
|
+
}
|
|
259
|
+
function resolveIrcOutboundSessionRoute(params) {
|
|
260
|
+
const target = normalizeIrcMessagingTarget(params.target);
|
|
261
|
+
if (!target) return null;
|
|
262
|
+
const chatType = isChannelTarget(target) ? "group" : "direct";
|
|
263
|
+
return buildChannelOutboundSessionRoute({
|
|
264
|
+
cfg: params.cfg,
|
|
265
|
+
agentId: params.agentId,
|
|
266
|
+
channel: "irc",
|
|
267
|
+
accountId: params.accountId,
|
|
268
|
+
recipientSessionExact: chatType === "direct" ? "direct-alias" : false,
|
|
269
|
+
peer: {
|
|
270
|
+
kind: chatType,
|
|
271
|
+
id: target
|
|
272
|
+
},
|
|
273
|
+
chatType,
|
|
274
|
+
from: `irc:${target}`,
|
|
275
|
+
to: target
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function looksLikeIrcTargetId(raw) {
|
|
279
|
+
const trimmed = raw.trim();
|
|
280
|
+
if (!trimmed) return false;
|
|
281
|
+
if (hasIrcControlChars(trimmed)) return false;
|
|
282
|
+
return IRC_TARGET_PATTERN$1.test(trimmed);
|
|
283
|
+
}
|
|
284
|
+
function normalizeIrcAllowEntry(raw) {
|
|
285
|
+
let value = normalizeLowercaseStringOrEmpty(raw);
|
|
286
|
+
if (!value) return "";
|
|
287
|
+
if (value.startsWith("irc:")) value = value.slice(4);
|
|
288
|
+
if (value.startsWith("user:")) value = value.slice(5);
|
|
289
|
+
return value.trim();
|
|
290
|
+
}
|
|
291
|
+
function buildIrcAllowlistCandidates(message, params) {
|
|
292
|
+
const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
|
|
293
|
+
const user = normalizeOptionalLowercaseString(message.senderUser);
|
|
294
|
+
const host = normalizeOptionalLowercaseString(message.senderHost);
|
|
295
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
296
|
+
if (nick && params?.allowNameMatching === true) candidates.add(nick);
|
|
297
|
+
if (nick && user) candidates.add(`${nick}!${user}`);
|
|
298
|
+
if (nick && host) candidates.add(`${nick}@${host}`);
|
|
299
|
+
if (nick && user && host) candidates.add(`${nick}!${user}@${host}`);
|
|
300
|
+
return [...candidates];
|
|
301
|
+
}
|
|
302
|
+
const ircIngressIdentity = defineStableChannelIngressIdentity({
|
|
303
|
+
key: "irc-id",
|
|
304
|
+
authentication: "asserted",
|
|
305
|
+
normalizeEntry: normalizeIrcStableEntry,
|
|
306
|
+
normalizeSubject: normalizeLowercaseStringOrEmpty,
|
|
307
|
+
sensitivity: "pii",
|
|
308
|
+
aliases: [
|
|
309
|
+
{
|
|
310
|
+
key: "irc-id-nick-user",
|
|
311
|
+
kind: "stable-id",
|
|
312
|
+
normalizeEntry: normalizeIrcNickUserEntry,
|
|
313
|
+
normalizeSubject: normalizeLowercaseStringOrEmpty,
|
|
314
|
+
authentication: "mutable",
|
|
315
|
+
sensitivity: "pii"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
key: "irc-id-nick-host",
|
|
319
|
+
kind: "stable-id",
|
|
320
|
+
authentication: "asserted",
|
|
321
|
+
normalizeEntry: normalizeIrcNickHostEntry,
|
|
322
|
+
normalizeSubject: normalizeLowercaseStringOrEmpty,
|
|
323
|
+
sensitivity: "pii"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
key: "irc-nick",
|
|
327
|
+
kind: "plugin:irc-nick",
|
|
328
|
+
normalizeEntry: normalizeIrcNickEntry,
|
|
329
|
+
normalizeSubject: normalizeLowercaseStringOrEmpty,
|
|
330
|
+
authentication: "mutable",
|
|
331
|
+
sensitivity: "pii"
|
|
332
|
+
}
|
|
333
|
+
],
|
|
334
|
+
isWildcardEntry: (entry) => normalizeIrcAllowEntry(entry) === "*",
|
|
335
|
+
resolveEntryId: ({ entryIndex, fieldKey }) => `irc-entry-${entryIndex + 1}:${fieldKey === "irc-nick" ? "nick" : "id"}`
|
|
336
|
+
});
|
|
337
|
+
function isBareNick(value) {
|
|
338
|
+
return !value.includes("!") && !value.includes("@");
|
|
339
|
+
}
|
|
340
|
+
function hasVerifiedHost(value) {
|
|
341
|
+
return value.includes("@");
|
|
342
|
+
}
|
|
343
|
+
function isHostlessNickUser(value) {
|
|
344
|
+
return value.includes("!") && !value.includes("@");
|
|
345
|
+
}
|
|
346
|
+
function normalizeIrcStableEntry(value) {
|
|
347
|
+
const normalized = normalizeIrcAllowEntry(value);
|
|
348
|
+
if (!normalized.includes("!") || !hasVerifiedHost(normalized)) return null;
|
|
349
|
+
return normalized;
|
|
350
|
+
}
|
|
351
|
+
function normalizeIrcNickHostEntry(value) {
|
|
352
|
+
const normalized = normalizeIrcAllowEntry(value);
|
|
353
|
+
return !normalized.includes("!") && hasVerifiedHost(normalized) ? normalized : null;
|
|
354
|
+
}
|
|
355
|
+
function normalizeIrcNickUserEntry(value) {
|
|
356
|
+
const normalized = normalizeIrcAllowEntry(value);
|
|
357
|
+
if (!normalized || normalized === "*" || !isHostlessNickUser(normalized)) return null;
|
|
358
|
+
return normalized;
|
|
359
|
+
}
|
|
360
|
+
function normalizeIrcNickEntry(value) {
|
|
361
|
+
const normalized = normalizeIrcAllowEntry(value);
|
|
362
|
+
if (!normalized || normalized === "*" || !isBareNick(normalized)) return null;
|
|
363
|
+
return normalized;
|
|
364
|
+
}
|
|
365
|
+
function createIrcIngressSubject(message) {
|
|
366
|
+
const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
|
|
367
|
+
const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
|
|
368
|
+
const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
|
|
369
|
+
return {
|
|
370
|
+
stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
|
|
371
|
+
aliases: {
|
|
372
|
+
"irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
|
|
373
|
+
"irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!") && candidate.includes("@")),
|
|
374
|
+
"irc-nick": nick
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region extensions/irc/src/outbound-base.ts
|
|
380
|
+
function sanitizeIrcAssistantText(text) {
|
|
381
|
+
return sanitizeForPlainText(sanitizeAssistantVisibleText(text));
|
|
382
|
+
}
|
|
383
|
+
const ircOutboundBaseAdapter = {
|
|
384
|
+
deliveryMode: "direct",
|
|
385
|
+
chunker: chunkTextForOutbound,
|
|
386
|
+
chunkerMode: "markdown",
|
|
387
|
+
textChunkLimit: 350,
|
|
388
|
+
sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
|
|
389
|
+
};
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region extensions/irc/src/protocol.ts
|
|
392
|
+
const IRC_TARGET_PATTERN = /^[^\s:]+$/u;
|
|
227
393
|
function parseIrcLine(line) {
|
|
228
394
|
const raw = line.replace(/[\r\n]+/g, "").trim();
|
|
229
395
|
if (!raw) return null;
|
|
@@ -302,7 +468,7 @@ function sanitizeIrcTarget(raw) {
|
|
|
302
468
|
if (!decoded) throw new Error("IRC target is required");
|
|
303
469
|
if (decoded !== decoded.trim()) throw new Error(`Invalid IRC target: ${raw}`);
|
|
304
470
|
if (hasIrcControlChars(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
|
|
305
|
-
if (!IRC_TARGET_PATTERN
|
|
471
|
+
if (!IRC_TARGET_PATTERN.test(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
|
|
306
472
|
return decoded;
|
|
307
473
|
}
|
|
308
474
|
function makeIrcMessageId() {
|
|
@@ -613,6 +779,8 @@ async function connectIrcClient(options) {
|
|
|
613
779
|
//#endregion
|
|
614
780
|
//#region extensions/irc/src/connect-options.ts
|
|
615
781
|
function buildIrcConnectOptions(account, overrides = {}) {
|
|
782
|
+
assertSecretOwnerAvailable("account", `irc:${account.accountId}`);
|
|
783
|
+
if (account.tokenStatus === "configured_unavailable") throw new Error(`IRC credentials for account "${account.accountId}" are configured but unavailable.`);
|
|
616
784
|
return {
|
|
617
785
|
host: account.host,
|
|
618
786
|
port: account.port,
|
|
@@ -632,68 +800,6 @@ function buildIrcConnectOptions(account, overrides = {}) {
|
|
|
632
800
|
};
|
|
633
801
|
}
|
|
634
802
|
//#endregion
|
|
635
|
-
//#region extensions/irc/src/normalize.ts
|
|
636
|
-
const IRC_TARGET_PATTERN = /^[^\s:]+$/u;
|
|
637
|
-
function isChannelTarget(target) {
|
|
638
|
-
return target.startsWith("#") || target.startsWith("&");
|
|
639
|
-
}
|
|
640
|
-
function normalizeIrcMessagingTarget(raw) {
|
|
641
|
-
const trimmed = raw.trim();
|
|
642
|
-
if (!trimmed) return;
|
|
643
|
-
let target = trimmed;
|
|
644
|
-
if (normalizeLowercaseStringOrEmpty(target).startsWith("irc:")) target = target.slice(4).trim();
|
|
645
|
-
if (normalizeLowercaseStringOrEmpty(target).startsWith("channel:")) {
|
|
646
|
-
target = target.slice(8).trim();
|
|
647
|
-
if (!target.startsWith("#") && !target.startsWith("&")) target = `#${target}`;
|
|
648
|
-
}
|
|
649
|
-
if (normalizeLowercaseStringOrEmpty(target).startsWith("user:")) target = target.slice(5).trim();
|
|
650
|
-
if (!target || !looksLikeIrcTargetId(target)) return;
|
|
651
|
-
return target;
|
|
652
|
-
}
|
|
653
|
-
function resolveIrcOutboundSessionRoute(params) {
|
|
654
|
-
const target = normalizeIrcMessagingTarget(params.target);
|
|
655
|
-
if (!target) return null;
|
|
656
|
-
const chatType = isChannelTarget(target) ? "group" : "direct";
|
|
657
|
-
return buildChannelOutboundSessionRoute({
|
|
658
|
-
cfg: params.cfg,
|
|
659
|
-
agentId: params.agentId,
|
|
660
|
-
channel: "irc",
|
|
661
|
-
accountId: params.accountId,
|
|
662
|
-
recipientSessionExact: chatType === "direct" ? "direct-alias" : false,
|
|
663
|
-
peer: {
|
|
664
|
-
kind: chatType,
|
|
665
|
-
id: target
|
|
666
|
-
},
|
|
667
|
-
chatType,
|
|
668
|
-
from: `irc:${target}`,
|
|
669
|
-
to: target
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
function looksLikeIrcTargetId(raw) {
|
|
673
|
-
const trimmed = raw.trim();
|
|
674
|
-
if (!trimmed) return false;
|
|
675
|
-
if (hasIrcControlChars(trimmed)) return false;
|
|
676
|
-
return IRC_TARGET_PATTERN.test(trimmed);
|
|
677
|
-
}
|
|
678
|
-
function normalizeIrcAllowEntry(raw) {
|
|
679
|
-
let value = normalizeLowercaseStringOrEmpty(raw);
|
|
680
|
-
if (!value) return "";
|
|
681
|
-
if (value.startsWith("irc:")) value = value.slice(4);
|
|
682
|
-
if (value.startsWith("user:")) value = value.slice(5);
|
|
683
|
-
return value.trim();
|
|
684
|
-
}
|
|
685
|
-
function buildIrcAllowlistCandidates(message, params) {
|
|
686
|
-
const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
|
|
687
|
-
const user = normalizeOptionalLowercaseString(message.senderUser);
|
|
688
|
-
const host = normalizeOptionalLowercaseString(message.senderHost);
|
|
689
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
690
|
-
if (nick && params?.allowNameMatching === true) candidates.add(nick);
|
|
691
|
-
if (nick && user) candidates.add(`${nick}!${user}`);
|
|
692
|
-
if (nick && host) candidates.add(`${nick}@${host}`);
|
|
693
|
-
if (nick && user && host) candidates.add(`${nick}!${user}@${host}`);
|
|
694
|
-
return [...candidates];
|
|
695
|
-
}
|
|
696
|
-
//#endregion
|
|
697
803
|
//#region extensions/irc/src/send.ts
|
|
698
804
|
function recordIrcOutboundActivity(accountId) {
|
|
699
805
|
try {
|
|
@@ -713,7 +819,7 @@ function resolveTarget(to, opts) {
|
|
|
713
819
|
if (fromOpt) return fromOpt;
|
|
714
820
|
throw new Error(`Invalid IRC target: ${to}`);
|
|
715
821
|
}
|
|
716
|
-
async function
|
|
822
|
+
async function sendIrcMessages(to, messages, opts, onDeliveryResult) {
|
|
717
823
|
const cfg = requireRuntimeConfig(opts.cfg, "IRC send");
|
|
718
824
|
const account = resolveIrcAccount({
|
|
719
825
|
cfg,
|
|
@@ -726,35 +832,108 @@ async function sendMessageIrc(to, text, opts) {
|
|
|
726
832
|
channel: "irc",
|
|
727
833
|
accountId: account.accountId
|
|
728
834
|
});
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
835
|
+
const preparedMessages = messages.map((message) => {
|
|
836
|
+
const prepared = stripMarkdown(convertMarkdownTables(message.text.trim(), tableMode));
|
|
837
|
+
if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
|
|
838
|
+
return {
|
|
839
|
+
payload: message.replyTo ? `${prepared}\n\n[reply:${message.replyTo}]` : prepared,
|
|
840
|
+
replyTo: message.replyTo
|
|
841
|
+
};
|
|
842
|
+
});
|
|
843
|
+
if (preparedMessages.length === 0) return [];
|
|
844
|
+
opts.abortSignal?.throwIfAborted();
|
|
845
|
+
let transient;
|
|
846
|
+
const client = opts.client?.isReady() ? opts.client : transient = await connectIrcClient(buildIrcConnectOptions(account, {
|
|
847
|
+
connectTimeoutMs: 12e3,
|
|
848
|
+
abortSignal: opts.abortSignal
|
|
849
|
+
}));
|
|
850
|
+
const results = [];
|
|
851
|
+
try {
|
|
852
|
+
opts.abortSignal?.throwIfAborted();
|
|
853
|
+
if (transient && (target.startsWith("#") || target.startsWith("&"))) client.join(target);
|
|
854
|
+
for (const message of preparedMessages) {
|
|
855
|
+
opts.abortSignal?.throwIfAborted();
|
|
856
|
+
if (!client.isReady()) throw new Error("IRC connection closed before send");
|
|
857
|
+
await opts.onPlatformSendDispatch?.();
|
|
858
|
+
opts.abortSignal?.throwIfAborted();
|
|
859
|
+
if (!client.isReady()) throw new Error("IRC connection closed before send");
|
|
860
|
+
client.sendPrivmsg(target, message.payload);
|
|
861
|
+
recordIrcOutboundActivity(account.accountId);
|
|
862
|
+
const messageId = makeIrcMessageId();
|
|
863
|
+
const result = {
|
|
748
864
|
messageId,
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
865
|
+
target,
|
|
866
|
+
receipt: createMessageReceiptFromOutboundResults({
|
|
867
|
+
results: [{
|
|
868
|
+
channel: "irc",
|
|
869
|
+
messageId,
|
|
870
|
+
conversationId: target
|
|
871
|
+
}],
|
|
872
|
+
kind: "text",
|
|
873
|
+
...message.replyTo ? { replyToId: message.replyTo } : {}
|
|
874
|
+
})
|
|
875
|
+
};
|
|
876
|
+
results.push(result);
|
|
877
|
+
await onDeliveryResult?.(result);
|
|
878
|
+
}
|
|
879
|
+
return results;
|
|
880
|
+
} finally {
|
|
881
|
+
transient?.quit("sent");
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
async function sendMessageIrc(to, text, opts) {
|
|
885
|
+
const result = (await sendIrcMessages(to, [{
|
|
886
|
+
text,
|
|
887
|
+
...opts.replyTo ? { replyTo: opts.replyTo } : {}
|
|
888
|
+
}], opts))[0];
|
|
889
|
+
if (!result) throw new Error("Message must be non-empty for IRC sends");
|
|
890
|
+
return result;
|
|
755
891
|
}
|
|
756
892
|
//#endregion
|
|
757
893
|
//#region extensions/irc/src/message-adapter.ts
|
|
894
|
+
function toIrcMessageResult({ target, ...result }) {
|
|
895
|
+
return {
|
|
896
|
+
...result,
|
|
897
|
+
target: {
|
|
898
|
+
kind: "conversation",
|
|
899
|
+
id: target
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
async function sendIrcMessage(...args) {
|
|
904
|
+
return toIrcMessageResult(await sendMessageIrc(...args));
|
|
905
|
+
}
|
|
906
|
+
const sendFormattedIrcText = async (ctx) => {
|
|
907
|
+
const { chunkMarkdownTextWithMode, resolveChunkMode, resolveTextChunkLimit } = await import("openclaw/plugin-sdk/reply-chunking");
|
|
908
|
+
const accountId = ctx.accountId ?? void 0;
|
|
909
|
+
const textLimit = ctx.formatting?.textLimit ?? resolveTextChunkLimit(ctx.cfg, "irc", accountId, { fallbackLimit: ircOutboundBaseAdapter.textChunkLimit });
|
|
910
|
+
const chunkMode = ctx.formatting?.chunkMode ?? resolveChunkMode(ctx.cfg, "irc", accountId);
|
|
911
|
+
const chunkText = (text) => ctx.formatting ? ircOutboundBaseAdapter.chunker(text, textLimit, { formatting: ctx.formatting }) : ircOutboundBaseAdapter.chunker(text, textLimit);
|
|
912
|
+
let chunks;
|
|
913
|
+
if (chunkMode === "newline") {
|
|
914
|
+
const blocks = chunkMarkdownTextWithMode(ctx.text, textLimit, chunkMode);
|
|
915
|
+
if (blocks.length === 0 && ctx.text) blocks.push(ctx.text);
|
|
916
|
+
chunks = blocks.flatMap((block) => {
|
|
917
|
+
const blockChunks = chunkText(block);
|
|
918
|
+
return blockChunks.length === 0 && block ? [block] : blockChunks;
|
|
919
|
+
});
|
|
920
|
+
} else chunks = chunkText(ctx.text);
|
|
921
|
+
const nextReplyToId = createReplyToFanout(ctx);
|
|
922
|
+
return (await sendIrcMessages(ctx.to, chunks.map((text) => {
|
|
923
|
+
const replyTo = nextReplyToId();
|
|
924
|
+
return replyTo ? {
|
|
925
|
+
text,
|
|
926
|
+
replyTo
|
|
927
|
+
} : { text };
|
|
928
|
+
}), {
|
|
929
|
+
cfg: ctx.cfg,
|
|
930
|
+
accountId,
|
|
931
|
+
abortSignal: ctx.abortSignal,
|
|
932
|
+
onPlatformSendDispatch: ctx.onPlatformSendDispatch
|
|
933
|
+
}, async (result) => {
|
|
934
|
+
await ctx.onDeliveryResult?.(attachChannelToResult("irc", toIrcMessageResult(result)));
|
|
935
|
+
})).map((result) => attachChannelToResult("irc", toIrcMessageResult(result)));
|
|
936
|
+
};
|
|
758
937
|
const ircMessageAdapter = defineChannelMessageAdapter({
|
|
759
938
|
id: "irc",
|
|
760
939
|
durableFinal: { capabilities: {
|
|
@@ -763,12 +942,12 @@ const ircMessageAdapter = defineChannelMessageAdapter({
|
|
|
763
942
|
replyTo: true
|
|
764
943
|
} },
|
|
765
944
|
send: {
|
|
766
|
-
text: async ({ cfg, to, text, accountId, replyToId }) => await
|
|
945
|
+
text: async ({ cfg, to, text, accountId, replyToId }) => await sendIrcMessage(to, text, {
|
|
767
946
|
cfg,
|
|
768
947
|
accountId: accountId ?? void 0,
|
|
769
948
|
replyTo: replyToId ?? void 0
|
|
770
949
|
}),
|
|
771
|
-
media: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) => await
|
|
950
|
+
media: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) => await sendIrcMessage(to, mediaUrl ? `${text}\n\nAttachment: ${mediaUrl}` : text, {
|
|
772
951
|
cfg,
|
|
773
952
|
accountId: accountId ?? void 0,
|
|
774
953
|
replyTo: replyToId ?? void 0
|
|
@@ -776,18 +955,6 @@ const ircMessageAdapter = defineChannelMessageAdapter({
|
|
|
776
955
|
}
|
|
777
956
|
});
|
|
778
957
|
//#endregion
|
|
779
|
-
//#region extensions/irc/src/outbound-base.ts
|
|
780
|
-
function sanitizeIrcAssistantText(text) {
|
|
781
|
-
return sanitizeForPlainText(sanitizeAssistantVisibleText(text));
|
|
782
|
-
}
|
|
783
|
-
const ircOutboundBaseAdapter = {
|
|
784
|
-
deliveryMode: "direct",
|
|
785
|
-
chunker: chunkTextForOutbound,
|
|
786
|
-
chunkerMode: "markdown",
|
|
787
|
-
textChunkLimit: 350,
|
|
788
|
-
sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
|
|
789
|
-
};
|
|
790
|
-
//#endregion
|
|
791
958
|
//#region extensions/irc/src/policy.ts
|
|
792
959
|
function resolveIrcGroupScope(params) {
|
|
793
960
|
const { "*": wildcard, ...groups } = params.groups ?? {};
|
|
@@ -1399,7 +1566,7 @@ const meta = {
|
|
|
1399
1566
|
systemImage: "number",
|
|
1400
1567
|
markdownCapable: true
|
|
1401
1568
|
};
|
|
1402
|
-
const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-
|
|
1569
|
+
const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-DZcQfOqp.js"));
|
|
1403
1570
|
function normalizePairingTarget(raw) {
|
|
1404
1571
|
const normalized = normalizeIrcAllowEntry(raw);
|
|
1405
1572
|
if (!normalized) return "";
|
|
@@ -1453,9 +1620,10 @@ const resolveIrcDmPolicy = createScopedDmSecurityResolver({
|
|
|
1453
1620
|
resolvePolicy: (account) => account.config.dmPolicy,
|
|
1454
1621
|
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
1455
1622
|
policyPathSuffix: "dmPolicy",
|
|
1623
|
+
classifyEntryAuthentication: identityEntryAuthenticationClassifier(ircIngressIdentity),
|
|
1456
1624
|
normalizeEntry: (raw) => normalizeIrcAllowEntry(raw)
|
|
1457
1625
|
});
|
|
1458
|
-
const
|
|
1626
|
+
const collectIrcGroupPolicyWarnings = createAllowlistProviderOpenWarningCollector({
|
|
1459
1627
|
providerConfigPresent: (cfg) => cfg.channels?.irc !== void 0,
|
|
1460
1628
|
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
|
1461
1629
|
buildOpenWarning: {
|
|
@@ -1463,7 +1631,19 @@ const collectIrcSecurityWarnings = composeAccountWarningCollectors(createAllowli
|
|
|
1463
1631
|
openBehavior: "allows all channels and senders (mention-gated)",
|
|
1464
1632
|
remediation: "Prefer channels.irc.groupPolicy=\"allowlist\" with channels.irc.groups"
|
|
1465
1633
|
}
|
|
1466
|
-
})
|
|
1634
|
+
});
|
|
1635
|
+
const collectIrcOpenGroupFindings = createConditionalWarningCollector.findings({
|
|
1636
|
+
collectWarnings: collectIrcGroupPolicyWarnings,
|
|
1637
|
+
checkId: "channels.irc.groups.open",
|
|
1638
|
+
severity: "critical",
|
|
1639
|
+
title: "IRC security warning"
|
|
1640
|
+
});
|
|
1641
|
+
const collectIrcSecurityWarnings = (params) => [
|
|
1642
|
+
...collectIrcOpenGroupFindings(params),
|
|
1643
|
+
...!params.account.config.tls ? ["- IRC TLS is disabled (channels.irc.tls=false); traffic and credentials are plaintext."] : [],
|
|
1644
|
+
...params.account.config.nickserv?.register ? ["- IRC NickServ registration is enabled (channels.irc.nickserv.register=true); this sends \"REGISTER\" on every connect. Disable after first successful registration."] : [],
|
|
1645
|
+
...params.account.config.nickserv?.register && !params.account.config.nickserv.password?.trim() ? ["- IRC NickServ registration is enabled but no NickServ password is resolved; set channels.irc.nickserv.password, channels.irc.nickserv.passwordFile, or IRC_NICKSERV_PASSWORD."] : []
|
|
1646
|
+
];
|
|
1467
1647
|
const ircPlugin = createChatChannelPlugin({
|
|
1468
1648
|
base: {
|
|
1469
1649
|
id: "irc",
|
|
@@ -1630,7 +1810,10 @@ const ircPlugin = createChatChannelPlugin({
|
|
|
1630
1810
|
collectWarnings: collectIrcSecurityWarnings
|
|
1631
1811
|
},
|
|
1632
1812
|
outbound: {
|
|
1633
|
-
base:
|
|
1813
|
+
base: {
|
|
1814
|
+
...ircOutboundBaseAdapter,
|
|
1815
|
+
sendFormattedText: sendFormattedIrcText
|
|
1816
|
+
},
|
|
1634
1817
|
attachedResults: {
|
|
1635
1818
|
channel: "irc",
|
|
1636
1819
|
sendText: ({ onDeliveryResult: _onDeliveryResult, ...ctx }) => ircMessageAdapter.send.text(ctx),
|
|
@@ -1642,4 +1825,4 @@ const ircPlugin = createChatChannelPlugin({
|
|
|
1642
1825
|
}
|
|
1643
1826
|
});
|
|
1644
1827
|
//#endregion
|
|
1645
|
-
export {
|
|
1828
|
+
export { listIrcAccountIds as _, resolveIrcGroupRequireMention as a, connectIrcClient as c, sanitizeIrcAssistantText as d, createIrcIngressSubject as f, listEnabledIrcAccounts as g, normalizeIrcAllowEntry as h, resolveIrcGroupMatch as i, parseIrcLine as l, isChannelTarget as m, ircSetupWizard as n, sendMessageIrc as o, ircIngressIdentity as p, ircSetupAdapter as r, buildIrcConnectOptions as s, ircPlugin as t, parseIrcPrefix as u, resolveDefaultIrcAccountId as v, resolveIrcAccount as y };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as IrcChannelConfigSchema } from "./config-schema-
|
|
1
|
+
import { t as IrcChannelConfigSchema } from "./config-schema-CpB6AaUh.js";
|
|
2
2
|
export { IrcChannelConfigSchema };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as ircPlugin } from "./channel-
|
|
1
|
+
import { t as ircPlugin } from "./channel-BxbQEBnS.js";
|
|
2
2
|
export { ircPlugin };
|
|
@@ -1,51 +1,18 @@
|
|
|
1
|
-
import { a as resolveIrcGroupRequireMention, c as
|
|
1
|
+
import { a as resolveIrcGroupRequireMention, c as connectIrcClient, d as sanitizeIrcAssistantText, f as createIrcIngressSubject, h as normalizeIrcAllowEntry, i as resolveIrcGroupMatch, l as parseIrcLine, m as isChannelTarget, o as sendMessageIrc, p as ircIngressIdentity, s as buildIrcConnectOptions, u as parseIrcPrefix, y as resolveIrcAccount } from "./channel-BxbQEBnS.js";
|
|
2
2
|
import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
|
|
3
|
+
import { channelIngressRoutes, createChannelIngressResolver } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
3
4
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
5
|
import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
5
6
|
import { randomUUID } from "node:crypto";
|
|
6
7
|
import { resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
|
|
7
8
|
import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
8
9
|
import { logInboundDrop, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
9
|
-
import { channelIngressRoutes, createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
10
10
|
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
11
11
|
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
|
12
12
|
import { deliverFormattedTextWithAttachments } from "openclaw/plugin-sdk/reply-payload";
|
|
13
13
|
import { GROUP_POLICY_BLOCKED_LABEL, resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
14
14
|
//#region extensions/irc/src/inbound.ts
|
|
15
15
|
const CHANNEL_ID = "irc";
|
|
16
|
-
const ircIngressIdentity = defineStableChannelIngressIdentity({
|
|
17
|
-
key: "irc-id",
|
|
18
|
-
normalizeEntry: normalizeIrcStableEntry,
|
|
19
|
-
normalizeSubject: normalizeLowercaseStringOrEmpty,
|
|
20
|
-
sensitivity: "pii",
|
|
21
|
-
aliases: [
|
|
22
|
-
{
|
|
23
|
-
key: "irc-id-nick-user",
|
|
24
|
-
kind: "stable-id",
|
|
25
|
-
normalizeEntry: normalizeIrcNickUserEntry,
|
|
26
|
-
normalizeSubject: normalizeLowercaseStringOrEmpty,
|
|
27
|
-
dangerous: true,
|
|
28
|
-
sensitivity: "pii"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
key: "irc-id-nick-host",
|
|
32
|
-
kind: "stable-id",
|
|
33
|
-
normalizeEntry: () => null,
|
|
34
|
-
normalizeSubject: normalizeLowercaseStringOrEmpty,
|
|
35
|
-
sensitivity: "pii"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
key: "irc-nick",
|
|
39
|
-
kind: "plugin:irc-nick",
|
|
40
|
-
normalizeEntry: normalizeIrcNickEntry,
|
|
41
|
-
normalizeSubject: normalizeLowercaseStringOrEmpty,
|
|
42
|
-
dangerous: true,
|
|
43
|
-
sensitivity: "pii"
|
|
44
|
-
}
|
|
45
|
-
],
|
|
46
|
-
isWildcardEntry: (entry) => normalizeIrcAllowEntry(entry) === "*",
|
|
47
|
-
resolveEntryId: ({ entryIndex, fieldKey }) => `irc-entry-${entryIndex + 1}:${fieldKey === "irc-nick" ? "nick" : "id"}`
|
|
48
|
-
});
|
|
49
16
|
const escapeIrcRegexLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
17
|
const IRC_NICK_CHARACTER = String.raw`[A-Za-z0-9_\-\[\]\\\x60^{}|~]`;
|
|
51
18
|
const IRC_RFC1459_CASE_EQUIVALENTS = /* @__PURE__ */ new Map([
|
|
@@ -64,46 +31,9 @@ function buildIrcNickMentionPattern(value) {
|
|
|
64
31
|
return equivalent ? `[${escapeIrcRegexLiteral(character)}${escapeIrcRegexLiteral(equivalent)}]` : escapeIrcRegexLiteral(character);
|
|
65
32
|
}).join("");
|
|
66
33
|
}
|
|
67
|
-
function isBareNick(value) {
|
|
68
|
-
return !value.includes("!") && !value.includes("@");
|
|
69
|
-
}
|
|
70
|
-
function hasVerifiedHost(value) {
|
|
71
|
-
return value.includes("@");
|
|
72
|
-
}
|
|
73
|
-
function isHostlessNickUser(value) {
|
|
74
|
-
return value.includes("!") && !value.includes("@");
|
|
75
|
-
}
|
|
76
|
-
function normalizeIrcStableEntry(value) {
|
|
77
|
-
const normalized = normalizeIrcAllowEntry(value);
|
|
78
|
-
if (!normalized || normalized === "*" || !hasVerifiedHost(normalized)) return null;
|
|
79
|
-
return normalized;
|
|
80
|
-
}
|
|
81
|
-
function normalizeIrcNickUserEntry(value) {
|
|
82
|
-
const normalized = normalizeIrcAllowEntry(value);
|
|
83
|
-
if (!normalized || normalized === "*" || !isHostlessNickUser(normalized)) return null;
|
|
84
|
-
return normalized;
|
|
85
|
-
}
|
|
86
|
-
function normalizeIrcNickEntry(value) {
|
|
87
|
-
const normalized = normalizeIrcAllowEntry(value);
|
|
88
|
-
if (!normalized || normalized === "*" || !isBareNick(normalized)) return null;
|
|
89
|
-
return normalized;
|
|
90
|
-
}
|
|
91
34
|
function hasEntries(entries) {
|
|
92
35
|
return normalizeStringEntries(entries).some((entry) => normalizeIrcAllowEntry(entry));
|
|
93
36
|
}
|
|
94
|
-
function createIrcIngressSubject(message) {
|
|
95
|
-
const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
|
|
96
|
-
const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
|
|
97
|
-
const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
|
|
98
|
-
return {
|
|
99
|
-
stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
|
|
100
|
-
aliases: {
|
|
101
|
-
"irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
|
|
102
|
-
"irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!") && candidate.includes("@")),
|
|
103
|
-
"irc-nick": nick
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
37
|
function routeDescriptorsForIrcGroup(params) {
|
|
108
38
|
if (!params.isGroup) return [];
|
|
109
39
|
return channelIngressRoutes(params.groupPolicy === "allowlist" && {
|
|
@@ -455,14 +385,16 @@ function decodeIrcIngressPayload(payload, claimedId) {
|
|
|
455
385
|
}
|
|
456
386
|
function inspectIrcIngress(raw, phase) {
|
|
457
387
|
try {
|
|
388
|
+
const projection = inspectRawPrivmsg(raw.body.rawLine);
|
|
389
|
+
if (phase === "claim") raw.claimedProjection = projection;
|
|
458
390
|
return {
|
|
459
|
-
eventId: raw.eventId,
|
|
460
|
-
laneKey:
|
|
391
|
+
eventId: raw.body.eventId,
|
|
392
|
+
laneKey: projection.laneKey
|
|
461
393
|
};
|
|
462
394
|
} catch (error) {
|
|
463
395
|
if (phase === "admission" && error instanceof IrcIngressPayloadError) return {
|
|
464
|
-
eventId: raw.eventId,
|
|
465
|
-
laneKey: `invalid:${raw.eventId}`
|
|
396
|
+
eventId: raw.body.eventId,
|
|
397
|
+
laneKey: `invalid:${raw.body.eventId}`
|
|
466
398
|
};
|
|
467
399
|
throw error;
|
|
468
400
|
}
|
|
@@ -479,8 +411,8 @@ function createIrcIngressMonitor(options) {
|
|
|
479
411
|
inspect: (raw, context) => inspectIrcIngress(raw, context.phase),
|
|
480
412
|
payload: {
|
|
481
413
|
version: IRC_INGRESS_PAYLOAD_VERSION,
|
|
482
|
-
serialize: (raw) => raw,
|
|
483
|
-
deserialize: (body) => body,
|
|
414
|
+
serialize: (raw) => raw.body,
|
|
415
|
+
deserialize: (body) => ({ body }),
|
|
484
416
|
encode: ({ body }) => ({
|
|
485
417
|
version: IRC_INGRESS_PAYLOAD_VERSION,
|
|
486
418
|
...body
|
|
@@ -490,13 +422,13 @@ function createIrcIngressMonitor(options) {
|
|
|
490
422
|
},
|
|
491
423
|
deliver: (raw, lifecycle, claim) => {
|
|
492
424
|
const message = {
|
|
493
|
-
...
|
|
425
|
+
...raw.claimedProjection.message,
|
|
494
426
|
messageId: claim.id,
|
|
495
|
-
timestamp: raw.receivedAt
|
|
427
|
+
timestamp: raw.body.receivedAt
|
|
496
428
|
};
|
|
497
429
|
return options.dispatch(message, lifecycle, {
|
|
498
|
-
connectedNick: raw.connectedNick.trim(),
|
|
499
|
-
connectionEpoch: raw.connectionEpoch.trim()
|
|
430
|
+
connectedNick: raw.body.connectedNick.trim(),
|
|
431
|
+
connectionEpoch: raw.body.connectionEpoch.trim()
|
|
500
432
|
});
|
|
501
433
|
},
|
|
502
434
|
pollIntervalMs: options.pollIntervalMs ?? IRC_INGRESS_POLL_INTERVAL_MS,
|
|
@@ -526,13 +458,13 @@ function createIrcIngressMonitor(options) {
|
|
|
526
458
|
const receivedAt = Date.now();
|
|
527
459
|
const normalizedNick = connectedNick.trim();
|
|
528
460
|
if (!normalizedNick) return Promise.reject(/* @__PURE__ */ new Error("IRC ingress connected nickname is required."));
|
|
529
|
-
return monitor.admit({
|
|
461
|
+
return monitor.admit({ body: {
|
|
530
462
|
eventId,
|
|
531
463
|
rawLine,
|
|
532
464
|
receivedAt,
|
|
533
465
|
connectionEpoch: epoch,
|
|
534
466
|
connectedNick: normalizedNick
|
|
535
|
-
}, { receivedAt }).then(() => void 0);
|
|
467
|
+
} }, { receivedAt }).then(() => void 0);
|
|
536
468
|
}
|
|
537
469
|
};
|
|
538
470
|
},
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createChannelConfigUiHints } from "openclaw/plugin-sdk/channel-core";
|
|
2
|
-
import { ChannelGroupEntrySchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, buildChannelConfigSchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
|
|
2
|
+
import { ChannelGroupEntrySchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, ReplyToModeSchema, buildChannelConfigSchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region extensions/irc/src/config-ui-hints.ts
|
|
5
5
|
const ircChannelConfigUiHints = {
|
|
@@ -60,6 +60,7 @@ const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannel
|
|
|
60
60
|
name: z.string().optional(),
|
|
61
61
|
enabled: z.boolean().optional(),
|
|
62
62
|
configWrites: z.boolean().optional(),
|
|
63
|
+
replyToMode: ReplyToModeSchema.optional(),
|
|
63
64
|
dangerouslyAllowNameMatching: z.boolean().optional(),
|
|
64
65
|
host: z.string().optional(),
|
|
65
66
|
port: z.number().int().min(1).max(65535).optional(),
|
|
@@ -76,7 +77,6 @@ const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannel
|
|
|
76
77
|
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
|
77
78
|
groups: z.record(z.string(), ChannelGroupEntrySchema.optional()).optional(),
|
|
78
79
|
channels: z.array(z.string()).optional(),
|
|
79
|
-
mentionPatterns: z.array(z.string()).optional(),
|
|
80
80
|
markdown: MarkdownConfigSchema,
|
|
81
81
|
...ReplyRuntimeConfigSchemaShape
|
|
82
82
|
}).strict(), {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
|
2
|
-
//#region extensions/irc/doctor-
|
|
1
|
+
import { asObjectRecord, defineChannelAliasMigration, stripRetiredChannelKeys } from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
|
2
|
+
//#region extensions/irc/config-doctor-api.ts
|
|
3
3
|
const streamingAliasMigration = defineChannelAliasMigration({
|
|
4
4
|
channelId: "irc",
|
|
5
5
|
streaming: {
|
|
@@ -8,9 +8,39 @@ const streamingAliasMigration = defineChannelAliasMigration({
|
|
|
8
8
|
},
|
|
9
9
|
accountStreamingReplacesRoot: true
|
|
10
10
|
});
|
|
11
|
-
const
|
|
11
|
+
const RETIRED_IRC_KEYS = /* @__PURE__ */ new Set(["mentionPatterns"]);
|
|
12
|
+
const legacyConfigRules = [
|
|
13
|
+
...streamingAliasMigration.legacyConfigRules,
|
|
14
|
+
{
|
|
15
|
+
path: ["channels", "irc"],
|
|
16
|
+
match: (value) => Object.hasOwn(asObjectRecord(value) ?? {}, "mentionPatterns"),
|
|
17
|
+
message: "channels.irc.mentionPatterns was accepted but never read; configure mention patterns with messages.groupChat.mentionPatterns. Run \"openclaw doctor --fix\"."
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
path: [
|
|
21
|
+
"channels",
|
|
22
|
+
"irc",
|
|
23
|
+
"accounts"
|
|
24
|
+
],
|
|
25
|
+
match: (value) => Object.values(asObjectRecord(value) ?? {}).some((account) => Object.hasOwn(asObjectRecord(account) ?? {}, "mentionPatterns")),
|
|
26
|
+
message: "channels.irc.accounts.<id>.mentionPatterns was accepted but never read; configure mention patterns with messages.groupChat.mentionPatterns. Run \"openclaw doctor --fix\"."
|
|
27
|
+
}
|
|
28
|
+
];
|
|
12
29
|
function normalizeCompatibilityConfig({ cfg }) {
|
|
13
|
-
|
|
30
|
+
const aliases = streamingAliasMigration.normalizeChannelConfig({ cfg });
|
|
31
|
+
const changes = [...aliases.changes];
|
|
32
|
+
return {
|
|
33
|
+
config: stripRetiredChannelKeys({
|
|
34
|
+
cfg: aliases.config,
|
|
35
|
+
channelId: "irc",
|
|
36
|
+
keys: RETIRED_IRC_KEYS,
|
|
37
|
+
scope: "root-and-accounts",
|
|
38
|
+
onRemove: (removed) => {
|
|
39
|
+
changes.push(`Removed ${removed.pathPrefix}.${removed.key}; it was never read.`);
|
|
40
|
+
}
|
|
41
|
+
}).config,
|
|
42
|
+
changes
|
|
43
|
+
};
|
|
14
44
|
}
|
|
15
45
|
//#endregion
|
|
16
46
|
export { legacyConfigRules, normalizeCompatibilityConfig };
|
package/openclaw.plugin.json
CHANGED
|
@@ -29,6 +29,26 @@
|
|
|
29
29
|
"configWrites": {
|
|
30
30
|
"type": "boolean"
|
|
31
31
|
},
|
|
32
|
+
"replyToMode": {
|
|
33
|
+
"anyOf": [
|
|
34
|
+
{
|
|
35
|
+
"type": "string",
|
|
36
|
+
"const": "off"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"type": "string",
|
|
40
|
+
"const": "first"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "string",
|
|
44
|
+
"const": "all"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"type": "string",
|
|
48
|
+
"const": "batched"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
32
52
|
"dangerouslyAllowNameMatching": {
|
|
33
53
|
"type": "boolean"
|
|
34
54
|
},
|
|
@@ -227,12 +247,6 @@
|
|
|
227
247
|
"type": "string"
|
|
228
248
|
}
|
|
229
249
|
},
|
|
230
|
-
"mentionPatterns": {
|
|
231
|
-
"type": "array",
|
|
232
|
-
"items": {
|
|
233
|
-
"type": "string"
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
250
|
"markdown": {
|
|
237
251
|
"type": "object",
|
|
238
252
|
"properties": {
|
|
@@ -355,6 +369,26 @@
|
|
|
355
369
|
"configWrites": {
|
|
356
370
|
"type": "boolean"
|
|
357
371
|
},
|
|
372
|
+
"replyToMode": {
|
|
373
|
+
"anyOf": [
|
|
374
|
+
{
|
|
375
|
+
"type": "string",
|
|
376
|
+
"const": "off"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"type": "string",
|
|
380
|
+
"const": "first"
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
"type": "string",
|
|
384
|
+
"const": "all"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
"type": "string",
|
|
388
|
+
"const": "batched"
|
|
389
|
+
}
|
|
390
|
+
]
|
|
391
|
+
},
|
|
358
392
|
"dangerouslyAllowNameMatching": {
|
|
359
393
|
"type": "boolean"
|
|
360
394
|
},
|
|
@@ -553,12 +587,6 @@
|
|
|
553
587
|
"type": "string"
|
|
554
588
|
}
|
|
555
589
|
},
|
|
556
|
-
"mentionPatterns": {
|
|
557
|
-
"type": "array",
|
|
558
|
-
"items": {
|
|
559
|
-
"type": "string"
|
|
560
|
-
}
|
|
561
|
-
},
|
|
562
590
|
"markdown": {
|
|
563
591
|
"type": "object",
|
|
564
592
|
"properties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/irc",
|
|
3
|
-
"version": "2026.8.1
|
|
3
|
+
"version": "2026.8.1",
|
|
4
4
|
"description": "OpenClaw IRC channel plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"openclaw": {
|
|
@@ -121,10 +121,10 @@
|
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
123
|
"compat": {
|
|
124
|
-
"pluginApi": ">=2026.8.1
|
|
124
|
+
"pluginApi": ">=2026.8.1"
|
|
125
125
|
},
|
|
126
126
|
"build": {
|
|
127
|
-
"openclawVersion": "2026.8.1
|
|
127
|
+
"openclawVersion": "2026.8.1",
|
|
128
128
|
"bundledDist": false
|
|
129
129
|
},
|
|
130
130
|
"release": {
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
"README.md"
|
|
150
150
|
],
|
|
151
151
|
"peerDependencies": {
|
|
152
|
-
"openclaw": ">=2026.8.1
|
|
152
|
+
"openclaw": ">=2026.8.1"
|
|
153
153
|
},
|
|
154
154
|
"peerDependenciesMeta": {
|
|
155
155
|
"openclaw": {
|