@openclaw/irc 2026.8.1-beta.3 → 2026.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js 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-BO1GdA4V.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-BxbQEBnS.js";
2
2
  import { n as setIrcRuntime } from "./runtime-xy-FcjJC.js";
3
3
  export { ircPlugin, ircSetupAdapter, ircSetupWizard, listEnabledIrcAccounts, listIrcAccountIds, resolveDefaultIrcAccountId, resolveIrcAccount, setIrcRuntime };
@@ -1,10 +1,11 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-DdB_h5mL.js";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-CpB6AaUh.js";
2
2
  import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
3
3
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-D_DC3YWj.js";
4
4
  import { createAccountListHelpers, describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
5
5
  import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
6
6
  import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
7
7
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin, parseOptionalDelimitedEntries } from "openclaw/plugin-sdk/channel-core";
8
+ import { 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";
@@ -16,9 +17,10 @@ import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
16
17
  import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
17
18
  import { resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
18
19
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringifiedOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
19
- import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
20
+ import { createAccountStatusSink, createMessageReceiptFromOutboundResults, createReplyToFanout, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
20
21
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
21
22
  import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
23
+ import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
22
24
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
23
25
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
24
26
  import net from "node:net";
@@ -197,7 +199,7 @@ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAll
197
199
  });
198
200
  //#endregion
199
201
  //#region extensions/irc/src/gateway.ts
200
- const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-CSpBc1ef.js"));
202
+ const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-DZcQfOqp.js"));
201
203
  async function startIrcGatewayAccount(ctx) {
202
204
  const account = ctx.account;
203
205
  const statusSink = createAccountStatusSink({
@@ -236,8 +238,158 @@ function stripIrcControlChars(value) {
236
238
  return out;
237
239
  }
238
240
  //#endregion
239
- //#region extensions/irc/src/protocol.ts
241
+ //#region extensions/irc/src/normalize.ts
240
242
  const IRC_TARGET_PATTERN$1 = /^[^\s:]+$/u;
243
+ function isChannelTarget(target) {
244
+ return target.startsWith("#") || target.startsWith("&");
245
+ }
246
+ function normalizeIrcMessagingTarget(raw) {
247
+ const trimmed = raw.trim();
248
+ if (!trimmed) return;
249
+ let target = trimmed;
250
+ if (normalizeLowercaseStringOrEmpty(target).startsWith("irc:")) target = target.slice(4).trim();
251
+ if (normalizeLowercaseStringOrEmpty(target).startsWith("channel:")) {
252
+ target = target.slice(8).trim();
253
+ if (!target.startsWith("#") && !target.startsWith("&")) target = `#${target}`;
254
+ }
255
+ if (normalizeLowercaseStringOrEmpty(target).startsWith("user:")) target = target.slice(5).trim();
256
+ if (!target || !looksLikeIrcTargetId(target)) return;
257
+ return target;
258
+ }
259
+ function resolveIrcOutboundSessionRoute(params) {
260
+ const target = normalizeIrcMessagingTarget(params.target);
261
+ if (!target) return null;
262
+ const chatType = isChannelTarget(target) ? "group" : "direct";
263
+ return buildChannelOutboundSessionRoute({
264
+ cfg: params.cfg,
265
+ agentId: params.agentId,
266
+ channel: "irc",
267
+ accountId: params.accountId,
268
+ recipientSessionExact: chatType === "direct" ? "direct-alias" : false,
269
+ peer: {
270
+ kind: chatType,
271
+ id: target
272
+ },
273
+ chatType,
274
+ from: `irc:${target}`,
275
+ to: target
276
+ });
277
+ }
278
+ function looksLikeIrcTargetId(raw) {
279
+ const trimmed = raw.trim();
280
+ if (!trimmed) return false;
281
+ if (hasIrcControlChars(trimmed)) return false;
282
+ return IRC_TARGET_PATTERN$1.test(trimmed);
283
+ }
284
+ function normalizeIrcAllowEntry(raw) {
285
+ let value = normalizeLowercaseStringOrEmpty(raw);
286
+ if (!value) return "";
287
+ if (value.startsWith("irc:")) value = value.slice(4);
288
+ if (value.startsWith("user:")) value = value.slice(5);
289
+ return value.trim();
290
+ }
291
+ function buildIrcAllowlistCandidates(message, params) {
292
+ const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
293
+ const user = normalizeOptionalLowercaseString(message.senderUser);
294
+ const host = normalizeOptionalLowercaseString(message.senderHost);
295
+ const candidates = /* @__PURE__ */ new Set();
296
+ if (nick && params?.allowNameMatching === true) candidates.add(nick);
297
+ if (nick && user) candidates.add(`${nick}!${user}`);
298
+ if (nick && host) candidates.add(`${nick}@${host}`);
299
+ if (nick && user && host) candidates.add(`${nick}!${user}@${host}`);
300
+ return [...candidates];
301
+ }
302
+ const ircIngressIdentity = defineStableChannelIngressIdentity({
303
+ key: "irc-id",
304
+ authentication: "asserted",
305
+ normalizeEntry: normalizeIrcStableEntry,
306
+ normalizeSubject: normalizeLowercaseStringOrEmpty,
307
+ sensitivity: "pii",
308
+ aliases: [
309
+ {
310
+ key: "irc-id-nick-user",
311
+ kind: "stable-id",
312
+ normalizeEntry: normalizeIrcNickUserEntry,
313
+ normalizeSubject: normalizeLowercaseStringOrEmpty,
314
+ authentication: "mutable",
315
+ sensitivity: "pii"
316
+ },
317
+ {
318
+ key: "irc-id-nick-host",
319
+ kind: "stable-id",
320
+ authentication: "asserted",
321
+ normalizeEntry: normalizeIrcNickHostEntry,
322
+ normalizeSubject: normalizeLowercaseStringOrEmpty,
323
+ sensitivity: "pii"
324
+ },
325
+ {
326
+ key: "irc-nick",
327
+ kind: "plugin:irc-nick",
328
+ normalizeEntry: normalizeIrcNickEntry,
329
+ normalizeSubject: normalizeLowercaseStringOrEmpty,
330
+ authentication: "mutable",
331
+ sensitivity: "pii"
332
+ }
333
+ ],
334
+ isWildcardEntry: (entry) => normalizeIrcAllowEntry(entry) === "*",
335
+ resolveEntryId: ({ entryIndex, fieldKey }) => `irc-entry-${entryIndex + 1}:${fieldKey === "irc-nick" ? "nick" : "id"}`
336
+ });
337
+ function isBareNick(value) {
338
+ return !value.includes("!") && !value.includes("@");
339
+ }
340
+ function hasVerifiedHost(value) {
341
+ return value.includes("@");
342
+ }
343
+ function isHostlessNickUser(value) {
344
+ return value.includes("!") && !value.includes("@");
345
+ }
346
+ function normalizeIrcStableEntry(value) {
347
+ const normalized = normalizeIrcAllowEntry(value);
348
+ if (!normalized.includes("!") || !hasVerifiedHost(normalized)) return null;
349
+ return normalized;
350
+ }
351
+ function normalizeIrcNickHostEntry(value) {
352
+ const normalized = normalizeIrcAllowEntry(value);
353
+ return !normalized.includes("!") && hasVerifiedHost(normalized) ? normalized : null;
354
+ }
355
+ function normalizeIrcNickUserEntry(value) {
356
+ const normalized = normalizeIrcAllowEntry(value);
357
+ if (!normalized || normalized === "*" || !isHostlessNickUser(normalized)) return null;
358
+ return normalized;
359
+ }
360
+ function normalizeIrcNickEntry(value) {
361
+ const normalized = normalizeIrcAllowEntry(value);
362
+ if (!normalized || normalized === "*" || !isBareNick(normalized)) return null;
363
+ return normalized;
364
+ }
365
+ function createIrcIngressSubject(message) {
366
+ const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
367
+ const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
368
+ const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
369
+ return {
370
+ stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
371
+ aliases: {
372
+ "irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
373
+ "irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!") && candidate.includes("@")),
374
+ "irc-nick": nick
375
+ }
376
+ };
377
+ }
378
+ //#endregion
379
+ //#region extensions/irc/src/outbound-base.ts
380
+ function sanitizeIrcAssistantText(text) {
381
+ return sanitizeForPlainText(sanitizeAssistantVisibleText(text));
382
+ }
383
+ const ircOutboundBaseAdapter = {
384
+ deliveryMode: "direct",
385
+ chunker: chunkTextForOutbound,
386
+ chunkerMode: "markdown",
387
+ textChunkLimit: 350,
388
+ sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
389
+ };
390
+ //#endregion
391
+ //#region extensions/irc/src/protocol.ts
392
+ const IRC_TARGET_PATTERN = /^[^\s:]+$/u;
241
393
  function parseIrcLine(line) {
242
394
  const raw = line.replace(/[\r\n]+/g, "").trim();
243
395
  if (!raw) return null;
@@ -316,7 +468,7 @@ function sanitizeIrcTarget(raw) {
316
468
  if (!decoded) throw new Error("IRC target is required");
317
469
  if (decoded !== decoded.trim()) throw new Error(`Invalid IRC target: ${raw}`);
318
470
  if (hasIrcControlChars(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
319
- if (!IRC_TARGET_PATTERN$1.test(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
471
+ if (!IRC_TARGET_PATTERN.test(decoded)) throw new Error(`Invalid IRC target: ${raw}`);
320
472
  return decoded;
321
473
  }
322
474
  function makeIrcMessageId() {
@@ -648,68 +800,6 @@ function buildIrcConnectOptions(account, overrides = {}) {
648
800
  };
649
801
  }
650
802
  //#endregion
651
- //#region extensions/irc/src/normalize.ts
652
- const IRC_TARGET_PATTERN = /^[^\s:]+$/u;
653
- function isChannelTarget(target) {
654
- return target.startsWith("#") || target.startsWith("&");
655
- }
656
- function normalizeIrcMessagingTarget(raw) {
657
- const trimmed = raw.trim();
658
- if (!trimmed) return;
659
- let target = trimmed;
660
- if (normalizeLowercaseStringOrEmpty(target).startsWith("irc:")) target = target.slice(4).trim();
661
- if (normalizeLowercaseStringOrEmpty(target).startsWith("channel:")) {
662
- target = target.slice(8).trim();
663
- if (!target.startsWith("#") && !target.startsWith("&")) target = `#${target}`;
664
- }
665
- if (normalizeLowercaseStringOrEmpty(target).startsWith("user:")) target = target.slice(5).trim();
666
- if (!target || !looksLikeIrcTargetId(target)) return;
667
- return target;
668
- }
669
- function resolveIrcOutboundSessionRoute(params) {
670
- const target = normalizeIrcMessagingTarget(params.target);
671
- if (!target) return null;
672
- const chatType = isChannelTarget(target) ? "group" : "direct";
673
- return buildChannelOutboundSessionRoute({
674
- cfg: params.cfg,
675
- agentId: params.agentId,
676
- channel: "irc",
677
- accountId: params.accountId,
678
- recipientSessionExact: chatType === "direct" ? "direct-alias" : false,
679
- peer: {
680
- kind: chatType,
681
- id: target
682
- },
683
- chatType,
684
- from: `irc:${target}`,
685
- to: target
686
- });
687
- }
688
- function looksLikeIrcTargetId(raw) {
689
- const trimmed = raw.trim();
690
- if (!trimmed) return false;
691
- if (hasIrcControlChars(trimmed)) return false;
692
- return IRC_TARGET_PATTERN.test(trimmed);
693
- }
694
- function normalizeIrcAllowEntry(raw) {
695
- let value = normalizeLowercaseStringOrEmpty(raw);
696
- if (!value) return "";
697
- if (value.startsWith("irc:")) value = value.slice(4);
698
- if (value.startsWith("user:")) value = value.slice(5);
699
- return value.trim();
700
- }
701
- function buildIrcAllowlistCandidates(message, params) {
702
- const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
703
- const user = normalizeOptionalLowercaseString(message.senderUser);
704
- const host = normalizeOptionalLowercaseString(message.senderHost);
705
- const candidates = /* @__PURE__ */ new Set();
706
- if (nick && params?.allowNameMatching === true) candidates.add(nick);
707
- if (nick && user) candidates.add(`${nick}!${user}`);
708
- if (nick && host) candidates.add(`${nick}@${host}`);
709
- if (nick && user && host) candidates.add(`${nick}!${user}@${host}`);
710
- return [...candidates];
711
- }
712
- //#endregion
713
803
  //#region extensions/irc/src/send.ts
714
804
  function recordIrcOutboundActivity(accountId) {
715
805
  try {
@@ -729,7 +819,7 @@ function resolveTarget(to, opts) {
729
819
  if (fromOpt) return fromOpt;
730
820
  throw new Error(`Invalid IRC target: ${to}`);
731
821
  }
732
- async function sendMessageIrc(to, text, opts) {
822
+ async function sendIrcMessages(to, messages, opts, onDeliveryResult) {
733
823
  const cfg = requireRuntimeConfig(opts.cfg, "IRC send");
734
824
  const account = resolveIrcAccount({
735
825
  cfg,
@@ -742,37 +832,66 @@ async function sendMessageIrc(to, text, opts) {
742
832
  channel: "irc",
743
833
  accountId: account.accountId
744
834
  });
745
- const prepared = stripMarkdown(convertMarkdownTables(text.trim(), tableMode));
746
- if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
747
- const payload = opts.replyTo ? `${prepared}\n\n[reply:${opts.replyTo}]` : prepared;
748
- const client = opts.client;
749
- if (client?.isReady()) client.sendPrivmsg(target, payload);
750
- else {
751
- const transient = await connectIrcClient(buildIrcConnectOptions(account, { connectTimeoutMs: 12e3 }));
752
- if (target.startsWith("#") || target.startsWith("&")) transient.join(target);
753
- transient.sendPrivmsg(target, payload);
754
- transient.quit("sent");
755
- }
756
- recordIrcOutboundActivity(account.accountId);
757
- const messageId = makeIrcMessageId();
758
- return {
759
- messageId,
760
- target,
761
- receipt: createMessageReceiptFromOutboundResults({
762
- results: [{
763
- channel: "irc",
835
+ const preparedMessages = messages.map((message) => {
836
+ const prepared = stripMarkdown(convertMarkdownTables(message.text.trim(), tableMode));
837
+ if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
838
+ return {
839
+ payload: message.replyTo ? `${prepared}\n\n[reply:${message.replyTo}]` : prepared,
840
+ replyTo: message.replyTo
841
+ };
842
+ });
843
+ if (preparedMessages.length === 0) return [];
844
+ opts.abortSignal?.throwIfAborted();
845
+ let transient;
846
+ const client = opts.client?.isReady() ? opts.client : transient = await connectIrcClient(buildIrcConnectOptions(account, {
847
+ connectTimeoutMs: 12e3,
848
+ abortSignal: opts.abortSignal
849
+ }));
850
+ const results = [];
851
+ try {
852
+ opts.abortSignal?.throwIfAborted();
853
+ if (transient && (target.startsWith("#") || target.startsWith("&"))) client.join(target);
854
+ for (const message of preparedMessages) {
855
+ opts.abortSignal?.throwIfAborted();
856
+ if (!client.isReady()) throw new Error("IRC connection closed before send");
857
+ await opts.onPlatformSendDispatch?.();
858
+ opts.abortSignal?.throwIfAborted();
859
+ if (!client.isReady()) throw new Error("IRC connection closed before send");
860
+ client.sendPrivmsg(target, message.payload);
861
+ recordIrcOutboundActivity(account.accountId);
862
+ const messageId = makeIrcMessageId();
863
+ const result = {
764
864
  messageId,
765
- conversationId: target
766
- }],
767
- kind: "text",
768
- ...opts.replyTo ? { replyToId: opts.replyTo } : {}
769
- })
770
- };
865
+ target,
866
+ receipt: createMessageReceiptFromOutboundResults({
867
+ results: [{
868
+ channel: "irc",
869
+ messageId,
870
+ conversationId: target
871
+ }],
872
+ kind: "text",
873
+ ...message.replyTo ? { replyToId: message.replyTo } : {}
874
+ })
875
+ };
876
+ results.push(result);
877
+ await onDeliveryResult?.(result);
878
+ }
879
+ return results;
880
+ } finally {
881
+ transient?.quit("sent");
882
+ }
883
+ }
884
+ async function sendMessageIrc(to, text, opts) {
885
+ const result = (await sendIrcMessages(to, [{
886
+ text,
887
+ ...opts.replyTo ? { replyTo: opts.replyTo } : {}
888
+ }], opts))[0];
889
+ if (!result) throw new Error("Message must be non-empty for IRC sends");
890
+ return result;
771
891
  }
772
892
  //#endregion
773
893
  //#region extensions/irc/src/message-adapter.ts
774
- async function sendIrcMessage(...args) {
775
- const { target, ...result } = await sendMessageIrc(...args);
894
+ function toIrcMessageResult({ target, ...result }) {
776
895
  return {
777
896
  ...result,
778
897
  target: {
@@ -781,6 +900,40 @@ async function sendIrcMessage(...args) {
781
900
  }
782
901
  };
783
902
  }
903
+ async function sendIrcMessage(...args) {
904
+ return toIrcMessageResult(await sendMessageIrc(...args));
905
+ }
906
+ const sendFormattedIrcText = async (ctx) => {
907
+ const { chunkMarkdownTextWithMode, resolveChunkMode, resolveTextChunkLimit } = await import("openclaw/plugin-sdk/reply-chunking");
908
+ const accountId = ctx.accountId ?? void 0;
909
+ const textLimit = ctx.formatting?.textLimit ?? resolveTextChunkLimit(ctx.cfg, "irc", accountId, { fallbackLimit: ircOutboundBaseAdapter.textChunkLimit });
910
+ const chunkMode = ctx.formatting?.chunkMode ?? resolveChunkMode(ctx.cfg, "irc", accountId);
911
+ const chunkText = (text) => ctx.formatting ? ircOutboundBaseAdapter.chunker(text, textLimit, { formatting: ctx.formatting }) : ircOutboundBaseAdapter.chunker(text, textLimit);
912
+ let chunks;
913
+ if (chunkMode === "newline") {
914
+ const blocks = chunkMarkdownTextWithMode(ctx.text, textLimit, chunkMode);
915
+ if (blocks.length === 0 && ctx.text) blocks.push(ctx.text);
916
+ chunks = blocks.flatMap((block) => {
917
+ const blockChunks = chunkText(block);
918
+ return blockChunks.length === 0 && block ? [block] : blockChunks;
919
+ });
920
+ } else chunks = chunkText(ctx.text);
921
+ const nextReplyToId = createReplyToFanout(ctx);
922
+ return (await sendIrcMessages(ctx.to, chunks.map((text) => {
923
+ const replyTo = nextReplyToId();
924
+ return replyTo ? {
925
+ text,
926
+ replyTo
927
+ } : { text };
928
+ }), {
929
+ cfg: ctx.cfg,
930
+ accountId,
931
+ abortSignal: ctx.abortSignal,
932
+ onPlatformSendDispatch: ctx.onPlatformSendDispatch
933
+ }, async (result) => {
934
+ await ctx.onDeliveryResult?.(attachChannelToResult("irc", toIrcMessageResult(result)));
935
+ })).map((result) => attachChannelToResult("irc", toIrcMessageResult(result)));
936
+ };
784
937
  const ircMessageAdapter = defineChannelMessageAdapter({
785
938
  id: "irc",
786
939
  durableFinal: { capabilities: {
@@ -802,18 +955,6 @@ const ircMessageAdapter = defineChannelMessageAdapter({
802
955
  }
803
956
  });
804
957
  //#endregion
805
- //#region extensions/irc/src/outbound-base.ts
806
- function sanitizeIrcAssistantText(text) {
807
- return sanitizeForPlainText(sanitizeAssistantVisibleText(text));
808
- }
809
- const ircOutboundBaseAdapter = {
810
- deliveryMode: "direct",
811
- chunker: chunkTextForOutbound,
812
- chunkerMode: "markdown",
813
- textChunkLimit: 350,
814
- sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
815
- };
816
- //#endregion
817
958
  //#region extensions/irc/src/policy.ts
818
959
  function resolveIrcGroupScope(params) {
819
960
  const { "*": wildcard, ...groups } = params.groups ?? {};
@@ -1425,7 +1566,7 @@ const meta = {
1425
1566
  systemImage: "number",
1426
1567
  markdownCapable: true
1427
1568
  };
1428
- const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-CSpBc1ef.js"));
1569
+ const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-DZcQfOqp.js"));
1429
1570
  function normalizePairingTarget(raw) {
1430
1571
  const normalized = normalizeIrcAllowEntry(raw);
1431
1572
  if (!normalized) return "";
@@ -1479,6 +1620,7 @@ const resolveIrcDmPolicy = createScopedDmSecurityResolver({
1479
1620
  resolvePolicy: (account) => account.config.dmPolicy,
1480
1621
  resolveAllowFrom: (account) => account.config.allowFrom,
1481
1622
  policyPathSuffix: "dmPolicy",
1623
+ classifyEntryAuthentication: identityEntryAuthenticationClassifier(ircIngressIdentity),
1482
1624
  normalizeEntry: (raw) => normalizeIrcAllowEntry(raw)
1483
1625
  });
1484
1626
  const collectIrcGroupPolicyWarnings = createAllowlistProviderOpenWarningCollector({
@@ -1668,7 +1810,10 @@ const ircPlugin = createChatChannelPlugin({
1668
1810
  collectWarnings: collectIrcSecurityWarnings
1669
1811
  },
1670
1812
  outbound: {
1671
- base: ircOutboundBaseAdapter,
1813
+ base: {
1814
+ ...ircOutboundBaseAdapter,
1815
+ sendFormattedText: sendFormattedIrcText
1816
+ },
1672
1817
  attachedResults: {
1673
1818
  channel: "irc",
1674
1819
  sendText: ({ onDeliveryResult: _onDeliveryResult, ...ctx }) => ircMessageAdapter.send.text(ctx),
@@ -1680,4 +1825,4 @@ const ircPlugin = createChatChannelPlugin({
1680
1825
  }
1681
1826
  });
1682
1827
  //#endregion
1683
- export { resolveDefaultIrcAccountId as _, resolveIrcGroupRequireMention as a, buildIrcAllowlistCandidates as c, buildIrcConnectOptions as d, connectIrcClient as f, listIrcAccountIds as g, listEnabledIrcAccounts as h, resolveIrcGroupMatch as i, isChannelTarget as l, parseIrcPrefix as m, ircSetupWizard as n, sanitizeIrcAssistantText as o, parseIrcLine as p, ircSetupAdapter as r, sendMessageIrc as s, ircPlugin as t, normalizeIrcAllowEntry as u, resolveIrcAccount as v };
1828
+ export { listIrcAccountIds as _, resolveIrcGroupRequireMention as a, connectIrcClient as c, sanitizeIrcAssistantText as d, createIrcIngressSubject as f, listEnabledIrcAccounts as g, normalizeIrcAllowEntry as h, resolveIrcGroupMatch as i, parseIrcLine as l, isChannelTarget as m, ircSetupWizard as n, sendMessageIrc as o, ircIngressIdentity as p, ircSetupAdapter as r, buildIrcConnectOptions as s, ircPlugin as t, parseIrcPrefix as u, resolveDefaultIrcAccountId as v, resolveIrcAccount as y };
@@ -1,2 +1,2 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-DdB_h5mL.js";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-CpB6AaUh.js";
2
2
  export { IrcChannelConfigSchema };
@@ -1,2 +1,2 @@
1
- import { t as ircPlugin } from "./channel-BO1GdA4V.js";
1
+ import { t as ircPlugin } from "./channel-BxbQEBnS.js";
2
2
  export { ircPlugin };
@@ -1,51 +1,18 @@
1
- import { a as resolveIrcGroupRequireMention, c as buildIrcAllowlistCandidates, d as buildIrcConnectOptions, f as connectIrcClient, i as resolveIrcGroupMatch, l as isChannelTarget, m as parseIrcPrefix, o as sanitizeIrcAssistantText, p as parseIrcLine, s as sendMessageIrc, u as normalizeIrcAllowEntry, v as resolveIrcAccount } from "./channel-BO1GdA4V.js";
1
+ import { a as resolveIrcGroupRequireMention, c as connectIrcClient, d as sanitizeIrcAssistantText, f as createIrcIngressSubject, h as normalizeIrcAllowEntry, i as resolveIrcGroupMatch, l as parseIrcLine, m as isChannelTarget, o as sendMessageIrc, p as ircIngressIdentity, s as buildIrcConnectOptions, u as parseIrcPrefix, y as resolveIrcAccount } from "./channel-BxbQEBnS.js";
2
2
  import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
3
+ import { channelIngressRoutes, createChannelIngressResolver } from "openclaw/plugin-sdk/channel-ingress-runtime";
3
4
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
5
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
5
6
  import { randomUUID } from "node:crypto";
6
7
  import { resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
7
8
  import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
8
9
  import { logInboundDrop, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
9
- import { channelIngressRoutes, createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
10
10
  import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
11
11
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
12
12
  import { deliverFormattedTextWithAttachments } from "openclaw/plugin-sdk/reply-payload";
13
13
  import { GROUP_POLICY_BLOCKED_LABEL, resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
14
14
  //#region extensions/irc/src/inbound.ts
15
15
  const CHANNEL_ID = "irc";
16
- const ircIngressIdentity = defineStableChannelIngressIdentity({
17
- key: "irc-id",
18
- normalizeEntry: normalizeIrcStableEntry,
19
- normalizeSubject: normalizeLowercaseStringOrEmpty,
20
- sensitivity: "pii",
21
- aliases: [
22
- {
23
- key: "irc-id-nick-user",
24
- kind: "stable-id",
25
- normalizeEntry: normalizeIrcNickUserEntry,
26
- normalizeSubject: normalizeLowercaseStringOrEmpty,
27
- dangerous: true,
28
- sensitivity: "pii"
29
- },
30
- {
31
- key: "irc-id-nick-host",
32
- kind: "stable-id",
33
- normalizeEntry: () => null,
34
- normalizeSubject: normalizeLowercaseStringOrEmpty,
35
- sensitivity: "pii"
36
- },
37
- {
38
- key: "irc-nick",
39
- kind: "plugin:irc-nick",
40
- normalizeEntry: normalizeIrcNickEntry,
41
- normalizeSubject: normalizeLowercaseStringOrEmpty,
42
- dangerous: true,
43
- sensitivity: "pii"
44
- }
45
- ],
46
- isWildcardEntry: (entry) => normalizeIrcAllowEntry(entry) === "*",
47
- resolveEntryId: ({ entryIndex, fieldKey }) => `irc-entry-${entryIndex + 1}:${fieldKey === "irc-nick" ? "nick" : "id"}`
48
- });
49
16
  const escapeIrcRegexLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50
17
  const IRC_NICK_CHARACTER = String.raw`[A-Za-z0-9_\-\[\]\\\x60^{}|~]`;
51
18
  const IRC_RFC1459_CASE_EQUIVALENTS = /* @__PURE__ */ new Map([
@@ -64,46 +31,9 @@ function buildIrcNickMentionPattern(value) {
64
31
  return equivalent ? `[${escapeIrcRegexLiteral(character)}${escapeIrcRegexLiteral(equivalent)}]` : escapeIrcRegexLiteral(character);
65
32
  }).join("");
66
33
  }
67
- function isBareNick(value) {
68
- return !value.includes("!") && !value.includes("@");
69
- }
70
- function hasVerifiedHost(value) {
71
- return value.includes("@");
72
- }
73
- function isHostlessNickUser(value) {
74
- return value.includes("!") && !value.includes("@");
75
- }
76
- function normalizeIrcStableEntry(value) {
77
- const normalized = normalizeIrcAllowEntry(value);
78
- if (!normalized || normalized === "*" || !hasVerifiedHost(normalized)) return null;
79
- return normalized;
80
- }
81
- function normalizeIrcNickUserEntry(value) {
82
- const normalized = normalizeIrcAllowEntry(value);
83
- if (!normalized || normalized === "*" || !isHostlessNickUser(normalized)) return null;
84
- return normalized;
85
- }
86
- function normalizeIrcNickEntry(value) {
87
- const normalized = normalizeIrcAllowEntry(value);
88
- if (!normalized || normalized === "*" || !isBareNick(normalized)) return null;
89
- return normalized;
90
- }
91
34
  function hasEntries(entries) {
92
35
  return normalizeStringEntries(entries).some((entry) => normalizeIrcAllowEntry(entry));
93
36
  }
94
- function createIrcIngressSubject(message) {
95
- const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
96
- const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
97
- const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
98
- return {
99
- stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
100
- aliases: {
101
- "irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
102
- "irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!") && candidate.includes("@")),
103
- "irc-nick": nick
104
- }
105
- };
106
- }
107
37
  function routeDescriptorsForIrcGroup(params) {
108
38
  if (!params.isGroup) return [];
109
39
  return channelIngressRoutes(params.groupPolicy === "allowlist" && {
@@ -1,5 +1,5 @@
1
1
  import { createChannelConfigUiHints } from "openclaw/plugin-sdk/channel-core";
2
- import { ChannelGroupEntrySchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, buildChannelConfigSchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
2
+ import { ChannelGroupEntrySchema, DmPolicySchema, GroupPolicySchema, MarkdownConfigSchema, ReplyRuntimeConfigSchemaShape, ReplyToModeSchema, buildChannelConfigSchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
3
3
  import { z } from "zod";
4
4
  //#region extensions/irc/src/config-ui-hints.ts
5
5
  const ircChannelConfigUiHints = {
@@ -60,6 +60,7 @@ const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannel
60
60
  name: z.string().optional(),
61
61
  enabled: z.boolean().optional(),
62
62
  configWrites: z.boolean().optional(),
63
+ replyToMode: ReplyToModeSchema.optional(),
63
64
  dangerouslyAllowNameMatching: z.boolean().optional(),
64
65
  host: z.string().optional(),
65
66
  port: z.number().int().min(1).max(65535).optional(),
@@ -76,7 +77,6 @@ const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannel
76
77
  groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
77
78
  groups: z.record(z.string(), ChannelGroupEntrySchema.optional()).optional(),
78
79
  channels: z.array(z.string()).optional(),
79
- mentionPatterns: z.array(z.string()).optional(),
80
80
  markdown: MarkdownConfigSchema,
81
81
  ...ReplyRuntimeConfigSchemaShape
82
82
  }).strict(), {
@@ -1,5 +1,5 @@
1
- import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor-migrations";
2
- //#region extensions/irc/doctor-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 };
@@ -29,6 +29,26 @@
29
29
  "configWrites": {
30
30
  "type": "boolean"
31
31
  },
32
+ "replyToMode": {
33
+ "anyOf": [
34
+ {
35
+ "type": "string",
36
+ "const": "off"
37
+ },
38
+ {
39
+ "type": "string",
40
+ "const": "first"
41
+ },
42
+ {
43
+ "type": "string",
44
+ "const": "all"
45
+ },
46
+ {
47
+ "type": "string",
48
+ "const": "batched"
49
+ }
50
+ ]
51
+ },
32
52
  "dangerouslyAllowNameMatching": {
33
53
  "type": "boolean"
34
54
  },
@@ -227,12 +247,6 @@
227
247
  "type": "string"
228
248
  }
229
249
  },
230
- "mentionPatterns": {
231
- "type": "array",
232
- "items": {
233
- "type": "string"
234
- }
235
- },
236
250
  "markdown": {
237
251
  "type": "object",
238
252
  "properties": {
@@ -355,6 +369,26 @@
355
369
  "configWrites": {
356
370
  "type": "boolean"
357
371
  },
372
+ "replyToMode": {
373
+ "anyOf": [
374
+ {
375
+ "type": "string",
376
+ "const": "off"
377
+ },
378
+ {
379
+ "type": "string",
380
+ "const": "first"
381
+ },
382
+ {
383
+ "type": "string",
384
+ "const": "all"
385
+ },
386
+ {
387
+ "type": "string",
388
+ "const": "batched"
389
+ }
390
+ ]
391
+ },
358
392
  "dangerouslyAllowNameMatching": {
359
393
  "type": "boolean"
360
394
  },
@@ -553,12 +587,6 @@
553
587
  "type": "string"
554
588
  }
555
589
  },
556
- "mentionPatterns": {
557
- "type": "array",
558
- "items": {
559
- "type": "string"
560
- }
561
- },
562
590
  "markdown": {
563
591
  "type": "object",
564
592
  "properties": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/irc",
3
- "version": "2026.8.1-beta.3",
3
+ "version": "2026.8.1",
4
4
  "description": "OpenClaw IRC channel plugin",
5
5
  "type": "module",
6
6
  "openclaw": {
@@ -121,10 +121,10 @@
121
121
  }
122
122
  },
123
123
  "compat": {
124
- "pluginApi": ">=2026.8.1-beta.3"
124
+ "pluginApi": ">=2026.8.1"
125
125
  },
126
126
  "build": {
127
- "openclawVersion": "2026.8.1-beta.3",
127
+ "openclawVersion": "2026.8.1",
128
128
  "bundledDist": false
129
129
  },
130
130
  "release": {
@@ -149,7 +149,7 @@
149
149
  "README.md"
150
150
  ],
151
151
  "peerDependencies": {
152
- "openclaw": ">=2026.8.1-beta.3"
152
+ "openclaw": ">=2026.8.1"
153
153
  },
154
154
  "peerDependenciesMeta": {
155
155
  "openclaw": {