@openclaw/irc 2026.10.10 → 2026.11.5-beta.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.
Files changed (32) hide show
  1. package/dist/.setup/{channel-t-k-A5n4.mjs → channel-DRdPa321.mjs} +252 -294
  2. package/dist/.setup/channel-plugin-api-DFjxQCOi.mjs +2 -0
  3. package/dist/.setup/channel-runtime-D2sqTm8e.mjs +579 -0
  4. package/dist/.setup/{config-schema-DFqPW9AB.mjs → config-schema-CQuAQW7O.mjs} +1 -2
  5. package/dist/.source-module-urls/4751df78f545ae971ceb22b6e4bff6086f63313240899a1d9884b970424a305e.json +1 -1
  6. package/dist/api.js +1 -1
  7. package/dist/channel-config-api.js +1 -1
  8. package/dist/channel-plugin-api.js +1 -1
  9. package/dist/setup-entry.js +1 -1
  10. package/node_modules/zod/package.json +1 -1
  11. package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
  12. package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
  13. package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
  14. package/node_modules/zod/src/v4/core/compile.ts +9 -0
  15. package/node_modules/zod/src/v4/core/schemas.ts +35 -1
  16. package/node_modules/zod/src/v4/core/util.ts +13 -0
  17. package/node_modules/zod/src/v4/core/versions.ts +1 -1
  18. package/node_modules/zod/v4/core/compile.cjs +8 -0
  19. package/node_modules/zod/v4/core/compile.js +8 -0
  20. package/node_modules/zod/v4/core/schemas.cjs +30 -0
  21. package/node_modules/zod/v4/core/schemas.d.cts +3 -1
  22. package/node_modules/zod/v4/core/schemas.d.ts +3 -1
  23. package/node_modules/zod/v4/core/schemas.js +30 -0
  24. package/node_modules/zod/v4/core/util.cjs +15 -0
  25. package/node_modules/zod/v4/core/util.d.cts +2 -0
  26. package/node_modules/zod/v4/core/util.d.ts +2 -0
  27. package/node_modules/zod/v4/core/util.js +14 -0
  28. package/node_modules/zod/v4/core/versions.cjs +1 -1
  29. package/node_modules/zod/v4/core/versions.js +1 -1
  30. package/package.json +5 -5
  31. package/dist/.setup/channel-plugin-api-DRR9Fg5C.mjs +0 -2
  32. package/dist/.setup/channel-runtime-exb5AWhn.mjs +0 -648
@@ -1,13 +1,15 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-DFqPW9AB.mjs";
2
- import { n as getOptionalIrcRuntime } from "./runtime-CsdAoZex.mjs";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-CQuAQW7O.mjs";
3
2
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-0Ccz8X9F.mjs";
3
+ import { n as getOptionalIrcRuntime } from "./runtime-CsdAoZex.mjs";
4
4
  import { createAccountListHelpers, describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
5
5
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
6
6
  import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
7
7
  import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
8
8
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin, parseOptionalDelimitedEntries } from "openclaw/plugin-sdk/channel-core";
9
9
  import { defineStableChannelIngressIdentity, identityEntryAuthenticationClassifier } from "openclaw/plugin-sdk/channel-ingress-runtime";
