mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:01:01 +00:00
perf(channels): add lightweight doctor contract APIs
This commit is contained in:
5
extensions/imessage/doctor-contract-api.ts
Normal file
5
extensions/imessage/doctor-contract-api.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { ChannelDoctorLegacyConfigRule } from "openclaw/plugin-sdk/channel-contract";
|
||||
|
||||
// iMessage does not expose doctor legacy rules today. Keep that empty answer on
|
||||
// a lightweight contract surface so doctor scans stay off the full plugin path.
|
||||
export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [];
|
||||
@@ -1,2 +1,2 @@
|
||||
export { collectZalouserSecurityAuditFindings } from "./src/security-audit.js";
|
||||
export { legacyConfigRules, normalizeCompatibilityConfig } from "./src/doctor.js";
|
||||
export { legacyConfigRules, normalizeCompatibilityConfig } from "./src/doctor-contract.js";
|
||||
|
||||
1
extensions/zalouser/doctor-contract-api.ts
Normal file
1
extensions/zalouser/doctor-contract-api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { normalizeCompatibilityConfig, legacyConfigRules } from "./src/doctor-contract.js";
|
||||
156
extensions/zalouser/src/doctor-contract.ts
Normal file
156
extensions/zalouser/src/doctor-contract.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import type {
|
||||
ChannelDoctorConfigMutation,
|
||||
ChannelDoctorLegacyConfigRule,
|
||||
} from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
|
||||
type ZalouserChannelsConfig = NonNullable<OpenClawConfig["channels"]>;
|
||||
|
||||
function asObjectRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function hasLegacyZalouserGroupAllowAlias(value: unknown): boolean {
|
||||
const group = asObjectRecord(value);
|
||||
return Boolean(group && typeof group.allow === "boolean");
|
||||
}
|
||||
|
||||
function hasLegacyZalouserGroupAllowAliases(value: unknown): boolean {
|
||||
const groups = asObjectRecord(value);
|
||||
return Boolean(
|
||||
groups && Object.values(groups).some((group) => hasLegacyZalouserGroupAllowAlias(group)),
|
||||
);
|
||||
}
|
||||
|
||||
function hasLegacyZalouserAccountGroupAllowAliases(value: unknown): boolean {
|
||||
const accounts = asObjectRecord(value);
|
||||
if (!accounts) {
|
||||
return false;
|
||||
}
|
||||
return Object.values(accounts).some((account) => {
|
||||
const accountRecord = asObjectRecord(account);
|
||||
return Boolean(accountRecord && hasLegacyZalouserGroupAllowAliases(accountRecord.groups));
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeZalouserGroupAllowAliases(params: {
|
||||
groups: Record<string, unknown>;
|
||||
pathPrefix: string;
|
||||
changes: string[];
|
||||
}): { groups: Record<string, unknown>; changed: boolean } {
|
||||
let changed = false;
|
||||
const nextGroups: Record<string, unknown> = { ...params.groups };
|
||||
for (const [groupId, groupValue] of Object.entries(params.groups)) {
|
||||
const group = asObjectRecord(groupValue);
|
||||
if (!group || typeof group.allow !== "boolean") {
|
||||
continue;
|
||||
}
|
||||
const nextGroup = { ...group };
|
||||
if (typeof nextGroup.enabled !== "boolean") {
|
||||
nextGroup.enabled = group.allow;
|
||||
}
|
||||
delete nextGroup.allow;
|
||||
nextGroups[groupId] = nextGroup;
|
||||
changed = true;
|
||||
params.changes.push(
|
||||
`Moved ${params.pathPrefix}.${groupId}.allow → ${params.pathPrefix}.${groupId}.enabled (${String(nextGroup.enabled)}).`,
|
||||
);
|
||||
}
|
||||
return { groups: nextGroups, changed };
|
||||
}
|
||||
|
||||
function normalizeZalouserCompatibilityConfig(cfg: OpenClawConfig): ChannelDoctorConfigMutation {
|
||||
const channels = asObjectRecord(cfg.channels);
|
||||
const zalouser = asObjectRecord(channels?.zalouser);
|
||||
if (!zalouser) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
const changes: string[] = [];
|
||||
let updatedZalouser: Record<string, unknown> = zalouser;
|
||||
let changed = false;
|
||||
|
||||
const groups = asObjectRecord(updatedZalouser.groups);
|
||||
if (groups) {
|
||||
const normalized = normalizeZalouserGroupAllowAliases({
|
||||
groups,
|
||||
pathPrefix: "channels.zalouser.groups",
|
||||
changes,
|
||||
});
|
||||
if (normalized.changed) {
|
||||
updatedZalouser = { ...updatedZalouser, groups: normalized.groups };
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const accounts = asObjectRecord(updatedZalouser.accounts);
|
||||
if (accounts) {
|
||||
let accountsChanged = false;
|
||||
const nextAccounts: Record<string, unknown> = { ...accounts };
|
||||
for (const [accountId, accountValue] of Object.entries(accounts)) {
|
||||
const account = asObjectRecord(accountValue);
|
||||
if (!account) {
|
||||
continue;
|
||||
}
|
||||
const accountGroups = asObjectRecord(account.groups);
|
||||
if (!accountGroups) {
|
||||
continue;
|
||||
}
|
||||
const normalized = normalizeZalouserGroupAllowAliases({
|
||||
groups: accountGroups,
|
||||
pathPrefix: `channels.zalouser.accounts.${accountId}.groups`,
|
||||
changes,
|
||||
});
|
||||
if (!normalized.changed) {
|
||||
continue;
|
||||
}
|
||||
nextAccounts[accountId] = {
|
||||
...account,
|
||||
groups: normalized.groups,
|
||||
};
|
||||
accountsChanged = true;
|
||||
}
|
||||
if (accountsChanged) {
|
||||
updatedZalouser = { ...updatedZalouser, accounts: nextAccounts };
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
config: {
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
zalouser: updatedZalouser as ZalouserChannelsConfig["zalouser"],
|
||||
},
|
||||
},
|
||||
changes,
|
||||
};
|
||||
}
|
||||
|
||||
export const legacyConfigRules: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "zalouser", "groups"],
|
||||
message:
|
||||
'channels.zalouser.groups.<id>.allow is legacy; use channels.zalouser.groups.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyZalouserGroupAllowAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "zalouser", "accounts"],
|
||||
message:
|
||||
'channels.zalouser.accounts.<id>.groups.<id>.allow is legacy; use channels.zalouser.accounts.<id>.groups.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyZalouserAccountGroupAllowAliases,
|
||||
},
|
||||
];
|
||||
|
||||
export function normalizeCompatibilityConfig(params: {
|
||||
cfg: OpenClawConfig;
|
||||
}): ChannelDoctorConfigMutation {
|
||||
return normalizeZalouserCompatibilityConfig(params.cfg);
|
||||
}
|
||||
@@ -1,165 +1,14 @@
|
||||
import type {
|
||||
ChannelDoctorAdapter,
|
||||
ChannelDoctorConfigMutation,
|
||||
ChannelDoctorLegacyConfigRule,
|
||||
} from "openclaw/plugin-sdk/channel-contract";
|
||||
import type { ChannelDoctorAdapter } from "openclaw/plugin-sdk/channel-contract";
|
||||
import { createDangerousNameMatchingMutableAllowlistWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { legacyConfigRules, normalizeCompatibilityConfig } from "./doctor-contract.js";
|
||||
import { isZalouserMutableGroupEntry } from "./security-audit.js";
|
||||
|
||||
type ZalouserChannelsConfig = NonNullable<OpenClawConfig["channels"]>;
|
||||
|
||||
function asObjectRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
function hasLegacyZalouserGroupAllowAlias(value: unknown): boolean {
|
||||
const group = asObjectRecord(value);
|
||||
return Boolean(group && typeof group.allow === "boolean");
|
||||
}
|
||||
|
||||
function hasLegacyZalouserGroupAllowAliases(value: unknown): boolean {
|
||||
const groups = asObjectRecord(value);
|
||||
return Boolean(
|
||||
groups && Object.values(groups).some((group) => hasLegacyZalouserGroupAllowAlias(group)),
|
||||
);
|
||||
}
|
||||
|
||||
function hasLegacyZalouserAccountGroupAllowAliases(value: unknown): boolean {
|
||||
const accounts = asObjectRecord(value);
|
||||
if (!accounts) {
|
||||
return false;
|
||||
}
|
||||
return Object.values(accounts).some((account) => {
|
||||
const accountRecord = asObjectRecord(account);
|
||||
return Boolean(accountRecord && hasLegacyZalouserGroupAllowAliases(accountRecord.groups));
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeZalouserGroupAllowAliases(params: {
|
||||
groups: Record<string, unknown>;
|
||||
pathPrefix: string;
|
||||
changes: string[];
|
||||
}): { groups: Record<string, unknown>; changed: boolean } {
|
||||
let changed = false;
|
||||
const nextGroups: Record<string, unknown> = { ...params.groups };
|
||||
for (const [groupId, groupValue] of Object.entries(params.groups)) {
|
||||
const group = asObjectRecord(groupValue);
|
||||
if (!group || typeof group.allow !== "boolean") {
|
||||
continue;
|
||||
}
|
||||
const nextGroup = { ...group };
|
||||
if (typeof nextGroup.enabled !== "boolean") {
|
||||
nextGroup.enabled = group.allow;
|
||||
}
|
||||
delete nextGroup.allow;
|
||||
nextGroups[groupId] = nextGroup;
|
||||
changed = true;
|
||||
params.changes.push(
|
||||
`Moved ${params.pathPrefix}.${groupId}.allow → ${params.pathPrefix}.${groupId}.enabled (${String(nextGroup.enabled)}).`,
|
||||
);
|
||||
}
|
||||
return { groups: nextGroups, changed };
|
||||
}
|
||||
|
||||
function normalizeZalouserCompatibilityConfig(cfg: OpenClawConfig): ChannelDoctorConfigMutation {
|
||||
const channels = asObjectRecord(cfg.channels);
|
||||
const zalouser = asObjectRecord(channels?.zalouser);
|
||||
if (!zalouser) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
const changes: string[] = [];
|
||||
let updatedZalouser: Record<string, unknown> = zalouser;
|
||||
let changed = false;
|
||||
|
||||
const groups = asObjectRecord(updatedZalouser.groups);
|
||||
if (groups) {
|
||||
const normalized = normalizeZalouserGroupAllowAliases({
|
||||
groups,
|
||||
pathPrefix: "channels.zalouser.groups",
|
||||
changes,
|
||||
});
|
||||
if (normalized.changed) {
|
||||
updatedZalouser = { ...updatedZalouser, groups: normalized.groups };
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
const accounts = asObjectRecord(updatedZalouser.accounts);
|
||||
if (accounts) {
|
||||
let accountsChanged = false;
|
||||
const nextAccounts: Record<string, unknown> = { ...accounts };
|
||||
for (const [accountId, accountValue] of Object.entries(accounts)) {
|
||||
const account = asObjectRecord(accountValue);
|
||||
if (!account) {
|
||||
continue;
|
||||
}
|
||||
const accountGroups = asObjectRecord(account.groups);
|
||||
if (!accountGroups) {
|
||||
continue;
|
||||
}
|
||||
const normalized = normalizeZalouserGroupAllowAliases({
|
||||
groups: accountGroups,
|
||||
pathPrefix: `channels.zalouser.accounts.${accountId}.groups`,
|
||||
changes,
|
||||
});
|
||||
if (!normalized.changed) {
|
||||
continue;
|
||||
}
|
||||
nextAccounts[accountId] = {
|
||||
...account,
|
||||
groups: normalized.groups,
|
||||
};
|
||||
accountsChanged = true;
|
||||
}
|
||||
if (accountsChanged) {
|
||||
updatedZalouser = { ...updatedZalouser, accounts: nextAccounts };
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
config: {
|
||||
...cfg,
|
||||
channels: {
|
||||
...cfg.channels,
|
||||
zalouser: updatedZalouser as ZalouserChannelsConfig["zalouser"],
|
||||
},
|
||||
},
|
||||
changes,
|
||||
};
|
||||
}
|
||||
|
||||
const ZALOUSER_LEGACY_CONFIG_RULES: ChannelDoctorLegacyConfigRule[] = [
|
||||
{
|
||||
path: ["channels", "zalouser", "groups"],
|
||||
message:
|
||||
'channels.zalouser.groups.<id>.allow is legacy; use channels.zalouser.groups.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyZalouserGroupAllowAliases,
|
||||
},
|
||||
{
|
||||
path: ["channels", "zalouser", "accounts"],
|
||||
message:
|
||||
'channels.zalouser.accounts.<id>.groups.<id>.allow is legacy; use channels.zalouser.accounts.<id>.groups.<id>.enabled instead. Run "openclaw doctor --fix".',
|
||||
match: hasLegacyZalouserAccountGroupAllowAliases,
|
||||
},
|
||||
];
|
||||
|
||||
export const legacyConfigRules = ZALOUSER_LEGACY_CONFIG_RULES;
|
||||
|
||||
export function normalizeCompatibilityConfig(params: {
|
||||
cfg: OpenClawConfig;
|
||||
}): ChannelDoctorConfigMutation {
|
||||
return normalizeZalouserCompatibilityConfig(params.cfg);
|
||||
}
|
||||
|
||||
export const collectZalouserMutableAllowlistWarnings =
|
||||
createDangerousNameMatchingMutableAllowlistWarningCollector({
|
||||
channel: "zalouser",
|
||||
|
||||
Reference in New Issue
Block a user