chore: stabilize Zhinian pilot delivery
This commit is contained in:
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user