@openclaw/irc 2026.8.1-beta.2 → 2026.8.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as resolveDefaultIrcAccountId, g as listIrcAccountIds, h as listEnabledIrcAccounts, n as ircSetupWizard, r as ircSetupAdapter, t as ircPlugin, v as resolveIrcAccount } from "./channel-Bfg9kJRw.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-BO1GdA4V.js";
2
2
  import { n as setIrcRuntime } from "./runtime-xy-FcjJC.js";
3
3
  export { ircPlugin, ircSetupAdapter, ircSetupWizard, listEnabledIrcAccounts, listIrcAccountIds, resolveDefaultIrcAccountId, resolveIrcAccount, setIrcRuntime };
@@ -5,7 +5,7 @@ import { createAccountListHelpers, describeAccountSnapshot } from "openclaw/plug
5
5
  import { formatNormalizedAllowFromEntries } from "openclaw/plugin-sdk/allow-from";
6
6
  import { adaptScopedAccountAccessor, createScopedChannelConfigAdapter, createScopedDmSecurityResolver } from "openclaw/plugin-sdk/channel-config-helpers";
7
7
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin, parseOptionalDelimitedEntries } from "openclaw/plugin-sdk/channel-core";
8
- import { buildMutableAllowEntryDetector, collectStandardAllowlistLists, composeAccountWarningCollectors, createAllowlistProviderOpenWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, resolveScopeKeyCaseInsensitive, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
8
+ import { buildMutableAllowEntryDetector, collectStandardAllowlistLists, createAllowlistProviderOpenWarningCollector, createConditionalWarningCollector, createDangerousNameMatchingMutableAllowlistWarningCollector, resolveScopeKeyCaseInsensitive, resolveScopeRequireMention, resolveScopeToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
9
9
  import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister } from "openclaw/plugin-sdk/directory-runtime";
10
10
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
11
11
  import { buildBaseChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
@@ -14,7 +14,7 @@ import { DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normali
14
14
  import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
15
15
  import { isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
16
16
  import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
17
- import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
17
+ import { resolveSecretInputString } from "openclaw/plugin-sdk/secret-input";
18
18
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeStringEntries, normalizeStringifiedOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
19
19
  import { createAccountStatusSink, createMessageReceiptFromOutboundResults, defineChannelMessageAdapter, runPassiveAccountLifecycle, sanitizeForPlainText } from "openclaw/plugin-sdk/channel-outbound";
20
20
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
@@ -25,6 +25,7 @@ import net from "node:net";
25
25
  import tls from "node:tls";
26
26
  import { withTimeout } from "openclaw/plugin-sdk/security-runtime";
27
27
  import { randomUUID } from "node:crypto";
28
+ import { assertSecretOwnerAvailable } from "openclaw/plugin-sdk/channel-secret-owner-runtime";
28
29
  import { defineChannelSetupContract } from "openclaw/plugin-sdk/channel-setup";
29
30
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, normalizeAccountId as normalizeAccountId$1 } from "openclaw/plugin-sdk/routing";
30
31
  import { applyAccountNameToChannelSection, createAllowFromSection, createPromptParsedAllowFromForAccount, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, createTopLevelChannelAllowFromSetter, createTopLevelChannelDmPolicySetter, formatDocsLink, patchScopedAccountConfig, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
@@ -42,6 +43,16 @@ const { listAccountIds: listIrcAccountIds, resolveDefaultAccountId: resolveDefau
42
43
  hasImplicitDefaultAccount: (cfg) => Boolean((cfg.channels?.irc?.host?.trim() || process.env.IRC_HOST?.trim()) && (cfg.channels?.irc?.nick?.trim() || process.env.IRC_NICK?.trim()))
43
44
  });
44
45
  function resolvePassword(accountId, merged) {
46
+ const configPassword = resolveSecretInputString({
47
+ value: merged.password,
48
+ path: `channels.irc.accounts.${accountId}.password`,
49
+ mode: "inspect"
50
+ });
51
+ if (configPassword.status === "configured_unavailable") return {
52
+ password: "",
53
+ source: "config",
54
+ unavailable: true
55
+ };
45
56
  if (accountId === DEFAULT_ACCOUNT_ID) {
46
57
  const envPassword = process.env.IRC_PASSWORD?.trim();
47
58
  if (envPassword) return {
@@ -51,7 +62,7 @@ function resolvePassword(accountId, merged) {
51
62
  }
52
63
  if (merged.passwordFile?.trim()) {
53
64
  let diagnostic;
54
- const filePassword = tryReadSecretFileSync(merged.passwordFile, "IRC password file", {
65
+ const password = tryReadSecretFileSync(merged.passwordFile, "IRC password file", {
55
66
  rejectSymlink: true,
56
67
  credentialDiagnostic: {
57
68
  configPath: `channels.irc.accounts.${accountId}.passwordFile`,
@@ -60,8 +71,8 @@ function resolvePassword(accountId, merged) {
60
71
  }
61
72
  }
62
73
  });
63
- if (filePassword) return {
64
- password: filePassword,
74
+ if (password) return {
75
+ password,
65
76
  source: "passwordFile"
66
77
  };
67
78
  return {
@@ -70,12 +81,8 @@ function resolvePassword(accountId, merged) {
70
81
  diagnostic
71
82
  };
72
83
  }
73
- const configPassword = normalizeResolvedSecretInputString({
74
- value: merged.password,
75
- path: `channels.irc.accounts.${accountId}.password`
76
- });
77
- if (configPassword) return {
78
- password: configPassword,
84
+ if (configPassword.status === "available") return {
85
+ password: configPassword.value,
79
86
  source: "config"
80
87
  };
81
88
  return {
@@ -85,23 +92,29 @@ function resolvePassword(accountId, merged) {
85
92
  }
86
93
  function resolveNickServConfig(accountId, nickserv) {
87
94
  const base = nickserv ?? {};
95
+ const configPassword = resolveSecretInputString({
96
+ value: base.password,
97
+ path: `channels.irc.accounts.${accountId}.nickserv.password`,
98
+ mode: "inspect"
99
+ });
100
+ const unavailable = Boolean(configPassword.ref);
88
101
  const envPassword = accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICKSERV_PASSWORD?.trim() : void 0;
89
102
  const envRegisterEmail = accountId === DEFAULT_ACCOUNT_ID ? process.env.IRC_NICKSERV_REGISTER_EMAIL?.trim() : void 0;
90
103
  const passwordFile = base.passwordFile?.trim();
91
- let resolvedPassword = normalizeResolvedSecretInputString({
92
- value: base.password,
93
- path: `channels.irc.accounts.${accountId}.nickserv.password`
94
- }) || envPassword || "";
104
+ let resolvedPassword;
95
105
  let diagnostic;
96
- if (!resolvedPassword && passwordFile) resolvedPassword = tryReadSecretFileSync(passwordFile, "IRC NickServ password file", {
97
- rejectSymlink: true,
98
- credentialDiagnostic: {
99
- configPath: `channels.irc.accounts.${accountId}.nickserv.passwordFile`,
100
- report: (value) => {
101
- diagnostic = value;
106
+ if (!unavailable) {
107
+ resolvedPassword = configPassword.value || envPassword;
108
+ if (!resolvedPassword && passwordFile) resolvedPassword = tryReadSecretFileSync(passwordFile, "IRC NickServ password file", {
109
+ rejectSymlink: true,
110
+ credentialDiagnostic: {
111
+ configPath: `channels.irc.accounts.${accountId}.nickserv.passwordFile`,
112
+ report: (value) => {
113
+ diagnostic = value;
114
+ }
102
115
  }
103
- }
104
- }) ?? "";
116
+ });
117
+ }
105
118
  return {
106
119
  config: {
107
120
  ...base,
@@ -110,7 +123,8 @@ function resolveNickServConfig(accountId, nickserv) {
110
123
  password: resolvedPassword || void 0,
111
124
  registerEmail: base.registerEmail?.trim() || envRegisterEmail || void 0
112
125
  },
113
- diagnostic
126
+ diagnostic,
127
+ unavailable: base.enabled !== false && unavailable
114
128
  };
115
129
  }
116
130
  function resolveIrcAccount(params) {
@@ -154,7 +168,7 @@ function resolveIrcAccount(params) {
154
168
  realname,
155
169
  password: passwordResolution.password,
156
170
  passwordSource: passwordResolution.source,
157
- tokenStatus: diagnostics.length > 0 ? "configured_unavailable" : passwordResolution.password || nickservResolution.config.password ? "available" : "missing",
171
+ tokenStatus: diagnostics.length > 0 || passwordResolution.unavailable || nickservResolution.unavailable ? "configured_unavailable" : passwordResolution.password || nickservResolution.config.password ? "available" : "missing",
158
172
  ...diagnostics.length > 0 ? { credentialDiagnostics: diagnostics } : {},
159
173
  config
160
174
  };
@@ -183,7 +197,7 @@ const collectIrcMutableAllowlistWarnings = createDangerousNameMatchingMutableAll
183
197
  });
184
198
  //#endregion
185
199
  //#region extensions/irc/src/gateway.ts
186
- const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-kWMtRD4g.js"));
200
+ const loadIrcChannelRuntime$1 = createLazyRuntimeModule(() => import("./channel-runtime-CSpBc1ef.js"));
187
201
  async function startIrcGatewayAccount(ctx) {
188
202
  const account = ctx.account;
189
203
  const statusSink = createAccountStatusSink({
@@ -613,6 +627,8 @@ async function connectIrcClient(options) {
613
627
  //#endregion
614
628
  //#region extensions/irc/src/connect-options.ts
615
629
  function buildIrcConnectOptions(account, overrides = {}) {
630
+ assertSecretOwnerAvailable("account", `irc:${account.accountId}`);
631
+ if (account.tokenStatus === "configured_unavailable") throw new Error(`IRC credentials for account "${account.accountId}" are configured but unavailable.`);
616
632
  return {
617
633
  host: account.host,
618
634
  port: account.port,
@@ -755,6 +771,16 @@ async function sendMessageIrc(to, text, opts) {
755
771
  }
756
772
  //#endregion
757
773
  //#region extensions/irc/src/message-adapter.ts
774
+ async function sendIrcMessage(...args) {
775
+ const { target, ...result } = await sendMessageIrc(...args);
776
+ return {
777
+ ...result,
778
+ target: {
779
+ kind: "conversation",
780
+ id: target
781
+ }
782
+ };
783
+ }
758
784
  const ircMessageAdapter = defineChannelMessageAdapter({
759
785
  id: "irc",
760
786
  durableFinal: { capabilities: {
@@ -763,12 +789,12 @@ const ircMessageAdapter = defineChannelMessageAdapter({
763
789
  replyTo: true
764
790
  } },
765
791
  send: {
766
- text: async ({ cfg, to, text, accountId, replyToId }) => await sendMessageIrc(to, text, {
792
+ text: async ({ cfg, to, text, accountId, replyToId }) => await sendIrcMessage(to, text, {
767
793
  cfg,
768
794
  accountId: accountId ?? void 0,
769
795
  replyTo: replyToId ?? void 0
770
796
  }),
771
- media: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) => await sendMessageIrc(to, mediaUrl ? `${text}\n\nAttachment: ${mediaUrl}` : text, {
797
+ media: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) => await sendIrcMessage(to, mediaUrl ? `${text}\n\nAttachment: ${mediaUrl}` : text, {
772
798
  cfg,
773
799
  accountId: accountId ?? void 0,
774
800
  replyTo: replyToId ?? void 0
@@ -1399,7 +1425,7 @@ const meta = {
1399
1425
  systemImage: "number",
1400
1426
  markdownCapable: true
1401
1427
  };
1402
- const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-kWMtRD4g.js"));
1428
+ const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime-CSpBc1ef.js"));
1403
1429
  function normalizePairingTarget(raw) {
1404
1430
  const normalized = normalizeIrcAllowEntry(raw);
1405
1431
  if (!normalized) return "";
@@ -1455,7 +1481,7 @@ const resolveIrcDmPolicy = createScopedDmSecurityResolver({
1455
1481
  policyPathSuffix: "dmPolicy",
1456
1482
  normalizeEntry: (raw) => normalizeIrcAllowEntry(raw)
1457
1483
  });
1458
- const collectIrcSecurityWarnings = composeAccountWarningCollectors(createAllowlistProviderOpenWarningCollector({
1484
+ const collectIrcGroupPolicyWarnings = createAllowlistProviderOpenWarningCollector({
1459
1485
  providerConfigPresent: (cfg) => cfg.channels?.irc !== void 0,
1460
1486
  resolveGroupPolicy: (account) => account.config.groupPolicy,
1461
1487
  buildOpenWarning: {
@@ -1463,7 +1489,19 @@ const collectIrcSecurityWarnings = composeAccountWarningCollectors(createAllowli
1463
1489
  openBehavior: "allows all channels and senders (mention-gated)",
1464
1490
  remediation: "Prefer channels.irc.groupPolicy=\"allowlist\" with channels.irc.groups"
1465
1491
  }
1466
- }), (account) => !account.config.tls && "- IRC TLS is disabled (channels.irc.tls=false); traffic and credentials are plaintext.", (account) => account.config.nickserv?.register && "- IRC NickServ registration is enabled (channels.irc.nickserv.register=true); this sends \"REGISTER\" on every connect. Disable after first successful registration.", (account) => account.config.nickserv?.register && !account.config.nickserv.password?.trim() && "- IRC NickServ registration is enabled but no NickServ password is resolved; set channels.irc.nickserv.password, channels.irc.nickserv.passwordFile, or IRC_NICKSERV_PASSWORD.");
1492
+ });
1493
+ const collectIrcOpenGroupFindings = createConditionalWarningCollector.findings({
1494
+ collectWarnings: collectIrcGroupPolicyWarnings,
1495
+ checkId: "channels.irc.groups.open",
1496
+ severity: "critical",
1497
+ title: "IRC security warning"
1498
+ });
1499
+ const collectIrcSecurityWarnings = (params) => [
1500
+ ...collectIrcOpenGroupFindings(params),
1501
+ ...!params.account.config.tls ? ["- IRC TLS is disabled (channels.irc.tls=false); traffic and credentials are plaintext."] : [],
1502
+ ...params.account.config.nickserv?.register ? ["- IRC NickServ registration is enabled (channels.irc.nickserv.register=true); this sends \"REGISTER\" on every connect. Disable after first successful registration."] : [],
1503
+ ...params.account.config.nickserv?.register && !params.account.config.nickserv.password?.trim() ? ["- IRC NickServ registration is enabled but no NickServ password is resolved; set channels.irc.nickserv.password, channels.irc.nickserv.passwordFile, or IRC_NICKSERV_PASSWORD."] : []
1504
+ ];
1467
1505
  const ircPlugin = createChatChannelPlugin({
1468
1506
  base: {
1469
1507
  id: "irc",
@@ -1,2 +1,2 @@
1
- import { t as ircPlugin } from "./channel-Bfg9kJRw.js";
1
+ import { t as ircPlugin } from "./channel-BO1GdA4V.js";
2
2
  export { ircPlugin };
@@ -1,4 +1,4 @@
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";
1
+ import { a as resolveIrcGroupRequireMention, c as buildIrcAllowlistCandidates, d as buildIrcConnectOptions, f as connectIrcClient, i as resolveIrcGroupMatch, l as isChannelTarget, m as parseIrcPrefix, o as sanitizeIrcAssistantText, p as parseIrcLine, s as sendMessageIrc, u as normalizeIrcAllowEntry, v as resolveIrcAccount } from "./channel-BO1GdA4V.js";
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
4
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
@@ -455,14 +455,16 @@ function decodeIrcIngressPayload(payload, claimedId) {
455
455
  }
456
456
  function inspectIrcIngress(raw, phase) {
457
457
  try {
458
+ const projection = inspectRawPrivmsg(raw.body.rawLine);
459
+ if (phase === "claim") raw.claimedProjection = projection;
458
460
  return {
459
- eventId: raw.eventId,
460
- laneKey: inspectRawPrivmsg(raw.rawLine).laneKey
461
+ eventId: raw.body.eventId,
462
+ laneKey: projection.laneKey
461
463
  };
462
464
  } catch (error) {
463
465
  if (phase === "admission" && error instanceof IrcIngressPayloadError) return {
464
- eventId: raw.eventId,
465
- laneKey: `invalid:${raw.eventId}`
466
+ eventId: raw.body.eventId,
467
+ laneKey: `invalid:${raw.body.eventId}`
466
468
  };
467
469
  throw error;
468
470
  }
@@ -479,8 +481,8 @@ function createIrcIngressMonitor(options) {
479
481
  inspect: (raw, context) => inspectIrcIngress(raw, context.phase),
480
482
  payload: {
481
483
  version: IRC_INGRESS_PAYLOAD_VERSION,
482
- serialize: (raw) => raw,
483
- deserialize: (body) => body,
484
+ serialize: (raw) => raw.body,
485
+ deserialize: (body) => ({ body }),
484
486
  encode: ({ body }) => ({
485
487
  version: IRC_INGRESS_PAYLOAD_VERSION,
486
488
  ...body
@@ -490,13 +492,13 @@ function createIrcIngressMonitor(options) {
490
492
  },
491
493
  deliver: (raw, lifecycle, claim) => {
492
494
  const message = {
493
- ...inspectRawPrivmsg(raw.rawLine).message,
495
+ ...raw.claimedProjection.message,
494
496
  messageId: claim.id,
495
- timestamp: raw.receivedAt
497
+ timestamp: raw.body.receivedAt
496
498
  };
497
499
  return options.dispatch(message, lifecycle, {
498
- connectedNick: raw.connectedNick.trim(),
499
- connectionEpoch: raw.connectionEpoch.trim()
500
+ connectedNick: raw.body.connectedNick.trim(),
501
+ connectionEpoch: raw.body.connectionEpoch.trim()
500
502
  });
501
503
  },
502
504
  pollIntervalMs: options.pollIntervalMs ?? IRC_INGRESS_POLL_INTERVAL_MS,
@@ -526,13 +528,13 @@ function createIrcIngressMonitor(options) {
526
528
  const receivedAt = Date.now();
527
529
  const normalizedNick = connectedNick.trim();
528
530
  if (!normalizedNick) return Promise.reject(/* @__PURE__ */ new Error("IRC ingress connected nickname is required."));
529
- return monitor.admit({
531
+ return monitor.admit({ body: {
530
532
  eventId,
531
533
  rawLine,
532
534
  receivedAt,
533
535
  connectionEpoch: epoch,
534
536
  connectedNick: normalizedNick
535
- }, { receivedAt }).then(() => void 0);
537
+ } }, { receivedAt }).then(() => void 0);
536
538
  }
537
539
  };
538
540
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/irc",
3
- "version": "2026.8.1-beta.2",
3
+ "version": "2026.8.1-beta.3",
4
4
  "description": "OpenClaw IRC channel plugin",
5
5
  "type": "module",
6
6
  "openclaw": {
@@ -121,10 +121,10 @@
121
121
  }
122
122
  },
123
123
  "compat": {
124
- "pluginApi": ">=2026.8.1-beta.2"
124
+ "pluginApi": ">=2026.8.1-beta.3"
125
125
  },
126
126
  "build": {
127
- "openclawVersion": "2026.8.1-beta.2",
127
+ "openclawVersion": "2026.8.1-beta.3",
128
128
  "bundledDist": false
129
129
  },
130
130
  "release": {
@@ -149,7 +149,7 @@
149
149
  "README.md"
150
150
  ],
151
151
  "peerDependencies": {
152
- "openclaw": ">=2026.8.1-beta.2"
152
+ "openclaw": ">=2026.8.1-beta.3"
153
153
  },
154
154
  "peerDependenciesMeta": {
155
155
  "openclaw": {