mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-17 16:12:21 -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>
153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
// Signal plugin module implements shared behavior.
|
|
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
import {
|
|
adaptScopedAccountAccessor,
|
|
createScopedChannelConfigAdapter,
|
|
} from "openclaw/plugin-sdk/channel-config-helpers";
|
|
import { createRestrictSendersChannelSecurity } from "openclaw/plugin-sdk/channel-policy";
|
|
import { createChannelPluginBase, getChatChannelMeta } from "openclaw/plugin-sdk/core";
|
|
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
import { normalizeStringifiedEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
import { normalizeE164 } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
import {
|
|
listSignalAccountIds,
|
|
resolveDefaultSignalAccountId,
|
|
resolveSignalAccount,
|
|
type ResolvedSignalAccount,
|
|
} from "./accounts.js";
|
|
import { resolveSignalTarget } from "./aliases.js";
|
|
import { SignalChannelConfigSchema } from "./config-schema.js";
|
|
import { signalDoctor } from "./doctor.js";
|
|
import { createSignalSetupWizardProxy } from "./setup-core.js";
|
|
|
|
const SIGNAL_CHANNEL = "signal" as const;
|
|
|
|
async function loadSignalChannelRuntime() {
|
|
return await import("./channel.runtime.js");
|
|
}
|
|
|
|
export const signalSetupWizard = createSignalSetupWizardProxy(
|
|
async () => (await loadSignalChannelRuntime()).signalSetupWizard,
|
|
);
|
|
|
|
const signalConfigAdapterBase = createScopedChannelConfigAdapter<ResolvedSignalAccount>({
|
|
sectionKey: SIGNAL_CHANNEL,
|
|
listAccountIds: (cfg) => listSignalAccountIds(cfg),
|
|
resolveAccount: adaptScopedAccountAccessor((params) => resolveSignalAccount(params)),
|
|
defaultAccountId: (cfg) => resolveDefaultSignalAccountId(cfg),
|
|
clearBaseFields: ["account", "accountUuid", "transport", "name"],
|
|
resolveAllowFrom: (account: ResolvedSignalAccount) => account.config.allowFrom,
|
|
formatAllowFrom: (allowFrom) =>
|
|
normalizeStringifiedEntries(allowFrom)
|
|
.map((entry) => (entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))))
|
|
.filter(Boolean),
|
|
resolveDefaultTo: (account: ResolvedSignalAccount) => account.config.defaultTo,
|
|
});
|
|
|
|
export const signalConfigAdapter = {
|
|
...signalConfigAdapterBase,
|
|
resolveDefaultTo({
|
|
cfg,
|
|
accountId,
|
|
}: {
|
|
cfg: Parameters<typeof resolveSignalAccount>[0]["cfg"];
|
|
accountId?: string | null;
|
|
}) {
|
|
const raw = resolveSignalAccount({ cfg, accountId }).config.defaultTo;
|
|
if (typeof raw !== "string" || !raw.trim()) {
|
|
return undefined;
|
|
}
|
|
try {
|
|
return resolveSignalTarget({ cfg, accountId, input: raw })?.to ?? raw.trim();
|
|
} catch {
|
|
return raw.trim();
|
|
}
|
|
},
|
|
};
|
|
|
|
export const signalSecurityAdapter = createRestrictSendersChannelSecurity<ResolvedSignalAccount>({
|
|
channelKey: SIGNAL_CHANNEL,
|
|
resolveDmPolicy: (account) => account.config.dmPolicy,
|
|
resolveDmAllowFrom: (account) => account.config.allowFrom,
|
|
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
|
surface: "Signal groups",
|
|
openScope: "any member",
|
|
groupPolicyPath: "channels.signal.groupPolicy",
|
|
groupAllowFromPath: "channels.signal.groupAllowFrom",
|
|
mentionGated: false,
|
|
policyPathSuffix: "dmPolicy",
|
|
normalizeDmEntry: (raw) => normalizeE164(raw.replace(/^signal:/i, "").trim()),
|
|
});
|
|
|
|
export function createSignalPluginBase(params: {
|
|
setupWizard?: NonNullable<ChannelPlugin<ResolvedSignalAccount>["setupWizard"]>;
|
|
setupContract: NonNullable<ChannelPlugin<ResolvedSignalAccount>["setupContract"]>;
|
|
}): Pick<
|
|
ChannelPlugin<ResolvedSignalAccount>,
|
|
| "id"
|
|
| "meta"
|
|
| "setupWizard"
|
|
| "capabilities"
|
|
| "streaming"
|
|
| "reload"
|
|
| "configSchema"
|
|
| "config"
|
|
| "security"
|
|
| "setupContract"
|
|
| "messaging"
|
|
| "doctor"
|
|
> {
|
|
const base = createChannelPluginBase<ResolvedSignalAccount>({
|
|
id: SIGNAL_CHANNEL,
|
|
meta: {
|
|
...getChatChannelMeta(SIGNAL_CHANNEL),
|
|
},
|
|
setupWizard: params.setupWizard,
|
|
capabilities: {
|
|
chatTypes: ["direct", "group"],
|
|
media: true,
|
|
reactions: true,
|
|
},
|
|
streaming: {
|
|
blockStreamingCoalesceDefaults: { minChars: 1500, idleMs: 1000 },
|
|
},
|
|
reload: { configPrefixes: ["channels.signal"] },
|
|
configSchema: SignalChannelConfigSchema,
|
|
doctor: signalDoctor,
|
|
config: {
|
|
...signalConfigAdapter,
|
|
isConfigured: (account) => account.configured,
|
|
describeAccount: (account) =>
|
|
describeAccountSnapshot({
|
|
account,
|
|
configured: account.configured,
|
|
extra: {
|
|
baseUrl: account.baseUrl,
|
|
},
|
|
}),
|
|
},
|
|
security: signalSecurityAdapter,
|
|
setupContract: params.setupContract,
|
|
});
|
|
return {
|
|
...base,
|
|
messaging: {
|
|
defaultMarkdownTableMode: "bullets",
|
|
},
|
|
} as Pick<
|
|
ChannelPlugin<ResolvedSignalAccount>,
|
|
| "id"
|
|
| "meta"
|
|
| "setupWizard"
|
|
| "capabilities"
|
|
| "streaming"
|
|
| "reload"
|
|
| "configSchema"
|
|
| "config"
|
|
| "security"
|
|
| "setupContract"
|
|
| "messaging"
|
|
| "doctor"
|
|
>;
|
|
}
|