chore: stabilize Zhinian pilot delivery

This commit is contained in:
inman
2026-05-12 19:44:44 +08:00
parent 45389855e1
commit 20b5aff4ad
174 changed files with 41428 additions and 784 deletions

View File

@@ -34,6 +34,17 @@ const LEGACY_MINIMAX_OAUTH_PLUGIN_ID = 'minimax-portal-auth';
const MERGED_MINIMAX_PLUGIN_ID = 'minimax';
const YINIAN_DESKTOP_TOOLS_PROFILE = 'coding';
const YINIAN_INTERNAL_MODEL_REF = 'minimax/MiniMax-M2.7';
const YINIAN_FALLBACK_SKILL_IDS = ['docx', 'pdf', 'pptx', 'xlsx', 'design', 'image-search', 'web-search'];
const YINIAN_SKILLS_LIMITS = {
maxCandidatesPerRoot: 24,
maxSkillsLoadedPerSource: 24,
maxSkillsInPrompt: 24,
maxSkillsPromptChars: 9000,
maxSkillFileBytes: 256 * 1024,
};
const YINIAN_WEB_TOOL_GUARD_ENV = 'YINIAN_ENABLE_OPENCLAW_WEB_TOOLS';
const YINIAN_WEB_TOOL_DENY = ['group:web', 'web_search', 'web_fetch', 'x_search'];
const YINIAN_WEB_FETCH_TIMEOUT_SECONDS = 8;
const YINIAN_CORE_PLUGIN_IDS = new Set([
'minimax',
'cloud-sync',
@@ -1042,6 +1053,72 @@ function isPlainRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function arraysEqual(left: string[], right: string[]): boolean {
return left.length === right.length && left.every((value, index) => value === right[index]);
}
function mergeStringList(existing: unknown, additions: string[]): { value: string[]; modified: boolean } {
const original = Array.isArray(existing)
? existing.filter((value): value is string => typeof value === 'string').map((value) => value.trim()).filter(Boolean)
: [];
const next = [...original];
for (const item of additions) {
if (!next.includes(item)) next.push(item);
}
return {
value: next,
modified: !arraysEqual(original, next) || !Array.isArray(existing),
};
}
function applyYinianWebToolGuard(toolsConfig: Record<string, unknown>): boolean {
if (process.env[YINIAN_WEB_TOOL_GUARD_ENV] === '1') return false;
let modified = false;
const deny = mergeStringList(toolsConfig.deny, YINIAN_WEB_TOOL_DENY);
if (deny.modified) {
toolsConfig.deny = deny.value;
modified = true;
}
const webConfig = isPlainRecord(toolsConfig.web) ? toolsConfig.web : {};
const searchConfig = isPlainRecord(webConfig.search) ? webConfig.search : {};
if (searchConfig.enabled !== false) {
searchConfig.enabled = false;
webConfig.search = searchConfig;
modified = true;
}
const fetchConfig = isPlainRecord(webConfig.fetch) ? webConfig.fetch : {};
if (fetchConfig.enabled !== false || fetchConfig.timeoutSeconds !== YINIAN_WEB_FETCH_TIMEOUT_SECONDS) {
fetchConfig.enabled = false;
fetchConfig.timeoutSeconds = YINIAN_WEB_FETCH_TIMEOUT_SECONDS;
webConfig.fetch = fetchConfig;
modified = true;
}
if (!isPlainRecord(toolsConfig.web) || modified) {
toolsConfig.web = webConfig;
}
return modified;
}
function resolveYinianEnabledSkillIds(config: Record<string, unknown>): string[] {
const skills = isPlainRecord(config.skills) ? config.skills : {};
const entries = isPlainRecord(skills.entries) ? skills.entries : {};
const enabled = Object.entries(entries)
.filter(([, value]) => !isPlainRecord(value) || value.enabled !== false)
.map(([id]) => id.trim())
.filter(Boolean);
const unique = [...new Set(enabled)];
const ordered = [
...YINIAN_FALLBACK_SKILL_IDS.filter((id) => unique.includes(id)),
...unique.filter((id) => !YINIAN_FALLBACK_SKILL_IDS.includes(id)).sort((left, right) => left.localeCompare(right)),
];
return ordered.length > 0 ? ordered : [...YINIAN_FALLBACK_SKILL_IDS];
}
function isYinianManagedConfig(config: Record<string, unknown>): boolean {
const models = isPlainRecord(config.models) ? config.models : null;
const providers = models && isPlainRecord(models.providers) ? models.providers : null;
@@ -1919,6 +1996,11 @@ export async function sanitizeOpenClawConfig(): Promise<void> {
console.log('[sanitize] Set tools.exec.security="full" and tools.exec.ask="off" to disable exec approvals for ClawX desktop');
}
if (isYinianManagedConfig(config) && applyYinianWebToolGuard(toolsConfig)) {
toolsModified = true;
console.log(`[sanitize] Disabled unconfigured OpenClaw web tools for Yinian desktop chat stability (set ${YINIAN_WEB_TOOL_GUARD_ENV}=1 to opt in)`);
}
if (toolsModified) {
config.tools = toolsConfig;
modified = true;
@@ -1930,14 +2012,52 @@ export async function sanitizeOpenClawConfig(): Promise<void> {
// OpenClaw 默认每 30 分钟在 main session 跑一次 heartbeat。桌面端不
// 使用这类后台心跳任务,避免它变成普通用户可见的历史会话。
if (isYinianManagedConfig(config)) {
const skillsConfig = isPlainRecord(config.skills) ? config.skills : {};
const currentLimits = isPlainRecord(skillsConfig.limits) ? skillsConfig.limits : {};
let limitsModified = false;
const nextLimits = { ...currentLimits };
for (const [key, value] of Object.entries(YINIAN_SKILLS_LIMITS)) {
if (nextLimits[key] !== value) {
nextLimits[key] = value;
limitsModified = true;
}
}
if (limitsModified) {
skillsConfig.limits = nextLimits;
config.skills = skillsConfig;
modified = true;
console.log('[sanitize] Set Yinian managed skills discovery limits');
}
const agentsConfig = isPlainRecord(config.agents) ? config.agents : {};
const defaults = isPlainRecord(agentsConfig.defaults) ? agentsConfig.defaults : {};
if (!Array.isArray(defaults.skills)) {
defaults.skills = [];
const enabledSkillIds = resolveYinianEnabledSkillIds(config);
const currentDefaultSkills = Array.isArray(defaults.skills)
? defaults.skills.filter((value): value is string => typeof value === 'string')
: [];
if (!arraysEqual(currentDefaultSkills, enabledSkillIds)) {
defaults.skills = enabledSkillIds;
agentsConfig.defaults = defaults;
config.agents = agentsConfig;
modified = true;
console.log('[sanitize] Set agents.defaults.skills=[] for Yinian desktop lightweight chat');
console.log(`[sanitize] Set agents.defaults.skills=[${enabledSkillIds.join(', ')}] for Yinian managed skills`);
}
if (Array.isArray(agentsConfig.list)) {
const nextList = agentsConfig.list.map((entry) => {
if (!isPlainRecord(entry) || entry.id !== 'main') return entry;
const currentSkills = Array.isArray(entry.skills)
? entry.skills.filter((value): value is string => typeof value === 'string')
: [];
return arraysEqual(currentSkills, enabledSkillIds)
? entry
: { ...entry, skills: enabledSkillIds };
});
if (JSON.stringify(nextList) !== JSON.stringify(agentsConfig.list)) {
agentsConfig.list = nextList;
config.agents = agentsConfig;
modified = true;
console.log(`[sanitize] Set main agent skills=[${enabledSkillIds.join(', ')}]`);
}
}
const heartbeat = isPlainRecord(defaults.heartbeat) ? defaults.heartbeat : {};
if (heartbeat.every !== '0m') {