@openclaw/irc 2026.10.10-beta.5 → 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 (39) hide show
  1. package/dist/.setup/{channel-Bi4lj8N2.mjs → channel-DRdPa321.mjs} +266 -326
  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-Cy9tItsu.mjs → config-schema-CQuAQW7O.mjs} +1 -4
  5. package/dist/.setup/{runtime-C_V9tmpk.mjs → runtime-CsdAoZex.mjs} +1 -2
  6. package/dist/.setup/{secret-contract-C5i-29Hz.mjs → secret-contract-0Ccz8X9F.mjs} +1 -2
  7. package/dist/.setup/{secret-contract-api-4IT9_EK6.mjs → secret-contract-api-BabNUhhB.mjs} +1 -1
  8. package/dist/.source-module-urls/4751df78f545ae971ceb22b6e4bff6086f63313240899a1d9884b970424a305e.json +1 -1
  9. package/dist/api.js +2 -2
  10. package/dist/channel-config-api.js +1 -1
  11. package/dist/channel-plugin-api.js +1 -1
  12. package/dist/doctor-contract-api.js +6 -12
  13. package/dist/index.js +1 -2
  14. package/dist/runtime-api.js +1 -1
  15. package/dist/secret-contract-api.js +1 -1
  16. package/dist/setup-entry.js +3 -4
  17. package/node_modules/zod/package.json +1 -1
  18. package/node_modules/zod/src/v4/classic/tests/array.test.ts +27 -0
  19. package/node_modules/zod/src/v4/classic/tests/map.test.ts +7 -0
  20. package/node_modules/zod/src/v4/classic/tests/set.test.ts +6 -0
  21. package/node_modules/zod/src/v4/core/compile.ts +9 -0
  22. package/node_modules/zod/src/v4/core/schemas.ts +35 -1
  23. package/node_modules/zod/src/v4/core/util.ts +13 -0
  24. package/node_modules/zod/src/v4/core/versions.ts +1 -1
  25. package/node_modules/zod/v4/core/compile.cjs +8 -0
  26. package/node_modules/zod/v4/core/compile.js +8 -0
  27. package/node_modules/zod/v4/core/schemas.cjs +30 -0
  28. package/node_modules/zod/v4/core/schemas.d.cts +3 -1
  29. package/node_modules/zod/v4/core/schemas.d.ts +3 -1
  30. package/node_modules/zod/v4/core/schemas.js +30 -0
  31. package/node_modules/zod/v4/core/util.cjs +15 -0
  32. package/node_modules/zod/v4/core/util.d.cts +2 -0
  33. package/node_modules/zod/v4/core/util.d.ts +2 -0
  34. package/node_modules/zod/v4/core/util.js +14 -0
  35. package/node_modules/zod/v4/core/versions.cjs +1 -1
  36. package/node_modules/zod/v4/core/versions.js +1 -1
  37. package/package.json +5 -5
  38. package/dist/.setup/channel-plugin-api-1I8kWfv8.mjs +0 -2
  39. package/dist/.setup/channel-runtime-B4OMtNnX.mjs +0 -652
@@ -1,13 +1,15 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-Cy9tItsu.mjs";
2
- import { n as getOptionalIrcRuntime } from "./runtime-C_V9tmpk.mjs";
3
- import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-C5i-29Hz.mjs";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-CQuAQW7O.mjs";
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,24 +19,22 @@ 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";
37
- //#region extensions/irc/src/accounts.ts
37
+ // extensions/irc/src/accounts.ts
38
38
  const { listAccountIds: listIrcAccountIds, resolveDefaultAccountId: resolveDefaultIrcAccountId, resolveAccountConfig: mergeIrcAccountConfig } = createAccountListHelpers("irc", {
39
39
  normalizeAccountId,
40
40
  omitKeys: ["defaultAccount"],
@@ -186,44 +186,7 @@ function listEnabledIrcAccounts(cfg) {
186
186
  accountId
187
187
  })).filter((account) => account.enabled);
188
188
  }
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
- //#region extensions/irc/src/control-chars.ts
189
+ // extensions/irc/src/control-chars.ts
227
190
  function isIrcControlChar(charCode) {
228
191
  return charCode <= 31 || charCode === 127;
229
192
  }
@@ -232,8 +195,7 @@ function stripIrcControlChars(value) {
232
195
  for (const char of value) if (!isIrcControlChar(char.charCodeAt(0))) out += char;
233
196
  return out;
234
197
  }
235
- //#endregion
236
- //#region extensions/irc/src/normalize.ts
198
+ // extensions/irc/src/normalize.ts
237
199
  const IRC_TARGET_PATTERN$1 = /^[^\s:]+$/u;
