@openclaw/irc 2026.9.6 → 2026.9.7

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/assets/icon.png CHANGED
Binary file
@@ -2,42 +2,36 @@ import { t as IrcChannelConfigSchema } from "./config-schema-Cy9tItsu.mjs";
2
2
  import { t as getIrcRuntime } from "./runtime-xy-FcjJC.mjs";
3
3
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-D_DC3YWj.mjs";
4
4
  import { createAccountListHelpers, describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
5
+ import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
5
6
  import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
6
7
  import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
7
8
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin, parseOptionalDelimitedEntries } from "openclaw/plugin-sdk/channel-core";
8
9
  import { defineStableChannelIngressIdentity, identityEntryAuthenticationClassifier } from "openclaw/plugin-sdk/channel-ingress-runtime";
9
10
  import { buildMutableAllowEntryDetector, collectStandardAllowlistLists, createAllowlistProviderOpenWarningCollector, createConditionalWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, resolveScopeKeyCaseInsensitive, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
11
+ import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
10
12
  import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister } from "openclaw/plugin-sdk/directory-runtime";
11
13
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
12
14
  import { buildBaseChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
13
15
  import { resolveAccountWithDefaultFallback } from "openclaw/plugin-sdk/account-core";
14
- import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
15
- import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
16
+ import { parseTcpPort } from "openclaw/plugin-sdk/number-runtime";
16
17
  import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
17
18
  import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
18
19
  import { resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
19
20
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringifiedOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
20
21
  import { createAccountStatusSink, createMessageReceiptFromOutboundResults, createReplyToFanout, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
21
- import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
22
- import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
23
22
  import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
23
+ import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
24
+ import { randomUUID } from "node:crypto";
24
25
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
25
26
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
26
27
  import net from "node:net";
27
28
  import tls from "node:tls";
28
29
  import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
29
- import { randomUUID } from "node:crypto";
30
30
  import { assertSecretOwnerAvailable } from "openclaw/plugin-sdk/channel-secret-owner-runtime";
31
31
  import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
32
32
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
33
33
  import { applyAccountNameToChannelSection, createAllowFromSection, createPromptParsedAllowFromForAccount, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicySetter, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
34
34
  //#region extensions/irc/src/accounts.ts
35
- function parseIntEnv(value) {
36
- if (!value?.trim()) return;
37
- const parsed = parseStrictPositiveInteger(value);
38
- if (parsed === void 0 || parsed > 65535) return;
39
- return parsed;
40
- }
41
35
  const { listAccountIds: listIrcAccountIds, resolveDefaultAccountId: resolveDefaultIrcAccountId, resolveAccountConfig: mergeIrcAccountConfig } = createAccountListHelpers("irc", {
42
36
  normalizeAccountId,
43
37
  omitKeys: ["defaultAccount"],
@@ -136,13 +130,13 @@ function resolveIrcAccount(params) {
136
130
  const accountEnabled = merged.enabled !== false;
137
131
  const enabled = baseEnabled && accountEnabled;
138
132
  const tls = typeof merged.tls === "boolean" ? merged.tls : accountId === DEFAULT_ACCOUNT_ID && process.env.IRC_TLS ? isTruthyEnvValue(process.env.IRC_TLS) : true;
139
- const envPort = accountId === DEFAULT_ACCOUNT_ID ? parseIntEnv(process.env.IRC_PORT) : void 0;
133
+ const envPort = accountId === DEFAULT_ACCOUNT_ID ? parseTcpPort(process.env.IRC_PORT) : void 0;
140
134
  const port = merged.port ?? envPort ?? (tls ? 6697 : 6667);
141
135
  const envChannels = accountId === DEFAULT_ACCOUNT_ID ? parseOptionalDelimitedEntries(process.env.IRC_CHANNELS) : void 0;
142
- const host = (merged.host?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_HOST?.trim() : "") || "").trim();
143
- const nick = (merged.nick?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICK?.trim() : "") || "").trim();
144
- const username = (merged.username?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_USERNAME?.trim() : "") || nick || "openclaw").trim();
145
- const realname = (merged.realname?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_REALNAME?.trim() : "") || "OpenClaw").trim();
136
+ const host = merged.host?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_HOST?.trim() : "") || "";
137
+ const nick = merged.nick?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICK?.trim() : "") || "";
138
+ const username = merged.username?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_USERNAME?.trim() : "") || nick || "openclaw";
139
+ const realname = merged.realname?.trim() || (accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_REALNAME?.trim() : "") || "OpenClaw";
146
140
  const passwordResolution = resolvePassword(accountId, merged);
147
141
  const nickservResolution = resolveNickServConfig(accountId, merged.nickserv);
