@openclaw/irc 2026.9.1-beta.1 → 2026.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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-W4DLS5ip.js";
1
+ import { _ as listIrcAccountIds, g as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin, v as resolveDefaultIrcAccountId, y as resolveIrcAccount } from "./channel-C0XrtiC0.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,11 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-BDXGjxOr.js";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-DcYE4WjJ.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 { defineStableChannelIngressIdentity, identityEntryAuthenticationClassifier } from "openclaw/plugin-sdk/channel-ingress-runtime";
8
9
  import { buildMutableAllowEntryDetector, collectStandardAllowlistLists, createAllowlistProviderOpenWarningCollector, createConditionalWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, resolveScopeKeyCaseInsensitive, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
9
10
  import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister } from "openclaw/plugin-sdk/directory-runtime";
10
11
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
@@ -188,9 +189,12 @@ function listEnabledIrcAccounts(cfg) {
188
189
  accountId
189
190
  })).filter((account) => account.enabled);
190
191
  }
192
+ //#endregion
193
+ //#region extensions/irc/src/doctor.ts
194
+ const isIrcMutableAllowEntry = buildMutableAllowEntryDetector({ stableIdPattern: /^(?:(?:irc|user):)*[^@]*@/i });
191
195
  const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAllowlistWarningCollector({
192
196
  channel: "irc",
193
- detector: buildMutableAllowEntryDetector({ stableIdPattern: /^(?:(?:irc|user):)*[^@]*@/i }),
197
+ detector: isIrcMutableAllowEntry,
194
198
  collectLists: (scope) => collectStandardAllowlistLists(scope, {
195
199
  includeGroups: true,
196
200
  groupField: "allowFrom"
@@ -198,7 +202,7 @@ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAll
198
202
  });
199
203
  //#endregion
200
204
  //#region extensions/irc/src/gateway.ts
201
- const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-CA62Mbrh.js"));
205
+ const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-Bhm49qnu.js"));
202
206
  async function startIrcGatewayAccount(ctx) {
203
207
  const account = ctx.account;
204
208
  const statusSink = createAccountStatusSink({
@@ -223,18 +227,6 @@ async function startIrcGatewayAccount(ctx) {
223
227
  });
224
228
  }
225
229
  //#endregion
226
- //#region extensions/irc/src/outbound-base.ts
227
- function sanitizeIrcAssistantText(text) {
228
- return sanitizeForPlainText(sanitizeAssistantVisibleText(text));
229
- }
230
- const ircOutboundBaseAdapter = {
231
- deliveryMode: "direct",
232
- chunker: chunkTextForOutbound,
233
- chunkerMode: "markdown",
234
- textChunkLimit: 350,
235
- sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
236
- };
237
- //#endregion
238
230
  //#region extensions/irc/src/control-chars.ts
239
231
  function isIrcControlChar(charCode) {
240
232
  return charCode <= 31 || charCode === 127;
@@ -249,8 +241,158 @@ function stripIrcControlChars(value) {
249
241
  return out;
250
242
  }
251
243
  //#endregion
252
- //#region extensions/irc/src/protocol.ts
244
+ //#region extensions/irc/src/normalize.ts
253
245
  const IRC_TARGET_PATTERN$1 = /^[^\s:]+$/u;
246
+ function isChannelTarget(target) {
247
+ return target.startsWith("#") || target.startsWith("&");
248
+ }
249
+ function normalizeIrcMessagingTarget(raw) {
250
+ const trimmed = raw.trim();
251
+ if (!trimmed) return;
252
+ let target = trimmed;
253
+ if (normalizeLowercaseStringOrEmpty(target).startsWith("irc:")) target = target.slice(4).trim();
254
+ if (normalizeLowercaseStringOrEmpty(target).startsWith("channel:")) {
255
+ target = target.slice(8).trim();
256
+ if (!target.startsWith("#") && !target.startsWith("&")) target = `#${target}`;
257
+ }
258
+ if (normalizeLowercaseStringOrEmpty(target).startsWith("user:")) target = target.slice(5).trim();
259
+ if (!target || !looksLikeIrcTargetId(target)) return;
260
+ return target;
261
+ }
262
+ function resolveIrcOutboundSessionRoute(params) {
263
+ const target = normalizeIrcMessagingTarget(params.target);
264
+ if (!target) return null;
265
+ const chatType = isChannelTarget(target) ? "group" : "direct";
266
+ return buildChannelOutboundSessionRoute({
267
+ cfg: params.cfg,
268
+ agentId: params.agentId,
269
+ channel: "irc",
270
+ accountId: params.accountId,
271
+ recipientSessionExact: chatType === "direct" ? "direct-alias" : false,
272
+ peer: {
273
+ kind: chatType,
274
+ id: target
275
+ },
276
+ chatType,
277
+ from: `irc:${target}`,
278
+ to: target
279
+ });
280
+ }
281
+ function looksLikeIrcTargetId(raw) {
282
+ const trimmed = raw.trim();
283
+ if (!trimmed) return false;
284
+ if (hasIrcControlChars(trimmed)) return false;
285
+ return IRC_TARGET_PATTERN$1.test(trimmed);
286
+ }
287
+ function normalizeIrcAllowEntry(raw) {
288
+ let value = normalizeLowercaseStringOrEmpty(raw);
289
+ if (!value) return "";
290
+ if (value.startsWith("irc:")) value = value.slice(4);
291
+ if (value.startsWith("user:")) value = value.slice(5);
292
+ return value.trim();
293
+ }
294
+ function buildIrcAllowlistCandidates(message, params) {
295
+ const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
296
+ const user = normalizeOptionalLowercaseString(message.senderUser);
297
+ const host = normalizeOptionalLowercaseString(message.senderHost);
298
+ const candidates = /* @__PURE__ */ new Set();
299
+ if (nick && params?.allowNameMatching === true) candidates.add(nick);
300
+ if (nick && user) candidates.add(`${nick}!${user}`);
301
+ if (nick && host) candidates.add(`${nick}@${host}`);
302
+ if (nick && user && host) candidates.add(`${nick}!${user}@${host}`);
303
+ return [...candidates];
304
+ }
305
+ const ircIngressIdentity = defineStableChannelIngressIdentity({
306
+ key: "irc-id",
307
+ authentication: "asserted",
308
+ normalizeEntry: normalizeIrcStableEntry,
309
+ normalizeSubject: normalizeLowercaseStringOrEmpty,
310
+ sensitivity: "pii",
311
+ aliases: [
312
+ {
313
+ key: "irc-id-nick-user",
314
+ kind: "stable-id",
315
+ normalizeEntry: normalizeIrcNickUserEntry,
316
+ normalizeSubject: normalizeLowercaseStringOrEmpty,
317
+ authentication: "mutable",
318
+ sensitivity: "pii"
319
+ },
320
+ {
321
+ key: "irc-id-nick-host",
322
+ kind: "stable-id",
323
+ authentication: "asserted",
324
+ normalizeEntry: normalizeIrcNickHostEntry,
325
+ normalizeSubject: normalizeLowercaseStringOrEmpty,
326
+ sensitivity: "pii"
327
+ },
328
+ {
329
+ key: "irc-nick",
330
+ kind: "plugin:irc-nick",
331
+ normalizeEntry: normalizeIrcNickEntry,
332
+ normalizeSubject: normalizeLowercaseStringOrEmpty,
333
+ authentication: "mutable",
334
+ sensitivity: "pii"
335
+ }
336
+ ],
337
+ isWildcardEntry: (entry) => normalizeIrcAllowEntry(entry) === "*",
338
+ resolveEntryId: ({ entryIndex, fieldKey }) => `irc-entry-${entryIndex + 1}:${fieldKey === "irc-nick" ? "nick" : "id"}`
339
+ });
340
+ function isBareNick(value) {
341
+ return !value.includes("!") && !value.includes("@");
342
+ }
343
+ function hasVerifiedHost(value) {
344
+ return value.includes("@");
345
+ }
346
+ function isHostlessNickUser(value) {
347
+ return value.includes("!") && !value.includes("@");
348
+ }
349
+ function normalizeIrcStableEntry(value) {
350
+ const normalized = normalizeIrcAllowEntry(value);
351
+ if (!normalized.includes("!") || !hasVerifiedHost(normalized)) return null;
352
+ return normalized;
353
+ }
354
+ function normalizeIrcNickHostEntry(value) {
355
+ const normalized = normalizeIrcAllowEntry(value);
356
+ return !normalized.includes("!") && hasVerifiedHost(normalized) ? normalized : null;
357
+ }
358
+ function normalizeIrcNickUserEntry(value) {
359
+ const normalized = normalizeIrcAllowEntry(value);
360
+ if (!normalized || normalized === "*" || !isHostlessNickUser(normalized)) return null;
361
+ return normalized;
362
+ }
363
+ function normalizeIrcNickEntry(value) {
364
+ const normalized = normalizeIrcAllowEntry(value);
365
+ if (!normalized || normalized === "*" || !isBareNick(normalized)) return null;
366
+ return normalized;
367
+ }
368
+ function createIrcIngressSubject(message) {
369
+ const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
370
+ const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
371
+ const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
372
+ return {
373
+ stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
374
+ aliases: {
375
+ "irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
376
+ "irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!") && candidate.includes("@")),
377
+ "irc-nick": nick
378
+ }
379
+ };
380
+ }
381
+ //#endregion
382
+ //#region extensions/irc/src/outbound-base.ts
383
+ function sanitizeIrcAssistantText(text) {
384
+ return sanitizeForPlainText(sanitizeAssistantVisibleText(text));
385
+ }
386
+ const ircOutboundBaseAdapter = {
387
+ deliveryMode: "direct",
388
+ chunker: chunkTextForOutbound,
389
+ chunkerMode: "markdown",
390
+ textChunkLimit: 350,
391
+ sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
392
+ };
393
+ //#endregion
394
+ //#region extensions/irc/src/protocol.ts
395
+ const IRC_TARGET_PATTERN = /^[^\s:]+$/u;
254
396
  function parseIrcLine(line) {
255
397
  const raw = line.replace(/[\r\n]+/g, "").trim();
256
398
  if (!raw) return null;
@@ -329,7 +471,7 @@ function sanitizeIrcTarget(raw) {
329
471
  if (!decoded) throw new Error("IRC target is required");
330
472
  if (decoded !== decoded.trim()) throw new Error(`Invalid IRC target: ${raw}`);
331
473
  if (hasIrcControlChars(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
332
- if (!IRC_TARGET_PATTERN$1.test(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
474
+ if (!IRC_TARGET_PATTERN.test(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
333
475
  return decoded;
334
476
  }
335
477
  function makeIrcMessageId() {
@@ -661,68 +803,6 @@ function buildIrcConnectOptions(account, overrides = {}) {
661
803
  };
662
804
  }
663
805
  //#endregion
664
- //#region extensions/irc/src/normalize.ts
665
- const IRC_TARGET_PATTERN = /^[^\s:]+$/u;
666
- function isChannelTarget(target) {
667
- return target.startsWith("#") || target.startsWith("&");
668
- }
669
- function normalizeIrcMessagingTarget(raw) {
670
- const trimmed = raw.trim();
671
- if (!trimmed) return;
672
- let target = trimmed;
673
- if (normalizeLowercaseStringOrEmpty(target).startsWith("irc:")) target = target.slice(4).trim();
674
- if (normalizeLowercaseStringOrEmpty(target).startsWith("channel:")) {
675
- target = target.slice(8).trim();
676
- if (!target.startsWith("#") && !target.startsWith("&")) target = `#${target}`;
677
- }
678
- if (normalizeLowercaseStringOrEmpty(target).startsWith("user:")) target = target.slice(5).trim();
679
- if (!target || !looksLikeIrcTargetId(target)) return;
680
- return target;
681
- }
682
- function resolveIrcOutboundSessionRoute(params) {
683
- const target = normalizeIrcMessagingTarget(params.target);
684
- if (!target) return null;
685
- const chatType = isChannelTarget(target) ? "group" : "direct";
686
- return buildChannelOutboundSessionRoute({
687
- cfg: params.cfg,
688
- agentId: params.agentId,
689
- channel: "irc",
690
- accountId: params.accountId,
691
- recipientSessionExact: chatType === "direct" ? "direct-alias" : false,
692
- peer: {
693
- kind: chatType,
694
- id: target
695
- },
696
- chatType,
697
- from: `irc:${target}`,
698
- to: target
699
- });
700
- }
701
- function looksLikeIrcTargetId(raw) {
702
- const trimmed = raw.trim();
703
- if (!trimmed) return false;
704
- if (hasIrcControlChars(trimmed)) return false;
705
- return IRC_TARGET_PATTERN.test(trimmed);
706
- }
707
- function normalizeIrcAllowEntry(raw) {
708
- let value = normalizeLowercaseStringOrEmpty(raw);
709
- if (!value) return "";
710
- if (value.startsWith("irc:")) value = value.slice(4);
711
- if (value.startsWith("user:")) value = value.slice(5);
712
- return value.trim();
713
- }
714
- function buildIrcAllowlistCandidates(message, params) {
715
- const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
716
- const user = normalizeOptionalLowercaseString(message.senderUser);
717
- const host = normalizeOptionalLowercaseString(message.senderHost);
718
- const candidates = /* @__PURE__ */ new Set();
719
- if (nick && params?.allowNameMatching === true) candidates.add(nick);
720
- if (nick && user) candidates.add(`${nick}!${user}`);
721
- if (nick && host) candidates.add(`${nick}@${host}`);
722
- if (nick && user && host) candidates.add(`${nick}!${user}@${host}`);
723
- return [...candidates];
724
- }
725
- //#endregion
726
806
  //#region extensions/irc/src/send.ts
727
807
  function recordIrcOutboundActivity(accountId) {
728
808
  try {
@@ -735,35 +815,27 @@ function recordIrcOutboundActivity(accountId) {
735
815
  if (!(error instanceof Error) || error.message !== "IRC runtime not initialized") throw error;
736
816
  }
737
817
  }
738
- function resolveTarget(to, opts) {
739
- const fromArg = normalizeIrcMessagingTarget(to);
740
- if (fromArg) return fromArg;
741
- const fromOpt = normalizeIrcMessagingTarget(opts?.target ?? "");
742
- if (fromOpt) return fromOpt;
743
- throw new Error(`Invalid IRC target: ${to}`);
744
- }
745
- async function sendIrcMessages(to, messages, opts, onDeliveryResult) {
818
+ async function sendIrcMessages(to, text, opts, planMessages = (preparedText) => [{
819
+ text: preparedText,
820
+ replyTo: opts.replyTo
821
+ }], onDeliveryResult) {
746
822
  const cfg = requireRuntimeConfig(opts.cfg, "IRC send");
747
823
  const account = resolveIrcAccount({
748
824
  cfg,
749
825
  accountId: opts.accountId
750
826
  });
751
827
  if (!account.configured) throw new Error(`IRC is not configured for account "${account.accountId}" (need host and nick in channels.irc).`);
752
- const target = resolveTarget(to, opts);
828
+ const target = normalizeIrcMessagingTarget(to);
829
+ if (!target) throw new Error(`Invalid IRC target: ${to}`);
753
830
  const tableMode = resolveMarkdownTableMode({
754
831
  cfg,
755
832
  channel: "irc",
756
833
  accountId: account.accountId
757
834
  });
758
- const preparedMessages = messages.map((message) => {
759
- const prepared = stripMarkdown(convertMarkdownTables(message.text.trim(), tableMode));
760
- if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
761
- return {
762
- payload: message.replyTo ? `${prepared}\n\n[reply:${message.replyTo}]` : prepared,
763
- replyTo: message.replyTo
764
- };
765
- });
766
- if (preparedMessages.length === 0) return [];
835
+ if (!text) return [];
836
+ const prepared = stripMarkdown(convertMarkdownTables(text.trim(), tableMode));
837
+ if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
838
+ const messages = planMessages(prepared);
767
839
  opts.abortSignal?.throwIfAborted();
768
840
  let transient;
769
841
  const client = opts.client?.isReady() ? opts.client : transient = await connectIrcClient(buildIrcConnectOptions(account, {
@@ -774,13 +846,13 @@ async function sendIrcMessages(to, messages, opts, onDeliveryResult) {
774
846
  try {
775
847
  opts.abortSignal?.throwIfAborted();
776
848
  if (transient && (target.startsWith("#") || target.startsWith("&"))) client.join(target);
777
- for (const message of preparedMessages) {
849
+ for (const message of messages) {
778
850
  opts.abortSignal?.throwIfAborted();
779
851
  if (!client.isReady()) throw new Error("IRC connection closed before send");
780
852
  await opts.onPlatformSendDispatch?.();
781
853
  opts.abortSignal?.throwIfAborted();
782
854
  if (!client.isReady()) throw new Error("IRC connection closed before send");
783
- client.sendPrivmsg(target, message.payload);
855
+ client.sendPrivmsg(target, message.replyTo ? `${message.text}\n\n[reply:${message.replyTo}]` : message.text);
784
856
  recordIrcOutboundActivity(account.accountId);
785
857
  const messageId = makeIrcMessageId();
786
858
  const result = {
@@ -805,10 +877,7 @@ async function sendIrcMessages(to, messages, opts, onDeliveryResult) {
805
877
  }
806
878
  }
807
879
  async function sendMessageIrc(to, text, opts) {
808
- const result = (await sendIrcMessages(to, [{
809
- text,
810
- ...opts.replyTo ? { replyTo: opts.replyTo } : {}
811
- }], opts))[0];
880
+ const result = (await sendIrcMessages(to, text, opts))[0];
812
881
  if (!result) throw new Error("Message must be non-empty for IRC sends");
813
882
  return result;
814
883
  }
@@ -823,37 +892,30 @@ function toIrcMessageResult({ target, ...result }) {
823
892
  }
824
893
  };
825
894
  }
826
- async function sendIrcMessage(...args) {
827
- return toIrcMessageResult(await sendMessageIrc(...args));
895
+ async function sendIrcMessage(ctx, text = ctx.text) {
896
+ return toIrcMessageResult(await sendMessageIrc(ctx.to, text, {
897
+ cfg: ctx.cfg,
898
+ accountId: ctx.accountId ?? void 0,
899
+ replyTo: ctx.replyToId ?? void 0,
900
+ abortSignal: ctx.signal,
901
+ onPlatformSendDispatch: ctx.onPlatformSendDispatch
902
+ }));
828
903
  }
829
904
  const sendFormattedIrcText = async (ctx) => {
830
- const { chunkMarkdownTextWithMode, resolveChunkMode, resolveTextChunkLimit } = await import("openclaw/plugin-sdk/reply-chunking");
905
+ const { chunkTextWithMode, resolveChunkMode, resolveTextChunkLimit } = await import("openclaw/plugin-sdk/reply-chunking");
831
906
  const accountId = ctx.accountId ?? void 0;
832
907
  const textLimit = ctx.formatting?.textLimit ?? resolveTextChunkLimit(ctx.cfg, "irc", accountId, { fallbackLimit: ircOutboundBaseAdapter.textChunkLimit });
833
908
  const chunkMode = ctx.formatting?.chunkMode ?? resolveChunkMode(ctx.cfg, "irc", accountId);
834
- const chunkText = (text) => ctx.formatting ? ircOutboundBaseAdapter.chunker(text, textLimit, { formatting: ctx.formatting }) : ircOutboundBaseAdapter.chunker(text, textLimit);
835
- let chunks;
836
- if (chunkMode === "newline") {
837
- const blocks = chunkMarkdownTextWithMode(ctx.text, textLimit, chunkMode);
838
- if (blocks.length === 0 && ctx.text) blocks.push(ctx.text);
839
- chunks = blocks.flatMap((block) => {
840
- const blockChunks = chunkText(block);
841
- return blockChunks.length === 0 && block ? [block] : blockChunks;
842
- });
843
- } else chunks = chunkText(ctx.text);
844
909
  const nextReplyToId = createReplyToFanout(ctx);
845
- return (await sendIrcMessages(ctx.to, chunks.map((text) => {
846
- const replyTo = nextReplyToId();
847
- return replyTo ? {
848
- text,
849
- replyTo
850
- } : { text };
851
- }), {
910
+ return (await sendIrcMessages(ctx.to, ctx.text, {
852
911
  cfg: ctx.cfg,
853
912
  accountId,
854
913
  abortSignal: ctx.abortSignal,
855
914
  onPlatformSendDispatch: ctx.onPlatformSendDispatch
856
- }, async (result) => {
915
+ }, (preparedText) => chunkTextWithMode(preparedText, textLimit, chunkMode).map((text) => ({
916
+ text,
917
+ replyTo: nextReplyToId()
918
+ })), async (result) => {
857
919
  await ctx.onDeliveryResult?.(attachChannelToResult("irc", toIrcMessageResult(result)));
858
920
  })).map((result) => attachChannelToResult("irc", toIrcMessageResult(result)));
859
921
  };
@@ -865,16 +927,8 @@ const ircMessageAdapter = defineChannelMessageAdapter({
865
927
  replyTo: true
866
928
  } },
867
929
  send: {
868
- text: async ({ cfg, to, text, accountId, replyToId }) => await sendIrcMessage(to, text, {
869
- cfg,
870
- accountId: accountId ?? void 0,
871
- replyTo: replyToId ?? void 0
872
- }),
873
- media: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) => await sendIrcMessage(to, mediaUrl ? `${text}\n\nAttachment: ${mediaUrl}` : text, {
874
- cfg,
875
- accountId: accountId ?? void 0,
876
- replyTo: replyToId ?? void 0
877
- })
930
+ text: sendIrcMessage,
931
+ media: (ctx) => sendIrcMessage(ctx, ctx.mediaUrl ? `${ctx.text}\n\nAttachment: ${ctx.mediaUrl}` : ctx.text)
878
932
  }
879
933
  });
880
934
  //#endregion
@@ -1317,7 +1371,8 @@ const ircSetupWizard = {
1317
1371
  return String(defaultPort);
1318
1372
  },
1319
1373
  validate: ({ value }) => {
1320
- const parsed = parseStrictPositiveInteger(normalizeStringifiedOptionalString(value) ?? "");
1374
+ const raw = normalizeStringifiedOptionalString(value) ?? "";
1375
+ const parsed = parseStrictPositiveInteger(raw);
1321
1376
  return parsed !== void 0 && parsed <= 65535 ? void 0 : "Use a port between 1 and 65535";
1322
1377
  },
1323
1378
  normalizeValue: ({ value }) => String(parsePort(value, 6697)),
@@ -1489,7 +1544,7 @@ const meta = {
1489
1544
  systemImage: "number",
1490
1545
  markdownCapable: true
1491
1546
  };
1492
- const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-CA62Mbrh.js"));
1547
+ const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-Bhm49qnu.js"));
1493
1548
  function normalizePairingTarget(raw) {
1494
1549
  const normalized = normalizeIrcAllowEntry(raw);
1495
1550
  if (!normalized) return "";
@@ -1543,6 +1598,7 @@ const resolveIrcDmPolicy = createScopedDmSecurityResolver({
1543
1598
  resolvePolicy: (account) => account.config.dmPolicy,
1544
1599
  resolveAllowFrom: (account) => account.config.allowFrom,
1545
1600
  policyPathSuffix: "dmPolicy",
1601
+ classifyEntryAuthentication: identityEntryAuthenticationClassifier(ircIngressIdentity),
1546
1602
  normalizeEntry: (raw) => normalizeIrcAllowEntry(raw)
1547
1603
  });
1548
1604
  const collectIrcGroupPolicyWarnings = createAllowlistProviderOpenWarningCollector({
@@ -1580,7 +1636,10 @@ const ircPlugin = createChatChannelPlugin({
1580
1636
  media: true,
1581
1637
  blockStreaming: true
1582
1638
  },
1583
- reload: { configPrefixes: ["channels.irc"] },
1639
+ reload: {
1640
+ configPrefixes: ["channels.irc"],
1641
+ accountScopedRestart: true
1642
+ },
1584
1643
  configSchema: IrcChannelConfigSchema,
1585
1644
  config: {
1586
1645
  ...ircConfigAdapter,
@@ -1747,4 +1806,4 @@ const ircPlugin = createChatChannelPlugin({
1747
1806
  }
1748
1807
  });
1749
1808
  //#endregion
1750
- export { resolveDefaultIrcAccountId as _, resolveIrcGroupRequireMention as a, isChannelTarget as c, connectIrcClient as d, parseIrcLine as f, listIrcAccountIds as g, listEnabledIrcAccounts as h, resolveIrcGroupMatch as i, normalizeIrcAllowEntry as l, sanitizeIrcAssistantText as m, ircSetupWizard as n, sendMessageIrc as o, parseIrcPrefix as p, ircSetupAdapter as r, buildIrcAllowlistCandidates as s, ircPlugin as t, buildIrcConnectOptions as u, resolveIrcAccount as v };
1809
+ export { listIrcAccountIds as _, resolveIrcGroupRequireMention as a, connectIrcClient as c, sanitizeIrcAssistantText as d, createIrcIngressSubject as f, listEnabledIrcAccounts as g, normalizeIrcAllowEntry as h, resolveIrcGroupMatch as i, parseIrcLine as l, isChannelTarget as m, ircSetupWizard as n, sendMessageIrc as o, ircIngressIdentity as p, ircSetupAdapter as r, buildIrcConnectOptions as s, ircPlugin as t, parseIrcPrefix as u, resolveDefaultIrcAccountId as v, resolveIrcAccount as y };
@@ -1,2 +1,2 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-BDXGjxOr.js";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-DcYE4WjJ.js";
2
2
  export { IrcChannelConfigSchema };
@@ -1,2 +1,2 @@
1
- import { t as ircPlugin } from "./channel-W4DLS5ip.js";
1
+ import { t as ircPlugin } from "./channel-C0XrtiC0.js";
2
2
  export { ircPlugin };
@@ -1,51 +1,18 @@
1
- import { a as resolveIrcGroupRequireMention, c as isChannelTarget, d as connectIrcClient, f as parseIrcLine, i as resolveIrcGroupMatch, l as normalizeIrcAllowEntry, m as sanitizeIrcAssistantText, o as sendMessageIrc, p as parseIrcPrefix, s as buildIrcAllowlistCandidates, u as buildIrcConnectOptions, v as resolveIrcAccount } from "./channel-W4DLS5ip.js";
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-C0XrtiC0.js";
2
2
  import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
3
+ import { channelIngressRoutes, createChannelIngressResolver } from "openclaw/plugin-sdk/channel-ingress-runtime";
3
4
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
5
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
5
6
  import { randomUUID } from "node:crypto";
6
7
  import { resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
7
8
  import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
8
9
  import { logInboundDrop, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
9
- import { channelIngressRoutes, createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
10
10
  import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
11
11
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
12
12
  import { deliverFormattedTextWithAttachments } from "openclaw/plugin-sdk/reply-payload";
13
13
  import { GROUP_POLICY_BLOCKED_LABEL, resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
14
14
  //#region extensions/irc/src/inbound.ts
15
15
  const CHANNEL_ID = "irc";
16
- const ircIngressIdentity = defineStableChannelIngressIdentity({
17
- key: "irc-id",
18
- normalizeEntry: normalizeIrcStableEntry,
19
- normalizeSubject: normalizeLowercaseStringOrEmpty,
20
- sensitivity: "pii",
21
- aliases: [
22
- {
23
- key: "irc-id-nick-user",
24
- kind: "stable-id",
25
- normalizeEntry: normalizeIrcNickUserEntry,
26
- normalizeSubject: normalizeLowercaseStringOrEmpty,
27
- dangerous: true,
28
- sensitivity: "pii"
29
- },
30
- {
31
- key: "irc-id-nick-host",
32
- kind: "stable-id",
33
- normalizeEntry: () => null,
34
- normalizeSubject: normalizeLowercaseStringOrEmpty,
35
- sensitivity: "pii"
36
- },
37
- {
38
- key: "irc-nick",
39
- kind: "plugin:irc-nick",
40
- normalizeEntry: normalizeIrcNickEntry,
41
- normalizeSubject: normalizeLowercaseStringOrEmpty,
42
- dangerous: true,
43
- sensitivity: "pii"
44
- }
45
- ],
46
- isWildcardEntry: (entry) => normalizeIrcAllowEntry(entry) === "*",
47
- resolveEntryId: ({ entryIndex, fieldKey }) => `irc-entry-${entryIndex + 1}:${fieldKey === "irc-nick" ? "nick" : "id"}`
48
- });
49
16
  const escapeIrcRegexLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50
17
  const IRC_NICK_CHARACTER = String.raw`[A-Za-z0-9_\-\[\]\\\x60^{}|~]`;
51
18
  const IRC_RFC1459_CASE_EQUIVALENTS = /* @__PURE__ */ new Map([
@@ -64,46 +31,9 @@ function buildIrcNickMentionPattern(value) {
64
31
  return equivalent ? `[${escapeIrcRegexLiteral(character)}${escapeIrcRegexLiteral(equivalent)}]` : escapeIrcRegexLiteral(character);
65
32
  }).join("");
66
33
  }
67
- function isBareNick(value) {
68
- return !value.includes("!") && !value.includes("@");
69
- }
70
- function hasVerifiedHost(value) {
71
- return value.includes("@");
72
- }
73
- function isHostlessNickUser(value) {
74
- return value.includes("!") && !value.includes("@");
75
- }
76
- function normalizeIrcStableEntry(value) {
77
- const normalized = normalizeIrcAllowEntry(value);
78
- if (!normalized || normalized === "*" || !hasVerifiedHost(normalized)) return null;
79
- return normalized;
80
- }
81
- function normalizeIrcNickUserEntry(value) {
82
- const normalized = normalizeIrcAllowEntry(value);
83
- if (!normalized || normalized === "*" || !isHostlessNickUser(normalized)) return null;
84
- return normalized;
85
- }
86
- function normalizeIrcNickEntry(value) {
87
- const normalized = normalizeIrcAllowEntry(value);
88
- if (!normalized || normalized === "*" || !isBareNick(normalized)) return null;
89
- return normalized;
90
- }
91
34
  function hasEntries(entries) {
92
35
  return normalizeStringEntries(entries).some((entry) => normalizeIrcAllowEntry(entry));
93
36
  }
94
- function createIrcIngressSubject(message) {
95
- const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
96
- const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
97
- const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
98
- return {
99
- stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
100
- aliases: {
101
- "irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
102
- "irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!") && candidate.includes("@")),
103
- "irc-nick": nick
104
- }
105
- };
106
- }
107
37
  function routeDescriptorsForIrcGroup(params) {
108
38
  if (!params.isGroup) return [];
109
39
  return channelIngressRoutes(params.groupPolicy === "allowlist" && {
@@ -287,10 +217,11 @@ async function handleIrcInbound(params) {
287
217
  });
288
218
  return { kind: "completed" };
289
219
  }
290
- if (message.isGroup) if (access.routeAccess.reason === "channel_not_allowlisted") runtime.log?.(`irc: drop channel ${message.target} (not allowlisted)`);
291
- else if (access.routeAccess.reason === "channel_disabled") runtime.log?.(`irc: drop channel ${message.target} (disabled)`);
292
- else runtime.log?.(`irc: drop group sender ${senderDisplay} (policy=${groupPolicy})`);
293
- else runtime.log?.(`irc: drop DM sender ${senderDisplay} (dmPolicy=${dmPolicy})`);
220
+ if (message.isGroup) {
221
+ if (access.routeAccess.reason === "channel_not_allowlisted") runtime.log?.(`irc: drop channel ${message.target} (not allowlisted)`);
222
+ else if (access.routeAccess.reason === "channel_disabled") runtime.log?.(`irc: drop channel ${message.target} (disabled)`);
223
+ else runtime.log?.(`irc: drop group sender ${senderDisplay} (policy=${groupPolicy})`);
224
+ } else runtime.log?.(`irc: drop DM sender ${senderDisplay} (dmPolicy=${dmPolicy})`);
294
225
  return { kind: "completed" };
295
226
  }
296
227
  if (turnAdoptionLifecycle?.abortSignal.aborted) return {
@@ -56,7 +56,7 @@ const IrcNickServSchema = z.object({
56
56
  message: "channels.irc.nickserv.register=true requires channels.irc.nickserv.registerEmail"
57
57
  });
58
58
  });
59
- const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannelSchema(z.object({
59
+ const IrcAccountSchemaBase = z.object({
60
60
  name: z.string().optional(),
61
61
  enabled: z.boolean().optional(),
62
62
  configWrites: z.boolean().optional(),
@@ -77,10 +77,10 @@ const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannel
77
77
  groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
78
78
  groups: z.record(z.string(), ChannelGroupEntrySchema.optional()).optional(),
79
79
  channels: z.array(z.string()).optional(),
80
- mentionPatterns: z.array(z.string()).optional(),
81
80
  markdown: MarkdownConfigSchema,
82
81
  ...ReplyRuntimeConfigSchemaShape
83
- }).strict(), {
82
+ }).strict();
83
+ const IrcConfigSchema = buildMultiAccountChannelSchema(IrcAccountSchemaBase, {
84
84
  optionalAccount: true,
85
85
  refine: (value, ctx) => {
86
86
  requireOpenAllowFrom({
@@ -91,6 +91,7 @@ const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannel
91
91
  message: "channels.irc.dmPolicy=\"open\" requires channels.irc.allowFrom to include \"*\""
92
92
  });
93
93
  }
94
- }), { uiHints: ircChannelConfigUiHints });
94
+ });
95
+ const IrcChannelConfigSchema = buildChannelConfigSchema(IrcConfigSchema, { uiHints: ircChannelConfigUiHints });
95
96
  //#endregion
96
97
  export { IrcChannelConfigSchema as t };
@@ -1,5 +1,5 @@
1
- import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor-migrations";
2
- //#region extensions/irc/doctor-contract-api.ts
1
+ import { asObjectRecord, defineChannelAliasMigration, stripRetiredChannelKeys } from "openclaw/plugin-sdk/runtime-doctor-migrations";
2
+ //#region extensions/irc/config-doctor-api.ts
3
3
  const streamingAliasMigration = defineChannelAliasMigration({
4
4
  channelId: "irc",
5
5
  streaming: {
@@ -8,9 +8,39 @@ const streamingAliasMigration = defineChannelAliasMigration({
8
8
  },
9
9
  accountStreamingReplacesRoot: true
10
10
  });
11
- const legacyConfigRules = streamingAliasMigration.legacyConfigRules;
11
+ const RETIRED_IRC_KEYS = /* @__PURE__ */ new Set(["mentionPatterns"]);
12
+ const legacyConfigRules = [
13
+ ...streamingAliasMigration.legacyConfigRules,
14
+ {
15
+ path: ["channels", "irc"],
16
+ match: (value) => Object.hasOwn(asObjectRecord(value) ?? {}, "mentionPatterns"),
17
+ message: "channels.irc.mentionPatterns was accepted but never read; configure mention patterns with messages.groupChat.mentionPatterns. Run \"openclaw doctor --fix\"."
18
+ },
19
+ {
20
+ path: [
21
+ "channels",
22
+ "irc",
23
+ "accounts"
24
+ ],
25
+ match: (value) => Object.values(asObjectRecord(value) ?? {}).some((account) => Object.hasOwn(asObjectRecord(account) ?? {}, "mentionPatterns")),
26
+ message: "channels.irc.accounts.<id>.mentionPatterns was accepted but never read; configure mention patterns with messages.groupChat.mentionPatterns. Run \"openclaw doctor --fix\"."
27
+ }
28
+ ];
12
29
  function normalizeCompatibilityConfig({ cfg }) {
13
- return streamingAliasMigration.normalizeChannelConfig({ cfg });
30
+ const aliases = streamingAliasMigration.normalizeChannelConfig({ cfg });
31
+ const changes = [...aliases.changes];
32
+ return {
33
+ config: stripRetiredChannelKeys({
34
+ cfg: aliases.config,
35
+ channelId: "irc",
36
+ keys: RETIRED_IRC_KEYS,
37
+ scope: "root-and-accounts",
38
+ onRemove: (removed) => {
39
+ changes.push(`Removed ${removed.pathPrefix}.${removed.key}; it was never read.`);
40
+ }
41
+ }).config,
42
+ changes
43
+ };
14
44
  }
15
45
  //#endregion
16
46
  export { legacyConfigRules, normalizeCompatibilityConfig };
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "id": "irc",
3
+ "name": "IRC",
4
+ "description": "OpenClaw IRC channel plugin.",
3
5
  "doctorContract": {
4
6
  "configRepair": true
5
7
  },
@@ -247,12 +249,6 @@
247
249
  "type": "string"
248
250
  }
249
251
  },
250
- "mentionPatterns": {
251
- "type": "array",
252
- "items": {
253
- "type": "string"
254
- }
255
- },
256
252
  "markdown": {
257
253
  "type": "object",
258
254
  "properties": {
@@ -593,12 +589,6 @@
593
589
  "type": "string"
594
590
  }
595
591
  },
596
- "mentionPatterns": {
597
- "type": "array",
598
- "items": {
599
- "type": "string"
600
- }
601
- },
602
592
  "markdown": {
603
593
  "type": "object",
604
594
  "properties": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/irc",
3
- "version": "2026.9.1-beta.1",
3
+ "version": "2026.9.2",
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.1-beta.1"
124
+ "pluginApi": ">=2026.9.2"
125
125
  },
126
126
  "build": {
127
- "openclawVersion": "2026.9.1-beta.1",
127
+ "openclawVersion": "2026.9.2",
128
128
  "bundledDist": false
129
129
  },
130
130
  "release": {
@@ -149,7 +149,7 @@
149
149
  "README.md"
150
150
  ],
151
151
  "peerDependencies": {
152
- "openclaw": ">=2026.9.1-beta.1"
152
+ "openclaw": ">=2026.9.2"
153
153
  },
154
154
  "peerDependenciesMeta": {
155
155
  "openclaw": {