@openclaw/irc 2026.7.2-beta.7 → 2026.8.1-beta.3
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-DoavT7vs.js → channel-BO1GdA4V.js} +81 -39
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-runtime-DsN_0hPp.js → channel-runtime-CSpBc1ef.js} +56 -27
- package/dist/{config-schema-BPoye3--.js → config-schema-DdB_h5mL.js} +1 -0
- package/dist/doctor-contract-api.js +1 -1
- package/openclaw.plugin.json +9 -0
- package/package.json +9 -5
package/dist/api.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { _ as resolveDefaultIrcAccountId, g as listIrcAccountIds, h as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin, v as resolveIrcAccount } from "./channel-
|
|
1
|
+
import { _ as resolveDefaultIrcAccountId, g as listIrcAccountIds, h as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin, v as resolveIrcAccount } from "./channel-BO1GdA4V.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,11 @@
|
|
|
1
|
-
import { t as IrcChannelConfigSchema } from "./config-schema-
|
|
1
|
+
import { t as IrcChannelConfigSchema } from "./config-schema-DdB_h5mL.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 { buildMutableAllowEntryDetector, collectStandardAllowlistLists,
|
|
8
|
+
import { buildMutableAllowEntryDetector, collectStandardAllowlistLists, createAllowlistProviderOpenWarningCollector, createConditionalWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, resolveScopeKeyCaseInsensitive, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
|
|
9
9
|
import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister } from "openclaw/plugin-sdk/directory-runtime";
|
|
10
10
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
11
11
|
import { buildBaseChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
|
|
@@ -14,17 +14,18 @@ import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normali
|
|
|
14
14
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
15
15
|
import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
|
|
16
16
|
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
17
|
-
import {
|
|
17
|
+
import { resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
18
18
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringifiedOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
19
19
|
import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
|
|
20
20
|
import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
|
|
21
|
-
import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
21
|
+
import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
|
|
22
22
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
23
23
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
24
24
|
import net from "node:net";
|
|
25
25
|
import tls from "node:tls";
|
|
26
26
|
import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
27
27
|
import { randomUUID } from "node:crypto";
|
|
28
|
+
import { assertSecretOwnerAvailable } from "openclaw/plugin-sdk/channel-secret-owner-runtime";
|
|
28
29
|
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
29
30
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
|
|
30
31
|
import { applyAccountNameToChannelSection, createAllowFromSection, createPromptParsedAllowFromForAccount, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicySetter, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
@@ -42,6 +43,16 @@ const { listAccountIds: listIrcAccountIds, resolveDefaultAccountId: resolveDefau
|
|
|
42
43
|
hasImplicitDefaultAccount: (cfg) => Boolean((cfg.channels?.irc?.host?.trim() || process.env.IRC_HOST?.trim()) && (cfg.channels?.irc?.nick?.trim() || process.env.IRC_NICK?.trim()))
|
|
43
44
|
});
|
|
44
45
|
function resolvePassword(accountId, merged) {
|
|
46
|
+
const configPassword = resolveSecretInputString({
|
|
47
|
+
value: merged.password,
|
|
48
|
+
path: `channels.irc.accounts.${accountId}.password`,
|
|
49
|
+
mode: "inspect"
|
|
50
|
+
});
|
|
51
|
+
if (configPassword.status === "configured_unavailable") return {
|
|
52
|
+
password: "",
|
|
53
|
+
source: "config",
|
|
54
|
+
unavailable: true
|
|
55
|
+
};
|
|
45
56
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
46
57
|
const envPassword = process.env.IRC_PASSWORD?.trim();
|
|
47
58
|
if (envPassword) return {
|
|
@@ -51,7 +62,7 @@ function resolvePassword(accountId, merged) {
|
|
|
51
62
|
}
|
|
52
63
|
if (merged.passwordFile?.trim()) {
|
|
53
64
|
let diagnostic;
|
|
54
|
-
const
|
|
65
|
+
const password = tryReadSecretFileSync(merged.passwordFile, "IRC password file", {
|
|
55
66
|
rejectSymlink: true,
|
|
56
67
|
credentialDiagnostic: {
|
|
57
68
|
configPath: `channels.irc.accounts.${accountId}.passwordFile`,
|
|
@@ -60,8 +71,8 @@ function resolvePassword(accountId, merged) {
|
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
73
|
});
|
|
63
|
-
if (
|
|
64
|
-
password
|
|
74
|
+
if (password) return {
|
|
75
|
+
password,
|
|
65
76
|
source: "passwordFile"
|
|
66
77
|
};
|
|
67
78
|
return {
|
|
@@ -70,12 +81,8 @@ function resolvePassword(accountId, merged) {
|
|
|
70
81
|
diagnostic
|
|
71
82
|
};
|
|
72
83
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
path: `channels.irc.accounts.${accountId}.password`
|
|
76
|
-
});
|
|
77
|
-
if (configPassword) return {
|
|
78
|
-
password: configPassword,
|
|
84
|
+
if (configPassword.status === "available") return {
|
|
85
|
+
password: configPassword.value,
|
|
79
86
|
source: "config"
|
|
80
87
|
};
|
|
81
88
|
return {
|
|
@@ -85,23 +92,29 @@ function resolvePassword(accountId, merged) {
|
|
|
85
92
|
}
|
|
86
93
|
function resolveNickServConfig(accountId, nickserv) {
|
|
87
94
|
const base = nickserv ?? {};
|
|
95
|
+
const configPassword = resolveSecretInputString({
|
|
96
|
+
value: base.password,
|
|
97
|
+
path: `channels.irc.accounts.${accountId}.nickserv.password`,
|
|
98
|
+
mode: "inspect"
|
|
99
|
+
});
|
|
100
|
+
const unavailable = Boolean(configPassword.ref);
|
|
88
101
|
const envPassword = accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICKSERV_PASSWORD?.trim() : void 0;
|
|
89
102
|
const envRegisterEmail = accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICKSERV_REGISTER_EMAIL?.trim() : void 0;
|
|
90
103
|
const passwordFile = base.passwordFile?.trim();
|
|
91
|
-
let resolvedPassword
|
|
92
|
-
value: base.password,
|
|
93
|
-
path: `channels.irc.accounts.${accountId}.nickserv.password`
|
|
94
|
-
}) || envPassword || "";
|
|
104
|
+
let resolvedPassword;
|
|
95
105
|
let diagnostic;
|
|
96
|
-
if (!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
if (!unavailable) {
|
|
107
|
+
resolvedPassword = configPassword.value || envPassword;
|
|
108
|
+
if (!resolvedPassword && passwordFile) resolvedPassword = tryReadSecretFileSync(passwordFile, "IRC NickServ password file", {
|
|
109
|
+
rejectSymlink: true,
|
|
110
|
+
credentialDiagnostic: {
|
|
111
|
+
configPath: `channels.irc.accounts.${accountId}.nickserv.passwordFile`,
|
|
112
|
+
report: (value) => {
|
|
113
|
+
diagnostic = value;
|
|
114
|
+
}
|
|
102
115
|
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
105
118
|
return {
|
|
106
119
|
config: {
|
|
107
120
|
...base,
|
|
@@ -110,7 +123,8 @@ function resolveNickServConfig(accountId, nickserv) {
|
|
|
110
123
|
password: resolvedPassword || void 0,
|
|
111
124
|
registerEmail: base.registerEmail?.trim() || envRegisterEmail || void 0
|
|
112
125
|
},
|
|
113
|
-
diagnostic
|
|
126
|
+
diagnostic,
|
|
127
|
+
unavailable: base.enabled !== false && unavailable
|
|
114
128
|
};
|
|
115
129
|
}
|
|
116
130
|
function resolveIrcAccount(params) {
|
|
@@ -154,7 +168,7 @@ function resolveIrcAccount(params) {
|
|
|
154
168
|
realname,
|
|
155
169
|
password: passwordResolution.password,
|
|
156
170
|
passwordSource: passwordResolution.source,
|
|
157
|
-
tokenStatus: diagnostics.length > 0 ? "configured_unavailable" : passwordResolution.password || nickservResolution.config.password ? "available" : "missing",
|
|
171
|
+
tokenStatus: diagnostics.length > 0 || passwordResolution.unavailable || nickservResolution.unavailable ? "configured_unavailable" : passwordResolution.password || nickservResolution.config.password ? "available" : "missing",
|
|
158
172
|
...diagnostics.length > 0 ? { credentialDiagnostics: diagnostics } : {},
|
|
159
173
|
config
|
|
160
174
|
};
|
|
@@ -183,7 +197,7 @@ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAll
|
|
|
183
197
|
});
|
|
184
198
|
//#endregion
|
|
185
199
|
//#region extensions/irc/src/gateway.ts
|
|
186
|
-
const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-
|
|
200
|
+
const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-CSpBc1ef.js"));
|
|
187
201
|
async function startIrcGatewayAccount(ctx) {
|
|
188
202
|
const account = ctx.account;
|
|
189
203
|
const statusSink = createAccountStatusSink({
|
|
@@ -334,7 +348,7 @@ function takeIrcPrivmsgChunk(text, maxChars, maxBytes) {
|
|
|
334
348
|
if (splitAt >= Math.floor(fitted.length / 2)) return fitted.slice(0, splitAt);
|
|
335
349
|
return fitted;
|
|
336
350
|
}
|
|
337
|
-
function
|
|
351
|
+
function toIrcError(err) {
|
|
338
352
|
if (err instanceof Error) return err;
|
|
339
353
|
return new Error(typeof err === "string" ? err : JSON.stringify(err));
|
|
340
354
|
}
|
|
@@ -391,7 +405,7 @@ async function connectIrcClient(options) {
|
|
|
391
405
|
rejectReady = reject;
|
|
392
406
|
});
|
|
393
407
|
const fail = (err) => {
|
|
394
|
-
const error =
|
|
408
|
+
const error = toIrcError(err);
|
|
395
409
|
if (options.onError) options.onError(error);
|
|
396
410
|
if (!ready && rejectReady) {
|
|
397
411
|
rejectReady(error);
|
|
@@ -613,6 +627,8 @@ async function connectIrcClient(options) {
|
|
|
613
627
|
//#endregion
|
|
614
628
|
//#region extensions/irc/src/connect-options.ts
|
|
615
629
|
function buildIrcConnectOptions(account, overrides = {}) {
|
|
630
|
+
assertSecretOwnerAvailable("account", `irc:${account.accountId}`);
|
|
631
|
+
if (account.tokenStatus === "configured_unavailable") throw new Error(`IRC credentials for account "${account.accountId}" are configured but unavailable.`);
|
|
616
632
|
return {
|
|
617
633
|
host: account.host,
|
|
618
634
|
port: account.port,
|
|
@@ -726,9 +742,9 @@ async function sendMessageIrc(to, text, opts) {
|
|
|
726
742
|
channel: "irc",
|
|
727
743
|
accountId: account.accountId
|
|
728
744
|
});
|
|
729
|
-
const prepared = convertMarkdownTables(text.trim(), tableMode);
|
|
745
|
+
const prepared = stripMarkdown(convertMarkdownTables(text.trim(), tableMode));
|
|
746
|
+
if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
|
|
730
747
|
const payload = opts.replyTo ? `${prepared}\n\n[reply:${opts.replyTo}]` : prepared;
|
|
731
|
-
if (!payload.trim()) throw new Error("Message must be non-empty for IRC sends");
|
|
732
748
|
const client = opts.client;
|
|
733
749
|
if (client?.isReady()) client.sendPrivmsg(target, payload);
|
|
734
750
|
else {
|
|
@@ -755,6 +771,16 @@ async function sendMessageIrc(to, text, opts) {
|
|
|
755
771
|
}
|
|
756
772
|
//#endregion
|
|
757
773
|
//#region extensions/irc/src/message-adapter.ts
|
|
774
|
+
async function sendIrcMessage(...args) {
|
|
775
|
+
const { target, ...result } = await sendMessageIrc(...args);
|
|
776
|
+
return {
|
|
777
|
+
...result,
|
|
778
|
+
target: {
|
|
779
|
+
kind: "conversation",
|
|
780
|
+
id: target
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
}
|
|
758
784
|
const ircMessageAdapter = defineChannelMessageAdapter({
|
|
759
785
|
id: "irc",
|
|
760
786
|
durableFinal: { capabilities: {
|
|
@@ -763,12 +789,12 @@ const ircMessageAdapter = defineChannelMessageAdapter({
|
|
|
763
789
|
replyTo: true
|
|
764
790
|
} },
|
|
765
791
|
send: {
|
|
766
|
-
text: async ({ cfg, to, text, accountId, replyToId }) => await
|
|
792
|
+
text: async ({ cfg, to, text, accountId, replyToId }) => await sendIrcMessage(to, text, {
|
|
767
793
|
cfg,
|
|
768
794
|
accountId: accountId ?? void 0,
|
|
769
795
|
replyTo: replyToId ?? void 0
|
|
770
796
|
}),
|
|
771
|
-
media: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) => await
|
|
797
|
+
media: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) => await sendIrcMessage(to, mediaUrl ? `${text}\n\nAttachment: ${mediaUrl}` : text, {
|
|
772
798
|
cfg,
|
|
773
799
|
accountId: accountId ?? void 0,
|
|
774
800
|
replyTo: replyToId ?? void 0
|
|
@@ -1028,7 +1054,8 @@ const ircSetupContract = defineChannelSetupContract({
|
|
|
1028
1054
|
cli: {
|
|
1029
1055
|
flags: "--use-env",
|
|
1030
1056
|
description: "Use IRC environment configuration"
|
|
1031
|
-
}
|
|
1057
|
+
},
|
|
1058
|
+
envVars: ["IRC_HOST", "IRC_NICK"]
|
|
1032
1059
|
}
|
|
1033
1060
|
},
|
|
1034
1061
|
legacyAdapter: ircSetupAdapter
|
|
@@ -1398,7 +1425,7 @@ const meta = {
|
|
|
1398
1425
|
systemImage: "number",
|
|
1399
1426
|
markdownCapable: true
|
|
1400
1427
|
};
|
|
1401
|
-
const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-
|
|
1428
|
+
const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-CSpBc1ef.js"));
|
|
1402
1429
|
function normalizePairingTarget(raw) {
|
|
1403
1430
|
const normalized = normalizeIrcAllowEntry(raw);
|
|
1404
1431
|
if (!normalized) return "";
|
|
@@ -1454,7 +1481,7 @@ const resolveIrcDmPolicy = createScopedDmSecurityResolver({
|
|
|
1454
1481
|
policyPathSuffix: "dmPolicy",
|
|
1455
1482
|
normalizeEntry: (raw) => normalizeIrcAllowEntry(raw)
|
|
1456
1483
|
});
|
|
1457
|
-
const
|
|
1484
|
+
const collectIrcGroupPolicyWarnings = createAllowlistProviderOpenWarningCollector({
|
|
1458
1485
|
providerConfigPresent: (cfg) => cfg.channels?.irc !== void 0,
|
|
1459
1486
|
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
|
1460
1487
|
buildOpenWarning: {
|
|
@@ -1462,7 +1489,19 @@ const collectIrcSecurityWarnings = composeAccountWarningCollectors(createAllowli
|
|
|
1462
1489
|
openBehavior: "allows all channels and senders (mention-gated)",
|
|
1463
1490
|
remediation: "Prefer channels.irc.groupPolicy=\"allowlist\" with channels.irc.groups"
|
|
1464
1491
|
}
|
|
1465
|
-
})
|
|
1492
|
+
});
|
|
1493
|
+
const collectIrcOpenGroupFindings = createConditionalWarningCollector.findings({
|
|
1494
|
+
collectWarnings: collectIrcGroupPolicyWarnings,
|
|
1495
|
+
checkId: "channels.irc.groups.open",
|
|
1496
|
+
severity: "critical",
|
|
1497
|
+
title: "IRC security warning"
|
|
1498
|
+
});
|
|
1499
|
+
const collectIrcSecurityWarnings = (params) => [
|
|
1500
|
+
...collectIrcOpenGroupFindings(params),
|
|
1501
|
+
...!params.account.config.tls ? ["- IRC TLS is disabled (channels.irc.tls=false); traffic and credentials are plaintext."] : [],
|
|
1502
|
+
...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."] : [],
|
|
1503
|
+
...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."] : []
|
|
1504
|
+
];
|
|
1466
1505
|
const ircPlugin = createChatChannelPlugin({
|
|
1467
1506
|
base: {
|
|
1468
1507
|
id: "irc",
|
|
@@ -1470,7 +1509,6 @@ const ircPlugin = createChatChannelPlugin({
|
|
|
1470
1509
|
...meta,
|
|
1471
1510
|
quickstartAllowFrom: true
|
|
1472
1511
|
},
|
|
1473
|
-
setup: ircSetupAdapter,
|
|
1474
1512
|
setupContract: ircSetupContract,
|
|
1475
1513
|
setupWizard: ircSetupWizard,
|
|
1476
1514
|
capabilities: {
|
|
@@ -1532,6 +1570,10 @@ const ircPlugin = createChatChannelPlugin({
|
|
|
1532
1570
|
messaging: {
|
|
1533
1571
|
targetPrefixes: ["irc"],
|
|
1534
1572
|
normalizeTarget: normalizeIrcMessagingTarget,
|
|
1573
|
+
inferTargetChatType: ({ to }) => {
|
|
1574
|
+
const target = normalizeIrcMessagingTarget(to);
|
|
1575
|
+
return target ? isChannelTarget(target) ? "group" : "direct" : void 0;
|
|
1576
|
+
},
|
|
1535
1577
|
resolveOutboundSessionRoute: (params) => resolveIrcOutboundSessionRoute(params),
|
|
1536
1578
|
targetResolver: {
|
|
1537
1579
|
looksLikeId: looksLikeIrcTargetId,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as IrcChannelConfigSchema } from "./config-schema-
|
|
1
|
+
import { t as IrcChannelConfigSchema } from "./config-schema-DdB_h5mL.js";
|
|
2
2
|
export { IrcChannelConfigSchema };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as ircPlugin } from "./channel-
|
|
1
|
+
import { t as ircPlugin } from "./channel-BO1GdA4V.js";
|
|
2
2
|
export { ircPlugin };
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { a as resolveIrcGroupRequireMention, c as buildIrcAllowlistCandidates, d as buildIrcConnectOptions, f as connectIrcClient, i as resolveIrcGroupMatch, l as isChannelTarget, m as parseIrcPrefix, o as sanitizeIrcAssistantText, p as parseIrcLine, s as sendMessageIrc, u as normalizeIrcAllowEntry, v as resolveIrcAccount } from "./channel-
|
|
1
|
+
import { a as resolveIrcGroupRequireMention, c as buildIrcAllowlistCandidates, d as buildIrcConnectOptions, f as connectIrcClient, i as resolveIrcGroupMatch, l as isChannelTarget, m as parseIrcPrefix, o as sanitizeIrcAssistantText, p as parseIrcLine, s as sendMessageIrc, u as normalizeIrcAllowEntry, v as resolveIrcAccount } from "./channel-BO1GdA4V.js";
|
|
2
2
|
import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
|
|
3
3
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import { resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
|
|
7
|
-
import {
|
|
7
|
+
import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
8
|
+
import { logInboundDrop, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
8
9
|
import { channelIngressRoutes, createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
9
10
|
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
10
11
|
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
|
@@ -46,6 +47,23 @@ const ircIngressIdentity = defineStableChannelIngressIdentity({
|
|
|
46
47
|
resolveEntryId: ({ entryIndex, fieldKey }) => `irc-entry-${entryIndex + 1}:${fieldKey === "irc-nick" ? "nick" : "id"}`
|
|
47
48
|
});
|
|
48
49
|
const escapeIrcRegexLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
50
|
+
const IRC_NICK_CHARACTER = String.raw`[A-Za-z0-9_\-\[\]\\\x60^{}|~]`;
|
|
51
|
+
const IRC_RFC1459_CASE_EQUIVALENTS = /* @__PURE__ */ new Map([
|
|
52
|
+
["[", "{"],
|
|
53
|
+
["{", "["],
|
|
54
|
+
["]", "}"],
|
|
55
|
+
["}", "]"],
|
|
56
|
+
["\\", "|"],
|
|
57
|
+
["|", "\\"],
|
|
58
|
+
["^", "~"],
|
|
59
|
+
["~", "^"]
|
|
60
|
+
]);
|
|
61
|
+
function buildIrcNickMentionPattern(value) {
|
|
62
|
+
return Array.from(value, (character) => {
|
|
63
|
+
const equivalent = IRC_RFC1459_CASE_EQUIVALENTS.get(character);
|
|
64
|
+
return equivalent ? `[${escapeIrcRegexLiteral(character)}${escapeIrcRegexLiteral(equivalent)}]` : escapeIrcRegexLiteral(character);
|
|
65
|
+
}).join("");
|
|
66
|
+
}
|
|
49
67
|
function isBareNick(value) {
|
|
50
68
|
return !value.includes("!") && !value.includes("@");
|
|
51
69
|
}
|
|
@@ -165,7 +183,7 @@ async function handleIrcInbound(params) {
|
|
|
165
183
|
const hasControlCommand = core.channel.text.hasControlCommand(rawBody, config);
|
|
166
184
|
const mentionRegexes = core.channel.mentions.buildMentionRegexes(config);
|
|
167
185
|
const mentionNick = connectedNick?.trim() || account.nick;
|
|
168
|
-
const explicitMentionRegex = mentionNick ? new RegExp(
|
|
186
|
+
const explicitMentionRegex = mentionNick ? new RegExp(`(?<!${IRC_NICK_CHARACTER})${buildIrcNickMentionPattern(mentionNick)}(?!${IRC_NICK_CHARACTER})[:,]?`, "i") : null;
|
|
169
187
|
const wasMentioned = core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes) || (explicitMentionRegex ? explicitMentionRegex.test(rawBody) : false);
|
|
170
188
|
const requireMention = message.isGroup ? resolveIrcGroupRequireMention({
|
|
171
189
|
groups: account.config.groups,
|
|
@@ -173,6 +191,17 @@ async function handleIrcInbound(params) {
|
|
|
173
191
|
}) : false;
|
|
174
192
|
const routeGroupAllowFrom = normalizeStringEntries(groupMatch.groupConfig?.allowFrom?.length ? groupMatch.groupConfig.allowFrom : groupMatch.wildcardConfig?.allowFrom);
|
|
175
193
|
const accessGroupPolicy = groupPolicy === "open" && (hasEntries(account.config.groupAllowFrom) || hasEntries(routeGroupAllowFrom)) ? "allowlist" : groupPolicy;
|
|
194
|
+
const channelTarget = message.target.startsWith("#") || message.target.startsWith("&") ? message.target : `#${message.target}`;
|
|
195
|
+
const peerId = message.isGroup ? channelTarget : message.senderNick;
|
|
196
|
+
const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
|
|
197
|
+
cfg: config,
|
|
198
|
+
channel: CHANNEL_ID,
|
|
199
|
+
accountId: account.accountId,
|
|
200
|
+
peer: {
|
|
201
|
+
kind: message.isGroup ? "group" : "direct",
|
|
202
|
+
id: peerId
|
|
203
|
+
}
|
|
204
|
+
});
|
|
176
205
|
const access = await createChannelIngressResolver({
|
|
177
206
|
channelId: CHANNEL_ID,
|
|
178
207
|
accountId: account.accountId,
|
|
@@ -185,6 +214,12 @@ async function handleIrcInbound(params) {
|
|
|
185
214
|
kind: message.isGroup ? "group" : "direct",
|
|
186
215
|
id: message.target
|
|
187
216
|
},
|
|
217
|
+
contextBinding: {
|
|
218
|
+
agentId: route.agentId,
|
|
219
|
+
sessionKey: route.sessionKey,
|
|
220
|
+
...message.messageId ? { messageId: message.messageId } : {},
|
|
221
|
+
inboundEventKind: "user_request"
|
|
222
|
+
},
|
|
188
223
|
route: routeDescriptorsForIrcGroup({
|
|
189
224
|
isGroup: message.isGroup,
|
|
190
225
|
groupPolicy,
|
|
@@ -262,17 +297,6 @@ async function handleIrcInbound(params) {
|
|
|
262
297
|
kind: "failed-retryable",
|
|
263
298
|
error: turnAdoptionLifecycle.abortSignal.reason
|
|
264
299
|
};
|
|
265
|
-
const channelTarget = message.target.startsWith("#") || message.target.startsWith("&") ? message.target : `#${message.target}`;
|
|
266
|
-
const peerId = message.isGroup ? channelTarget : message.senderNick;
|
|
267
|
-
const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
|
|
268
|
-
cfg: config,
|
|
269
|
-
channel: CHANNEL_ID,
|
|
270
|
-
accountId: account.accountId,
|
|
271
|
-
peer: {
|
|
272
|
-
kind: message.isGroup ? "group" : "direct",
|
|
273
|
-
id: peerId
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
300
|
const fromLabel = message.isGroup ? message.target : senderDisplay;
|
|
277
301
|
const body = buildEnvelope({
|
|
278
302
|
channel: "IRC",
|
|
@@ -282,7 +306,8 @@ async function handleIrcInbound(params) {
|
|
|
282
306
|
});
|
|
283
307
|
const groupSystemPrompt = normalizeOptionalString(groupMatch.groupConfig?.systemPrompt);
|
|
284
308
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
285
|
-
const ctxPayload =
|
|
309
|
+
const ctxPayload = core.channel.inbound.buildContext({
|
|
310
|
+
channelIngress: access,
|
|
286
311
|
channel: CHANNEL_ID,
|
|
287
312
|
accountId: route.accountId,
|
|
288
313
|
messageId: message.messageId,
|
|
@@ -430,14 +455,16 @@ function decodeIrcIngressPayload(payload, claimedId) {
|
|
|
430
455
|
}
|
|
431
456
|
function inspectIrcIngress(raw, phase) {
|
|
432
457
|
try {
|
|
458
|
+
const projection = inspectRawPrivmsg(raw.body.rawLine);
|
|
459
|
+
if (phase === "claim") raw.claimedProjection = projection;
|
|
433
460
|
return {
|
|
434
|
-
eventId: raw.eventId,
|
|
435
|
-
laneKey:
|
|
461
|
+
eventId: raw.body.eventId,
|
|
462
|
+
laneKey: projection.laneKey
|
|
436
463
|
};
|
|
437
464
|
} catch (error) {
|
|
438
465
|
if (phase === "admission" && error instanceof IrcIngressPayloadError) return {
|
|
439
|
-
eventId: raw.eventId,
|
|
440
|
-
laneKey: `invalid:${raw.eventId}`
|
|
466
|
+
eventId: raw.body.eventId,
|
|
467
|
+
laneKey: `invalid:${raw.body.eventId}`
|
|
441
468
|
};
|
|
442
469
|
throw error;
|
|
443
470
|
}
|
|
@@ -454,8 +481,8 @@ function createIrcIngressMonitor(options) {
|
|
|
454
481
|
inspect: (raw, context) => inspectIrcIngress(raw, context.phase),
|
|
455
482
|
payload: {
|
|
456
483
|
version: IRC_INGRESS_PAYLOAD_VERSION,
|
|
457
|
-
serialize: (raw) => raw,
|
|
458
|
-
deserialize: (body) => body,
|
|
484
|
+
serialize: (raw) => raw.body,
|
|
485
|
+
deserialize: (body) => ({ body }),
|
|
459
486
|
encode: ({ body }) => ({
|
|
460
487
|
version: IRC_INGRESS_PAYLOAD_VERSION,
|
|
461
488
|
...body
|
|
@@ -465,13 +492,13 @@ function createIrcIngressMonitor(options) {
|
|
|
465
492
|
},
|
|
466
493
|
deliver: (raw, lifecycle, claim) => {
|
|
467
494
|
const message = {
|
|
468
|
-
...
|
|
495
|
+
...raw.claimedProjection.message,
|
|
469
496
|
messageId: claim.id,
|
|
470
|
-
timestamp: raw.receivedAt
|
|
497
|
+
timestamp: raw.body.receivedAt
|
|
471
498
|
};
|
|
472
499
|
return options.dispatch(message, lifecycle, {
|
|
473
|
-
connectedNick: raw.connectedNick.trim(),
|
|
474
|
-
connectionEpoch: raw.connectionEpoch.trim()
|
|
500
|
+
connectedNick: raw.body.connectedNick.trim(),
|
|
501
|
+
connectionEpoch: raw.body.connectionEpoch.trim()
|
|
475
502
|
});
|
|
476
503
|
},
|
|
477
504
|
pollIntervalMs: options.pollIntervalMs ?? IRC_INGRESS_POLL_INTERVAL_MS,
|
|
@@ -501,13 +528,13 @@ function createIrcIngressMonitor(options) {
|
|
|
501
528
|
const receivedAt = Date.now();
|
|
502
529
|
const normalizedNick = connectedNick.trim();
|
|
503
530
|
if (!normalizedNick) return Promise.reject(/* @__PURE__ */ new Error("IRC ingress connected nickname is required."));
|
|
504
|
-
return monitor.admit({
|
|
531
|
+
return monitor.admit({ body: {
|
|
505
532
|
eventId,
|
|
506
533
|
rawLine,
|
|
507
534
|
receivedAt,
|
|
508
535
|
connectionEpoch: epoch,
|
|
509
536
|
connectedNick: normalizedNick
|
|
510
|
-
}, { receivedAt }).then(() => void 0);
|
|
537
|
+
} }, { receivedAt }).then(() => void 0);
|
|
511
538
|
}
|
|
512
539
|
};
|
|
513
540
|
},
|
|
@@ -592,6 +619,7 @@ async function monitorIrcProvider(opts) {
|
|
|
592
619
|
});
|
|
593
620
|
function scheduleReconnect() {
|
|
594
621
|
if (stopped || monitorAbort.signal.aborted || reconnectTimer) return;
|
|
622
|
+
opts.statusSink?.({ lifecycle: "recovering" });
|
|
595
623
|
reconnectTimer = setTimeout(() => {
|
|
596
624
|
reconnectTimer = null;
|
|
597
625
|
connect().catch((error) => {
|
|
@@ -648,6 +676,7 @@ async function monitorIrcProvider(opts) {
|
|
|
648
676
|
return;
|
|
649
677
|
}
|
|
650
678
|
ingress.start();
|
|
679
|
+
opts.statusSink?.(channelReadyPatch());
|
|
651
680
|
logger.info(`[${account.accountId}] connected to ${account.host}:${account.port}${account.tls ? " (tls)" : ""} as ${nextClient.nick}`);
|
|
652
681
|
}
|
|
653
682
|
try {
|
|
@@ -59,6 +59,7 @@ const IrcNickServSchema = z.object({
|
|
|
59
59
|
const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannelSchema(z.object({
|
|
60
60
|
name: z.string().optional(),
|
|
61
61
|
enabled: z.boolean().optional(),
|
|
62
|
+
configWrites: z.boolean().optional(),
|
|
62
63
|
dangerouslyAllowNameMatching: z.boolean().optional(),
|
|
63
64
|
host: z.string().optional(),
|
|
64
65
|
port: z.number().int().min(1).max(65535).optional(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor";
|
|
1
|
+
import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor-migrations";
|
|
2
2
|
//#region extensions/irc/doctor-contract-api.ts
|
|
3
3
|
const streamingAliasMigration = defineChannelAliasMigration({
|
|
4
4
|
channelId: "irc",
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "irc",
|
|
3
|
+
"doctorContract": {
|
|
4
|
+
"configRepair": true
|
|
5
|
+
},
|
|
3
6
|
"activation": {
|
|
4
7
|
"onStartup": false
|
|
5
8
|
},
|
|
@@ -23,6 +26,9 @@
|
|
|
23
26
|
"enabled": {
|
|
24
27
|
"type": "boolean"
|
|
25
28
|
},
|
|
29
|
+
"configWrites": {
|
|
30
|
+
"type": "boolean"
|
|
31
|
+
},
|
|
26
32
|
"dangerouslyAllowNameMatching": {
|
|
27
33
|
"type": "boolean"
|
|
28
34
|
},
|
|
@@ -346,6 +352,9 @@
|
|
|
346
352
|
"enabled": {
|
|
347
353
|
"type": "boolean"
|
|
348
354
|
},
|
|
355
|
+
"configWrites": {
|
|
356
|
+
"type": "boolean"
|
|
357
|
+
},
|
|
349
358
|
"dangerouslyAllowNameMatching": {
|
|
350
359
|
"type": "boolean"
|
|
351
360
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/irc",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.8.1-beta.3",
|
|
4
4
|
"description": "OpenClaw IRC channel plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"openclaw": {
|
|
@@ -111,16 +111,20 @@
|
|
|
111
111
|
"cli": {
|
|
112
112
|
"flags": "--use-env",
|
|
113
113
|
"description": "Use IRC environment configuration"
|
|
114
|
-
}
|
|
114
|
+
},
|
|
115
|
+
"envVars": [
|
|
116
|
+
"IRC_HOST",
|
|
117
|
+
"IRC_NICK"
|
|
118
|
+
]
|
|
115
119
|
}
|
|
116
120
|
]
|
|
117
121
|
}
|
|
118
122
|
},
|
|
119
123
|
"compat": {
|
|
120
|
-
"pluginApi": ">=2026.
|
|
124
|
+
"pluginApi": ">=2026.8.1-beta.3"
|
|
121
125
|
},
|
|
122
126
|
"build": {
|
|
123
|
-
"openclawVersion": "2026.
|
|
127
|
+
"openclawVersion": "2026.8.1-beta.3",
|
|
124
128
|
"bundledDist": false
|
|
125
129
|
},
|
|
126
130
|
"release": {
|
|
@@ -145,7 +149,7 @@
|
|
|
145
149
|
"README.md"
|
|
146
150
|
],
|
|
147
151
|
"peerDependencies": {
|
|
148
|
-
"openclaw": ">=2026.
|
|
152
|
+
"openclaw": ">=2026.8.1-beta.3"
|
|
149
153
|
},
|
|
150
154
|
"peerDependenciesMeta": {
|
|
151
155
|
"openclaw": {
|