10
+ import { createAccountStatusSink, createMessageReceiptFromOutboundResults, createReplyToFanout, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
10
11
  import { buildMutableAllowEntryDetector, collectStandardAllowlistLists, createAllowlistProviderOpenWarningCollector, createConditionalWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, resolveScopeKeyCaseInsensitive, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
12
+ import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
11
13
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
12
14
  import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister } from "openclaw/plugin-sdk/directory-runtime";
13
15
  import { buildProbeChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
@@ -17,20 +19,18 @@ import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
17
19
  import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
18
20
  import { resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
19
21
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringifiedOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
20
- import { createAccountStatusSink, createMessageReceiptFromOutboundResults, createReplyToFanout, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
21
22
  import { containsAsciiControlCharacter as hasIrcControlChars } from "openclaw/plugin-sdk/string-normalization-runtime";
22
- import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
23
23
  import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
24
- import { randomUUID } from "node:crypto";
25
- import { createChannelPartialDeliveryError, isChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
26
- import { captureEffectAuthority } from "openclaw/plugin-sdk/fetch-runtime";
27
- import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
28
- import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
29
24
  import net from "node:net";
30
25
  import tls from "node:tls";
26
+ import { createChannelPartialDeliveryError, isChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
31
27
  import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
28
+ import { captureEffectAuthority } from "openclaw/plugin-sdk/fetch-runtime";
32
29
  import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
33
30
  import { assertSecretOwnerAvailable } from "openclaw/plugin-sdk/channel-secret-owner-runtime";
31
+ import { randomUUID } from "node:crypto";
32
+ import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
33
+ import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
34
34
  import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
35
35
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
36
36
  import { applyAccountNameToChannelSection, createAllowFromSection, createPromptParsedAllowFromForAccount, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicySetter, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
@@ -186,40 +186,6 @@ function listEnabledIrcAccounts(cfg) {
186
186
  accountId
187
187
  })).filter((account) => account.enabled);
188
188
  }
189
- // extensions/irc/src/doctor.ts
190
- const isIrcMutableAllowEntry = buildMutableAllowEntryDetector({ stableIdPattern: /^(?:(?:irc|user):)*[^@]*@/i });
191
- const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAllowlistWarningCollector({
192
- channel: "irc",
193
- detector: isIrcMutableAllowEntry,
194
- collectLists: (scope) => collectStandardAllowlistLists(scope, {
195
- includeGroups: true,
196
- groupField: "allowFrom"
197
- })
198
- });
199
- // extensions/irc/src/gateway.ts
200
- async function startIrcGatewayAccount(ctx) {
201
- const account = ctx.account;
202
- const statusSink = createAccountStatusSink({
203
- accountId: ctx.accountId,
204
- setStatus: ctx.setStatus
205
- });
206
- if (!account.configured) throw new Error(`IRC is not configured for account "${account.accountId}" (need host and nick in channels.irc).`);
207
- ctx.log?.info?.(`[${account.accountId}] starting IRC provider (${account.host}:${account.port}${account.tls ? " tls" : ""})`);
208
- const { monitorIrcProvider } = await import("./channel-runtime-exb5AWhn.mjs");
209
- await runPassiveAccountLifecycle({
210
- abortSignal: ctx.abortSignal,
211
- start: async () => await monitorIrcProvider({
212
- accountId: account.accountId,
213
- config: ctx.cfg,
214
- runtime: ctx.runtime,
215
- abortSignal: ctx.abortSignal,
216
- statusSink
217
- }),
218
- stop: async (monitor) => {
219
- await monitor.stop();
220
- }
221
- });
222
- }
223
189
  // extensions/irc/src/control-chars.ts
224
190
  function isIrcControlChar(charCode) {
225
191
  return charCode <= 31 || charCode === 127;
@@ -279,17 +245,6 @@ function normalizeIrcAllowEntry(raw) {
279
245
  if (value.startsWith("user:")) value = value.slice(5);
280
246
  return value.trim();
281
247
  }
282
- function buildIrcAllowlistCandidates(message, params) {
283
- const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
284
- const user = normalizeOptionalLowercaseString(message.senderUser);
285
- const host = normalizeOptionalLowercaseString(message.senderHost);
286
- const candidates = /* @__PURE__ */ new Set();
287
- if (nick && params?.allowNameMatching === true) candidates.add(nick);
288
- if (nick && user) candidates.add(`${nick}!${user}`);
289
- if (nick && host) candidates.add(`${nick}@${host}`);
290
- if (nick && user && host) candidates.add(`${nick}!${user}@${host}`);
291
- return [...candidates];
292
- }
293
248
  const ircIngressIdentity = defineStableChannelIngressIdentity({
294
249
  key: "irc-id",
295
250
  authentication: "asserted",
@@ -354,14 +309,20 @@ function normalizeIrcNickEntry(value) {
354
309
  return normalized;
355
310
  }
356
311
  function createIrcIngressSubject(message) {
357
- const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
358
- const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
359
312
  const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
313
+ const user = normalizeOptionalLowercaseString(message.senderUser);
314
+ const host = normalizeOptionalLowercaseString(message.senderHost);
315
+ const candidates = [
316
+ nick,
317
+ nick && user ? `${nick}!${user}` : "",
318
+ nick && host ? `${nick}@${host}` : "",
319
+ nick && user && host ? `${nick}!${user}@${host}` : ""
320
+ ];
360
321
  return {
361
- stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
322
+ stableId: candidates.findLast(hasVerifiedHost) ?? nick,
362
323
  aliases: {
363
324
  "irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
364
- "irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!")),
325
+ "irc-id-nick-host": candidates.find((candidate) => hasVerifiedHost(candidate) && !candidate.includes("!")),
365
326
  "irc-nick": nick
366
327
  }
367
328
  };
@@ -377,6 +338,49 @@ const ircOutboundBaseAdapter = {
377
338
  textChunkLimit: 350,
378
339
  sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
379
340
  };
341
+ // extensions/irc/src/policy.ts
342
+ function resolveIrcGroupScope(params) {
343
+ const { "*": wildcard, ...groups } = params.groups ?? {};
344
+ const project = (entry) => ({
345
+ requireMention: entry.requireMention,
346
+ tools: entry.tools
347
+ });
348
+ const tree = {
349
+ defaults: wildcard ? project(wildcard) : void 0,
350
+ scopes: Object.fromEntries(Object.entries(groups).map(([key, entry]) => [key, project(entry)]))
351
+ };
352
+ const key = resolveScopeKeyCaseInsensitive(tree, params.target);
353
+ return {
354
+ tree,
355
+ path: key ? [key] : []
356
+ };
357
+ }
358
+ function resolveIrcGroupMatch(params) {
359
+ const { path } = resolveIrcGroupScope(params);
360
+ const key = path[0];
361
+ const groupConfig = key ? params.groups?.[key] : void 0;
362
+ const wildcardConfig = params.groups?.["*"];
363
+ return {
364
+ allowed: Boolean(groupConfig ?? wildcardConfig),
365
+ groupConfig,
366
+ wildcardConfig,
367
+ hasConfiguredGroups: Object.keys(params.groups ?? {}).length > 0
368
+ };
369
+ }
370
+ function resolveIrcGroupRequireMention(params) {
371
+ const { tree, path } = resolveIrcGroupScope(params);
372
+ return resolveScopeRequireMention({
373
+ tree,
374
+ path
375
+ });
376
+ }
377
+ function resolveIrcGroupToolPolicy(params) {
378
+ const { tree, path } = resolveIrcGroupScope(params);
379
+ return resolveScopeToolsPolicy({
380
+ tree,
381
+ path
382
+ });
383
+ }
380
384
  // packages/normalization-core/src/utf16-slice.ts
381
385
  function isHighSurrogate(codeUnit) {
382
386
  return codeUnit >= 55296 && codeUnit <= 56319;
@@ -449,7 +453,6 @@ function parseIrcLine(line) {
449
453
  cursor = cursor.slice(spaceIdx + 1);
450
454
  }
451
455
  return {
452
- raw,
453
456
  prefix,
454
457
  command: command.toUpperCase(),
455
458
  params,
@@ -476,8 +479,7 @@ function parseIrcPrefix(prefix) {
476
479
  user
477
480
  };
478
481
  }
479
- if (prefix.includes(".")) return { server: prefix };
480
- return { nick: prefix };
482
+ return prefix.includes(".") ? {} : { nick: prefix };
481
483
  }
482
484
  function sanitizeIrcOutboundText(text) {
483
485
  return stripIrcControlChars(text.replace(/\r?\n/g, " ")).trim();
@@ -691,7 +693,6 @@ async function connectIrcClient(options) {
691
693
  buffer = buffer.slice(idx + 1);
692
694
  idx = buffer.indexOf("\n");
693
695
  if (!rawLine) continue;
694
- options.onLine?.(rawLine);
695
696
  const line = parseIrcLine(rawLine);
696
697
  if (!line) continue;
697
698
  if (line.command === "PING") {
@@ -802,7 +803,6 @@ async function connectIrcClient(options) {
802
803
  return currentNick;
803
804
  },
804
805
  isReady: () => ready && !closed,
805
- sendRaw,
806
806
  join,
807
807
  sendPrivmsg,
808
808
  quit,
@@ -821,16 +821,44 @@ function buildIrcConnectOptions(account, overrides = {}) {
821
821
  username: account.username,
822
822
  realname: account.realname,
823
823
  password: account.password,
824
- nickserv: {
825
- enabled: account.config.nickserv?.enabled,
826
- service: account.config.nickserv?.service,
827
- password: account.config.nickserv?.password,
828
- register: account.config.nickserv?.register,
829
- registerEmail: account.config.nickserv?.registerEmail
830
- },
824
+ nickserv: account.config.nickserv,
831
825
  ...overrides
832
826
  };
833
827
  }
828
+ // extensions/irc/src/probe.ts
829
+ async function probeIrc(cfg, opts) {
830
+ const account = resolveIrcAccount({
831
+ cfg,
832
+ accountId: opts?.accountId
833
+ });
834
+ const base = {
835
+ ok: false,
836
+ host: account.host,
837
+ port: account.port,
838
+ tls: account.tls,
839
+ nick: account.nick
840
+ };
841
+ if (!account.configured) return {
842
+ ...base,
843
+ error: "missing host or nick"
844
+ };
845
+ const started = Date.now();
846
+ try {
847
+ const client = await connectIrcClient(buildIrcConnectOptions(account, { connectTimeoutMs: opts?.timeoutMs ?? 8e3 }));
848
+ const elapsed = Date.now() - started;
849
+ client.quit("probe");
850
+ return {
851
+ ...base,
852
+ ok: true,
853
+ latencyMs: elapsed
854
+ };
855
+ } catch (err) {
856
+ return {
857
+ ...base,
858
+ error: err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err)
859
+ };
860
+ }
861
+ }
834
862
  // extensions/irc/src/send.ts
835
863
  async function sendIrcMessages(to, text, opts, planMessages = (preparedText) => [{
836
864
  text: preparedText,
@@ -855,15 +883,14 @@ async function sendIrcMessages(to, text, opts, planMessages = (preparedText) =>
855
883
  if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
856
884
  const messages = planMessages(prepared);
857
885
  opts.abortSignal?.throwIfAborted();
858
- let transient;
859
- const client = opts.client?.isReady() ? opts.client : transient = await connectIrcClient(buildIrcConnectOptions(account, {
886
+ const client = await connectIrcClient(buildIrcConnectOptions(account, {
860
887
  connectTimeoutMs: 12e3,
861
888
  abortSignal: opts.abortSignal
862
889
  }));
863
890
  const results = [];
864
891
  try {
865
892
  opts.abortSignal?.throwIfAborted();
866
- if (transient && (target.startsWith("#") || target.startsWith("&"))) await effect.initiate(() => {
893
+ if (target.startsWith("#") || target.startsWith("&")) await effect.initiate(() => {
867
894
  opts.abortSignal?.throwIfAborted();
868
895
  if (!client.isReady()) throw new Error("IRC connection closed before join");
869
896
  client.join(target);
@@ -920,7 +947,7 @@ async function sendIrcMessages(to, text, opts, planMessages = (preparedText) =>
920
947
  visibleReplySent: true
921
948
  });
922
949
  } finally {
923
- transient?.quit("sent");
950
+ client.quit("sent");
924
951
  }
925
952
  }
926
953
  async function sendMessageIrc(to, text, opts) {
@@ -928,136 +955,6 @@ async function sendMessageIrc(to, text, opts) {
928
955
  if (!result) throw new Error("Message must be non-empty for IRC sends");
929
956
  return result;
930
957
  }
931
- // extensions/irc/src/message-adapter.ts
932
- function toIrcMessageResult({ target, ...result }) {
933
- return {
934
- ...result,
935
- target: {
936
- kind: "conversation",
937
- id: target
938
- }
939
- };
940
- }
941
- async function sendIrcMessage(ctx, text = ctx.text) {
942
- return toIrcMessageResult(await sendMessageIrc(ctx.to, text, {
943
- cfg: ctx.cfg,
944
- accountId: ctx.accountId ?? void 0,
945
- replyTo: ctx.replyToId ?? void 0,
946
- abortSignal: ctx.signal,
947
- onPlatformSendDispatch: ctx.onPlatformSendDispatch
948
- }));
949
- }
950
- const sendFormattedIrcText = async (ctx) => {
951
- const { chunkTextWithMode, resolveChunkMode, resolveTextChunkLimit } = await import("openclaw/plugin-sdk/reply-chunking");
952
- const accountId = ctx.accountId ?? void 0;
953
- const textLimit = ctx.formatting?.textLimit ?? resolveTextChunkLimit(ctx.cfg, "irc", accountId, { fallbackLimit: ircOutboundBaseAdapter.textChunkLimit });
954
- const chunkMode = ctx.formatting?.chunkMode ?? resolveChunkMode(ctx.cfg, "irc", accountId);
955
- const nextReplyToId = createReplyToFanout(ctx);
956
- return (await sendIrcMessages(ctx.to, ctx.text, {
957
- cfg: ctx.cfg,
958
- accountId,
959
- abortSignal: ctx.abortSignal,
960
- onPlatformSendDispatch: ctx.onPlatformSendDispatch
961
- }, (preparedText) => chunkTextWithMode(preparedText, textLimit, chunkMode).map((text) => ({
962
- text,
963
- replyTo: nextReplyToId()
964
- })), async (result) => {
965
- await ctx.onDeliveryResult?.(attachChannelToResult("irc", toIrcMessageResult(result)));
966
- })).map((result) => attachChannelToResult("irc", toIrcMessageResult(result)));
967
- };
968
- const ircMessageAdapter = defineChannelMessageAdapter({
969
- id: "irc",
970
- durableFinal: { capabilities: {
971
- text: true,
972
- media: true,
973
- replyTo: true
974
- } },
975
- send: {
976
- text: sendIrcMessage,
977
- media: (ctx) => sendIrcMessage(ctx, ctx.mediaUrl ? `${ctx.text}\n\nAttachment: ${ctx.mediaUrl}` : ctx.text)
978
- }
979
- });
980
- // extensions/irc/src/policy.ts
981
- function resolveIrcGroupScope(params) {
982
- const { "*": wildcard, ...groups } = params.groups ?? {};
983
- const project = (entry) => ({
984
- requireMention: entry.requireMention,
985
- tools: entry.tools
986
- });
987
- const tree = {
988
- defaults: wildcard ? project(wildcard) : void 0,
989
- scopes: Object.fromEntries(Object.entries(groups).map(([key, entry]) => [key, project(entry)]))
990
- };
991
- const key = resolveScopeKeyCaseInsensitive(tree, params.target);
992
- return {
993
- tree,
994
- path: key ? [key] : []
995
- };
996
- }
997
- function resolveIrcGroupMatch(params) {
998
- const { path } = resolveIrcGroupScope(params);
999
- const key = path[0];
1000
- const groupConfig = key ? params.groups?.[key] : void 0;
1001
- const wildcardConfig = params.groups?.["*"];
1002
- return {
1003
- allowed: Boolean(groupConfig ?? wildcardConfig),
1004
- groupConfig,
1005
- wildcardConfig,
1006
- hasConfiguredGroups: Object.keys(params.groups ?? {}).length > 0
1007
- };
1008
- }
1009
- function resolveIrcGroupRequireMention(params) {
1010
- const { tree, path } = resolveIrcGroupScope(params);
1011
- return resolveScopeRequireMention({
1012
- tree,
1013
- path
1014
- });
1015
- }
1016
- function resolveIrcGroupToolPolicy(params) {
1017
- const { tree, path } = resolveIrcGroupScope(params);
1018
- return resolveScopeToolsPolicy({
1019
- tree,
1020
- path
1021
- });
1022
- }
1023
- // extensions/irc/src/probe.ts
1024
- function formatError(err) {
1025
- if (err instanceof Error) return err.message;
1026
- return typeof err === "string" ? err : JSON.stringify(err);
1027
- }
1028
- async function probeIrc(cfg, opts) {
1029
- const account = resolveIrcAccount({
1030
- cfg,
1031
- accountId: opts?.accountId
1032
- });
1033
- const base = {
1034
- ok: false,
1035
- host: account.host,
1036
- port: account.port,
1037
- tls: account.tls,
1038
- nick: account.nick
1039
- };
1040
- if (!account.configured) return {
1041
- ...base,
1042
- error: "missing host or nick"
1043
- };
1044
- const started = Date.now();
1045
- try {
1046
- const client = await connectIrcClient(buildIrcConnectOptions(account, { connectTimeoutMs: opts?.timeoutMs ?? 8e3 }));
1047
- const elapsed = Date.now() - started;
1048
- client.quit("probe");
1049
- return {
1050
- ...base,
1051
- ok: true,
1052
- latencyMs: elapsed
1053
- };
1054
- } catch (err) {
1055
- return {
1056
- ...base,
1057
- error: formatError(err)
1058
- };
1059
- }
1060
- }
1061
958
  // extensions/irc/src/setup-core.ts
1062
959
  const channel$1 = "irc";
1063
960
  const setIrcDmPolicy = createTopLevelChannelDmPolicySetter({ channel: channel$1 });
@@ -1237,67 +1134,16 @@ function normalizeGroupEntry(raw) {
1237
1134
  return `#${normalized.replace(/^#+/, "")}`;
1238
1135
  }
1239
1136
  function parseAutoJoinChannels(value) {
1240
- return splitSetupEntries(value).map(normalizeGroupEntry).filter((entry) => Boolean(entry && entry !== "*")).filter(isChannelTarget);
1137
+ return splitSetupEntries(value).map(normalizeGroupEntry).filter((entry) => Boolean(entry && entry !== "*"));
1241
1138
  }
1242
- const promptIrcAllowFrom = createPromptParsedAllowFromForAccount({
1243
- defaultAccountId: resolveDefaultIrcAccountId,
1244
- noteTitle: t("wizard.irc.allowlistTitle"),
1245
- noteLines: [
1139
+ function ircAllowlistHelpLines() {
1140
+ return [
1246
1141
  t("wizard.irc.allowlistIntro"),
1247
1142
  t("wizard.irc.examples"),
1248
1143
  "- alice",
1249
1144
  "- alice!ident@example.org",
1250
1145
  t("wizard.irc.multipleEntries")
1251
- ],
1252
- message: t("wizard.irc.allowFromPrompt"),
1253
- placeholder: "alice, bob!ident@example.org",
1254
- parseEntries: (raw) => ({ entries: normalizeStringEntries(splitSetupEntries(raw).map(normalizeIrcAllowEntry)) }),
1255
- getExistingAllowFrom: ({ cfg }) => cfg.channels?.irc?.allowFrom ?? [],
1256
- applyAllowFrom: ({ cfg, allowFrom }) => setIrcAllowFrom(cfg, allowFrom)
1257
- });
1258
- async function promptIrcNickServConfig(params) {
1259
- const existing = resolveIrcAccount({
1260
- cfg: params.cfg,
1261
- accountId: params.accountId
1262
- }).config.nickserv;
1263
- const hasExisting = Boolean(existing?.password || existing?.passwordFile);
1264
- if (!await params.prompter.confirm({
1265
- message: hasExisting ? t("wizard.irc.nickServUpdatePrompt") : t("wizard.irc.nickServConfigurePrompt"),
1266
- initialValue: hasExisting
1267
- })) return params.cfg;
1268
- const service = (await params.prompter.text({
1269
- message: t("wizard.irc.nickServServicePrompt"),
1270
- initialValue: existing?.service || "NickServ",
1271
- validate: (value) => normalizeStringifiedOptionalString(value) ? void 0 : "Required"
1272
- })).trim();
1273
- const useEnvPassword = params.accountId === DEFAULT_ACCOUNT_ID$1 && Boolean(process.env.IRC_NICKSERV_PASSWORD?.trim()) && !(existing?.password || existing?.passwordFile) ? await params.prompter.confirm({
1274
- message: t("wizard.irc.nickServPasswordEnvPrompt"),
1275
- initialValue: true
1276
- }) : false;
1277
- const password = useEnvPassword ? void 0 : (await params.prompter.text({
1278
- message: t("wizard.irc.nickServPasswordPrompt"),
1279
- validate: () => void 0
1280
- })).trim();
1281
- if (!password && !useEnvPassword) return setIrcNickServ(params.cfg, params.accountId, {
1282
- enabled: false,
1283
- service
1284
- });
1285
- const register = await params.prompter.confirm({
1286
- message: t("wizard.irc.nickServRegisterPrompt"),
1287
- initialValue: existing?.register ?? false
1288
- });
1289
- const registerEmail = register ? (await params.prompter.text({
1290
- message: t("wizard.irc.nickServRegisterEmailPrompt"),
1291
- initialValue: existing?.registerEmail || (params.accountId === DEFAULT_ACCOUNT_ID$1 ? process.env.IRC_NICKSERV_REGISTER_EMAIL : void 0),
1292
- validate: (value) => normalizeStringifiedOptionalString(value) ? void 0 : "Required"
1293
- })).trim() : void 0;
1294
- return setIrcNickServ(params.cfg, params.accountId, {
1295
- enabled: true,
1296
- service,
1297
- ...password ? { password } : {},
1298
- register,
1299
- ...registerEmail ? { registerEmail } : {}
1300
- });
1146
+ ];
1301
1147
  }
1302
1148
  const ircDmPolicy = {
1303
1149
  label: "IRC",
@@ -1306,7 +1152,16 @@ const ircDmPolicy = {
1306
1152
  allowFromKey: "channels.irc.allowFrom",
1307
1153
  getCurrent: (cfg) => cfg.channels?.irc?.dmPolicy ?? "pairing",
1308
1154
  setPolicy: (cfg, policy) => setIrcDmPolicy(cfg, policy),
1309
- promptAllowFrom: promptIrcAllowFrom
1155
+ promptAllowFrom: createPromptParsedAllowFromForAccount({
1156
+ defaultAccountId: resolveDefaultIrcAccountId,
1157
+ noteTitle: t("wizard.irc.allowlistTitle"),
1158
+ noteLines: ircAllowlistHelpLines(),
1159
+ message: t("wizard.irc.allowFromPrompt"),
1160
+ placeholder: "alice, bob!ident@example.org",
1161
+ parseEntries: (raw) => ({ entries: normalizeStringEntries(splitSetupEntries(raw).map(normalizeIrcAllowEntry)) }),
1162
+ getExistingAllowFrom: ({ cfg }) => cfg.channels?.irc?.allowFrom ?? [],
1163
+ applyAllowFrom: ({ cfg, allowFrom }) => setIrcAllowFrom(cfg, allowFrom)
1164
+ })
1310
1165
  };
1311
1166
  const ircSetupWizard = {
1312
1167
  channel,
@@ -1471,13 +1326,7 @@ const ircSetupWizard = {
1471
1326
  },
1472
1327
  allowFrom: createAllowFromSection({
1473
1328
  helpTitle: t("wizard.irc.allowlistTitle"),
1474
- helpLines: [
1475
- t("wizard.irc.allowlistIntro"),
1476
- t("wizard.irc.examples"),
1477
- "- alice",
1478
- "- alice!ident@example.org",
1479
- t("wizard.irc.multipleEntries")
1480
- ],
1329
+ helpLines: ircAllowlistHelpLines(),
1481
1330
  message: t("wizard.irc.allowFromPrompt"),
1482
1331
  placeholder: "alice, bob!ident@example.org",
1483
1332
  invalidWithoutCredentialNote: t("wizard.irc.allowFromInvalid"),
@@ -1507,11 +1356,51 @@ const ircSetupWizard = {
1507
1356
  }
1508
1357
  }
1509
1358
  }
1510
- next = await promptIrcNickServConfig({
1511
- cfg: next,
1512
- prompter,
1513
- accountId
1514
- });
1359
+ const nickServConfig = next;
1360
+ next = await (async () => {
1361
+ const existing = resolveIrcAccount({
1362
+ cfg: nickServConfig,
1363
+ accountId
1364
+ }).config.nickserv;
1365
+ const hasExisting = Boolean(existing?.password || existing?.passwordFile);
1366
+ if (!await prompter.confirm({
1367
+ message: hasExisting ? t("wizard.irc.nickServUpdatePrompt") : t("wizard.irc.nickServConfigurePrompt"),
1368
+ initialValue: hasExisting
1369
+ })) return nickServConfig;
1370
+ const service = (await prompter.text({
1371
+ message: t("wizard.irc.nickServServicePrompt"),
1372
+ initialValue: existing?.service || "NickServ",
1373
+ validate: (value) => normalizeStringifiedOptionalString(value) ? void 0 : "Required"
1374
+ })).trim();
1375
+ const useEnvPassword = accountId === DEFAULT_ACCOUNT_ID$1 && Boolean(process.env.IRC_NICKSERV_PASSWORD?.trim()) && !(existing?.password || existing?.passwordFile) ? await prompter.confirm({
1376
+ message: t("wizard.irc.nickServPasswordEnvPrompt"),
1377
+ initialValue: true
1378
+ }) : false;
1379
+ const password = useEnvPassword ? void 0 : (await prompter.text({
1380
+ message: t("wizard.irc.nickServPasswordPrompt"),
1381
+ validate: () => void 0
1382
+ })).trim();
1383
+ if (!password && !useEnvPassword) return setIrcNickServ(nickServConfig, accountId, {
1384
+ enabled: false,
1385
+ service
1386
+ });
1387
+ const register = await prompter.confirm({
1388
+ message: t("wizard.irc.nickServRegisterPrompt"),
1389
+ initialValue: existing?.register ?? false
1390
+ });
1391
+ const registerEmail = register ? (await prompter.text({
1392
+ message: t("wizard.irc.nickServRegisterEmailPrompt"),
1393
+ initialValue: existing?.registerEmail || (accountId === DEFAULT_ACCOUNT_ID$1 ? process.env.IRC_NICKSERV_REGISTER_EMAIL : void 0),
1394
+ validate: (value) => normalizeStringifiedOptionalString(value) ? void 0 : "Required"
1395
+ })).trim() : void 0;
1396
+ return setIrcNickServ(nickServConfig, accountId, {
1397
+ enabled: true,
1398
+ service,
1399
+ ...password ? { password } : {},
1400
+ register,
1401
+ ...registerEmail ? { registerEmail } : {}
1402
+ });
1403
+ })();
1515
1404
  return { cfg: next };
1516
1405
  },
1517
1406
  completionNote: {
@@ -1526,6 +1415,73 @@ const ircSetupWizard = {
1526
1415
  disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
1527
1416
  };
1528
1417
  // extensions/irc/src/channel.ts
1418
+ function ircAccountDetails(account) {
1419
+ return {
1420
+ host: account.host,
1421
+ port: account.port,
1422
+ tls: account.tls,
1423
+ nick: account.nick,
1424
+ passwordSource: account.passwordSource,
1425
+ tokenStatus: account.tokenStatus
1426
+ };
1427
+ }
1428
+ function toIrcMessageResult({ target, ...result }) {
1429
+ return {
1430
+ ...result,
1431
+ target: {
1432
+ kind: "conversation",
1433
+ id: target
1434
+ }
1435
+ };
1436
+ }
1437
+ async function sendIrcMessage(ctx, text = ctx.text) {
1438
+ return toIrcMessageResult(await sendMessageIrc(ctx.to, text, {
1439
+ cfg: ctx.cfg,
1440
+ accountId: ctx.accountId ?? void 0,
1441
+ replyTo: ctx.replyToId ?? void 0,
1442
+ abortSignal: ctx.signal,
1443
+ onPlatformSendDispatch: ctx.onPlatformSendDispatch
1444
+ }));
1445
+ }
1446
+ const sendFormattedIrcText = async (ctx) => {
1447
+ const { chunkTextWithMode, resolveChunkMode, resolveTextChunkLimit } = await import("openclaw/plugin-sdk/reply-chunking");
1448
+ const accountId = ctx.accountId ?? void 0;
1449
+ const textLimit = ctx.formatting?.textLimit ?? resolveTextChunkLimit(ctx.cfg, "irc", accountId, { fallbackLimit: ircOutboundBaseAdapter.textChunkLimit });
1450
+ const chunkMode = ctx.formatting?.chunkMode ?? resolveChunkMode(ctx.cfg, "irc", accountId);
1451
+ const nextReplyToId = createReplyToFanout(ctx);
1452
+ return (await sendIrcMessages(ctx.to, ctx.text, {
1453
+ cfg: ctx.cfg,
1454
+ accountId,
1455
+ abortSignal: ctx.abortSignal,
1456
+ onPlatformSendDispatch: ctx.onPlatformSendDispatch
1457
+ }, (preparedText) => chunkTextWithMode(preparedText, textLimit, chunkMode).map((text) => ({
1458
+ text,
1459
+ replyTo: nextReplyToId()
1460
+ })), async (result) => {
1461
+ await ctx.onDeliveryResult?.(attachChannelToResult("irc", toIrcMessageResult(result)));
1462
+ })).map((result) => attachChannelToResult("irc", toIrcMessageResult(result)));
1463
+ };
1464
+ const ircMessageAdapter = defineChannelMessageAdapter({
1465
+ id: "irc",
1466
+ durableFinal: { capabilities: {
1467
+ text: true,
1468
+ media: true,
1469
+ replyTo: true
1470
+ } },
1471
+ send: {
1472
+ text: sendIrcMessage,
1473
+ media: (ctx) => sendIrcMessage(ctx, ctx.mediaUrl ? `${ctx.text}\n\nAttachment: ${ctx.mediaUrl}` : ctx.text)
1474
+ }
1475
+ });
1476
+ const isIrcMutableAllowEntry = buildMutableAllowEntryDetector({ stableIdPattern: /^(?:(?:irc|user):)*[^@]*@/i });
1477
+ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAllowlistWarningCollector({
1478
+ channel: "irc",
1479
+ detector: isIrcMutableAllowEntry,
1480
+ collectLists: (scope) => collectStandardAllowlistLists(scope, {
1481
+ includeGroups: true,
1482
+ groupField: "allowFrom"
1483
+ })
1484
+ });
1529
1485
  const meta = {
1530
1486
  id: "irc",
1531
1487
  label: "IRC",
@@ -1641,14 +1597,7 @@ const ircPlugin = createChatChannelPlugin({
1641
1597
  describeAccount: (account) => describeAccountSnapshot({
1642
1598
  account,
1643
1599
  configured: account.configured,
1644
- extra: {
1645
- host: account.host,
1646
- port: account.port,
1647
- tls: account.tls,
1648
- nick: account.nick,
1649
- passwordSource: account.passwordSource,
1650
- tokenStatus: account.tokenStatus
1651
- }
1600
+ extra: ircAccountDetails(account)
1652
1601
  })
1653
1602
  },
1654
1603
  secrets: {
@@ -1751,20 +1700,29 @@ const ircPlugin = createChatChannelPlugin({
1751
1700
  name: account.name,
1752
1701
  enabled: account.enabled,
1753
1702
  configured: account.configured,
1754
- extra: {
1755
- host: account.host,
1756
- port: account.port,
1757
- tls: account.tls,
1758
- nick: account.nick,
1759
- passwordSource: account.passwordSource,
1760
- tokenStatus: account.tokenStatus
1761
- }
1703
+ extra: ircAccountDetails(account)
1762
1704
  })
1763
1705
  }),
1764
- gateway: { startAccount: async (ctx) => await startIrcGatewayAccount({
1765
- ...ctx,
1766
- cfg: ctx.cfg
1767
- }) }
1706
+ gateway: { startAccount: async (ctx) => {
1707
+ const account = ctx.account;
1708
+ const statusSink = createAccountStatusSink(ctx);
1709
+ if (!account.configured) throw new Error(`IRC is not configured for account "${account.accountId}" (need host and nick in channels.irc).`);
1710
+ ctx.log?.info?.(`[${account.accountId}] starting IRC provider (${account.host}:${account.port}${account.tls ? " tls" : ""})`);
1711
+ const { monitorIrcProvider } = await import("./channel-runtime-D2sqTm8e.mjs");
1712
+ await runPassiveAccountLifecycle({
1713
+ abortSignal: ctx.abortSignal,
1714
+ start: async () => await monitorIrcProvider({
1715
+ accountId: account.accountId,
1716
+ config: ctx.cfg,
1717
+ runtime: ctx.runtime,
1718
+ abortSignal: ctx.abortSignal,
1719
+ statusSink
1720
+ }),
1721
+ stop: async (monitor) => {
1722
+ await monitor.stop();
1723
+ }
1724
+ });
1725
+ } }
1768
1726
  },
1769
1727
  pairing: { text: {
1770
1728
  idLabel: "ircUser",
@@ -1773,7 +1731,7 @@ const ircPlugin = createChatChannelPlugin({
1773
1731
  notify: async ({ cfg, id, message, accountId }) => {
1774
1732
  const target = normalizePairingTarget(id);
1775
1733
  if (!target) throw new Error(`invalid IRC pairing id: ${id}`);
1776
- const { sendMessageIrc } = await import("./channel-runtime-exb5AWhn.mjs");
1734
+ const { sendMessageIrc } = await import("./channel-runtime-D2sqTm8e.mjs");
1777
1735
  await sendMessageIrc(target, message, {
1778
1736
  cfg,
1779
1737
  accountId
@@ -1799,4 +1757,4 @@ const ircPlugin = createChatChannelPlugin({
1799
1757
  }
1800
1758
  }
1801
1759
  });
1802
- 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 };
1760
+ export { listIrcAccountIds as _, buildIrcConnectOptions as a, parseIrcPrefix as c, sanitizeIrcAssistantText as d, createIrcIngressSubject as f, listEnabledIrcAccounts as g, normalizeIrcAllowEntry as h, sendMessageIrc as i, resolveIrcGroupMatch as l, isChannelTarget as m, ircSetupWizard as n, connectIrcClient as o, ircIngressIdentity as p, ircSetupAdapter as r, parseIrcLine as s, ircPlugin as t, resolveIrcGroupRequireMention as u, resolveDefaultIrcAccountId as v, resolveIrcAccount as y };