2e6b0c1818
- 新增 server/skills/ 模块: - schema.mjs:adm_skills / adm_skill_flags / adm_skill_invocations 三表建表 + seed - index.mjs:SkillService(listSkills / getSkill / invokeSkill / setSkillEnabled / setSkillFlag) - flag-resolver.mjs:优先级链 user > plan > caller_type > global - goosed-client.mjs:HTTPS + 自签名证书 + round-robin 双节点支持 - executors/goosed.mjs:一次性 goosed session,SSE 收集结果 - design-catalog.mjs:54 个 awesome-design-md DESIGN.md 本地 bundle - routes.mjs:/api/skills/* (public) + /admin-api/skills/* (admin) - 设计 DESIGN.md 无需 auth 即可访问(GET /api/skills/designs) - Admin 可一键 PATCH /admin-api/skills/:slug/enabled 开关任意 skill - GOOSED_URLS 环境变量支持本地单节点/103双节点兼容 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
2.1 KiB
JavaScript
67 lines
2.1 KiB
JavaScript
/**
|
|
* SkillFlagResolver — decides whether a skill invocation is permitted.
|
|
*
|
|
* Priority chain (highest → lowest):
|
|
* 1. skill_flags WHERE scope_type='user' AND scope_id=userId
|
|
* 2. skill_flags WHERE scope_type='plan' AND scope_id=planName
|
|
* 3. skill_flags WHERE scope_type='caller_type' AND scope_id=callerType
|
|
* 4. skills.enabled (global on/off)
|
|
*
|
|
* Returns { allowed: boolean, reason?: string }
|
|
*/
|
|
|
|
export async function resolveSkillFlag(pool, skillId, { userId, planName, callerType } = {}) {
|
|
// Fetch skill base enabled + any flag overrides in one query
|
|
const [rows] = await pool.query(
|
|
`SELECT scope_type, scope_id, enabled
|
|
FROM adm_skill_flags
|
|
WHERE skill_id = ?
|
|
ORDER BY FIELD(scope_type, 'user', 'plan', 'caller_type', 'global')`,
|
|
[skillId],
|
|
);
|
|
|
|
// Build override map: "user:alice" → true/false
|
|
const overrides = new Map(rows.map((r) => [`${r.scope_type}:${r.scope_id ?? ''}`, r.enabled === 1]));
|
|
|
|
// Walk priority chain
|
|
if (userId !== undefined) {
|
|
const key = `user:${userId}`;
|
|
if (overrides.has(key)) {
|
|
const enabled = overrides.get(key);
|
|
return enabled
|
|
? { allowed: true }
|
|
: { allowed: false, reason: 'skill_disabled_for_user' };
|
|
}
|
|
}
|
|
|
|
if (planName) {
|
|
const key = `plan:${planName}`;
|
|
if (overrides.has(key)) {
|
|
const enabled = overrides.get(key);
|
|
return enabled
|
|
? { allowed: true }
|
|
: { allowed: false, reason: 'skill_disabled_for_plan' };
|
|
}
|
|
}
|
|
|
|
if (callerType) {
|
|
const key = `caller_type:${callerType}`;
|
|
if (overrides.has(key)) {
|
|
const enabled = overrides.get(key);
|
|
return enabled
|
|
? { allowed: true }
|
|
: { allowed: false, reason: 'skill_disabled_for_caller_type' };
|
|
}
|
|
}
|
|
|
|
// Fall through to global flag (already fetched with the skill row)
|
|
const globalKey = 'global:';
|
|
if (overrides.has(globalKey)) {
|
|
const enabled = overrides.get(globalKey);
|
|
return enabled ? { allowed: true } : { allowed: false, reason: 'skill_disabled_globally' };
|
|
}
|
|
|
|
// No override rows; rely on skills.enabled (fetched by caller)
|
|
return { allowed: true };
|
|
}
|