@openclaw/irc 2026.11.3 → 2026.11.5-beta.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.
Files changed (36) hide show
  1. package/dist/.setup/{channel-Bi4lj8N2.mjs → channel-DgvKz42q.mjs} +254 -299
  2. package/dist/.setup/channel-plugin-api-WXJUjGsf.mjs +2 -0
  3. package/dist/.setup/channel-runtime-D4vKt7TE.mjs +582 -0
  4. package/dist/.setup/{config-schema-Cy9tItsu.mjs → config-schema-CHwjI_aV.mjs} +1 -3
  5. package/dist/.setup/secret-contract-api-4IT9_EK6.mjs +2 -0
  6. package/dist/.source-module-urls/4751df78f545ae971ceb22b6e4bff6086f63313240899a1d9884b970424a305e.json +1 -0
  7. package/dist/api.js +1 -1
  8. package/dist/channel-config-api.js +1 -2
  9. package/dist/channel-plugin-api.js +1 -2
  10. package/dist/runtime-api.js +1 -2
  11. package/dist/secret-contract-api.js +1 -2
  12. package/dist/setup-entry.js +2 -2
  13. package/node_modules/zod/package.json +1 -1
  14. package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
  15. package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
  16. package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
  17. package/node_modules/zod/src/v4/core/compile.ts +9 -0
  18. package/node_modules/zod/src/v4/core/schemas.ts +35 -1
  19. package/node_modules/zod/src/v4/core/util.ts +13 -0
  20. package/node_modules/zod/src/v4/core/versions.ts +1 -1
  21. package/node_modules/zod/v4/core/compile.cjs +8 -0
  22. package/node_modules/zod/v4/core/compile.js +8 -0
  23. package/node_modules/zod/v4/core/schemas.cjs +30 -0
  24. package/node_modules/zod/v4/core/schemas.d.cts +3 -1
  25. package/node_modules/zod/v4/core/schemas.d.ts +3 -1
  26. package/node_modules/zod/v4/core/schemas.js +30 -0
  27. package/node_modules/zod/v4/core/util.cjs +15 -0
  28. package/node_modules/zod/v4/core/util.d.cts +2 -0
  29. package/node_modules/zod/v4/core/util.d.ts +2 -0
  30. package/node_modules/zod/v4/core/util.js +14 -0
  31. package/node_modules/zod/v4/core/versions.cjs +1 -1
  32. package/node_modules/zod/v4/core/versions.js +1 -1
  33. package/package.json +5 -5
  34. package/dist/.setup/channel-plugin-api-U3Pg1KLp.mjs +0 -3
  35. package/dist/.setup/channel-runtime-B4OMtNnX.mjs +0 -652
  36. package/dist/.setup/secret-contract-api-ClqD0Rpm.mjs +0 -3
@@ -1,13 +1,15 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-Cy9tItsu.mjs";
2
- import { n as getOptionalIrcRuntime } from "./runtime-C_V9tmpk.mjs";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-CHwjI_aV.mjs";
3
2
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-C5i-29Hz.mjs";
3
+ import { n as getOptionalIrcRuntime } from "./runtime-C_V9tmpk.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";
@@ -187,42 +187,6 @@ function listEnabledIrcAccounts(cfg) {
187
187
  })).filter((account) => account.enabled);
188
188
  }
189
189
  //#endregion
190
- //#region extensions/irc/src/doctor.ts
191
- const isIrcMutableAllowEntry = buildMutableAllowEntryDetector({ stableIdPattern: /^(?:(?:irc|user):)*[^@]*@/i });
192
- const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAllowlistWarningCollector({
193
- channel: "irc",
194
- detector: isIrcMutableAllowEntry,
195
- collectLists: (scope) => collectStandardAllowlistLists(scope, {
196
- includeGroups: true,
197
- groupField: "allowFrom"
198
- })
199
- });
200
- //#endregion
201
- //#region extensions/irc/src/gateway.ts
202
- async function startIrcGatewayAccount(ctx) {
203
- const account = ctx.account;
204
- const statusSink = createAccountStatusSink({
205
- accountId: ctx.accountId,
206
- setStatus: ctx.setStatus
207
- });
208
- if (!account.configured) throw new Error(`IRC is not configured for account "${account.accountId}" (need host and nick in channels.irc).`);
209
- ctx.log?.info?.(`[${account.accountId}] starting IRC provider (${account.host}:${account.port}${account.tls ? " tls" : ""})`);
210
- const { monitorIrcProvider } = await import("./channel-runtime-B4OMtNnX.mjs");
211
- await runPassiveAccountLifecycle({
212
- abortSignal: ctx.abortSignal,
213
- start: async () => await monitorIrcProvider({
214
- accountId: account.accountId,
215
- config: ctx.cfg,
216
- runtime: ctx.runtime,
217
- abortSignal: ctx.abortSignal,
218
- statusSink
219
- }),
220
- stop: async (monitor) => {
221
- await monitor.stop();
222
- }
223
- });
224
- }
225
- //#endregion
226
190
  //#region extensions/irc/src/control-chars.ts