238
200
  function isChannelTarget(target) {
239
201
  return target.startsWith("#") || target.startsWith("&");
@@ -283,17 +245,6 @@ function normalizeIrcAllowEntry(raw) {
283
245
  if (value.startsWith("user:")) value = value.slice(5);
284
246
  return value.trim();
285
247
  }
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
248
  const ircIngressIdentity = defineStableChannelIngressIdentity({
298
249
  key: "irc-id",
299
250
  authentication: "asserted",
@@ -358,20 +309,25 @@ function normalizeIrcNickEntry(value) {
358
309
  return normalized;
359
310
  }
360
311
  function createIrcIngressSubject(message) {
361
- const candidates = buildIrcAllowlistCandidates(message, { allowNameMatching: true });
362
- const stableCandidates = candidates.filter((candidate) => hasVerifiedHost(candidate));
363
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
+ ];
364
321
  return {
365
- stableId: stableCandidates[stableCandidates.length - 1] ?? nick,
322
+ stableId: candidates.findLast(hasVerifiedHost) ?? nick,
366
323
  aliases: {
367
324
  "irc-id-nick-user": candidates.find((candidate) => isHostlessNickUser(candidate)),
368
- "irc-id-nick-host": stableCandidates.find((candidate) => !candidate.includes("!")),
325
+ "irc-id-nick-host": candidates.find((candidate) => hasVerifiedHost(candidate) && !candidate.includes("!")),
369
326
  "irc-nick": nick
370
327
  }
371
328
  };
372
329
  }
373
- //#endregion
374
- //#region extensions/irc/src/outbound-base.ts
330
+ // extensions/irc/src/outbound-base.ts
375
331
  function sanitizeIrcAssistantText(text) {
376
332
  return sanitizeForPlainText(sanitizeAssistantVisibleText(text));
377
333
  }
@@ -382,8 +338,50 @@ const ircOutboundBaseAdapter = {
382
338
  textChunkLimit: 350,
383
339
  sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
384
340
  };
385
- //#endregion
386
- //#region packages/normalization-core/src/utf16-slice.ts
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
+ }
384
+ // packages/normalization-core/src/utf16-slice.ts
387
385
  function isHighSurrogate(codeUnit) {
388
386
  return codeUnit >= 55296 && codeUnit <= 56319;
389
387
  }
@@ -396,8 +394,7 @@ function avoidTrailingHighSurrogateBreak(text, start, end) {
396
394
  const adjusted = end - 1;
397
395
  return adjusted > start ? adjusted : end + 1;
398
396
  }
399
- //#endregion
400
- //#region packages/normalization-core/src/grapheme.ts
397
+ // packages/normalization-core/src/grapheme.ts
401
398
  let graphemeSegmenter;
402
399
  function getGraphemeSegmenter() {
403
400
  graphemeSegmenter ??= new Intl.Segmenter(void 0, { granularity: "grapheme" });
@@ -421,8 +418,7 @@ function findGraphemeChunkEnd(text, start, maxEnd, preferredEnd = maxEnd, allowP
421
418
  if (end <= start && preferred < hardEnd) end = hardEnd === text.length ? hardEnd : segments.containing(hardEnd)?.index ?? hardEnd;
422
419
  return end > start ? end : allowPartial ? avoidTrailingHighSurrogateBreak(text, start, hardEnd) : start;
423
420
  }
424
- //#endregion
425
- //#region extensions/irc/src/protocol.ts
421
+ // extensions/irc/src/protocol.ts
426
422
  const IRC_TARGET_PATTERN = /^[^\s:]+$/u;
427
423
  function parseIrcLine(line) {
428
424
  const raw = line.replace(/[\r\n]+/g, "").trim();
@@ -457,7 +453,6 @@ function parseIrcLine(line) {
457
453
  cursor = cursor.slice(spaceIdx + 1);
458
454
  }
459
455
  return {
460
- raw,
461
456
  prefix,
462
457
  command: command.toUpperCase(),
463
458
  params,
@@ -484,8 +479,7 @@ function parseIrcPrefix(prefix) {
484
479
  user
485
480
  };
486
481
  }
487
- if (prefix.includes(".")) return { server: prefix };
488
- return { nick: prefix };
482
+ return prefix.includes(".") ? {} : { nick: prefix };
489
483
  }
490
484
  function sanitizeIrcOutboundText(text) {
491
485
  return stripIrcControlChars(text.replace(/\r?\n/g, " ")).trim();
@@ -495,8 +489,7 @@ function sanitizeIrcTarget(raw) {
495
489
  if (raw !== raw.trim() || hasIrcControlChars(raw) || !IRC_TARGET_PATTERN.test(raw)) throw new Error(`Invalid IRC target: ${raw}`);
496
490
  return raw;
497
491
  }
498
- //#endregion
499
- //#region extensions/irc/src/client.ts
492
+ // extensions/irc/src/client.ts
500
493
  const IRC_ERROR_CODES = /* @__PURE__ */ new Set([
501
494
  "432",
502
495
  "464",
@@ -700,7 +693,6 @@ async function connectIrcClient(options) {
700
693
  buffer = buffer.slice(idx + 1);
701
694
  idx = buffer.indexOf("\n");
702
695
  if (!rawLine) continue;
703
- options.onLine?.(rawLine);
704
696
  const line = parseIrcLine(rawLine);
705
697
  if (!line) continue;
706
698
  if (line.command === "PING") {
@@ -811,15 +803,13 @@ async function connectIrcClient(options) {
811
803
  return currentNick;
812
804
  },
813
805
  isReady: () => ready && !closed,
814
- sendRaw,
815
806
  join,
816
807
  sendPrivmsg,
817
808
  quit,
818
809
  close
819
810
  };
820
811
  }
821
- //#endregion
822
- //#region extensions/irc/src/connect-options.ts
812
+ // extensions/irc/src/connect-options.ts
823
813
  function buildIrcConnectOptions(account, overrides = {}) {
824
814
  assertSecretOwnerAvailable("account", `irc:${account.accountId}`);
825
815
  if (account.tokenStatus === "configured_unavailable") throw new Error(`IRC credentials for account "${account.accountId}" are configured but unavailable.`);
@@ -831,18 +821,45 @@ function buildIrcConnectOptions(account, overrides = {}) {
831
821
  username: account.username,
832
822
  realname: account.realname,
833
823
  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
- },
824
+ nickserv: account.config.nickserv,
841
825
  ...overrides
842
826
  };
843
827
  }
844
- //#endregion
845
- //#region extensions/irc/src/send.ts
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
+ }
862
+ // extensions/irc/src/send.ts
846
863
  async function sendIrcMessages(to, text, opts, planMessages = (preparedText) => [{
847
864
  text: preparedText,
848
865
  replyTo: opts.replyTo
@@ -866,15 +883,14 @@ async function sendIrcMessages(to, text, opts, planMessages = (preparedText) =>
866
883
  if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
867
884
  const messages = planMessages(prepared);
868
885
  opts.abortSignal?.throwIfAborted();
869
- let transient;
870
- const client = opts.client?.isReady() ? opts.client : transient = await connectIrcClient(buildIrcConnectOptions(account, {
886
+ const client = await connectIrcClient(buildIrcConnectOptions(account, {
871
887
  connectTimeoutMs: 12e3,
872
888
  abortSignal: opts.abortSignal
873
889
  }));
874
890
  const results = [];
875
891
  try {
876
892
  opts.abortSignal?.throwIfAborted();
877
- if (transient && (target.startsWith("#") || target.startsWith("&"))) await effect.initiate(() => {
893
+ if (target.startsWith("#") || target.startsWith("&")) await effect.initiate(() => {
878
894
  opts.abortSignal?.throwIfAborted();
879
895
  if (!client.isReady()) throw new Error("IRC connection closed before join");
880
896
  client.join(target);
@@ -931,7 +947,7 @@ async function sendIrcMessages(to, text, opts, planMessages = (preparedText) =>
931
947
  visibleReplySent: true
932
948
  });
933
949
  } finally {
934
- transient?.quit("sent");
950
+ client.quit("sent");
935
951
  }
936
952
  }
937
953
  async function sendMessageIrc(to, text, opts) {
@@ -939,141 +955,7 @@ async function sendMessageIrc(to, text, opts) {
939
955
  if (!result) throw new Error("Message must be non-empty for IRC sends");
940
956
  return result;
941
957
  }
942
- //#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
- //#region extensions/irc/src/setup-core.ts
958
+ // extensions/irc/src/setup-core.ts
1077
959
  const channel$1 = "irc";
1078
960
  const setIrcDmPolicy = createTopLevelChannelDmPolicySetter({ channel: channel$1 });
1079
961
  const setIrcAllowFrom = createTopLevelChannelAllowFromSetter({ channel: channel$1 });
@@ -1222,8 +1104,7 @@ const ircSetupContract = defineChannelSetupContract({
1222
1104
  },
1223
1105
  legacyAdapter: ircSetupAdapter
1224
1106
  });
1225
- //#endregion
1226
- //#region extensions/irc/src/setup-surface.ts
1107
+ // extensions/irc/src/setup-surface.ts
1227
1108
  const t = createSetupTranslator();
1228
1109
  const channel = "irc";
1229
1110
  const USE_ENV_FLAG = "__ircUseEnv";
@@ -1253,67 +1134,16 @@ function normalizeGroupEntry(raw) {
1253
1134
  return `#${normalized.replace(/^#+/, "")}`;
1254
1135
  }
1255
1136
  function parseAutoJoinChannels(value) {
1256
- return splitSetupEntries(value).map(normalizeGroupEntry).filter((entry) => Boolean(entry && entry !== "*")).filter(isChannelTarget);
1137
+ return splitSetupEntries(value).map(normalizeGroupEntry).filter((entry) => Boolean(entry && entry !== "*"));
1257
1138
  }
1258
- const promptIrcAllowFrom = createPromptParsedAllowFromForAccount({
1259
- defaultAccountId: resolveDefaultIrcAccountId,
1260
- noteTitle: t("wizard.irc.allowlistTitle"),
1261
- noteLines: [
1139
+ function ircAllowlistHelpLines() {
1140
+ return [
1262
1141
  t("wizard.irc.allowlistIntro"),
1263
1142
  t("wizard.irc.examples"),
1264
1143
  "- alice",
1265
1144
  "- alice!ident@example.org",
1266
1145
  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
- });
1146
+ ];
1317
1147
  }
1318
1148
  const ircDmPolicy = {
1319
1149
  label: "IRC",
@@ -1322,7 +1152,16 @@ const ircDmPolicy = {
1322
1152
  allowFromKey: "channels.irc.allowFrom",
1323
1153
  getCurrent: (cfg) => cfg.channels?.irc?.dmPolicy ?? "pairing",
1324
1154
  setPolicy: (cfg, policy) => setIrcDmPolicy(cfg, policy),
1325
- 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
+ })
1326
1165
  };
1327
1166
  const ircSetupWizard = {
1328
1167
  channel,
@@ -1487,13 +1326,7 @@ const ircSetupWizard = {
1487
1326
  },
1488
1327
  allowFrom: createAllowFromSection({
1489
1328
  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
- ],
1329
+ helpLines: ircAllowlistHelpLines(),
1497
1330
  message: t("wizard.irc.allowFromPrompt"),
1498
1331
  placeholder: "alice, bob!ident@example.org",
1499
1332
  invalidWithoutCredentialNote: t("wizard.irc.allowFromInvalid"),
@@ -1523,11 +1356,51 @@ const ircSetupWizard = {
1523
1356
  }
1524
1357
  }
1525
1358
  }
1526
- next = await promptIrcNickServConfig({
1527
- cfg: next,
1528
- prompter,
1529
- accountId
1530
- });
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
+ })();
1531
1404
  return { cfg: next };
1532
1405
  },
1533
1406
  completionNote: {
@@ -1541,8 +1414,74 @@ const ircSetupWizard = {
1541
1414
  dmPolicy: ircDmPolicy,
1542
1415
  disable: (cfg) => setSetupChannelEnabled(cfg, channel, false)
1543
1416
  };
1544
- //#endregion
1545
- //#region extensions/irc/src/channel.ts
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
+ });
1546
1485
  const meta = {
1547
1486
  id: "irc",
1548
1487
  label: "IRC",
@@ -1658,14 +1597,7 @@ const ircPlugin = createChatChannelPlugin({
1658
1597
  describeAccount: (account) => describeAccountSnapshot({
1659
1598
  account,
1660
1599
  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
- }
1600
+ extra: ircAccountDetails(account)
1669
1601
  })
1670
1602
  },
1671
1603
  secrets: {
@@ -1768,20 +1700,29 @@ const ircPlugin = createChatChannelPlugin({
1768
1700
  name: account.name,
1769
1701
  enabled: account.enabled,
1770
1702
  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
- }
1703
+ extra: ircAccountDetails(account)
1779
1704
  })
1780
1705
  }),
1781
- gateway: { startAccount: async (ctx) => await startIrcGatewayAccount({
1782
- ...ctx,
1783
- cfg: ctx.cfg
1784
- }) }
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
+ } }
1785
1726
  },
1786
1727
  pairing: { text: {
1787
1728
  idLabel: "ircUser",
@@ -1790,7 +1731,7 @@ const ircPlugin = createChatChannelPlugin({
1790
1731
  notify: async ({ cfg, id, message, accountId }) => {
1791
1732
  const target = normalizePairingTarget(id);
1792
1733
  if (!target) throw new Error(`invalid IRC pairing id: ${id}`);
1793
- const { sendMessageIrc } = await import("./channel-runtime-B4OMtNnX.mjs");
1734
+ const { sendMessageIrc } = await import("./channel-runtime-D2sqTm8e.mjs");
1794
1735
  await sendMessageIrc(target, message, {
1795
1736
  cfg,
1796
1737
  accountId
@@ -1816,5 +1757,4 @@ const ircPlugin = createChatChannelPlugin({
1816
1757
  }
1817
1758
  }
1818
1759
  });
1819
- //#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 };
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 };