mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-15 07:04:01 -06:00
4a2a600809
* feat(channels): add channel-owned setup contracts * test(channels): align legacy setup fixtures * chore(channels): regenerate config and SDK baselines after rebase * fix(update): run fresh doctor after current-process core changes * fix(channels): align add pre-scan with execution precedence * style(cli): format channels-cli test additions * fix(channels): restore option-before-positional channel resolution via metadata arity scan * fix(channels): keep help flags out of metadata arity escalation * test(update): mock fresh post-update doctor in current-process suites * style: format review fixes and correct entrypoint mock type * fix(channels): register only modern contract options for dual-publishing plugins * test(update): align downgrade suites with fresh-doctor child invocation * docs(channels): record empty-contract and input-forwarding invariants * fix(line): keep the shipped --token switch as a channel access token alias * fix(signal): stop treating exact cross-family loopback endpoints as bind-aligned * chore(config): regenerate docs config baselines after second rebase * style: format rebased channels add tests * fix(channels): enforce field-key and flag-name agreement in setup contracts * fix(signal): detect container endpoints for bare --http-url setup * fix(signal): ignore unconfigured accounts in transport collision checks * fix(channels): validate negated setup flags in contract and normalizer * fix(signal): preserve existing transport kind when setup detection is unreachable * style(signal): use direct boolean check in collision guard * style(signal): type test config literals * docs(update): record two-read design of fresh-doctor validation gate * fix(channels): satisfy post-rebase architecture gates * docs: refresh channel setup map --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
183 lines
5.8 KiB
TypeScript
183 lines
5.8 KiB
TypeScript
// Irc plugin module implements setup core behavior.
|
|
import {
|
|
defineChannelSetupContract,
|
|
type ChannelSetupAdapter,
|
|
type ChannelSetupInput,
|
|
} from "openclaw/plugin-sdk/channel-setup";
|
|
import type { DmPolicy } from "openclaw/plugin-sdk/config-contracts";
|
|
import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
|
|
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
|
|
import {
|
|
applyAccountNameToChannelSection,
|
|
createSetupInputPresenceValidator,
|
|
createTopLevelChannelAllowFromSetter,
|
|
createTopLevelChannelDmPolicySetter,
|
|
patchScopedAccountConfig,
|
|
} from "openclaw/plugin-sdk/setup";
|
|
import type { CoreConfig, IrcAccountConfig, IrcNickServConfig } from "./types.js";
|
|
|
|
const channel = "irc" as const;
|
|
const setIrcTopLevelDmPolicy = createTopLevelChannelDmPolicySetter({
|
|
channel,
|
|
});
|
|
const setIrcTopLevelAllowFrom = createTopLevelChannelAllowFromSetter({
|
|
channel,
|
|
});
|
|
const validateIrcRequiredSetupInput = createSetupInputPresenceValidator({
|
|
whenNotUseEnv: [
|
|
{ someOf: ["host"], message: "IRC requires host." },
|
|
{ someOf: ["nick"], message: "IRC requires nick." },
|
|
],
|
|
});
|
|
|
|
type IrcSetupInput = ChannelSetupInput & {
|
|
host?: string;
|
|
port?: number | string;
|
|
tls?: boolean;
|
|
nick?: string;
|
|
username?: string;
|
|
realname?: string;
|
|
channels?: string[];
|
|
password?: string;
|
|
};
|
|
|
|
export function parsePort(raw: string, fallback: number): number {
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) {
|
|
return fallback;
|
|
}
|
|
const parsed = parseStrictPositiveInteger(trimmed);
|
|
if (parsed === undefined || parsed > 65535) {
|
|
return fallback;
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function validateIrcPortInput(input: ChannelSetupInput): string | null {
|
|
const raw = (input as IrcSetupInput).port;
|
|
if (raw === undefined || raw === null || raw === "") {
|
|
return null;
|
|
}
|
|
const parsed = parseStrictPositiveInteger(String(raw));
|
|
return parsed !== undefined && parsed <= 65535 ? null : "IRC port must be between 1 and 65535.";
|
|
}
|
|
|
|
export function updateIrcAccountConfig(
|
|
cfg: CoreConfig,
|
|
accountId: string,
|
|
patch: Partial<IrcAccountConfig>,
|
|
): CoreConfig {
|
|
return patchScopedAccountConfig({
|
|
cfg,
|
|
channelKey: channel,
|
|
accountId,
|
|
patch,
|
|
ensureChannelEnabled: false,
|
|
ensureAccountEnabled: false,
|
|
}) as CoreConfig;
|
|
}
|
|
|
|
export function setIrcDmPolicy(cfg: CoreConfig, dmPolicy: DmPolicy): CoreConfig {
|
|
return setIrcTopLevelDmPolicy(cfg, dmPolicy) as CoreConfig;
|
|
}
|
|
|
|
export function setIrcAllowFrom(cfg: CoreConfig, allowFrom: string[]): CoreConfig {
|
|
return setIrcTopLevelAllowFrom(cfg, allowFrom) as CoreConfig;
|
|
}
|
|
|
|
export function setIrcNickServ(
|
|
cfg: CoreConfig,
|
|
accountId: string,
|
|
nickserv?: IrcNickServConfig,
|
|
): CoreConfig {
|
|
return updateIrcAccountConfig(cfg, accountId, { nickserv });
|
|
}
|
|
|
|
export function setIrcGroupAccess(
|
|
cfg: CoreConfig,
|
|
accountId: string,
|
|
policy: "open" | "allowlist" | "disabled",
|
|
entries: string[],
|
|
normalizeGroupEntry: (raw: string) => string | null,
|
|
): CoreConfig {
|
|
if (policy !== "allowlist") {
|
|
return updateIrcAccountConfig(cfg, accountId, { enabled: true, groupPolicy: policy });
|
|
}
|
|
const normalizedEntries = [
|
|
...new Set(entries.flatMap((entry) => normalizeGroupEntry(entry) ?? [])),
|
|
];
|
|
const groups = Object.fromEntries(normalizedEntries.map((entry) => [entry, {}]));
|
|
return updateIrcAccountConfig(cfg, accountId, {
|
|
enabled: true,
|
|
groupPolicy: "allowlist",
|
|
groups,
|
|
});
|
|
}
|
|
|
|
export const ircSetupAdapter: ChannelSetupAdapter = {
|
|
singleAccountKeysToMove: ["password"],
|
|
resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
|
|
applyAccountName: ({ cfg, accountId, name }) =>
|
|
applyAccountNameToChannelSection({
|
|
cfg,
|
|
channelKey: channel,
|
|
accountId,
|
|
name,
|
|
}),
|
|
validateInput: (params) =>
|
|
validateIrcRequiredSetupInput(params) ?? validateIrcPortInput(params.input),
|
|
applyAccountConfig: ({ cfg, accountId, input }) => {
|
|
const setupInput = input as IrcSetupInput;
|
|
const namedConfig = applyAccountNameToChannelSection({
|
|
cfg,
|
|
channelKey: channel,
|
|
accountId,
|
|
name: setupInput.name,
|
|
});
|
|
const portInput =
|
|
typeof setupInput.port === "number" ? String(setupInput.port) : (setupInput.port ?? "");
|
|
const patch: Partial<IrcAccountConfig> = {
|
|
enabled: true,
|
|
host: setupInput.host?.trim(),
|
|
port: portInput ? parsePort(portInput, setupInput.tls === false ? 6667 : 6697) : undefined,
|
|
tls: setupInput.tls,
|
|
nick: setupInput.nick?.trim(),
|
|
username: setupInput.username?.trim(),
|
|
realname: setupInput.realname?.trim(),
|
|
password: setupInput.password?.trim(),
|
|
channels: setupInput.channels,
|
|
};
|
|
return patchScopedAccountConfig({
|
|
cfg: namedConfig,
|
|
channelKey: channel,
|
|
accountId,
|
|
patch,
|
|
}) as CoreConfig;
|
|
},
|
|
};
|
|
|
|
export const ircSetupContract = defineChannelSetupContract({
|
|
fields: {
|
|
host: { kind: "string", cli: { flags: "--host <host>", description: "IRC server host" } },
|
|
port: { kind: "string", cli: { flags: "--port <port>", description: "IRC server port" } },
|
|
tls: { kind: "boolean", cli: { flags: "--tls", description: "Use TLS for IRC" } },
|
|
nick: { kind: "string", cli: { flags: "--nick <nick>", description: "IRC nickname" } },
|
|
username: { kind: "string", cli: { flags: "--username <name>", description: "IRC username" } },
|
|
realname: { kind: "string", cli: { flags: "--realname <name>", description: "IRC real name" } },
|
|
channels: {
|
|
kind: "string-list",
|
|
cli: { flags: "--channels <names>", description: "IRC channels" },
|
|
},
|
|
password: {
|
|
kind: "string",
|
|
sensitive: true,
|
|
cli: { flags: "--password <password>", description: "IRC server password" },
|
|
},
|
|
useEnv: {
|
|
kind: "boolean",
|
|
cli: { flags: "--use-env", description: "Use IRC environment configuration" },
|
|
},
|
|
},
|
|
legacyAdapter: ircSetupAdapter,
|
|
});
|