227
191
  function isIrcControlChar(charCode) {
228
192
  return charCode <= 31 || charCode === 127;
@@ -283,17 +247,6 @@ function normalizeIrcAllowEntry(raw) {
283
247
  if (value.startsWith("user:")) value = value.slice(5);
284
248
  return value.trim();
285
249
  }
286
- function buildIrcAllowlistCandidates(message, params) {
287
- const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
288
- const user = normalizeOptionalLowercaseString(message.senderUser);
289
- const host = normalizeOptionalLowercaseString(message.senderHost);
290
- const candidates = /* @__PURE__ */ new Set();
291
- if (nick && params?.allowNameMatching === true) candidates.add(nick);
292
- if (nick && user) candidates.add(`${nick}!${user}`);
293
- if (nick && host) candidates.add(`${nick}@${host}`);
294
- if (nick && user && host) candidates.add(`${nick}!${user}@${host}`);
295
- return [...candidates];
296
- }
297
250
  const ircIngressIdentity = defineStableChannelIngressIdentity({
298
251
  key: "irc-id",
299
252
  authentication: "asserted",
@@ -358,14 +311,20 @@ function normalizeIrcNickEntry(value) {
358
311
  return normalized;
359
312
  }
360
313
  function createIrcIngressSubject(message) {
361
- const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
362
- const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
363
314
  const nick = normalizeLowercaseStringOrEmpty(message.senderNick);
315
+ const user = normalizeOptionalLowercaseString(message.senderUser);
316
+ const host = normalizeOptionalLowercaseString(message.senderHost);
317
+ const candidates = [
318
+ nick,
319
+ nick && user ? `${nick}!${user}` : "",
320
+ nick && host ? `${nick}@${host}` : "",
321
+ nick && user && host ? `${nick}!${user}@${host}` : ""
322
+ ];
364
323
  return {
365
- stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
324
+ stableId: candidates.findLast(hasVerifiedHost) ?? nick,
366
325
  aliases: {
367
326
  "irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
368
- "irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!")),
327
+ "irc-id-nick-host": candidates.find((candidate) => hasVerifiedHost(candidate) && !candidate.includes("!")),
369
328
  "irc-nick": nick
370
329
  }
371
330
  };
@@ -383,6 +342,50 @@ const ircOutboundBaseAdapter = {
383
342
  sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
384
343
  };
385
344
  //#endregion
345
+ //#region extensions/irc/src/policy.ts
346
+ function resolveIrcGroupScope(params) {
347
+ const { "*": wildcard, ...groups } = params.groups ?? {};
348
+ const project = (entry) => ({
349
+ requireMention: entry.requireMention,
350
+ tools: entry.tools
351
+ });
352
+ const tree = {
353
+ defaults: wildcard ? project(wildcard) : void 0,
354
+ scopes: Object.fromEntries(Object.entries(groups).map(([key, entry]) => [key, project(entry)]))
355
+ };
356
+ const key = resolveScopeKeyCaseInsensitive(tree, params.target);
357
+ return {
358
+ tree,
359
+ path: key ? [key] : []
360
+ };
361
+ }
362
+ function resolveIrcGroupMatch(params) {
363
+ const { path } = resolveIrcGroupScope(params);
364
+ const key = path[0];
365
+ const groupConfig = key ? params.groups?.[key] : void 0;
366
+ const wildcardConfig = params.groups?.["*"];
367
+ return {
368
+ allowed: Boolean(groupConfig ?? wildcardConfig),
369
+ groupConfig,
370
+ wildcardConfig,
371
+ hasConfiguredGroups: Object.keys(params.groups ?? {}).length > 0
372
+ };
373
+ }
374
+ function resolveIrcGroupRequireMention(params) {
375
+ const { tree, path } = resolveIrcGroupScope(params);
376
+ return resolveScopeRequireMention({
377
+ tree,
378
+ path
379
+ });
380
+ }
381
+ function resolveIrcGroupToolPolicy(params) {
382
+ const { tree, path } = resolveIrcGroupScope(params);
383
+ return resolveScopeToolsPolicy({
384
+ tree,
385
+ path
386
+ });
387
+ }
388
+ //#endregion
386
389
  //#region packages/normalization-core/src/utf16-slice.ts
387
390
  function isHighSurrogate(codeUnit) {
388
391
  return codeUnit >= 55296 && codeUnit <= 56319;
@@ -457,7 +460,6 @@ function parseIrcLine(line) {
457
460
  cursor = cursor.slice(spaceIdx + 1);
458
461
  }
459
462
  return {
460
- raw,
461
463
  prefix,
462
464
  command: command.toUpperCase(),
463
465
  params,
@@ -484,8 +486,7 @@ function parseIrcPrefix(prefix) {
484
486
  user
485
487
  };
486
488
  }
487
- if (prefix.includes(".")) return { server: prefix };
488
- return { nick: prefix };
489
+ return prefix.includes(".") ? {} : { nick: prefix };
489
490
  }
490
491
  function sanitizeIrcOutboundText(text) {
491
492
  return stripIrcControlChars(text.replace(/\r?\n/g, " ")).trim();
@@ -700,7 +701,6 @@ async function connectIrcClient(options) {
700
701
  buffer = buffer.slice(idx + 1);
701
702
  idx = buffer.indexOf("\n");
702
703
  if (!rawLine) continue;
703
- options.onLine?.(rawLine);
704
704
  const line = parseIrcLine(rawLine);
705
705
  if (!line) continue;
706
706
  if (line.command === "PING") {
@@ -811,7 +811,6 @@ async function connectIrcClient(options) {
811
811
  return currentNick;
812
812
  },
813
813
  isReady: () => ready && !closed,
814
- sendRaw,
815
814
  join,
816
815
  sendPrivmsg,
817
816
  quit,
@@ -831,17 +830,46 @@ function buildIrcConnectOptions(account, overrides = {}) {
831
830
  username: account.username,
832
831
  realname: account.realname,
833
832
  password: account.password,
834
- nickserv: {
835
- enabled: account.config.nickserv?.enabled,
836
- service: account.config.nickserv?.service,
837
- password: account.config.nickserv?.password,
838
- register: account.config.nickserv?.register,
839
- registerEmail: account.config.nickserv?.registerEmail
840
- },
833
+ nickserv: account.config.nickserv,
841
834
  ...overrides
842
835
  };
843
836
  }
844
837
  //#endregion
838
+ //#region extensions/irc/src/probe.ts
839
+ async function probeIrc(cfg, opts) {
840
+ const account = resolveIrcAccount({
841
+ cfg,
842
+ accountId: opts?.accountId
843
+ });
844
+ const base = {
845
+ ok: false,
846
+ host: account.host,
847
+ port: account.port,
848
+ tls: account.tls,
849
+ nick: account.nick
850
+ };
851
+ if (!account.configured) return {
852
+ ...base,
853
+ error: "missing host or nick"
854
+ };
855
+ const started = Date.now();
856
+ try {
857
+ const client = await connectIrcClient(buildIrcConnectOptions(account, { connectTimeoutMs: opts?.timeoutMs ?? 8e3 }));
858
+ const elapsed = Date.now() - started;
859
+ client.quit("probe");
860
+ return {
861
+ ...base,
862
+ ok: true,
863
+ latencyMs: elapsed
864
+ };
865
+ } catch (err) {
866
+ return {
867
+ ...base,
868
+ error: err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err)
869
+ };
870
+ }
871
+ }
872
+ //#endregion
845
873
  //#region extensions/irc/src/send.ts
846
874
  async function sendIrcMessages(to, text, opts, planMessages = (preparedText) => [{
847
875
  text: preparedText,
@@ -866,15 +894,14 @@ async function sendIrcMessages(to, text, opts, planMessages = (preparedText) =>
866
894
  if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
867
895
  const messages = planMessages(prepared);
868
896
  opts.abortSignal?.throwIfAborted();
869
- let transient;
870
- const client = opts.client?.isReady() ? opts.client : transient = await connectIrcClient(buildIrcConnectOptions(account, {
897
+ const client = await connectIrcClient(buildIrcConnectOptions(account, {
871
898
  connectTimeoutMs: 12e3,
872
899
  abortSignal: opts.abortSignal
873
900
  }));
874
901
  const results = [];
875
902
  try {
876
903
  opts.abortSignal?.throwIfAborted();
877
- if (transient && (target.startsWith("#") || target.startsWith("&"))) await effect.initiate(() => {
904
+ if (target.startsWith("#") || target.startsWith("&")) await effect.initiate(() => {
878
905
  opts.abortSignal?.throwIfAborted();
879
906
  if (!client.isReady()) throw new Error("IRC connection closed before join");
880
907
  client.join(target);
@@ -931,7 +958,7 @@ async function sendIrcMessages(to, text, opts, planMessages = (preparedText) =>
931
958
  visibleReplySent: true
932
959
  });
933
960
  } finally {
934
- transient?.quit("sent");
961
+ client.quit("sent");
935
962
  }
936
963
  }
937
964
  async function sendMessageIrc(to, text, opts) {
@@ -940,139 +967,6 @@ async function sendMessageIrc(to, text, opts) {
940
967
  return result;
941
968
  }
942
969
  //#endregion
943
- //#region extensions/irc/src/message-adapter.ts
944
- function toIrcMessageResult({ target, ...result }) {
945
- return {
946
- ...result,
947
- target: {
948
- kind: "conversation",
949
- id: target
950
- }
951
- };
952
- }
953
- async function sendIrcMessage(ctx, text = ctx.text) {
954
- return toIrcMessageResult(await sendMessageIrc(ctx.to, text, {
955
- cfg: ctx.cfg,
956
- accountId: ctx.accountId ?? void 0,
957
- replyTo: ctx.replyToId ?? void 0,
958
- abortSignal: ctx.signal,
959
- onPlatformSendDispatch: ctx.onPlatformSendDispatch
960
- }));
961
- }
962
- const sendFormattedIrcText = async (ctx) => {
963
- const { chunkTextWithMode, resolveChunkMode, resolveTextChunkLimit } = await import("openclaw/plugin-sdk/reply-chunking");
964
- const accountId = ctx.accountId ?? void 0;
965
- const textLimit = ctx.formatting?.textLimit ?? resolveTextChunkLimit(ctx.cfg, "irc", accountId, { fallbackLimit: ircOutboundBaseAdapter.textChunkLimit });
966
- const chunkMode = ctx.formatting?.chunkMode ?? resolveChunkMode(ctx.cfg, "irc", accountId);
967
- const nextReplyToId = createReplyToFanout(ctx);
968
- return (await sendIrcMessages(ctx.to, ctx.text, {
969
- cfg: ctx.cfg,
970
- accountId,
971
- abortSignal: ctx.abortSignal,
972
- onPlatformSendDispatch: ctx.onPlatformSendDispatch
973
- }, (preparedText) => chunkTextWithMode(preparedText, textLimit, chunkMode).map((text) => ({
974
- text,
975
- replyTo: nextReplyToId()
976
- })), async (result) => {
977
- await ctx.onDeliveryResult?.(attachChannelToResult("irc", toIrcMessageResult(result)));
978
- })).map((result) => attachChannelToResult("irc", toIrcMessageResult(result)));
979
- };
980
- const ircMessageAdapter = defineChannelMessageAdapter({
981
- id: "irc",
982
- durableFinal: { capabilities: {
983
- text: true,
984
- media: true,
985
- replyTo: true
986
- } },
987
- send: {
988
- text: sendIrcMessage,
989
- media: (ctx) => sendIrcMessage(ctx, ctx.mediaUrl ? `${ctx.text}\n\nAttachment: ${ctx.mediaUrl}` : ctx.text)
990
- }
991
- });
992
- //#endregion
993
- //#region extensions/irc/src/policy.ts
994
- function resolveIrcGroupScope(params) {
995
- const { "*": wildcard, ...groups } = params.groups ?? {};
996
- const project = (entry) => ({
997
- requireMention: entry.requireMention,
998
- tools: entry.tools
999
- });
1000
- const tree = {
1001
- defaults: wildcard ? project(wildcard) : void 0,
1002
- scopes: Object.fromEntries(Object.entries(groups).map(([key, entry]) => [key, project(entry)]))
1003
- };
1004
- const key = resolveScopeKeyCaseInsensitive(tree, params.target);
1005
- return {
1006
- tree,
1007
- path: key ? [key] : []
1008
- };
1009
- }
1010
- function resolveIrcGroupMatch(params) {
1011
- const { path } = resolveIrcGroupScope(params);
1012
- const key = path[0];
1013
- const groupConfig = key ? params.groups?.[key] : void 0;
1014
- const wildcardConfig = params.groups?.["*"];
1015
- return {
1016
- allowed: Boolean(groupConfig ?? wildcardConfig),
1017
- groupConfig,
1018
- wildcardConfig,
1019
- hasConfiguredGroups: Object.keys(params.groups ?? {}).length > 0
1020
- };
1021
- }
1022
- function resolveIrcGroupRequireMention(params) {
1023
- const { tree, path } = resolveIrcGroupScope(params);
1024
- return resolveScopeRequireMention({
1025
- tree,
1026
- path
1027
- });
1028
- }
1029
- function resolveIrcGroupToolPolicy(params) {
1030
- const { tree, path } = resolveIrcGroupScope(params);
1031
- return resolveScopeToolsPolicy({
1032
- tree,
1033
- path
1034
- });
1035
- }
1036
- //#endregion
1037
- //#region extensions/irc/src/probe.ts
1038
- function formatError(err) {
1039
- if (err instanceof Error) return err.message;
1040
- return typeof err === "string" ? err : JSON.stringify(err);
1041
- }
1042
- async function probeIrc(cfg, opts) {
1043
- const account = resolveIrcAccount({
1044
- cfg,
1045
- accountId: opts?.accountId
1046
- });
1047
- const base = {
1048
- ok: false,
1049
- host: account.host,
1050
- port: account.port,
1051
- tls: account.tls,
1052
- nick: account.nick
1053
- };
1054
- if (!account.configured) return {
1055
- ...base,
1056
- error: "missing host or nick"
1057
- };
1058
- const started = Date.now();
1059
- try {
1060
- const client = await connectIrcClient(buildIrcConnectOptions(account, { connectTimeoutMs: opts?.timeoutMs ?? 8e3 }));
1061
- const elapsed = Date.now() - started;
1062
- client.quit("probe");
1063
- return {
1064
- ...base,
1065
- ok: true,
1066
- latencyMs: elapsed
1067
- };
1068
- } catch (err) {
1069
- return {
1070
- ...base,
1071
- error: formatError(err)
1072
- };
1073
- }
1074
- }
1075
- //#endregion
1076
970
  //#region extensions/irc/src/setup-core.ts
1077
971
  const channel$1 = "irc";
1078
972
  const setIrcDmPolicy = createTopLevelChannelDmPolicySetter({ channel: channel$1 });
@@ -1253,67 +1147,16 @@ function normalizeGroupEntry(raw) {
1253
1147
  return `#${normalized.replace(/^#+/, "")}`;
1254
1148
  }
1255
1149
  function parseAutoJoinChannels(value) {
1256
- return splitSetupEntries(value).map(normalizeGroupEntry).filter((entry) => Boolean(entry && entry !== "*")).filter(isChannelTarget);
1150
+ return splitSetupEntries(value).map(normalizeGroupEntry).filter((entry) => Boolean(entry && entry !== "*"));
1257
1151
  }
1258
- const promptIrcAllowFrom = createPromptParsedAllowFromForAccount({
1259
- defaultAccountId: resolveDefaultIrcAccountId,
1260
- noteTitle: t("wizard.irc.allowlistTitle"),
1261
- noteLines: [
1152
+ function ircAllowlistHelpLines() {
1153
+ return [
1262
1154
  t("wizard.irc.allowlistIntro"),
1263
1155
  t("wizard.irc.examples"),
1264
1156
  "- alice",
1265
1157
  "- alice!ident@example.org",
1266
1158
  t("wizard.irc.multipleEntries")
1267
- ],
1268
- message: t("wizard.irc.allowFromPrompt"),
1269
- placeholder: "alice, bob!ident@example.org",
1270
- parseEntries: (raw) => ({ entries: normalizeStringEntries(splitSetupEntries(raw).map(normalizeIrcAllowEntry)) }),
1271
- getExistingAllowFrom: ({ cfg }) => cfg.channels?.irc?.allowFrom ?? [],
1272
- applyAllowFrom: ({ cfg, allowFrom }) => setIrcAllowFrom(cfg, allowFrom)
1273
- });
1274
- async function promptIrcNickServConfig(params) {
1275
- const existing = resolveIrcAccount({
1276
- cfg: params.cfg,
1277
- accountId: params.accountId
1278
- }).config.nickserv;
1279
- const hasExisting = Boolean(existing?.password || existing?.passwordFile);
1280
- if (!await params.prompter.confirm({
1281
- message: hasExisting ? t("wizard.irc.nickServUpdatePrompt") : t("wizard.irc.nickServConfigurePrompt"),
1282
- initialValue: hasExisting
1283
- })) return params.cfg;
1284
- const service = (await params.prompter.text({
1285
- message: t("wizard.irc.nickServServicePrompt"),
1286
- initialValue: existing?.service || "NickServ",
1287
- validate: (value) => normalizeStringifiedOptionalString(value) ? void 0 : "Required"
1288
- })).trim();
1289
- const useEnvPassword = params.accountId === DEFAULT_ACCOUNT_ID$1 && Boolean(process.env.IRC_NICKSERV_PASSWORD?.trim()) && !(existing?.password || existing?.passwordFile) ? await params.prompter.confirm({
1290
- message: t("wizard.irc.nickServPasswordEnvPrompt"),
1291
- initialValue: true
1292
- }) : false;
1293
- const password = useEnvPassword ? void 0 : (await params.prompter.text({
1294
- message: t("wizard.irc.nickServPasswordPrompt"),
1295
- validate: () => void 0
1296
- })).trim();
1297
- if (!password && !useEnvPassword) return setIrcNickServ(params.cfg, params.accountId, {
1298
- enabled: false,
1299
- service
1300
- });
1301
- const register = await params.prompter.confirm({
1302
- message: t("wizard.irc.nickServRegisterPrompt"),
1303
- initialValue: existing?.register ?? false
1304
- });
1305
- const registerEmail = register ? (await params.prompter.text({
1306
- message: t("wizard.irc.nickServRegisterEmailPrompt"),
1307
- initialValue: existing?.registerEmail || (params.accountId === DEFAULT_ACCOUNT_ID$1 ? process.env.IRC_NICKSERV_REGISTER_EMAIL : void 0),
1308
- validate: (value) => normalizeStringifiedOptionalString(value) ? void 0 : "Required"
1309
- })).trim() : void 0;
1310
- return setIrcNickServ(params.cfg, params.accountId, {
1311
- enabled: true,
1312
- service,
1313
- ...password ? { password } : {},
1314
- register,
1315
- ...registerEmail ? { registerEmail } : {}
1316
- });
1159
+ ];
1317
1160
  }
1318
1161
  const ircDmPolicy = {
1319
1162
  label: "IRC",
@@ -1322,7 +1165,16 @@ const ircDmPolicy = {
1322
1165
  allowFromKey: "channels.irc.allowFrom",
1323
1166
  getCurrent: (cfg) => cfg.channels?.irc?.dmPolicy ?? "pairing",
1324
1167
  setPolicy: (cfg, policy) => setIrcDmPolicy(cfg, policy),
1325
- promptAllowFrom: promptIrcAllowFrom
1168
+ promptAllowFrom: createPromptParsedAllowFromForAccount({
1169
+ defaultAccountId: resolveDefaultIrcAccountId,
1170
+ noteTitle: t("wizard.irc.allowlistTitle"),
1171
+ noteLines: ircAllowlistHelpLines(),
1172
+ message: t("wizard.irc.allowFromPrompt"),
1173
+ placeholder: "alice, bob!ident@example.org",
1174
+ parseEntries: (raw) => ({ entries: normalizeStringEntries(splitSetupEntries(raw).map(normalizeIrcAllowEntry)) }),
1175
+ getExistingAllowFrom: ({ cfg }) => cfg.channels?.irc?.allowFrom ?? [],
1176
+ applyAllowFrom: ({ cfg, allowFrom }) => setIrcAllowFrom(cfg, allowFrom)
1177
+ })
1326
1178
  };
1327
1179
  const ircSetupWizard = {
1328
1180
  channel,
@@ -1487,13 +1339,7 @@ const ircSetupWizard = {
1487
1339
  },
1488
1340
  allowFrom: createAllowFromSection({
1489
1341
  helpTitle: t("wizard.irc.allowlistTitle"),
1490
- helpLines: [
1491
- t("wizard.irc.allowlistIntro"),
1492
- t("wizard.irc.examples"),
1493
- "- alice",
1494
- "- alice!ident@example.org",
1495
- t("wizard.irc.multipleEntries")
1496
- ],
1342
+ helpLines: ircAllowlistHelpLines(),
1497
1343
  message: t("wizard.irc.allowFromPrompt"),
1498
1344
  placeholder: "alice, bob!ident@example.org",
1499
1345
  invalidWithoutCredentialNote: t("wizard.irc.allowFromInvalid"),
@@ -1523,11 +1369,51 @@ const ircSetupWizard = {
1523
1369
  }
1524
1370
  }
1525
1371
  }
1526
- next = await promptIrcNickServConfig({
1527
- cfg: next,
1528
- prompter,
1529
- accountId
1530
- });
1372
+ const nickServConfig = next;
1373
+ next = await (async () => {
1374
+ const existing = resolveIrcAccount({
1375
+ cfg: nickServConfig,
1376
+ accountId
1377
+ }).config.nickserv;
1378
+ const hasExisting = Boolean(existing?.password || existing?.passwordFile);
1379
+ if (!await prompter.confirm({
1380
+ message: hasExisting ? t("wizard.irc.nickServUpdatePrompt") : t("wizard.irc.nickServConfigurePrompt"),
1381
+ initialValue: hasExisting
1382
+ })) return nickServConfig;
1383
+ const service = (await prompter.text({
1384
+ message: t("wizard.irc.nickServServicePrompt"),
1385
+ initialValue: existing?.service || "NickServ",
1386
+ validate: (value) => normalizeStringifiedOptionalString(value) ? void 0 : "Required"
1387
+ })).trim();
1388
+ const useEnvPassword = accountId === DEFAULT_ACCOUNT_ID$1 && Boolean(process.env.IRC_NICKSERV_PASSWORD?.trim()) && !(existing?.password || existing?.passwordFile) ? await prompter.confirm({
1389
+ message: t("wizard.irc.nickServPasswordEnvPrompt"),
1390
+ initialValue: true
1391
+ }) : false;
1392
+ const password = useEnvPassword ? void 0 : (await prompter.text({
1393
+ message: t("wizard.irc.nickServPasswordPrompt"),
1394
+ validate: () => void 0
1395
+ })).trim();
1396
+ if (!password && !useEnvPassword) return setIrcNickServ(nickServConfig, accountId, {
1397
+ enabled: false,
1398
+ service
1399
+ });
1400
+ const register = await prompter.confirm({
1401
+ message: t("wizard.irc.nickServRegisterPrompt"),
1402
+ initialValue: existing?.register ?? false
1403
+ });
1404
+ const registerEmail = register ? (await prompter.text({
1405
+ message: t("wizard.irc.nickServRegisterEmailPrompt"),
1406
+ initialValue: existing?.registerEmail || (accountId === DEFAULT_ACCOUNT_ID$1 ? process.env.IRC_NICKSERV_REGISTER_EMAIL : void 0),
1407
+ validate: (value) => normalizeStringifiedOptionalString(value) ? void 0 : "Required"
1408
+ })).trim() : void 0;
1409
+ return setIrcNickServ(nickServConfig, accountId, {
1410
+ enabled: true,
1411
+ service,
1412
+ ...password ? { password } : {},
1413
+ register,
1414
+ ...registerEmail ? { registerEmail } : {}
1415
+ });
1416
+ })();
1531
1417
  return { cfg: next };
1532
1418
  },
1533
1419
  completionNote: {
@@ -1543,6 +1429,73 @@ const ircSetupWizard = {
1543
1429
  };
1544
1430
  //#endregion
1545
1431
  //#region extensions/irc/src/channel.ts
1432
+ function ircAccountDetails(account) {
1433
+ return {
1434
+ host: account.host,
1435
+ port: account.port,
1436
+ tls: account.tls,
1437
+ nick: account.nick,
1438
+ passwordSource: account.passwordSource,
1439
+ tokenStatus: account.tokenStatus
1440
+ };
1441
+ }
1442
+ function toIrcMessageResult({ target, ...result }) {
1443
+ return {
1444
+ ...result,
1445
+ target: {
1446
+ kind: "conversation",
1447
+ id: target
1448
+ }
1449
+ };
1450
+ }
1451
+ async function sendIrcMessage(ctx, text = ctx.text) {
1452
+ return toIrcMessageResult(await sendMessageIrc(ctx.to, text, {
1453
+ cfg: ctx.cfg,
1454
+ accountId: ctx.accountId ?? void 0,
1455
+ replyTo: ctx.replyToId ?? void 0,
1456
+ abortSignal: ctx.signal,
1457
+ onPlatformSendDispatch: ctx.onPlatformSendDispatch
1458
+ }));
1459
+ }
1460
+ const sendFormattedIrcText = async (ctx) => {
1461
+ const { chunkTextWithMode, resolveChunkMode, resolveTextChunkLimit } = await import("openclaw/plugin-sdk/reply-chunking");
1462
+ const accountId = ctx.accountId ?? void 0;
1463
+ const textLimit = ctx.formatting?.textLimit ?? resolveTextChunkLimit(ctx.cfg, "irc", accountId, { fallbackLimit: ircOutboundBaseAdapter.textChunkLimit });
1464
+ const chunkMode = ctx.formatting?.chunkMode ?? resolveChunkMode(ctx.cfg, "irc", accountId);
1465
+ const nextReplyToId = createReplyToFanout(ctx);
1466
+ return (await sendIrcMessages(ctx.to, ctx.text, {
1467
+ cfg: ctx.cfg,
1468
+ accountId,
1469
+ abortSignal: ctx.abortSignal,
1470
+ onPlatformSendDispatch: ctx.onPlatformSendDispatch
1471
+ }, (preparedText) => chunkTextWithMode(preparedText, textLimit, chunkMode).map((text) => ({
1472
+ text,
1473
+ replyTo: nextReplyToId()
1474
+ })), async (result) => {
1475
+ await ctx.onDeliveryResult?.(attachChannelToResult("irc", toIrcMessageResult(result)));
1476
+ })).map((result) => attachChannelToResult("irc", toIrcMessageResult(result)));
1477
+ };
1478
+ const ircMessageAdapter = defineChannelMessageAdapter({
1479
+ id: "irc",
1480
+ durableFinal: { capabilities: {
1481
+ text: true,
1482
+ media: true,
1483
+ replyTo: true
1484
+ } },
1485
+ send: {
1486
+ text: sendIrcMessage,
1487
+ media: (ctx) => sendIrcMessage(ctx, ctx.mediaUrl ? `${ctx.text}\n\nAttachment: ${ctx.mediaUrl}` : ctx.text)
1488
+ }
1489
+ });
1490
+ const isIrcMutableAllowEntry = buildMutableAllowEntryDetector({ stableIdPattern: /^(?:(?:irc|user):)*[^@]*@/i });
1491
+ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAllowlistWarningCollector({
1492
+ channel: "irc",
1493
+ detector: isIrcMutableAllowEntry,
1494
+ collectLists: (scope) => collectStandardAllowlistLists(scope, {
1495
+ includeGroups: true,
1496
+ groupField: "allowFrom"
1497
+ })
1498
+ });
1546
1499
  const meta = {
1547
1500
  id: "irc",
1548
1501
  label: "IRC",
@@ -1658,14 +1611,7 @@ const ircPlugin = createChatChannelPlugin({
1658
1611
  describeAccount: (account) => describeAccountSnapshot({
1659
1612
  account,
1660
1613
  configured: account.configured,
1661
- extra: {
1662
- host: account.host,
1663
- port: account.port,
1664
- tls: account.tls,
1665
- nick: account.nick,
1666
- passwordSource: account.passwordSource,
1667
- tokenStatus: account.tokenStatus
1668
- }
1614
+ extra: ircAccountDetails(account)
1669
1615
  })
1670
1616
  },
1671
1617
  secrets: {
@@ -1768,20 +1714,29 @@ const ircPlugin = createChatChannelPlugin({
1768
1714
  name: account.name,
1769
1715
  enabled: account.enabled,
1770
1716
  configured: account.configured,
1771
- extra: {
1772
- host: account.host,
1773
- port: account.port,
1774
- tls: account.tls,
1775
- nick: account.nick,
1776
- passwordSource: account.passwordSource,
1777
- tokenStatus: account.tokenStatus
1778
- }
1717
+ extra: ircAccountDetails(account)
1779
1718
  })
1780
1719
  }),
1781
- gateway: { startAccount: async (ctx) => await startIrcGatewayAccount({
1782
- ...ctx,
1783
- cfg: ctx.cfg
1784
- }) }
1720
+ gateway: { startAccount: async (ctx) => {
1721
+ const account = ctx.account;
1722
+ const statusSink = createAccountStatusSink(ctx);
1723
+ if (!account.configured) throw new Error(`IRC is not configured for account "${account.accountId}" (need host and nick in channels.irc).`);
1724
+ ctx.log?.info?.(`[${account.accountId}] starting IRC provider (${account.host}:${account.port}${account.tls ? " tls" : ""})`);
1725
+ const { monitorIrcProvider } = await import("./channel-runtime-D4vKt7TE.mjs");
1726
+ await runPassiveAccountLifecycle({
1727
+ abortSignal: ctx.abortSignal,
1728
+ start: async () => await monitorIrcProvider({
1729
+ accountId: account.accountId,
1730
+ config: ctx.cfg,
1731
+ runtime: ctx.runtime,
1732
+ abortSignal: ctx.abortSignal,
1733
+ statusSink
1734
+ }),
1735
+ stop: async (monitor) => {
1736
+ await monitor.stop();
1737
+ }
1738
+ });
1739
+ } }
1785
1740
  },
1786
1741
  pairing: { text: {
1787
1742
  idLabel: "ircUser",
@@ -1790,7 +1745,7 @@ const ircPlugin = createChatChannelPlugin({
1790
1745
  notify: async ({ cfg, id, message, accountId }) => {
1791
1746
  const target = normalizePairingTarget(id);
1792
1747
  if (!target) throw new Error(`invalid IRC pairing id: ${id}`);
1793
- const { sendMessageIrc } = await import("./channel-runtime-B4OMtNnX.mjs");
1748
+ const { sendMessageIrc } = await import("./channel-runtime-D4vKt7TE.mjs");
1794
1749
  await sendMessageIrc(target, message, {
1795
1750
  cfg,
1796
1751
  accountId
@@ -1817,4 +1772,4 @@ const ircPlugin = createChatChannelPlugin({
1817
1772
  }
1818
1773
  });
1819
1774
  //#endregion
1820
- 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 };
1775
+ 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 };