@openclaw/irc 2026.7.2-beta.5 → 2026.8.1-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.
package/dist/api.js CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as resolveIrcAccount, g as resolveDefaultIrcAccountId, h as listIrcAccountIds, m as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin } from "./channel-BZclM5wY.js";
1
+ import { _ as resolveDefaultIrcAccountId, g as listIrcAccountIds, h as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin, v as resolveIrcAccount } from "./channel-Bfg9kJRw.js";
2
2
  import { n as setIrcRuntime } from "./runtime-xy-FcjJC.js";
3
3
  export { ircPlugin, ircSetupAdapter, ircSetupWizard, listEnabledIrcAccounts, listIrcAccountIds, resolveDefaultIrcAccountId, resolveIrcAccount, setIrcRuntime };
@@ -1,4 +1,4 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-BPoye3--.js";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-DdB_h5mL.js";
2
2
  import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
3
3
  import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-D_DC3YWj.js";
4
4
  import { createAccountListHelpers, describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
@@ -12,12 +12,13 @@ import { buildBaseChannelStatusSummary, createComputedAccountStatusAdapter, crea
12
12
  import { resolveAccountWithDefaultFallback } from "openclaw/plugin-sdk/account-core";
13
13
  import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
14
14
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
15
+ import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
15
16
  import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
16
17
  import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
17
18
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringifiedOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
18
19
  import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
19
20
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
20
- import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
21
+ import { chunkTextForOutbound, convertMarkdownTables, sanitizeAssistantVisibleText, stripMarkdown } from "openclaw/plugin-sdk/text-chunking";
21
22
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
22
23
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
23
24
  import net from "node:net";
@@ -28,16 +29,6 @@ import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
28
29
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
29
30
  import { applyAccountNameToChannelSection, createAllowFromSection, createPromptParsedAllowFromForAccount, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicySetter, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
30
31
  //#region extensions/irc/src/accounts.ts
31
- const TRUTHY_ENV = /* @__PURE__ */ new Set([
32
- "true",
33
- "1",
34
- "yes",
35
- "on"
36
- ]);
37
- function parseTruthy(value) {
38
- if (!value) return false;
39
- return TRUTHY_ENV.has(normalizeLowercaseStringOrEmpty(value));
40
- }
41
32
  function parseIntEnv(value) {
42
33
  if (!value?.trim()) return;
43
34
  const parsed = parseStrictPositiveInteger(value);
@@ -128,7 +119,7 @@ function resolveIrcAccount(params) {
128
119
  const merged = mergeIrcAccountConfig(params.cfg, accountId);
129
120
  const accountEnabled = merged.enabled !== false;
130
121
  const enabled = baseEnabled && accountEnabled;
131
- const tls = typeof merged.tls === "boolean" ? merged.tls : accountId === DEFAULT_ACCOUNT_ID && process.env.IRC_TLS ? parseTruthy(process.env.IRC_TLS) : true;
122
+ const tls = typeof merged.tls === "boolean" ? merged.tls : accountId === DEFAULT_ACCOUNT_ID && process.env.IRC_TLS ? isTruthyEnvValue(process.env.IRC_TLS) : true;
132
123
  const envPort = accountId === DEFAULT_ACCOUNT_ID ? parseIntEnv(process.env.IRC_PORT) : void 0;
133
124
  const port = merged.port ?? envPort ?? (tls ? 6697 : 6667);
134
125
  const envChannels = accountId === DEFAULT_ACCOUNT_ID ? parseOptionalDelimitedEntries(process.env.IRC_CHANNELS) : void 0;
@@ -192,7 +183,7 @@ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAll
192
183
  });
193
184
  //#endregion
194
185
  //#region extensions/irc/src/gateway.ts
195
- const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-CRzAwKGK.js"));
186
+ const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-kWMtRD4g.js"));
196
187
  async function startIrcGatewayAccount(ctx) {
197
188
  const account = ctx.account;
198
189
  const statusSink = createAccountStatusSink({
@@ -343,7 +334,7 @@ function takeIrcPrivmsgChunk(text, maxChars, maxBytes) {
343
334
  if (splitAt >= Math.floor(fitted.length / 2)) return fitted.slice(0, splitAt);
344
335
  return fitted;
345
336
  }
346
- function toError(err) {
337
+ function toIrcError(err) {
347
338
  if (err instanceof Error) return err;
348
339
  return new Error(typeof err === "string" ? err : JSON.stringify(err));
349
340
  }
@@ -400,7 +391,7 @@ async function connectIrcClient(options) {
400
391
  rejectReady = reject;
401
392
  });
402
393
  const fail = (err) => {
403
- const error = toError(err);
394
+ const error = toIrcError(err);
404
395
  if (options.onError) options.onError(error);
405
396
  if (!ready && rejectReady) {
406
397
  rejectReady(error);
@@ -735,9 +726,9 @@ async function sendMessageIrc(to, text, opts) {
735
726
  channel: "irc",
736
727
  accountId: account.accountId
737
728
  });
738
- const prepared = convertMarkdownTables(text.trim(), tableMode);
729
+ const prepared = stripMarkdown(convertMarkdownTables(text.trim(), tableMode));
730
+ if (!prepared.trim()) throw new Error("Message must be non-empty for IRC sends");
739
731
  const payload = opts.replyTo ? `${prepared}\n\n[reply:${opts.replyTo}]` : prepared;
740
- if (!payload.trim()) throw new Error("Message must be non-empty for IRC sends");
741
732
  const client = opts.client;
742
733
  if (client?.isReady()) client.sendPrivmsg(target, payload);
743
734
  else {
@@ -786,12 +777,15 @@ const ircMessageAdapter = defineChannelMessageAdapter({
786
777
  });
787
778
  //#endregion
788
779
  //#region extensions/irc/src/outbound-base.ts
780
+ function sanitizeIrcAssistantText(text) {
781
+ return sanitizeForPlainText(sanitizeAssistantVisibleText(text));
782
+ }
789
783
  const ircOutboundBaseAdapter = {
790
784
  deliveryMode: "direct",
791
785
  chunker: chunkTextForOutbound,
792
786
  chunkerMode: "markdown",
793
787
  textChunkLimit: 350,
794
- sanitizeText: ({ text }) => sanitizeForPlainText(sanitizeAssistantVisibleText(text))
788
+ sanitizeText: ({ text }) => sanitizeIrcAssistantText(text)
795
789
  };
796
790
  //#endregion
797
791
  //#region extensions/irc/src/policy.ts
@@ -1034,7 +1028,8 @@ const ircSetupContract = defineChannelSetupContract({
1034
1028
  cli: {
1035
1029
  flags: "--use-env",
1036
1030
  description: "Use IRC environment configuration"
1037
- }
1031
+ },
1032
+ envVars: ["IRC_HOST", "IRC_NICK"]
1038
1033
  }
1039
1034
  },
1040
1035
  legacyAdapter: ircSetupAdapter
@@ -1404,7 +1399,7 @@ const meta = {
1404
1399
  systemImage: "number",
1405
1400
  markdownCapable: true
1406
1401
  };
1407
- const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-CRzAwKGK.js"));
1402
+ const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-kWMtRD4g.js"));
1408
1403
  function normalizePairingTarget(raw) {
1409
1404
  const normalized = normalizeIrcAllowEntry(raw);
1410
1405
  if (!normalized) return "";
@@ -1476,7 +1471,6 @@ const ircPlugin = createChatChannelPlugin({
1476
1471
  ...meta,
1477
1472
  quickstartAllowFrom: true
1478
1473
  },
1479
- setup: ircSetupAdapter,
1480
1474
  setupContract: ircSetupContract,
1481
1475
  setupWizard: ircSetupWizard,
1482
1476
  capabilities: {
@@ -1538,6 +1532,10 @@ const ircPlugin = createChatChannelPlugin({
1538
1532
  messaging: {
1539
1533
  targetPrefixes: ["irc"],
1540
1534
  normalizeTarget: normalizeIrcMessagingTarget,
1535
+ inferTargetChatType: ({ to }) => {
1536
+ const target = normalizeIrcMessagingTarget(to);
1537
+ return target ? isChannelTarget(target) ? "group" : "direct" : void 0;
1538
+ },
1541
1539
  resolveOutboundSessionRoute: (params) => resolveIrcOutboundSessionRoute(params),
1542
1540
  targetResolver: {
1543
1541
  looksLikeId: looksLikeIrcTargetId,
@@ -1644,4 +1642,4 @@ const ircPlugin = createChatChannelPlugin({
1644
1642
  }
1645
1643
  });
1646
1644
  //#endregion
1647
- export { resolveIrcAccount as _, resolveIrcGroupRequireMention as a, isChannelTarget as c, connectIrcClient as d, parseIrcLine as f, resolveDefaultIrcAccountId as g, listIrcAccountIds as h, resolveIrcGroupMatch as i, normalizeIrcAllowEntry as l, listEnabledIrcAccounts as m, ircSetupWizard as n, sendMessageIrc as o, parseIrcPrefix as p, ircSetupAdapter as r, buildIrcAllowlistCandidates as s, ircPlugin as t, buildIrcConnectOptions as u };
1645
+ export { resolveDefaultIrcAccountId as _, resolveIrcGroupRequireMention as a, buildIrcAllowlistCandidates as c, buildIrcConnectOptions as d, connectIrcClient as f, listIrcAccountIds as g, listEnabledIrcAccounts as h, resolveIrcGroupMatch as i, isChannelTarget as l, parseIrcPrefix as m, ircSetupWizard as n, sanitizeIrcAssistantText as o, parseIrcLine as p, ircSetupAdapter as r, sendMessageIrc as s, ircPlugin as t, normalizeIrcAllowEntry as u, resolveIrcAccount as v };
@@ -1,2 +1,2 @@
1
- import { t as IrcChannelConfigSchema } from "./config-schema-BPoye3--.js";
1
+ import { t as IrcChannelConfigSchema } from "./config-schema-DdB_h5mL.js";
2
2
  export { IrcChannelConfigSchema };
@@ -1,2 +1,2 @@
1
- import { t as ircPlugin } from "./channel-BZclM5wY.js";
1
+ import { t as ircPlugin } from "./channel-Bfg9kJRw.js";
2
2
  export { ircPlugin };
@@ -1,10 +1,11 @@
1
- import { _ as resolveIrcAccount, a as resolveIrcGroupRequireMention, c as isChannelTarget, d as connectIrcClient, f as parseIrcLine, i as resolveIrcGroupMatch, l as normalizeIrcAllowEntry, o as sendMessageIrc, p as parseIrcPrefix, s as buildIrcAllowlistCandidates, u as buildIrcConnectOptions } from "./channel-BZclM5wY.js";
1
+ import { a as resolveIrcGroupRequireMention, c as buildIrcAllowlistCandidates, d as buildIrcConnectOptions, f as connectIrcClient, i as resolveIrcGroupMatch, l as isChannelTarget, m as parseIrcPrefix, o as sanitizeIrcAssistantText, p as parseIrcLine, s as sendMessageIrc, u as normalizeIrcAllowEntry, v as resolveIrcAccount } from "./channel-Bfg9kJRw.js";
2
2
  import { t as getIrcRuntime } from "./runtime-xy-FcjJC.js";
3
3
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
4
- import { bindIngressLifecycleToReplyOptions, createChannelIngressMonitor, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
4
+ import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { resolveLoggerBackedRuntime } from "openclaw/plugin-sdk/extension-shared";
7
- import { buildChannelInboundEventContext, logInboundDrop, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
7
+ import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
8
+ import { logInboundDrop, resolveChannelInboundRouteEnvelope } from "openclaw/plugin-sdk/channel-inbound";
8
9
  import { channelIngressRoutes, createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
9
10
  import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing";
10
11
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
@@ -46,6 +47,23 @@ const ircIngressIdentity = defineStableChannelIngressIdentity({
46
47
  resolveEntryId: ({ entryIndex, fieldKey }) => `irc-entry-${entryIndex + 1}:${fieldKey === "irc-nick" ? "nick" : "id"}`
47
48
  });
48
49
  const escapeIrcRegexLiteral = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50
+ const IRC_NICK_CHARACTER = String.raw`[A-Za-z0-9_\-\[\]\\\x60^{}|~]`;
51
+ const IRC_RFC1459_CASE_EQUIVALENTS = /* @__PURE__ */ new Map([
52
+ ["[", "{"],
53
+ ["{", "["],
54
+ ["]", "}"],
55
+ ["}", "]"],
56
+ ["\\", "|"],
57
+ ["|", "\\"],
58
+ ["^", "~"],
59
+ ["~", "^"]
60
+ ]);
61
+ function buildIrcNickMentionPattern(value) {
62
+ return Array.from(value, (character) => {
63
+ const equivalent = IRC_RFC1459_CASE_EQUIVALENTS.get(character);
64
+ return equivalent ? `[${escapeIrcRegexLiteral(character)}${escapeIrcRegexLiteral(equivalent)}]` : escapeIrcRegexLiteral(character);
65
+ }).join("");
66
+ }
49
67
  function isBareNick(value) {
50
68
  return !value.includes("!") && !value.includes("@");
51
69
  }
@@ -108,7 +126,10 @@ function routeDescriptorsForIrcGroup(params) {
108
126
  }
109
127
  async function deliverIrcReply(params) {
110
128
  await deliverFormattedTextWithAttachments({
111
- payload: params.payload,
129
+ payload: {
130
+ ...params.payload,
131
+ text: sanitizeIrcAssistantText(params.payload.text ?? "")
132
+ },
112
133
  send: async ({ text, replyToId }) => {
113
134
  if (params.sendReply) await params.sendReply(params.target, text, replyToId);
114
135
  else await sendMessageIrc(params.target, text, {
@@ -162,7 +183,7 @@ async function handleIrcInbound(params) {
162
183
  const hasControlCommand = core.channel.text.hasControlCommand(rawBody, config);
163
184
  const mentionRegexes = core.channel.mentions.buildMentionRegexes(config);
164
185
  const mentionNick = connectedNick?.trim() || account.nick;
165
- const explicitMentionRegex = mentionNick ? new RegExp(`\\b${escapeIrcRegexLiteral(mentionNick)}\\b[:,]?`, "i") : null;
186
+ const explicitMentionRegex = mentionNick ? new RegExp(`(?<!${IRC_NICK_CHARACTER})${buildIrcNickMentionPattern(mentionNick)}(?!${IRC_NICK_CHARACTER})[:,]?`, "i") : null;
166
187
  const wasMentioned = core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes) || (explicitMentionRegex ? explicitMentionRegex.test(rawBody) : false);
167
188
  const requireMention = message.isGroup ? resolveIrcGroupRequireMention({
168
189
  groups: account.config.groups,
@@ -170,6 +191,17 @@ async function handleIrcInbound(params) {
170
191
  }) : false;
171
192
  const routeGroupAllowFrom = normalizeStringEntries(groupMatch.groupConfig?.allowFrom?.length ? groupMatch.groupConfig.allowFrom : groupMatch.wildcardConfig?.allowFrom);
172
193
  const accessGroupPolicy = groupPolicy === "open" && (hasEntries(account.config.groupAllowFrom) || hasEntries(routeGroupAllowFrom)) ? "allowlist" : groupPolicy;
194
+ const channelTarget = message.target.startsWith("#") || message.target.startsWith("&") ? message.target : `#${message.target}`;
195
+ const peerId = message.isGroup ? channelTarget : message.senderNick;
196
+ const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
197
+ cfg: config,
198
+ channel: CHANNEL_ID,
199
+ accountId: account.accountId,
200
+ peer: {
201
+ kind: message.isGroup ? "group" : "direct",
202
+ id: peerId
203
+ }
204
+ });
173
205
  const access = await createChannelIngressResolver({
174
206
  channelId: CHANNEL_ID,
175
207
  accountId: account.accountId,
@@ -182,6 +214,12 @@ async function handleIrcInbound(params) {
182
214
  kind: message.isGroup ? "group" : "direct",
183
215
  id: message.target
184
216
  },
217
+ contextBinding: {
218
+ agentId: route.agentId,
219
+ sessionKey: route.sessionKey,
220
+ ...message.messageId ? { messageId: message.messageId } : {},
221
+ inboundEventKind: "user_request"
222
+ },
185
223
  route: routeDescriptorsForIrcGroup({
186
224
  isGroup: message.isGroup,
187
225
  groupPolicy,
@@ -259,17 +297,6 @@ async function handleIrcInbound(params) {
259
297
  kind: "failed-retryable",
260
298
  error: turnAdoptionLifecycle.abortSignal.reason
261
299
  };
262
- const channelTarget = message.target.startsWith("#") || message.target.startsWith("&") ? message.target : `#${message.target}`;
263
- const peerId = message.isGroup ? channelTarget : message.senderNick;
264
- const { route, buildEnvelope } = resolveChannelInboundRouteEnvelope({
265
- cfg: config,
266
- channel: CHANNEL_ID,
267
- accountId: account.accountId,
268
- peer: {
269
- kind: message.isGroup ? "group" : "direct",
270
- id: peerId
271
- }
272
- });
273
300
  const fromLabel = message.isGroup ? message.target : senderDisplay;
274
301
  const body = buildEnvelope({
275
302
  channel: "IRC",
@@ -279,7 +306,8 @@ async function handleIrcInbound(params) {
279
306
  });
280
307
  const groupSystemPrompt = normalizeOptionalString(groupMatch.groupConfig?.systemPrompt);
281
308
  const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(account.config);
282
- const ctxPayload = buildChannelInboundEventContext({
309
+ const ctxPayload = core.channel.inbound.buildContext({
310
+ channelIngress: access,
283
311
  channel: CHANNEL_ID,
284
312
  accountId: route.accountId,
285
313
  messageId: message.messageId,
@@ -386,15 +414,7 @@ async function handleIrcInbound(params) {
386
414
  //#region extensions/irc/src/irc-ingress.ts
387
415
  const IRC_INGRESS_PAYLOAD_VERSION = 1;
388
416
  const IRC_INGRESS_POLL_INTERVAL_MS = 1e3;
389
- const IRC_INGRESS_PRUNE_INTERVAL_MS = 3600 * 1e3;
390
- const IRC_INGRESS_TOMBSTONE_TTL_MS = 720 * 60 * 60 * 1e3;
391
- const IRC_INGRESS_TOMBSTONE_MAX_ENTRIES = 1e3;
392
- var IrcIngressPayloadError = class extends Error {
393
- constructor(message) {
394
- super(message);
395
- this.name = "IrcIngressPayloadError";
396
- }
397
- };
417
+ const IrcIngressPayloadError = createChannelIngressError("IrcIngressPayloadError");
398
418
  function inspectRawPrivmsg(rawLine) {
399
419
  const line = parseIrcLine(rawLine);
400
420
  if (!line || line.command !== "PRIVMSG") throw new IrcIngressPayloadError("IRC ingress row is not a PRIVMSG line.");
@@ -481,11 +501,8 @@ function createIrcIngressMonitor(options) {
481
501
  },
482
502
  pollIntervalMs: options.pollIntervalMs ?? IRC_INGRESS_POLL_INTERVAL_MS,
483
503
  retention: {
484
- pruneIntervalMs: IRC_INGRESS_PRUNE_INTERVAL_MS,
485
- completedTtlMs: IRC_INGRESS_TOMBSTONE_TTL_MS,
486
- completedMaxEntries: IRC_INGRESS_TOMBSTONE_MAX_ENTRIES,
487
- failedTtlMs: IRC_INGRESS_TOMBSTONE_TTL_MS,
488
- failedMaxEntries: IRC_INGRESS_TOMBSTONE_MAX_ENTRIES
504
+ completedMaxEntries: 1e3,
505
+ failedMaxEntries: 1e3
489
506
  },
490
507
  drain: {
491
508
  resolveNonRetryableFailure: resolveIrcIngressNonRetryableFailure,
@@ -600,6 +617,7 @@ async function monitorIrcProvider(opts) {
600
617
  });
601
618
  function scheduleReconnect() {
602
619
  if (stopped || monitorAbort.signal.aborted || reconnectTimer) return;
620
+ opts.statusSink?.({ lifecycle: "recovering" });
603
621
  reconnectTimer = setTimeout(() => {
604
622
  reconnectTimer = null;
605
623
  connect().catch((error) => {
@@ -656,6 +674,7 @@ async function monitorIrcProvider(opts) {
656
674
  return;
657
675
  }
658
676
  ingress.start();
677
+ opts.statusSink?.(channelReadyPatch());
659
678
  logger.info(`[${account.accountId}] connected to ${account.host}:${account.port}${account.tls ? " (tls)" : ""} as ${nextClient.nick}`);
660
679
  }
661
680
  try {
@@ -59,6 +59,7 @@ const IrcNickServSchema = z.object({
59
59
  const IrcChannelConfigSchema = buildChannelConfigSchema(buildMultiAccountChannelSchema(z.object({
60
60
  name: z.string().optional(),
61
61
  enabled: z.boolean().optional(),
62
+ configWrites: z.boolean().optional(),
62
63
  dangerouslyAllowNameMatching: z.boolean().optional(),
63
64
  host: z.string().optional(),
64
65
  port: z.number().int().min(1).max(65535).optional(),
@@ -1,4 +1,4 @@
1
- import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor";
1
+ import { defineChannelAliasMigration } from "openclaw/plugin-sdk/runtime-doctor-migrations";
2
2
  //#region extensions/irc/doctor-contract-api.ts
3
3
  const streamingAliasMigration = defineChannelAliasMigration({
4
4
  channelId: "irc",
@@ -1,5 +1,8 @@
1
1
  {
2
2
  "id": "irc",
3
+ "doctorContract": {
4
+ "configRepair": true
5
+ },
3
6
  "activation": {
4
7
  "onStartup": false
5
8
  },
@@ -23,6 +26,9 @@
23
26
  "enabled": {
24
27
  "type": "boolean"
25
28
  },
29
+ "configWrites": {
30
+ "type": "boolean"
31
+ },
26
32
  "dangerouslyAllowNameMatching": {
27
33
  "type": "boolean"
28
34
  },
@@ -346,6 +352,9 @@
346
352
  "enabled": {
347
353
  "type": "boolean"
348
354
  },
355
+ "configWrites": {
356
+ "type": "boolean"
357
+ },
349
358
  "dangerouslyAllowNameMatching": {
350
359
  "type": "boolean"
351
360
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/irc",
3
- "version": "2026.7.2-beta.5",
3
+ "version": "2026.8.1-beta.2",
4
4
  "description": "OpenClaw IRC channel plugin",
5
5
  "type": "module",
6
6
  "openclaw": {
@@ -111,16 +111,20 @@
111
111
  "cli": {
112
112
  "flags": "--use-env",
113
113
  "description": "Use IRC environment configuration"
114
- }
114
+ },
115
+ "envVars": [
116
+ "IRC_HOST",
117
+ "IRC_NICK"
118
+ ]
115
119
  }
116
120
  ]
117
121
  }
118
122
  },
119
123
  "compat": {
120
- "pluginApi": ">=2026.7.2-beta.5"
124
+ "pluginApi": ">=2026.8.1-beta.2"
121
125
  },
122
126
  "build": {
123
- "openclawVersion": "2026.7.2-beta.5",
127
+ "openclawVersion": "2026.8.1-beta.2",
124
128
  "bundledDist": false
125
129
  },
126
130
  "release": {
@@ -145,7 +149,7 @@
145
149
  "README.md"
146
150
  ],
147
151
  "peerDependencies": {
148
- "openclaw": ">=2026.7.2-beta.5"
152
+ "openclaw": ">=2026.8.1-beta.2"
149
153
  },
150
154
  "peerDependenciesMeta": {
151
155
  "openclaw": {