@openclaw/irc 2026.7.2-beta.2 → 2026.7.2-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +1 -1
- package/dist/{channel-DivPJINh.js → channel-BuxKUEja.js} +128 -21
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-runtime-Dg0LFlsh.js → channel-runtime-BrNJqIB_.js} +315 -103
- package/dist/{secret-contract-PJHaoTSe.js → secret-contract-D_DC3YWj.js} +1 -0
- package/dist/secret-contract-api.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/openclaw.plugin.json +0 -14
- package/package.json +91 -11
package/dist/api.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as resolveIrcAccount, g as resolveDefaultIrcAccountId, h as listIrcAccountIds, m as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin } from "./channel-BuxKUEja.js";
|
|
2
2
|
import { n as setIrcRuntime } from "./runtime-xy-FcjJC.js";
|
|
3
3
|
export { ircPlugin, ircSetupAdapter, ircSetupWizard, listEnabledIrcAccounts, listIrcAccountIds, resolveDefaultIrcAccountId, resolveIrcAccount, setIrcRuntime };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as IrcChannelConfigSchema } from "./config-schema-BPoye3--.js";
|
|
2
2
|
import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
|
|
3
|
-
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-
|
|
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
|
-
import { buildChannelOutboundSessionRoute, createChatChannelPlugin, parseOptionalDelimitedEntries
|
|
7
|
+
import { buildChannelOutboundSessionRoute, createChatChannelPlugin, parseOptionalDelimitedEntries } from "openclaw/plugin-sdk/channel-core";
|
|
8
8
|
import { buildMutableAllowEntryDetector, collectStandardAllowlistLists, composeAccountWarningCollectors, createAllowlistProviderOpenWarningCollector, 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";
|
|
@@ -12,18 +12,19 @@ import { buildBaseChannelStatusSummary, createComputedAccountStatusAdapter, crea
|
|
|
12
12
|
import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
|
|
13
13
|
import { resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
|
|
14
14
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
15
|
+
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
|
|
15
16
|
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
16
17
|
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringifiedOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
17
|
-
import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
|
|
18
|
+
import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
|
|
18
19
|
import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
|
|
19
20
|
import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
20
|
-
import { runStoppablePassiveMonitor } from "openclaw/plugin-sdk/extension-shared";
|
|
21
21
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
22
22
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
23
23
|
import net from "node:net";
|
|
24
24
|
import tls from "node:tls";
|
|
25
25
|
import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
|
|
26
26
|
import { randomUUID } from "node:crypto";
|
|
27
|
+
import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
|
|
27
28
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
|
|
28
29
|
import { applyAccountNameToChannelSection, createAllowFromSection, createPromptParsedAllowFromForAccount, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicySetter, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
|
|
29
30
|
//#region extensions/irc/src/accounts.ts
|
|
@@ -66,11 +67,25 @@ function resolvePassword(accountId, merged) {
|
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
69
|
if (merged.passwordFile?.trim()) {
|
|
69
|
-
|
|
70
|
+
let diagnostic;
|
|
71
|
+
const filePassword = tryReadSecretFileSync(merged.passwordFile, "IRC password file", {
|
|
72
|
+
rejectSymlink: true,
|
|
73
|
+
credentialDiagnostic: {
|
|
74
|
+
configPath: `channels.irc.accounts.${accountId}.passwordFile`,
|
|
75
|
+
report: (value) => {
|
|
76
|
+
diagnostic = value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
70
80
|
if (filePassword) return {
|
|
71
81
|
password: filePassword,
|
|
72
82
|
source: "passwordFile"
|
|
73
83
|
};
|
|
84
|
+
return {
|
|
85
|
+
password: "",
|
|
86
|
+
source: "passwordFile",
|
|
87
|
+
diagnostic
|
|
88
|
+
};
|
|
74
89
|
}
|
|
75
90
|
const configPassword = normalizeResolvedSecretInputString({
|
|
76
91
|
value: merged.password,
|
|
@@ -94,13 +109,25 @@ function resolveNickServConfig(accountId, nickserv) {
|
|
|
94
109
|
value: base.password,
|
|
95
110
|
path: `channels.irc.accounts.${accountId}.nickserv.password`
|
|
96
111
|
}) || envPassword || "";
|
|
97
|
-
|
|
112
|
+
let diagnostic;
|
|
113
|
+
if (!resolvedPassword && passwordFile) resolvedPassword = tryReadSecretFileSync(passwordFile, "IRC NickServ password file", {
|
|
114
|
+
rejectSymlink: true,
|
|
115
|
+
credentialDiagnostic: {
|
|
116
|
+
configPath: `channels.irc.accounts.${accountId}.nickserv.passwordFile`,
|
|
117
|
+
report: (value) => {
|
|
118
|
+
diagnostic = value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}) ?? "";
|
|
98
122
|
return {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
config: {
|
|
124
|
+
...base,
|
|
125
|
+
service: normalizeOptionalString(base.service),
|
|
126
|
+
passwordFile: passwordFile || void 0,
|
|
127
|
+
password: resolvedPassword || void 0,
|
|
128
|
+
registerEmail: base.registerEmail?.trim() || envRegisterEmail || void 0
|
|
129
|
+
},
|
|
130
|
+
diagnostic
|
|
104
131
|
};
|
|
105
132
|
}
|
|
106
133
|
function resolveIrcAccount(params) {
|
|
@@ -119,7 +146,8 @@ function resolveIrcAccount(params) {
|
|
|
119
146
|
const username = (merged.username?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_USERNAME?.trim() : "") || nick || "openclaw").trim();
|
|
120
147
|
const realname = (merged.realname?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_REALNAME?.trim() : "") || "OpenClaw").trim();
|
|
121
148
|
const passwordResolution = resolvePassword(accountId, merged);
|
|
122
|
-
const
|
|
149
|
+
const nickservResolution = resolveNickServConfig(accountId, merged.nickserv);
|
|
150
|
+
const diagnostics = [passwordResolution.diagnostic, nickservResolution.diagnostic].filter((diagnostic) => Boolean(diagnostic));
|
|
123
151
|
const config = {
|
|
124
152
|
...merged,
|
|
125
153
|
channels: merged.channels ?? envChannels,
|
|
@@ -129,7 +157,7 @@ function resolveIrcAccount(params) {
|
|
|
129
157
|
nick,
|
|
130
158
|
username,
|
|
131
159
|
realname,
|
|
132
|
-
nickserv
|
|
160
|
+
nickserv: nickservResolution.config
|
|
133
161
|
};
|
|
134
162
|
return {
|
|
135
163
|
accountId,
|
|
@@ -144,6 +172,8 @@ function resolveIrcAccount(params) {
|
|
|
144
172
|
realname,
|
|
145
173
|
password: passwordResolution.password,
|
|
146
174
|
passwordSource: passwordResolution.source,
|
|
175
|
+
tokenStatus: diagnostics.length > 0 ? "configured_unavailable" : passwordResolution.password || nickservResolution.config.password ? "available" : "missing",
|
|
176
|
+
...diagnostics.length > 0 ? { credentialDiagnostics: diagnostics } : {},
|
|
147
177
|
config
|
|
148
178
|
};
|
|
149
179
|
};
|
|
@@ -172,7 +202,7 @@ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAll
|
|
|
172
202
|
});
|
|
173
203
|
//#endregion
|
|
174
204
|
//#region extensions/irc/src/gateway.ts
|
|
175
|
-
const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-
|
|
205
|
+
const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-BrNJqIB_.js"));
|
|
176
206
|
async function startIrcGatewayAccount(ctx) {
|
|
177
207
|
const account = ctx.account;
|
|
178
208
|
const statusSink = createAccountStatusSink({
|
|
@@ -182,7 +212,7 @@ async function startIrcGatewayAccount(ctx) {
|
|
|
182
212
|
if (!account.configured) throw new Error(`IRC is not configured for account "${account.accountId}" (need host and nick in channels.irc).`);
|
|
183
213
|
ctx.log?.info?.(`[${account.accountId}] starting IRC provider (${account.host}:${account.port}${account.tls ? " tls" : ""})`);
|
|
184
214
|
const { monitorIrcProvider } = await loadIrcChannelRuntime$1();
|
|
185
|
-
await
|
|
215
|
+
await runPassiveAccountLifecycle({
|
|
186
216
|
abortSignal: ctx.abortSignal,
|
|
187
217
|
start: async () => await monitorIrcProvider({
|
|
188
218
|
accountId: account.accountId,
|
|
@@ -190,7 +220,10 @@ async function startIrcGatewayAccount(ctx) {
|
|
|
190
220
|
runtime: ctx.runtime,
|
|
191
221
|
abortSignal: ctx.abortSignal,
|
|
192
222
|
statusSink
|
|
193
|
-
})
|
|
223
|
+
}),
|
|
224
|
+
stop: async (monitor) => {
|
|
225
|
+
await monitor.stop();
|
|
226
|
+
}
|
|
194
227
|
});
|
|
195
228
|
}
|
|
196
229
|
//#endregion
|
|
@@ -524,7 +557,7 @@ async function connectIrcClient(options) {
|
|
|
524
557
|
if (line.command === "PRIVMSG") {
|
|
525
558
|
const targetParam = line.params[0];
|
|
526
559
|
const target = targetParam ? targetParam.trim() : "";
|
|
527
|
-
const text = line.trailing
|
|
560
|
+
const text = line.trailing ?? line.params[1] ?? "";
|
|
528
561
|
const prefix = parseIrcPrefix(line.prefix);
|
|
529
562
|
const senderNick = prefix.nick ? prefix.nick.trim() : "";
|
|
530
563
|
if (!target || !senderNick || !text.trim()) continue;
|
|
@@ -532,6 +565,7 @@ async function connectIrcClient(options) {
|
|
|
532
565
|
senderNick,
|
|
533
566
|
senderUser: prefix.user ? prefix.user.trim() : void 0,
|
|
534
567
|
senderHost: prefix.host ? prefix.host.trim() : void 0,
|
|
568
|
+
connectedNick: currentNick,
|
|
535
569
|
target,
|
|
536
570
|
text,
|
|
537
571
|
rawLine
|
|
@@ -909,6 +943,7 @@ function setIrcGroupAccess(cfg, accountId, policy, entries, normalizeGroupEntry)
|
|
|
909
943
|
});
|
|
910
944
|
}
|
|
911
945
|
const ircSetupAdapter = {
|
|
946
|
+
singleAccountKeysToMove: ["password"],
|
|
912
947
|
resolveAccountId: ({ accountId }) => normalizeAccountId$1(accountId),
|
|
913
948
|
applyAccountName: ({ cfg, accountId, name }) => applyAccountNameToChannelSection({
|
|
914
949
|
cfg,
|
|
@@ -945,6 +980,75 @@ const ircSetupAdapter = {
|
|
|
945
980
|
});
|
|
946
981
|
}
|
|
947
982
|
};
|
|
983
|
+
const ircSetupContract = defineChannelSetupContract({
|
|
984
|
+
fields: {
|
|
985
|
+
host: {
|
|
986
|
+
kind: "string",
|
|
987
|
+
cli: {
|
|
988
|
+
flags: "--host <host>",
|
|
989
|
+
description: "IRC server host"
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
port: {
|
|
993
|
+
kind: "string",
|
|
994
|
+
cli: {
|
|
995
|
+
flags: "--port <port>",
|
|
996
|
+
description: "IRC server port"
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
tls: {
|
|
1000
|
+
kind: "boolean",
|
|
1001
|
+
cli: {
|
|
1002
|
+
flags: "--tls",
|
|
1003
|
+
description: "Use TLS for IRC"
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
nick: {
|
|
1007
|
+
kind: "string",
|
|
1008
|
+
cli: {
|
|
1009
|
+
flags: "--nick <nick>",
|
|
1010
|
+
description: "IRC nickname"
|
|
1011
|
+
}
|
|
1012
|
+
},
|
|
1013
|
+
username: {
|
|
1014
|
+
kind: "string",
|
|
1015
|
+
cli: {
|
|
1016
|
+
flags: "--username <name>",
|
|
1017
|
+
description: "IRC username"
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
realname: {
|
|
1021
|
+
kind: "string",
|
|
1022
|
+
cli: {
|
|
1023
|
+
flags: "--realname <name>",
|
|
1024
|
+
description: "IRC real name"
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
channels: {
|
|
1028
|
+
kind: "string-list",
|
|
1029
|
+
cli: {
|
|
1030
|
+
flags: "--channels <names>",
|
|
1031
|
+
description: "IRC channels"
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
1034
|
+
password: {
|
|
1035
|
+
kind: "string",
|
|
1036
|
+
sensitive: true,
|
|
1037
|
+
cli: {
|
|
1038
|
+
flags: "--password <password>",
|
|
1039
|
+
description: "IRC server password"
|
|
1040
|
+
}
|
|
1041
|
+
},
|
|
1042
|
+
useEnv: {
|
|
1043
|
+
kind: "boolean",
|
|
1044
|
+
cli: {
|
|
1045
|
+
flags: "--use-env",
|
|
1046
|
+
description: "Use IRC environment configuration"
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
legacyAdapter: ircSetupAdapter
|
|
1051
|
+
});
|
|
948
1052
|
//#endregion
|
|
949
1053
|
//#region extensions/irc/src/setup-surface.ts
|
|
950
1054
|
const t = createSetupTranslator();
|
|
@@ -1310,7 +1414,7 @@ const meta = {
|
|
|
1310
1414
|
systemImage: "number",
|
|
1311
1415
|
markdownCapable: true
|
|
1312
1416
|
};
|
|
1313
|
-
const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-
|
|
1417
|
+
const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-BrNJqIB_.js"));
|
|
1314
1418
|
function normalizePairingTarget(raw) {
|
|
1315
1419
|
const normalized = normalizeIrcAllowEntry(raw);
|
|
1316
1420
|
if (!normalized) return "";
|
|
@@ -1383,6 +1487,7 @@ const ircPlugin = createChatChannelPlugin({
|
|
|
1383
1487
|
quickstartAllowFrom: true
|
|
1384
1488
|
},
|
|
1385
1489
|
setup: ircSetupAdapter,
|
|
1490
|
+
setupContract: ircSetupContract,
|
|
1386
1491
|
setupWizard: ircSetupWizard,
|
|
1387
1492
|
capabilities: {
|
|
1388
1493
|
chatTypes: ["direct", "group"],
|
|
@@ -1403,7 +1508,8 @@ const ircPlugin = createChatChannelPlugin({
|
|
|
1403
1508
|
port: account.port,
|
|
1404
1509
|
tls: account.tls,
|
|
1405
1510
|
nick: account.nick,
|
|
1406
|
-
passwordSource: account.passwordSource
|
|
1511
|
+
passwordSource: account.passwordSource,
|
|
1512
|
+
tokenStatus: account.tokenStatus
|
|
1407
1513
|
}
|
|
1408
1514
|
})
|
|
1409
1515
|
},
|
|
@@ -1510,7 +1616,8 @@ const ircPlugin = createChatChannelPlugin({
|
|
|
1510
1616
|
port: account.port,
|
|
1511
1617
|
tls: account.tls,
|
|
1512
1618
|
nick: account.nick,
|
|
1513
|
-
passwordSource: account.passwordSource
|
|
1619
|
+
passwordSource: account.passwordSource,
|
|
1620
|
+
tokenStatus: account.tokenStatus
|
|
1514
1621
|
}
|
|
1515
1622
|
})
|
|
1516
1623
|
}),
|
|
@@ -1558,4 +1665,4 @@ const ircPlugin = createChatChannelPlugin({
|
|
|
1558
1665
|
}
|
|
1559
1666
|
});
|
|
1560
1667
|
//#endregion
|
|
1561
|
-
export { resolveIrcGroupRequireMention as a, isChannelTarget as c, connectIrcClient as d,
|
|
1668
|
+
export { resolveIrcAccount as _, resolveIrcGroupRequireMention as a, isChannelTarget as c, connectIrcClient as d, parseIrcLine as f, resolveDefaultIrcAccountId as g, listIrcAccountIds as h, resolveIrcGroupMatch as i, normalizeIrcAllowEntry as l, listEnabledIrcAccounts as m, ircSetupWizard as n, sendMessageIrc as o, parseIrcPrefix as p, ircSetupAdapter as r, buildIrcAllowlistCandidates as s, ircPlugin as t, buildIrcConnectOptions as u };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as ircPlugin } from "./channel-
|
|
1
|
+
import { t as ircPlugin } from "./channel-BuxKUEja.js";
|
|
2
2
|
export { ircPlugin };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { a as resolveIrcGroupRequireMention, c as isChannelTarget, d as connectIrcClient, f as
|
|
1
|
+
import { _ as resolveIrcAccount, a as resolveIrcGroupRequireMention, c as isChannelTarget, d as connectIrcClient, f as parseIrcLine, i as resolveIrcGroupMatch, l as normalizeIrcAllowEntry, o as sendMessageIrc, p as parseIrcPrefix, s as buildIrcAllowlistCandidates, u as buildIrcConnectOptions } from "./channel-BuxKUEja.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
|
-
import { resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
4
|
+
import { bindIngressLifecycleToReplyOptions, createChannelIngressMonitor, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
5
6
|
import { resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
|
|
6
|
-
import { logInboundDrop } from "openclaw/plugin-sdk/channel-inbound";
|
|
7
|
+
import { buildChannelInboundEventContext, logInboundDrop, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
|
|
7
8
|
import { channelIngressRoutes, createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
8
9
|
import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
|
|
9
10
|
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
|
|
10
|
-
import { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";
|
|
11
11
|
import { deliverFormattedTextWithAttachments } from "openclaw/plugin-sdk/reply-payload";
|
|
12
12
|
import { GROUP_POLICY_BLOCKED_LABEL, resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
13
13
|
//#region extensions/irc/src/inbound.ts
|
|
@@ -121,7 +121,7 @@ async function deliverIrcReply(params) {
|
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
async function handleIrcInbound(params) {
|
|
124
|
-
const { message, account, config, runtime, connectedNick, statusSink } = params;
|
|
124
|
+
const { message, account, config, runtime, connectedNick, statusSink, turnAdoptionLifecycle } = params;
|
|
125
125
|
const core = getIrcRuntime();
|
|
126
126
|
const pairing = createChannelPairingController({
|
|
127
127
|
core,
|
|
@@ -129,7 +129,11 @@ async function handleIrcInbound(params) {
|
|
|
129
129
|
accountId: account.accountId
|
|
130
130
|
});
|
|
131
131
|
const rawBody = message.text?.trim() ?? "";
|
|
132
|
-
if (!rawBody) return;
|
|
132
|
+
if (!rawBody) return { kind: "completed" };
|
|
133
|
+
if (turnAdoptionLifecycle?.abortSignal.aborted) return {
|
|
134
|
+
kind: "failed-retryable",
|
|
135
|
+
error: turnAdoptionLifecycle.abortSignal.reason
|
|
136
|
+
};
|
|
133
137
|
statusSink?.({ lastInboundAt: message.timestamp });
|
|
134
138
|
const senderDisplay = message.senderHost ? `${message.senderNick}!${message.senderUser ?? "?"}@${message.senderHost}` : message.senderNick;
|
|
135
139
|
const allowNameMatching = isDangerousNameMatchingEnabled(account.config);
|
|
@@ -229,11 +233,11 @@ async function handleIrcInbound(params) {
|
|
|
229
233
|
}
|
|
230
234
|
});
|
|
231
235
|
runtime.log?.(`irc: drop DM sender ${senderDisplay} (dmPolicy=${dmPolicy})`);
|
|
232
|
-
return;
|
|
236
|
+
return { kind: "completed" };
|
|
233
237
|
}
|
|
234
238
|
if (access.ingress.admission === "skip") {
|
|
235
239
|
runtime.log?.(`irc: drop channel ${message.target} (missing-mention)`);
|
|
236
|
-
return;
|
|
240
|
+
return { kind: "completed" };
|
|
237
241
|
}
|
|
238
242
|
if (access.ingress.admission !== "dispatch") {
|
|
239
243
|
if (message.isGroup && access.ingress.decisiveGateId === "command" && access.commandAccess.shouldBlockControlCommand) {
|
|
@@ -243,29 +247,31 @@ async function handleIrcInbound(params) {
|
|
|
243
247
|
reason: "control command (unauthorized)",
|
|
244
248
|
target: senderDisplay
|
|
245
249
|
});
|
|
246
|
-
return;
|
|
250
|
+
return { kind: "completed" };
|
|
247
251
|
}
|
|
248
252
|
if (message.isGroup) if (access.routeAccess.reason === "channel_not_allowlisted") runtime.log?.(`irc: drop channel ${message.target} (not allowlisted)`);
|
|
249
253
|
else if (access.routeAccess.reason === "channel_disabled") runtime.log?.(`irc: drop channel ${message.target} (disabled)`);
|
|
250
254
|
else runtime.log?.(`irc: drop group sender ${senderDisplay} (policy=${groupPolicy})`);
|
|
251
255
|
else runtime.log?.(`irc: drop DM sender ${senderDisplay} (dmPolicy=${dmPolicy})`);
|
|
252
|
-
return;
|
|
256
|
+
return { kind: "completed" };
|
|
253
257
|
}
|
|
258
|
+
if (turnAdoptionLifecycle?.abortSignal.aborted) return {
|
|
259
|
+
kind: "failed-retryable",
|
|
260
|
+
error: turnAdoptionLifecycle.abortSignal.reason
|
|
261
|
+
};
|
|
254
262
|
const channelTarget = message.target.startsWith("#") || message.target.startsWith("&") ? message.target : `#${message.target}`;
|
|
255
263
|
const peerId = message.isGroup ? channelTarget : message.senderNick;
|
|
256
|
-
const { route, buildEnvelope } =
|
|
264
|
+
const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
|
|
257
265
|
cfg: config,
|
|
258
266
|
channel: CHANNEL_ID,
|
|
259
267
|
accountId: account.accountId,
|
|
260
268
|
peer: {
|
|
261
269
|
kind: message.isGroup ? "group" : "direct",
|
|
262
270
|
id: peerId
|
|
263
|
-
}
|
|
264
|
-
runtime: core.channel,
|
|
265
|
-
sessionStore: config.session?.store
|
|
271
|
+
}
|
|
266
272
|
});
|
|
267
273
|
const fromLabel = message.isGroup ? message.target : senderDisplay;
|
|
268
|
-
const
|
|
274
|
+
const body = buildEnvelope({
|
|
269
275
|
channel: "IRC",
|
|
270
276
|
from: fromLabel,
|
|
271
277
|
timestamp: message.timestamp,
|
|
@@ -273,39 +279,74 @@ async function handleIrcInbound(params) {
|
|
|
273
279
|
});
|
|
274
280
|
const groupSystemPrompt = normalizeOptionalString(groupMatch.groupConfig?.systemPrompt);
|
|
275
281
|
const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
|
|
276
|
-
const ctxPayload =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
282
|
+
const ctxPayload = buildChannelInboundEventContext({
|
|
283
|
+
channel: CHANNEL_ID,
|
|
284
|
+
accountId: route.accountId,
|
|
285
|
+
messageId: message.messageId,
|
|
286
|
+
timestamp: message.timestamp,
|
|
287
|
+
from: message.isGroup ? `channel:${channelTarget}` : `irc:${senderDisplay}`,
|
|
288
|
+
sender: {
|
|
289
|
+
id: senderDisplay,
|
|
290
|
+
name: message.senderNick || void 0
|
|
291
|
+
},
|
|
292
|
+
conversation: {
|
|
293
|
+
kind: message.isGroup ? "group" : "direct",
|
|
294
|
+
id: peerId,
|
|
295
|
+
label: fromLabel
|
|
296
|
+
},
|
|
297
|
+
route: {
|
|
298
|
+
agentId: route.agentId,
|
|
299
|
+
dmScope: route.dmScope,
|
|
300
|
+
accountId: route.accountId,
|
|
301
|
+
routeSessionKey: route.sessionKey
|
|
302
|
+
},
|
|
303
|
+
reply: {
|
|
304
|
+
to: message.isGroup ? `channel:${channelTarget}` : `irc:${peerId}`,
|
|
305
|
+
originatingTo: message.isGroup ? `channel:${channelTarget}` : `irc:${peerId}`
|
|
306
|
+
},
|
|
307
|
+
message: {
|
|
308
|
+
body,
|
|
309
|
+
bodyForAgent: rawBody,
|
|
310
|
+
rawBody,
|
|
311
|
+
commandBody: rawBody
|
|
312
|
+
},
|
|
313
|
+
access: {
|
|
314
|
+
commands: { authorized: commandAuthorized },
|
|
315
|
+
mentions: {
|
|
316
|
+
canDetectMention: message.isGroup,
|
|
317
|
+
wasMentioned
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
extra: {
|
|
321
|
+
GroupSubject: message.isGroup ? message.target : void 0,
|
|
322
|
+
GroupSystemPrompt: message.isGroup ? groupSystemPrompt : void 0
|
|
323
|
+
}
|
|
298
324
|
});
|
|
299
|
-
|
|
325
|
+
const ingressState = { handoff: "none" };
|
|
326
|
+
const trackedIngressLifecycle = turnAdoptionLifecycle ? {
|
|
327
|
+
...turnAdoptionLifecycle,
|
|
328
|
+
onAdopted: async () => {
|
|
329
|
+
ingressState.handoff = "adopted";
|
|
330
|
+
await turnAdoptionLifecycle.onAdopted();
|
|
331
|
+
},
|
|
332
|
+
onDeferred: () => {
|
|
333
|
+
ingressState.handoff = "deferred";
|
|
334
|
+
turnAdoptionLifecycle.onDeferred();
|
|
335
|
+
},
|
|
336
|
+
onAbandoned: async () => {
|
|
337
|
+
ingressState.handoff = "abandoned";
|
|
338
|
+
await turnAdoptionLifecycle.onAbandoned();
|
|
339
|
+
}
|
|
340
|
+
} : void 0;
|
|
341
|
+
await core.channel.inbound.dispatch({
|
|
300
342
|
cfg: config,
|
|
301
343
|
channel: CHANNEL_ID,
|
|
302
344
|
accountId: account.accountId,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
345
|
+
route: {
|
|
346
|
+
agentId: route.agentId,
|
|
347
|
+
sessionKey: route.sessionKey
|
|
348
|
+
},
|
|
306
349
|
ctxPayload,
|
|
307
|
-
recordInboundSession: core.channel.session.recordInboundSession,
|
|
308
|
-
dispatchReplyWithBufferedBlockDispatcher: core.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
|
|
309
350
|
delivery: {
|
|
310
351
|
deliver: async (payload) => {
|
|
311
352
|
await deliverIrcReply({
|
|
@@ -323,6 +364,7 @@ async function handleIrcInbound(params) {
|
|
|
323
364
|
},
|
|
324
365
|
replyPipeline: {},
|
|
325
366
|
replyOptions: {
|
|
367
|
+
...trackedIngressLifecycle ? bindIngressLifecycleToReplyOptions(trackedIngressLifecycle) : {},
|
|
326
368
|
skillFilter: groupMatch.groupConfig?.skills,
|
|
327
369
|
disableBlockStreaming: typeof blockStreamingEnabled === "boolean" ? !blockStreamingEnabled : void 0
|
|
328
370
|
},
|
|
@@ -330,23 +372,162 @@ async function handleIrcInbound(params) {
|
|
|
330
372
|
runtime.error?.(`irc: failed updating session meta: ${String(err)}`);
|
|
331
373
|
} }
|
|
332
374
|
});
|
|
375
|
+
if (turnAdoptionLifecycle?.abortSignal.aborted && ingressState.handoff === "none") return {
|
|
376
|
+
kind: "failed-retryable",
|
|
377
|
+
error: turnAdoptionLifecycle.abortSignal.reason
|
|
378
|
+
};
|
|
379
|
+
if (turnAdoptionLifecycle && ingressState.handoff === "none") {
|
|
380
|
+
await turnAdoptionLifecycle.onAdopted();
|
|
381
|
+
ingressState.handoff = "adopted";
|
|
382
|
+
}
|
|
383
|
+
return ingressState.handoff === "deferred" || ingressState.handoff === "abandoned" ? { kind: "deferred" } : { kind: "completed" };
|
|
333
384
|
}
|
|
334
385
|
//#endregion
|
|
335
|
-
//#region extensions/irc/src/
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
386
|
+
//#region extensions/irc/src/irc-ingress.ts
|
|
387
|
+
const IRC_INGRESS_PAYLOAD_VERSION = 1;
|
|
388
|
+
const IRC_INGRESS_POLL_INTERVAL_MS = 1e3;
|
|
389
|
+
const IRC_INGRESS_PRUNE_INTERVAL_MS = 3600 * 1e3;
|
|
390
|
+
const IRC_INGRESS_TOMBSTONE_TTL_MS = 720 * 60 * 60 * 1e3;
|
|
391
|
+
const IRC_INGRESS_TOMBSTONE_MAX_ENTRIES = 1e3;
|
|
392
|
+
var IrcIngressPayloadError = class extends Error {
|
|
393
|
+
constructor(message) {
|
|
394
|
+
super(message);
|
|
395
|
+
this.name = "IrcIngressPayloadError";
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
function inspectRawPrivmsg(rawLine) {
|
|
399
|
+
const line = parseIrcLine(rawLine);
|
|
400
|
+
if (!line || line.command !== "PRIVMSG") throw new IrcIngressPayloadError("IRC ingress row is not a PRIVMSG line.");
|
|
401
|
+
const rawTarget = line.params[0]?.trim() ?? "";
|
|
402
|
+
const text = line.trailing ?? line.params[1] ?? "";
|
|
403
|
+
const prefix = parseIrcPrefix(line.prefix);
|
|
404
|
+
const senderNick = prefix.nick?.trim() ?? "";
|
|
405
|
+
if (!rawTarget || !senderNick || !text.trim()) throw new IrcIngressPayloadError("IRC PRIVMSG line is missing target, sender, or text.");
|
|
406
|
+
const isGroup = isChannelTarget(rawTarget);
|
|
407
|
+
const target = isGroup ? rawTarget : senderNick;
|
|
408
|
+
const lanePeer = normalizeLowercaseStringOrEmpty(target);
|
|
409
|
+
return {
|
|
410
|
+
laneKey: `${isGroup ? "channel" : "direct"}:${lanePeer}`,
|
|
411
|
+
message: {
|
|
412
|
+
target,
|
|
413
|
+
rawTarget,
|
|
414
|
+
senderNick,
|
|
415
|
+
senderUser: prefix.user?.trim() || void 0,
|
|
416
|
+
senderHost: prefix.host?.trim() || void 0,
|
|
417
|
+
text,
|
|
418
|
+
isGroup
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function decodeIrcIngressPayload(payload, claimedId) {
|
|
423
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload) || typeof payload.eventId !== "string" || !Number.isSafeInteger(payload.receivedAt) || (payload.receivedAt ?? 0) <= 0 || typeof payload.connectionEpoch !== "string" || !payload.connectionEpoch?.trim() || typeof payload.connectedNick !== "string" || !payload.connectedNick?.trim() || typeof payload.rawLine !== "string") throw new IrcIngressPayloadError(`IRC ingress row ${claimedId} has invalid metadata.`);
|
|
424
|
+
const validPayload = payload;
|
|
425
|
+
return {
|
|
426
|
+
version: validPayload.version,
|
|
427
|
+
body: {
|
|
428
|
+
eventId: validPayload.eventId,
|
|
429
|
+
receivedAt: validPayload.receivedAt,
|
|
430
|
+
connectionEpoch: validPayload.connectionEpoch,
|
|
431
|
+
connectedNick: validPayload.connectedNick,
|
|
432
|
+
rawLine: validPayload.rawLine
|
|
433
|
+
}
|
|
343
434
|
};
|
|
435
|
+
}
|
|
436
|
+
function inspectIrcIngress(raw, phase) {
|
|
437
|
+
try {
|
|
438
|
+
return {
|
|
439
|
+
eventId: raw.eventId,
|
|
440
|
+
laneKey: inspectRawPrivmsg(raw.rawLine).laneKey
|
|
441
|
+
};
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (phase === "admission" && error instanceof IrcIngressPayloadError) return {
|
|
444
|
+
eventId: raw.eventId,
|
|
445
|
+
laneKey: `invalid:${raw.eventId}`
|
|
446
|
+
};
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function resolveIrcIngressNonRetryableFailure(error) {
|
|
451
|
+
return error instanceof IrcIngressPayloadError ? {
|
|
452
|
+
reason: "invalid-event",
|
|
453
|
+
message: error.message
|
|
454
|
+
} : null;
|
|
455
|
+
}
|
|
456
|
+
function createIrcIngressMonitor(options) {
|
|
457
|
+
const monitor = createChannelIngressMonitor({
|
|
458
|
+
queue: options.queue ?? (() => getIrcRuntime().state.openChannelIngressQueue({ accountId: options.accountId })),
|
|
459
|
+
inspect: (raw, context) => inspectIrcIngress(raw, context.phase),
|
|
460
|
+
payload: {
|
|
461
|
+
version: IRC_INGRESS_PAYLOAD_VERSION,
|
|
462
|
+
serialize: (raw) => raw,
|
|
463
|
+
deserialize: (body) => body,
|
|
464
|
+
encode: ({ body }) => ({
|
|
465
|
+
version: IRC_INGRESS_PAYLOAD_VERSION,
|
|
466
|
+
...body
|
|
467
|
+
}),
|
|
468
|
+
decode: (payload, { claim }) => decodeIrcIngressPayload(payload, claim.id),
|
|
469
|
+
createClaimError: (_kind, claim) => new IrcIngressPayloadError(`IRC ingress row ${claim.id} has invalid metadata.`)
|
|
470
|
+
},
|
|
471
|
+
deliver: (raw, lifecycle, claim) => {
|
|
472
|
+
const message = {
|
|
473
|
+
...inspectRawPrivmsg(raw.rawLine).message,
|
|
474
|
+
messageId: claim.id,
|
|
475
|
+
timestamp: raw.receivedAt
|
|
476
|
+
};
|
|
477
|
+
return options.dispatch(message, lifecycle, {
|
|
478
|
+
connectedNick: raw.connectedNick.trim(),
|
|
479
|
+
connectionEpoch: raw.connectionEpoch.trim()
|
|
480
|
+
});
|
|
481
|
+
},
|
|
482
|
+
pollIntervalMs: options.pollIntervalMs ?? IRC_INGRESS_POLL_INTERVAL_MS,
|
|
483
|
+
retention: {
|
|
484
|
+
pruneIntervalMs: IRC_INGRESS_PRUNE_INTERVAL_MS,
|
|
485
|
+
completedTtlMs: IRC_INGRESS_TOMBSTONE_TTL_MS,
|
|
486
|
+
completedMaxEntries: IRC_INGRESS_TOMBSTONE_MAX_ENTRIES,
|
|
487
|
+
failedTtlMs: IRC_INGRESS_TOMBSTONE_TTL_MS,
|
|
488
|
+
failedMaxEntries: IRC_INGRESS_TOMBSTONE_MAX_ENTRIES
|
|
489
|
+
},
|
|
490
|
+
drain: {
|
|
491
|
+
resolveNonRetryableFailure: resolveIrcIngressNonRetryableFailure,
|
|
492
|
+
...options.adoptionStallTimeoutMs === void 0 ? {} : { adoptionStallTimeoutMs: options.adoptionStallTimeoutMs },
|
|
493
|
+
onLog: (message) => options.runtime.log?.(`irc ${message}`)
|
|
494
|
+
},
|
|
495
|
+
createStoppedError: () => /* @__PURE__ */ new Error("IRC ingress is stopped."),
|
|
496
|
+
onError: (error) => options.runtime.error?.(`irc ingress drain failed: ${String(error)}`)
|
|
497
|
+
});
|
|
344
498
|
return {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
499
|
+
openConnection: (connectionEpoch = randomUUID()) => {
|
|
500
|
+
const epoch = connectionEpoch.trim();
|
|
501
|
+
if (!epoch) throw new Error("IRC ingress connection epoch is required.");
|
|
502
|
+
let sequence = 0;
|
|
503
|
+
return {
|
|
504
|
+
connectionEpoch: epoch,
|
|
505
|
+
accept: (rawLine, connectedNick) => {
|
|
506
|
+
if (monitor.isStopped()) return Promise.reject(/* @__PURE__ */ new Error("IRC ingress is stopped."));
|
|
507
|
+
sequence += 1;
|
|
508
|
+
const eventId = `local:${epoch}:${String(sequence).padStart(12, "0")}`;
|
|
509
|
+
const receivedAt = Date.now();
|
|
510
|
+
const normalizedNick = connectedNick.trim();
|
|
511
|
+
if (!normalizedNick) return Promise.reject(/* @__PURE__ */ new Error("IRC ingress connected nickname is required."));
|
|
512
|
+
return monitor.admit({
|
|
513
|
+
eventId,
|
|
514
|
+
rawLine,
|
|
515
|
+
receivedAt,
|
|
516
|
+
connectionEpoch: epoch,
|
|
517
|
+
connectedNick: normalizedNick
|
|
518
|
+
}, { receivedAt }).then(() => void 0);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
},
|
|
522
|
+
start: monitor.start,
|
|
523
|
+
pause: monitor.pause,
|
|
524
|
+
stop: monitor.stop,
|
|
525
|
+
waitForIdle: monitor.waitForIdle
|
|
348
526
|
};
|
|
349
527
|
}
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region extensions/irc/src/monitor.ts
|
|
530
|
+
const IRC_MONITOR_RECONNECT_DELAY_MS = 1e3;
|
|
350
531
|
async function monitorIrcProvider(opts) {
|
|
351
532
|
const core = getIrcRuntime();
|
|
352
533
|
const cfg = opts.config ?? core.config.current();
|
|
@@ -361,6 +542,8 @@ async function monitorIrcProvider(opts) {
|
|
|
361
542
|
accountId: account.accountId
|
|
362
543
|
});
|
|
363
544
|
let client = null;
|
|
545
|
+
let activeConnectionEpoch = null;
|
|
546
|
+
let ingressPause = Promise.resolve();
|
|
364
547
|
let reconnectTimer = null;
|
|
365
548
|
let stopped = false;
|
|
366
549
|
const monitorAbort = new AbortController();
|
|
@@ -373,6 +556,48 @@ async function monitorIrcProvider(opts) {
|
|
|
373
556
|
removeAbortListener = () => opts.abortSignal?.removeEventListener("abort", forwardAbort);
|
|
374
557
|
}
|
|
375
558
|
}
|
|
559
|
+
const ingress = createIrcIngressMonitor({
|
|
560
|
+
accountId: account.accountId,
|
|
561
|
+
runtime,
|
|
562
|
+
...opts.ingressQueue ? { queue: opts.ingressQueue } : {},
|
|
563
|
+
dispatch: async (message, turnAdoptionLifecycle, context) => {
|
|
564
|
+
const activeClient = client;
|
|
565
|
+
if (!activeClient || stopped || monitorAbort.signal.aborted) return {
|
|
566
|
+
kind: "failed-retryable",
|
|
567
|
+
error: /* @__PURE__ */ new Error("IRC transport disconnected before ingress dispatch.")
|
|
568
|
+
};
|
|
569
|
+
if (normalizeLowercaseStringOrEmpty(message.senderNick) === normalizeLowercaseStringOrEmpty(context.connectedNick)) return { kind: "completed" };
|
|
570
|
+
if (!message.isGroup && context.connectionEpoch !== activeConnectionEpoch) {
|
|
571
|
+
logger.warn?.(`[${account.accountId}] dropping replayed IRC DM after the connection changed`);
|
|
572
|
+
return { kind: "completed" };
|
|
573
|
+
}
|
|
574
|
+
if (opts.onMessage) {
|
|
575
|
+
await opts.onMessage(message, activeClient);
|
|
576
|
+
return { kind: "completed" };
|
|
577
|
+
}
|
|
578
|
+
return await handleIrcInbound({
|
|
579
|
+
message,
|
|
580
|
+
account,
|
|
581
|
+
config: cfg,
|
|
582
|
+
runtime,
|
|
583
|
+
connectedNick: context.connectedNick,
|
|
584
|
+
turnAdoptionLifecycle,
|
|
585
|
+
sendReply: async (target, text) => {
|
|
586
|
+
const replyClient = client;
|
|
587
|
+
if (!replyClient || !replyClient.isReady() || stopped || monitorAbort.signal.aborted) throw new Error("IRC transport disconnected before reply send.");
|
|
588
|
+
if (!message.isGroup && context.connectionEpoch !== activeConnectionEpoch) throw new Error("IRC connection changed before private reply send.");
|
|
589
|
+
replyClient.sendPrivmsg(target, text);
|
|
590
|
+
opts.statusSink?.({ lastOutboundAt: Date.now() });
|
|
591
|
+
core.channel.activity.record({
|
|
592
|
+
channel: "irc",
|
|
593
|
+
accountId: account.accountId,
|
|
594
|
+
direction: "outbound"
|
|
595
|
+
});
|
|
596
|
+
},
|
|
597
|
+
statusSink: opts.statusSink
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
});
|
|
376
601
|
function scheduleReconnect() {
|
|
377
602
|
if (stopped || monitorAbort.signal.aborted || reconnectTimer) return;
|
|
378
603
|
reconnectTimer = setTimeout(() => {
|
|
@@ -387,6 +612,7 @@ async function monitorIrcProvider(opts) {
|
|
|
387
612
|
}
|
|
388
613
|
async function connect() {
|
|
389
614
|
if (stopped || monitorAbort.signal.aborted) return;
|
|
615
|
+
const ingressConnection = ingress.openConnection();
|
|
390
616
|
const nextClient = await connectIrcClient(buildIrcConnectOptions(account, {
|
|
391
617
|
channels: account.config.channels,
|
|
392
618
|
abortSignal: monitorAbort.signal,
|
|
@@ -401,54 +627,20 @@ async function monitorIrcProvider(opts) {
|
|
|
401
627
|
},
|
|
402
628
|
onDisconnect: () => {
|
|
403
629
|
if (stopped || monitorAbort.signal.aborted) return;
|
|
630
|
+
ingressPause = ingress.pause();
|
|
631
|
+
if (activeConnectionEpoch === ingressConnection.connectionEpoch) activeConnectionEpoch = null;
|
|
404
632
|
client = null;
|
|
405
633
|
logger.warn?.(`[${account.accountId}] IRC connection closed; reconnecting in ${IRC_MONITOR_RECONNECT_DELAY_MS}ms`);
|
|
406
634
|
scheduleReconnect();
|
|
407
635
|
},
|
|
408
636
|
onPrivmsg: async (event) => {
|
|
409
|
-
|
|
410
|
-
if (normalizeLowercaseStringOrEmpty(event.senderNick) === normalizeLowercaseStringOrEmpty(
|
|
411
|
-
const inboundTarget = resolveIrcInboundTarget({
|
|
412
|
-
target: event.target,
|
|
413
|
-
senderNick: event.senderNick
|
|
414
|
-
});
|
|
415
|
-
const message = {
|
|
416
|
-
messageId: makeIrcMessageId(),
|
|
417
|
-
target: inboundTarget.target,
|
|
418
|
-
rawTarget: inboundTarget.rawTarget,
|
|
419
|
-
senderNick: event.senderNick,
|
|
420
|
-
senderUser: event.senderUser,
|
|
421
|
-
senderHost: event.senderHost,
|
|
422
|
-
text: event.text,
|
|
423
|
-
timestamp: Date.now(),
|
|
424
|
-
isGroup: inboundTarget.isGroup
|
|
425
|
-
};
|
|
637
|
+
await ingressConnection.accept(event.rawLine, event.connectedNick);
|
|
638
|
+
if (normalizeLowercaseStringOrEmpty(event.senderNick) === normalizeLowercaseStringOrEmpty(event.connectedNick)) return;
|
|
426
639
|
core.channel.activity.record({
|
|
427
640
|
channel: "irc",
|
|
428
641
|
accountId: account.accountId,
|
|
429
642
|
direction: "inbound",
|
|
430
|
-
at:
|
|
431
|
-
});
|
|
432
|
-
if (opts.onMessage) {
|
|
433
|
-
await opts.onMessage(message, client);
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
await handleIrcInbound({
|
|
437
|
-
message,
|
|
438
|
-
account,
|
|
439
|
-
config: cfg,
|
|
440
|
-
runtime,
|
|
441
|
-
connectedNick: client.nick,
|
|
442
|
-
sendReply: async (target, text) => {
|
|
443
|
-
client?.sendPrivmsg(target, text);
|
|
444
|
-
opts.statusSink?.({ lastOutboundAt: Date.now() });
|
|
445
|
-
core.channel.activity.record({
|
|
446
|
-
channel: "irc",
|
|
447
|
-
accountId: account.accountId,
|
|
448
|
-
direction: "outbound"
|
|
449
|
-
});
|
|
450
|
-
},
|
|
451
|
-
statusSink: opts.statusSink
|
|
643
|
+
at: Date.now()
|
|
452
644
|
});
|
|
453
645
|
}
|
|
454
646
|
}));
|
|
@@ -457,20 +649,40 @@ async function monitorIrcProvider(opts) {
|
|
|
457
649
|
return;
|
|
458
650
|
}
|
|
459
651
|
client = nextClient;
|
|
652
|
+
activeConnectionEpoch = ingressConnection.connectionEpoch;
|
|
653
|
+
await ingressPause;
|
|
654
|
+
if (client !== nextClient || !nextClient.isReady()) {
|
|
655
|
+
if (activeConnectionEpoch === ingressConnection.connectionEpoch) activeConnectionEpoch = null;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
ingress.start();
|
|
460
659
|
logger.info(`[${account.accountId}] connected to ${account.host}:${account.port}${account.tls ? " (tls)" : ""} as ${nextClient.nick}`);
|
|
461
660
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
661
|
+
try {
|
|
662
|
+
await connect();
|
|
663
|
+
} catch (error) {
|
|
465
664
|
removeAbortListener?.();
|
|
466
665
|
removeAbortListener = null;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
666
|
+
await ingress.stop();
|
|
667
|
+
throw error;
|
|
668
|
+
}
|
|
669
|
+
let stopTask;
|
|
670
|
+
return { stop: () => {
|
|
671
|
+
stopTask ??= (async () => {
|
|
672
|
+
stopped = true;
|
|
673
|
+
removeAbortListener?.();
|
|
674
|
+
removeAbortListener = null;
|
|
675
|
+
if (!monitorAbort.signal.aborted) monitorAbort.abort();
|
|
676
|
+
if (reconnectTimer) {
|
|
677
|
+
clearTimeout(reconnectTimer);
|
|
678
|
+
reconnectTimer = null;
|
|
679
|
+
}
|
|
680
|
+
client?.quit("shutdown");
|
|
681
|
+
client = null;
|
|
682
|
+
activeConnectionEpoch = null;
|
|
683
|
+
await ingress.stop();
|
|
684
|
+
})();
|
|
685
|
+
return stopTask;
|
|
474
686
|
} };
|
|
475
687
|
}
|
|
476
688
|
//#endregion
|
|
@@ -28,6 +28,7 @@ function collectRuntimeConfigAssignments(params) {
|
|
|
28
28
|
defaults: params.defaults,
|
|
29
29
|
context: params.context,
|
|
30
30
|
topLevelActive: isBaseFieldActiveForChannelSurface(surface, "nickserv") && isRecord(irc.nickserv) && isEnabledFlag(irc.nickserv),
|
|
31
|
+
topLevelInheritedAccountActive: ({ account, enabled }) => enabled && !Object.hasOwn(account, "nickserv") && isEnabledFlag(irc.nickserv),
|
|
31
32
|
topInactiveReason: "no enabled account inherits this top-level IRC nickserv config or NickServ is disabled.",
|
|
32
33
|
accountActive: ({ account, enabled }) => enabled && isRecord(account.nickserv) && isEnabledFlag(account.nickserv),
|
|
33
34
|
accountInactiveReason: "IRC account is disabled or NickServ is disabled for this account."
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-
|
|
1
|
+
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries, t as channelSecrets } from "./secret-contract-D_DC3YWj.js";
|
|
2
2
|
export { channelSecrets, collectRuntimeConfigAssignments, secretTargetRegistryEntries };
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/irc",
|
|
3
|
-
"version": "2026.7.2-beta.
|
|
3
|
+
"version": "2026.7.2-beta.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/irc",
|
|
9
|
-
"version": "2026.7.2-beta.
|
|
9
|
+
"version": "2026.7.2-beta.4",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"zod": "4.4.3"
|
|
12
12
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -6,20 +6,6 @@
|
|
|
6
6
|
"channels": [
|
|
7
7
|
"irc"
|
|
8
8
|
],
|
|
9
|
-
"channelEnvVars": {
|
|
10
|
-
"irc": [
|
|
11
|
-
"IRC_HOST",
|
|
12
|
-
"IRC_PORT",
|
|
13
|
-
"IRC_TLS",
|
|
14
|
-
"IRC_NICK",
|
|
15
|
-
"IRC_USERNAME",
|
|
16
|
-
"IRC_REALNAME",
|
|
17
|
-
"IRC_PASSWORD",
|
|
18
|
-
"IRC_CHANNELS",
|
|
19
|
-
"IRC_NICKSERV_PASSWORD",
|
|
20
|
-
"IRC_NICKSERV_REGISTER_EMAIL"
|
|
21
|
-
]
|
|
22
|
-
},
|
|
23
9
|
"configSchema": {
|
|
24
10
|
"type": "object",
|
|
25
11
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/irc",
|
|
3
|
-
"version": "2026.7.2-beta.
|
|
3
|
+
"version": "2026.7.2-beta.4",
|
|
4
4
|
"description": "OpenClaw IRC channel plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"openclaw": {
|
|
@@ -15,8 +15,19 @@
|
|
|
15
15
|
"allowInvalidConfigRecovery": true
|
|
16
16
|
},
|
|
17
17
|
"setupEntry": "./dist/setup-entry.js",
|
|
18
|
+
"setupFeatures": {
|
|
19
|
+
"configPromotion": true
|
|
20
|
+
},
|
|
18
21
|
"channel": {
|
|
19
22
|
"id": "irc",
|
|
23
|
+
"configuredState": {
|
|
24
|
+
"env": {
|
|
25
|
+
"allOf": [
|
|
26
|
+
"IRC_HOST",
|
|
27
|
+
"IRC_NICK"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
20
31
|
"label": "IRC",
|
|
21
32
|
"selectionLabel": "IRC (Server + Nick)",
|
|
22
33
|
"detailLabel": "IRC",
|
|
@@ -27,20 +38,89 @@
|
|
|
27
38
|
"internet-relay-chat"
|
|
28
39
|
],
|
|
29
40
|
"systemImage": "network",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
"setup": {
|
|
42
|
+
"fields": [
|
|
43
|
+
{
|
|
44
|
+
"key": "host",
|
|
45
|
+
"kind": "string",
|
|
46
|
+
"cli": {
|
|
47
|
+
"flags": "--host <host>",
|
|
48
|
+
"description": "IRC server host"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"key": "port",
|
|
53
|
+
"kind": "string",
|
|
54
|
+
"cli": {
|
|
55
|
+
"flags": "--port <port>",
|
|
56
|
+
"description": "IRC server port"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"key": "tls",
|
|
61
|
+
"kind": "boolean",
|
|
62
|
+
"cli": {
|
|
63
|
+
"flags": "--tls",
|
|
64
|
+
"description": "Use TLS for IRC"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"key": "nick",
|
|
69
|
+
"kind": "string",
|
|
70
|
+
"cli": {
|
|
71
|
+
"flags": "--nick <nick>",
|
|
72
|
+
"description": "IRC nickname"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"key": "username",
|
|
77
|
+
"kind": "string",
|
|
78
|
+
"cli": {
|
|
79
|
+
"flags": "--username <name>",
|
|
80
|
+
"description": "IRC username"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"key": "realname",
|
|
85
|
+
"kind": "string",
|
|
86
|
+
"cli": {
|
|
87
|
+
"flags": "--realname <name>",
|
|
88
|
+
"description": "IRC real name"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"key": "channels",
|
|
93
|
+
"kind": "string-list",
|
|
94
|
+
"cli": {
|
|
95
|
+
"flags": "--channels <names>",
|
|
96
|
+
"description": "IRC channels"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"key": "password",
|
|
101
|
+
"kind": "string",
|
|
102
|
+
"sensitive": true,
|
|
103
|
+
"cli": {
|
|
104
|
+
"flags": "--password <password>",
|
|
105
|
+
"description": "IRC server password"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"key": "useEnv",
|
|
110
|
+
"kind": "boolean",
|
|
111
|
+
"cli": {
|
|
112
|
+
"flags": "--use-env",
|
|
113
|
+
"description": "Use IRC environment configuration"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]
|
|
37
117
|
}
|
|
38
118
|
},
|
|
39
119
|
"compat": {
|
|
40
|
-
"pluginApi": ">=2026.7.2-beta.
|
|
120
|
+
"pluginApi": ">=2026.7.2-beta.4"
|
|
41
121
|
},
|
|
42
122
|
"build": {
|
|
43
|
-
"openclawVersion": "2026.7.2-beta.
|
|
123
|
+
"openclawVersion": "2026.7.2-beta.4",
|
|
44
124
|
"bundledDist": false
|
|
45
125
|
},
|
|
46
126
|
"release": {
|
|
@@ -66,7 +146,7 @@
|
|
|
66
146
|
"README.md"
|
|
67
147
|
],
|
|
68
148
|
"peerDependencies": {
|
|
69
|
-
"openclaw": ">=2026.7.2-beta.
|
|
149
|
+
"openclaw": ">=2026.7.2-beta.4"
|
|
70
150
|
},
|
|
71
151
|
"peerDependenciesMeta": {
|
|
72
152
|
"openclaw": {
|