148
142
  const diagnostics = [passwordResolution.diagnostic, nickservResolution.diagnostic].filter((diagnostic) => Boolean(diagnostic));
@@ -202,7 +196,7 @@ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAll
202
196
  });
203
197
  //#endregion
204
198
  //#region extensions/irc/src/gateway.ts
205
- const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-2EUgbrxg.mjs"));
199
+ const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-195XTdZC.mjs"));
206
200
  async function startIrcGatewayAccount(ctx) {
207
201
  const account = ctx.account;
208
202
  const statusSink = createAccountStatusSink({
@@ -474,9 +468,6 @@ function sanitizeIrcTarget(raw) {
474
468
  if (!IRC_TARGET_PATTERN.test(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
475
469
  return decoded;
476
470
  }
477
- function makeIrcMessageId() {
478
- return randomUUID();
479
- }
480
471
  //#endregion
481
472
  //#region extensions/irc/src/client.ts
482
473
  const IRC_ERROR_CODES = /* @__PURE__ */ new Set([
@@ -516,9 +507,6 @@ function buildFallbackNick(nick) {
516
507
  if (base.length >= maxNickLen) return `${base.slice(0, maxNickLen - suffix.length)}${suffix}`;
517
508
  return `${base}${suffix}`;
518
509
  }
519
- function normalizeIrcNick(value) {
520
- return normalizeLowercaseStringOrEmpty(value);
521
- }
522
510
  function buildIrcNickServCommands(options) {
523
511
  if (!options || options.enabled === false) return [];
524
512
  const password = sanitizeIrcOutboundText(options.password ?? "");
@@ -533,7 +521,7 @@ function buildIrcNickServCommands(options) {
533
521
  return commands;
534
522
  }
535
523
  async function connectIrcClient(options) {
536
- const timeoutMs = options.connectTimeoutMs != null ? options.connectTimeoutMs : 15e3;
524
+ const timeoutMs = options.connectTimeoutMs ?? 15e3;
537
525
  const messageChunkMaxChars = Math.max(1, Math.floor(options.messageChunkMaxChars ?? 350));
538
526
  if (!options.host.trim()) throw new Error("IRC host is required");
539
527
  if (!options.nick.trim()) throw new Error("IRC nick is required");
@@ -561,7 +549,7 @@ async function connectIrcClient(options) {
561
549
  });
562
550
  const fail = (err) => {
563
551
  const error = toIrcError(err);
564
- if (options.onError) options.onError(error);
552
+ options.onError?.(error);
565
553
  if (!ready && rejectReady) {
566
554
  rejectReady(error);
567
555
  rejectReady = null;
@@ -594,7 +582,7 @@ async function connectIrcClient(options) {
594
582
  if (!fallbackNickAttempted) {
595
583
  fallbackNickAttempted = true;
596
584
  const fallbackNick = buildFallbackNick(desiredNick);
597
- if (normalizeIrcNick(fallbackNick) !== normalizeIrcNick(currentNick)) try {
585
+ if (normalizeLowercaseStringOrEmpty(fallbackNick) !== normalizeLowercaseStringOrEmpty(currentNick)) try {
598
586
  sendRaw(`NICK ${fallbackNick}`);
599
587
  currentNick = fallbackNick;
600
588
  return true;
@@ -627,7 +615,7 @@ async function connectIrcClient(options) {
627
615
  closed = true;
628
616
  removeAbortListener?.();
629
617
  removeAbortListener = null;
630
- const safeReason = sanitizeIrcOutboundText(reason != null ? reason : "bye");
618
+ const safeReason = sanitizeIrcOutboundText(reason ?? "bye");
631
619
  try {
632
620
  if (safeReason) sendRaw(`QUIT :${safeReason}`);
633
621
  else sendRaw("QUIT");
@@ -650,30 +638,24 @@ async function connectIrcClient(options) {
650
638
  buffer = buffer.slice(idx + 1);
651
639
  idx = buffer.indexOf("\n");
652
640
  if (!rawLine) continue;
653
- if (options.onLine) options.onLine(rawLine);
641
+ options.onLine?.(rawLine);
654
642
  const line = parseIrcLine(rawLine);
655
643
  if (!line) continue;
656
644
  if (line.command === "PING") {
657
- const payload = line.trailing != null ? line.trailing : line.params[0] != null ? line.params[0] : "";
645
+ const payload = line.trailing ?? line.params[0] ?? "";
658
646
  sendRaw(`PONG :${payload}`);
659
647
  continue;
660
648
  }
661
649
  if (line.command === "NICK") {
662
650
  const prefix = parseIrcPrefix(line.prefix);
663
- if (prefix.nick && normalizeIrcNick(prefix.nick) === normalizeIrcNick(currentNick)) currentNick = (line.trailing != null ? line.trailing : line.params[0] != null ? line.params[0] : currentNick).trim();
651
+ if (prefix.nick && normalizeLowercaseStringOrEmpty(prefix.nick) === normalizeLowercaseStringOrEmpty(currentNick)) currentNick = (line.trailing ?? line.params[0] ?? currentNick).trim();
664
652
  continue;
665
653
  }
666
- if (!ready && IRC_NICK_COLLISION_CODES.has(line.command)) {
667
- if (tryRecoverNickCollision()) continue;
668
- const detail = line.trailing != null ? line.trailing : line.params.join(" ") || "nickname in use";
669
- fail(/* @__PURE__ */ new Error(`IRC login failed (${line.command}): ${detail}`));
670
- close();
671
- return;
672
- }
673
- if (!ready && IRC_ERROR_CODES.has(line.command)) {
674
- const detail = line.trailing != null ? line.trailing : line.params.join(" ") || "login rejected";
675
- fail(/* @__PURE__ */ new Error(`IRC login failed (${line.command}): ${detail}`));
676
- close();
654
+ const nickCollision = IRC_NICK_COLLISION_CODES.has(line.command);
655
+ if (!ready && (nickCollision || IRC_ERROR_CODES.has(line.command))) {
656
+ if (nickCollision && tryRecoverNickCollision()) continue;
657
+ const detail = line.trailing ?? (line.params.join(" ") || (nickCollision ? "nickname in use" : "login rejected"));
658
+ failAndClose(/* @__PURE__ */ new Error(`IRC login failed (${line.command}): ${detail}`));
677
659
  return;
678
660
  }
679
661
  if (line.command === "001") {
@@ -695,21 +677,20 @@ async function connectIrcClient(options) {
695
677
  fail(err);
696
678
  }
697
679
  }
698
- if (resolveReady) resolveReady();
680
+ resolveReady?.();
699
681
  resolveReady = null;
700
682
  rejectReady = null;
701
683
  continue;
702
684
  }
703
685
  if (line.command === "NOTICE") {
704
- if (options.onNotice) options.onNotice(line.trailing != null ? line.trailing : "", line.params[0]);
686
+ options.onNotice?.(line.trailing ?? "", line.params[0]);
705
687
  continue;
706
688
  }
707
689
  if (line.command === "PRIVMSG") {
708
- const targetParam = line.params[0];
709
- const target = targetParam ? targetParam.trim() : "";
690
+ const target = line.params[0]?.trim() ?? "";
710
691
  const text = line.trailing ?? line.params[1] ?? "";
711
692
  const prefix = parseIrcPrefix(line.prefix);
712
- const senderNick = prefix.nick ? prefix.nick.trim() : "";
693
+ const senderNick = prefix.nick?.trim() ?? "";
713
694
  if (!target || !senderNick || !text.trim()) continue;
714
695
  if (options.onPrivmsg) Promise.resolve(options.onPrivmsg({
715
696
  senderNick,
@@ -731,8 +712,7 @@ async function connectIrcClient(options) {
731
712
  sendRaw(`NICK ${options.nick.trim()}`);
732
713
  sendRaw(`USER ${options.username.trim()} 0 * :${sanitizeIrcOutboundText(options.realname)}`);
733
714
  } catch (err) {
734
- fail(err);
735
- close();
715
+ failAndClose(err);
736
716
  }
737
717
  });
738
718
  socket.once("error", (err) => {
@@ -854,7 +834,7 @@ async function sendIrcMessages(to, text, opts, planMessages = (preparedText) =>
854
834
  if (!client.isReady()) throw new Error("IRC connection closed before send");
855
835
  client.sendPrivmsg(target, message.text, message.replyTo);
856
836
  recordIrcOutboundActivity(account.accountId);
857
- const messageId = makeIrcMessageId();
837
+ const messageId = randomUUID();
858
838
  const result = {
859
839
  messageId,
860
840
  target,
@@ -1026,18 +1006,10 @@ const validateIrcRequiredSetupInput = createSetupInputPresenceValidator({ whenNo
1026
1006
  someOf: ["nick"],
1027
1007
  message: "IRC requires nick."
1028
1008
  }] });
1029
- function parsePort(raw, fallback) {
1030
- const trimmed = raw.trim();
1031
- if (!trimmed) return fallback;
1032
- const parsed = parseStrictPositiveInteger(trimmed);
1033
- if (parsed === void 0 || parsed > 65535) return fallback;
1034
- return parsed;
1035
- }
1036
1009
  function validateIrcPortInput(input) {
1037
1010
  const raw = input.port;
1038
1011
  if (raw === void 0 || raw === null || raw === "") return null;
1039
- const parsed = parseStrictPositiveInteger(String(raw));
1040
- return parsed !== void 0 && parsed <= 65535 ? null : "IRC port must be between 1 and 65535.";
1012
+ return parseTcpPort(String(raw)) !== null ? null : "IRC port must be between 1 and 65535.";
1041
1013
  }
1042
1014
  function updateIrcAccountConfig(cfg, accountId, patch) {
1043
1015
  return patchScopedAccountConfig({
@@ -1092,7 +1064,7 @@ const ircSetupAdapter = {
1092
1064
  const patch = {
1093
1065
  enabled: true,
1094
1066
  host: setupInput.host?.trim(),
1095
- port: portInput ? parsePort(portInput, setupInput.tls === false ? 6667 : 6697) : void 0,
1067
+ port: portInput ? parseTcpPort(portInput) ?? (setupInput.tls === false ? 6667 : 6697) : void 0,
1096
1068
  tls: setupInput.tls,
1097
1069
  nick: setupInput.nick?.trim(),
1098
1070
  username: setupInput.username?.trim(),
@@ -1184,6 +1156,22 @@ const t = createSetupTranslator();
1184
1156
  const channel = "irc";
1185
1157
  const USE_ENV_FLAG = "__ircUseEnv";
1186
1158
  const TLS_FLAG = "__ircTls";
1159
+ function ircAccountTextInput(configKey, input) {
1160
+ return {
1161
+ ...input,
1162
+ currentValue: ({ cfg, accountId }) => resolveIrcAccount({
1163
+ cfg,
1164
+ accountId
1165
+ }).config[configKey] || void 0,
1166
+ shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1",
1167
+ validate: ({ value }) => normalizeStringifiedOptionalString(value) ? void 0 : "Required",
1168
+ normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
1169
+ applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg, accountId, {
1170
+ enabled: true,
1171
+ [configKey]: value
1172
+ })
1173
+ };
1174
+ }
1187
1175
  function parseListInput(raw) {
1188
1176
  return normalizeStringEntries(raw.split(/[\n,;]+/g));
1189
1177
  }
@@ -1338,21 +1326,10 @@ const ircSetupWizard = {
1338
1326
  },
1339
1327
  credentials: [],
1340
1328
  textInputs: [
1341
- {
1329
+ ircAccountTextInput("host", {
1342
1330
  inputKey: "httpHost",
1343
- message: t("wizard.irc.serverHostPrompt"),
1344
- currentValue: ({ cfg, accountId }) => resolveIrcAccount({
1345
- cfg,
1346
- accountId
1347
- }).config.host || void 0,
1348
- shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1",
1349
- validate: ({ value }) => normalizeStringifiedOptionalString(value) ? void 0 : "Required",
1350
- normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
1351
- applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg, accountId, {
1352
- enabled: true,
1353
- host: value
1354
- })
1355
- },
1331
+ message: t("wizard.irc.serverHostPrompt")
1332
+ }),
1356
1333
  {
1357
1334
  inputKey: "httpPort",
1358
1335
  message: t("wizard.irc.serverPortPrompt"),
@@ -1372,68 +1349,34 @@ const ircSetupWizard = {
1372
1349
  },
1373
1350
  validate: ({ value }) => {
1374
1351
  const raw = normalizeStringifiedOptionalString(value) ?? "";
1375
- const parsed = parseStrictPositiveInteger(raw);
1376
- return parsed !== void 0 && parsed <= 65535 ? void 0 : "Use a port between 1 and 65535";
1352
+ return parseTcpPort(raw) !== null ? void 0 : "Use a port between 1 and 65535";
1377
1353
  },
1378
- normalizeValue: ({ value }) => String(parsePort(value, 6697)),
1354
+ normalizeValue: ({ value }) => String(parseTcpPort(value) ?? 6697),
1379
1355
  applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg, accountId, {
1380
1356
  enabled: true,
1381
- port: parsePort(value, 6697)
1357
+ port: parseTcpPort(value) ?? 6697
1382
1358
  })
1383
1359
  },
1384
- {
1360
+ ircAccountTextInput("nick", {
1385
1361
  inputKey: "token",
1386
- message: t("wizard.irc.nickPrompt"),
1387
- currentValue: ({ cfg, accountId }) => resolveIrcAccount({
1388
- cfg,
1389
- accountId
1390
- }).config.nick || void 0,
1391
- shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1",
1392
- validate: ({ value }) => normalizeStringifiedOptionalString(value) ? void 0 : "Required",
1393
- normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
1394
- applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg, accountId, {
1395
- enabled: true,
1396
- nick: value
1397
- })
1398
- },
1399
- {
1362
+ message: t("wizard.irc.nickPrompt")
1363
+ }),
1364
+ ircAccountTextInput("username", {
1400
1365
  inputKey: "userId",
1401
1366
  message: t("wizard.irc.usernamePrompt"),
1402
- currentValue: ({ cfg, accountId }) => resolveIrcAccount({
1403
- cfg,
1404
- accountId
1405
- }).config.username || void 0,
1406
- shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1",
1407
1367
  initialValue: ({ cfg, accountId, credentialValues }) => resolveIrcAccount({
1408
1368
  cfg,
1409
1369
  accountId
1410
- }).config.username || credentialValues.token || "openclaw",
1411
- validate: ({ value }) => normalizeStringifiedOptionalString(value) ? void 0 : "Required",
1412
- normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
1413
- applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg, accountId, {
1414
- enabled: true,
1415
- username: value
1416
- })
1417
- },
1418
- {
1370
+ }).config.username || credentialValues.token || "openclaw"
1371
+ }),
1372
+ ircAccountTextInput("realname", {
1419
1373
  inputKey: "deviceName",
1420
1374
  message: t("wizard.irc.realNamePrompt"),
1421
- currentValue: ({ cfg, accountId }) => resolveIrcAccount({
1422
- cfg,
1423
- accountId
1424
- }).config.realname || void 0,
1425
- shouldPrompt: ({ credentialValues }) => credentialValues[USE_ENV_FLAG] !== "1",
1426
1375
  initialValue: ({ cfg, accountId }) => resolveIrcAccount({
1427
1376
  cfg,
1428
1377
  accountId
1429
- }).config.realname || "OpenClaw",
1430
- validate: ({ value }) => normalizeStringifiedOptionalString(value) ? void 0 : "Required",
1431
- normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
1432
- applySet: async ({ cfg, accountId, value }) => updateIrcAccountConfig(cfg, accountId, {
1433
- enabled: true,
1434
- realname: value
1435
- })
1436
- },
1378
+ }).config.realname || "OpenClaw"
1379
+ }),
1437
1380
  {
1438
1381
  inputKey: "groupChannels",
1439
1382
  message: t("wizard.irc.autoJoinPrompt"),
@@ -1544,7 +1487,7 @@ const meta = {
1544
1487
  systemImage: "number",
1545
1488
  markdownCapable: true
1546
1489
  };
1547
- const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-2EUgbrxg.mjs"));
1490
+ const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-195XTdZC.mjs"));
1548
1491
  function normalizePairingTarget(raw) {
1549
1492
  const normalized = normalizeIrcAllowEntry(raw);
1550
1493
  if (!normalized) return "";
@@ -1697,7 +1640,7 @@ const ircPlugin = createChatChannelPlugin({
1697
1640
  const target = normalizeIrcMessagingTarget(to);
1698
1641
  return target ? isChannelTarget(target) ? "group" : "direct" : void 0;
1699
1642
  },
1700
- resolveOutboundSessionRoute: (params) => resolveIrcOutboundSessionRoute(params),
1643
+ resolveOutboundSessionRoute: resolveIrcOutboundSessionRoute,
1701
1644
  targetResolver: {
1702
1645
  looksLikeId: looksLikeIrcTargetId,
1703
1646
  hint: "<#channel|nick>"
@@ -1741,7 +1684,7 @@ const ircPlugin = createChatChannelPlugin({
1741
1684
  }
1742
1685
  }),
1743
1686
  status: createComputedAccountStatusAdapter({
1744
- defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID$2),
1687
+ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
1745
1688
  buildChannelSummary: ({ account, snapshot }) => ({
1746
1689
  ...buildBaseChannelStatusSummary(snapshot),
1747
1690
  host: account.host,
@@ -1,3 +1,3 @@
1
- import { t as ircPlugin } from "./channel-hBHc-p9Z.mjs";
1
+ import { t as ircPlugin } from "./channel-CO6Iez9u.mjs";
2
2
  import "../channel-plugin-api.js";
3
3
  export { ircPlugin };
@@ -1,4 +1,4 @@
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-hBHc-p9Z.mjs";
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-CO6Iez9u.mjs";
2
2
  import { t as getIrcRuntime } from "./runtime-xy-FcjJC.mjs";
3
3
  import { channelIngressRoutes } from "openclaw/plugin-sdk/channel-ingress-runtime";
4
4
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -11,9 +11,9 @@ import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pair
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
+ import { escapeRegExp } from "openclaw/plugin-sdk/text-utility-runtime";
14
15
  //#region extensions/irc/src/inbound.ts
15
16
  const CHANNEL_ID = "irc";
16
- const escapeIrcRegexLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17
17
  const IRC_NICK_CHARACTER = String.raw`[A-Za-z0-9_\-\[\]\\\x60^{}|~]`;
18
18
  const IRC_RFC1459_CASE_EQUIVALENTS = /* @__PURE__ */ new Map([
19
19
  ["[", "{"],
@@ -28,7 +28,7 @@ const IRC_RFC1459_CASE_EQUIVALENTS = /* @__PURE__ */ new Map([
28
28
  function buildIrcNickMentionPattern(value) {
29
29
  return Array.from(value, (character) => {
30
30
  const equivalent = IRC_RFC1459_CASE_EQUIVALENTS.get(character);
31
- return equivalent ? `[${escapeIrcRegexLiteral(character)}${escapeIrcRegexLiteral(equivalent)}]` : escapeIrcRegexLiteral(character);
31
+ return equivalent ? `[${escapeRegExp(character)}${escapeRegExp(equivalent)}]` : escapeRegExp(character);
32
32
  }).join("");
33
33
  }
34
34
  function hasEntries(entries) {
package/dist/api.js CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as listIrcAccountIds, g as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin, v as resolveDefaultIrcAccountId, y as resolveIrcAccount } from "./.setup/channel-hBHc-p9Z.mjs";
1
+ import { _ as listIrcAccountIds, g as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin, v as resolveDefaultIrcAccountId, y as resolveIrcAccount } from "./.setup/channel-CO6Iez9u.mjs";
2
2
  import { n as setIrcRuntime } from "./.setup/runtime-xy-FcjJC.mjs";
3
3
  export { ircPlugin, ircSetupAdapter, ircSetupWizard, listEnabledIrcAccounts, listIrcAccountIds, resolveDefaultIrcAccountId, resolveIrcAccount, setIrcRuntime };
@@ -1,2 +1,2 @@
1
- import { t as ircPlugin } from "./.setup/channel-hBHc-p9Z.mjs";
1
+ import { t as ircPlugin } from "./.setup/channel-CO6Iez9u.mjs";
2
2
  export { ircPlugin };
@@ -1,6 +1,188 @@
1
- import { asObjectRecord, defineChannelAliasMigration, stripRetiredChannelKeys } from "openclaw/plugin-sdk/runtime-doctor-migrations";
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __exportAll = (all, no_symbols) => {
7
+ let target = {};
8
+ for (var name in all) __defProp(target, name, {
9
+ get: all[name],
10
+ enumerable: true
11
+ });
12
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
13
+ return target;
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
17
+ key = keys[i];
18
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
19
+ get: ((k) => from[k]).bind(null, key),
20
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
+ });
22
+ }
23
+ return to;
24
+ };
25
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
26
+ //#endregion
27
+ //#region packages/normalization-core/src/record-coerce.ts
28
+ /** Type guard for non-array object records at browser-safe boundaries. */
29
+ function isRecord(value) {
30
+ return value !== null && typeof value === "object" && !Array.isArray(value);
31
+ }
32
+ /** Returns a non-array record or null. */
33
+ function asNullableRecord(value) {
34
+ return isRecord(value) ? value : null;
35
+ }
36
+ //#endregion
37
+ //#region src/config/channel-config-normalization.ts
38
+ /** Applies one channel-specific doctor migration to every object-shaped account. */
39
+ function normalizeChannelAccounts(params) {
40
+ const rawAccounts = asNullableRecord(params.entry.accounts);
41
+ if (!rawAccounts) return {
42
+ entry: params.entry,
43
+ changed: false
44
+ };
45
+ let changed = false;
46
+ const accounts = { ...rawAccounts };
47
+ for (const [accountId, value] of Object.entries(rawAccounts)) {
48
+ const account = asNullableRecord(value);
49
+ if (!account) continue;
50
+ const normalized = params.normalizeAccount({
51
+ account,
52
+ accountId,
53
+ pathPrefix: `${params.pathPrefix}.accounts.${accountId}`,
54
+ changes: params.changes
55
+ });
56
+ if (normalized.changed) {
57
+ accounts[accountId] = normalized.entry;
58
+ changed = true;
59
+ }
60
+ }
61
+ return changed ? {
62
+ entry: {
63
+ ...params.entry,
64
+ accounts
65
+ },
66
+ changed: true
67
+ } : {
68
+ entry: params.entry,
69
+ changed: false
70
+ };
71
+ }
72
+ /** Applies the same channel-specific doctor migration at root and account scope. */
73
+ function normalizeChannelConfigEntries(params) {
74
+ const changes = params.changes ?? [];
75
+ const channels = params.cfg.channels;
76
+ const entry = asNullableRecord(channels?.[params.channelId]);
77
+ if (!entry) return {
78
+ config: params.cfg,
79
+ changes
80
+ };
81
+ const channelPath = `channels.${params.channelId}`;
82
+ const root = params.normalizeEntry({
83
+ entry,
84
+ pathPrefix: channelPath,
85
+ changes
86
+ });
87
+ const accounts = normalizeChannelAccounts({
88
+ entry: root.entry,
89
+ pathPrefix: channelPath,
90
+ changes,
91
+ normalizeAccount: (accountParams) => params.normalizeEntry({
92
+ entry: accountParams.account,
93
+ accountId: accountParams.accountId,
94
+ pathPrefix: accountParams.pathPrefix,
95
+ changes: accountParams.changes
96
+ })
97
+ });
98
+ if (!root.changed && !accounts.changed) return {
99
+ config: params.cfg,
100
+ changes
101
+ };
102
+ return {
103
+ config: {
104
+ ...params.cfg,
105
+ channels: {
106
+ ...channels,
107
+ [params.channelId]: accounts.entry
108
+ }
109
+ },
110
+ changes
111
+ };
112
+ }
113
+ //#endregion
114
+ //#region src/plugin-sdk/legacy-webhook-listener-migration.ts
115
+ /** Preserve explicitly configured webhook listeners while moving ingress onto Gateway routes. */
116
+ function createLegacyWebhookListenerDoctorContract(params) {
117
+ const portKey = params.portKey ?? "webhookPort";
118
+ const hostKey = params.hostKey === void 0 ? "webhookHost" : params.hostKey;
119
+ const source = (entry) => params.webhookKey ? asNullableRecord(entry[params.webhookKey]) : entry;
120
+ const hasLegacy = (value) => {
121
+ const entry = asNullableRecord(value);
122
+ const listener = entry && source(entry);
123
+ return Boolean(listener && (Object.hasOwn(listener, portKey) || hostKey && Object.hasOwn(listener, hostKey)));
124
+ };
125
+ const prefix = `channels.${params.channelKey}`;
126
+ return {
127
+ legacyConfigRules: [{
128
+ path: ["channels", params.channelKey],
129
+ message: `${prefix} webhook listeners moved to Gateway routes. Run "openclaw doctor --fix" to preserve explicitly configured listener settings as legacyWebhook.`,
130
+ match: (value) => {
131
+ const accounts = asNullableRecord(asNullableRecord(value)?.accounts);
132
+ return hasLegacy(value) || Object.values(accounts ?? {}).some(hasLegacy);
133
+ }
134
+ }],
135
+ normalizeCompatibilityConfig: ({ cfg }) => {
136
+ const root = asNullableRecord(asNullableRecord(cfg.channels)?.[params.channelKey]);
137
+ const inherited = root && source(root);
138
+ const canonicalRoot = asNullableRecord(root?.legacyWebhook);
139
+ return normalizeChannelConfigEntries({
140
+ cfg,
141
+ channelId: params.channelKey,
142
+ normalizeEntry: ({ entry, accountId, pathPrefix, changes }) => {
143
+ const listener = source(entry);
144
+ if (!listener || !hasLegacy(entry)) return {
145
+ entry,
146
+ changed: false
147
+ };
148
+ const next = { ...entry };
149
+ const port = Object.hasOwn(listener, portKey) ? listener[portKey] : (accountId ? canonicalRoot?.port ?? inherited?.[portKey] : void 0) ?? params.defaultPort;
150
+ const inheritedHost = accountId ? canonicalRoot ? canonicalRoot.host : (hostKey ? inherited?.[hostKey] : void 0) ?? params.defaultHost : params.defaultHost;
151
+ const host = hostKey ? listener[hostKey] ?? inheritedHost : inheritedHost;
152
+ const legacyPath = [pathPrefix, params.webhookKey].filter(Boolean).join(".");
153
+ if (Object.hasOwn(entry, "legacyWebhook")) changes.push(`Removed ${legacyPath} legacy listener keys; ${pathPrefix}.legacyWebhook is already configured.`);
154
+ else if (accountId && root?.legacyWebhook === false) changes.push(`Removed ${legacyPath} legacy listener keys; ${prefix}.legacyWebhook: false keeps this account's inherited listener disabled.`);
155
+ else {
156
+ next.legacyWebhook = {
157
+ port,
158
+ ...host !== void 0 ? { host } : {}
159
+ };
160
+ changes.push(`Moved ${legacyPath} listener settings to ${pathPrefix}.legacyWebhook. Point the external callback or reverse proxy at the Gateway port and webhook path, verify delivery, then set legacyWebhook: false to disable legacy forwarding.`);
161
+ }
162
+ const updated = params.webhookKey ? { ...listener } : next;
163
+ delete updated[portKey];
164
+ if (hostKey) delete updated[hostKey];
165
+ if (params.webhookKey) {
166
+ if (Object.keys(updated).length) next[params.webhookKey] = updated;
167
+ else delete next[params.webhookKey];
168
+ }
169
+ return {
170
+ entry: next,
171
+ changed: true
172
+ };
173
+ }
174
+ });
175
+ }
176
+ };
177
+ }
178
+ //#endregion
179
+ //#region \0openclaw:bundled-sdk:openclaw/plugin-sdk/runtime-doctor-migrations
180
+ var runtime_doctor_migrations_exports = /* @__PURE__ */ __exportAll({ createLegacyWebhookListenerDoctorContract: () => createLegacyWebhookListenerDoctorContract });
181
+ import * as import_openclaw_plugin_sdk_runtime_doctor_migrations from "openclaw/plugin-sdk/runtime-doctor-migrations";
182
+ __reExport(runtime_doctor_migrations_exports, import_openclaw_plugin_sdk_runtime_doctor_migrations);
183
+ //#endregion
2
184
  //#region extensions/irc/config-doctor-api.ts
3
- const streamingAliasMigration = defineChannelAliasMigration({
185
+ const streamingAliasMigration = (0, runtime_doctor_migrations_exports.defineChannelAliasMigration)({
4
186
  channelId: "irc",
5
187
  streaming: {
6
188
  defaultMode: "partial",
@@ -13,7 +195,7 @@ const legacyConfigRules = [
13
195
  ...streamingAliasMigration.legacyConfigRules,
14
196
  {
15
197
  path: ["channels", "irc"],
16
- match: (value) => Object.hasOwn(asObjectRecord(value) ?? {}, "mentionPatterns"),
198
+ match: (value) => Object.hasOwn((0, runtime_doctor_migrations_exports.asObjectRecord)(value) ?? {}, "mentionPatterns"),
17
199
  message: "channels.irc.mentionPatterns was accepted but never read; configure mention patterns with messages.groupChat.mentionPatterns. Run \"openclaw doctor --fix\"."
18
200
  },
19
201
  {
@@ -22,7 +204,7 @@ const legacyConfigRules = [
22
204
  "irc",
23
205
  "accounts"
24
206
  ],
25
- match: (value) => Object.values(asObjectRecord(value) ?? {}).some((account) => Object.hasOwn(asObjectRecord(account) ?? {}, "mentionPatterns")),
207
+ match: (value) => Object.values((0, runtime_doctor_migrations_exports.asObjectRecord)(value) ?? {}).some((account) => Object.hasOwn((0, runtime_doctor_migrations_exports.asObjectRecord)(account) ?? {}, "mentionPatterns")),
26
208
  message: "channels.irc.accounts.<id>.mentionPatterns was accepted but never read; configure mention patterns with messages.groupChat.mentionPatterns. Run \"openclaw doctor --fix\"."
27
209
  }
28
210
  ];
@@ -30,7 +212,7 @@ function normalizeCompatibilityConfig({ cfg }) {
30
212
  const aliases = streamingAliasMigration.normalizeChannelConfig({ cfg });
31
213
  const changes = [...aliases.changes];
32
214
  return {
33
- config: stripRetiredChannelKeys({
215
+ config: (0, runtime_doctor_migrations_exports.stripRetiredChannelKeys)({
34
216
  cfg: aliases.config,
35
217
  channelId: "irc",
36
218
  keys: RETIRED_IRC_KEYS,
@@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
4
4
  var setup_entry_default = defineBundledChannelSetupEntry({
5
5
  importMetaUrl: import.meta.url,
6
6
  plugin: {
7
- specifier: fileURLToPath(new URL(".setup/channel-plugin-api-BVh8x-TZ.mjs", import.meta.url).href),
7
+ specifier: fileURLToPath(new URL(".setup/channel-plugin-api-DlMVsHyU.mjs", import.meta.url).href),
8
8
  exportName: "ircPlugin"
9
9
  },
10
10
  secrets: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/irc",
3
- "version": "2026.9.6",
3
+ "version": "2026.9.7",
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.9.6"
124
+ "pluginApi": ">=2026.9.7"
125
125
  },
126
126
  "build": {
127
- "openclawVersion": "2026.9.6",
127
+ "openclawVersion": "2026.9.7",
128
128
  "bundledDist": false
129
129
  },
130
130
  "release": {
@@ -151,7 +151,7 @@
151
151
  "assets/activity.svg"
152
152
  ],
153
153
  "peerDependencies": {
154
- "openclaw": ">=2026.9.6"
154
+ "openclaw": ">=2026.9.7"
155
155
  },
156
156
  "peerDependenciesMeta": {
157
157
  "openclaw": {