吸收数字员工技能策略开关

This commit is contained in:
baiyanyun
2026-06-09 16:28:18 +08:00
parent 9ad7881378
commit 4d8b8e4ad5
8 changed files with 249 additions and 30 deletions

View File

@@ -41,6 +41,7 @@ declare global {
getRuntimeRecords?: () => Promise<QimingclawRuntimeRecords>;
getUiState?: () => Promise<AdapterState>;
getSkillCapabilities?: () => Promise<QimingclawSkillCapabilities>;
setSkillEnabled?: (skillId: string, enabled: boolean) => Promise<{ ok?: boolean; skill_id?: string; enabled?: boolean; error?: string }>;
getPlanTemplates?: () => Promise<QimingclawPlanTemplateList>;
listMemories?: (options?: { query?: string; category?: string; limit?: number }) => Promise<QimingclawMemoryRecord[]>;
addMemory?: (input: QimingclawMemoryUpsertInput) => Promise<QimingclawMemoryRecord | null>;
@@ -697,7 +698,7 @@ export async function handleQimingclawDigitalApi<T>(
if (method === 'POST' && pathname.startsWith('/api/skill/') && pathname.endsWith('/enabled')) {
const skillId = decodeURIComponent(pathname.split('/')[3] || '');
const body = parseJsonBody<{ enabled?: boolean }>(options.body);
return { ok: true, skill_id: skillId, enabled: body.enabled ?? true } as T;
return await setBridgeSkillEnabled(skillId, body.enabled ?? true) as T;
}
return undefined;
@@ -768,7 +769,34 @@ async function readBridgeSkillCapabilities(): Promise<SkillSpec[]> {
}
async function buildSkills(): Promise<SkillSpec[]> {
return dedupeSkills([...SKILLS, ...await readBridgeSkillCapabilities()]);
const bridgeSkills = await readBridgeSkillCapabilities();
return dedupeSkills([...bridgeSkills, ...SKILLS]);
}
async function setBridgeSkillEnabled(
skillId: string,
enabled: boolean,
): Promise<{ ok: boolean; skill_id: string; enabled: boolean; error?: string }> {
const bridge = window.QimingClawBridge?.digital;
if (!bridge?.setSkillEnabled) {
return { ok: false, skill_id: skillId, enabled, error: 'qimingclaw_bridge_unavailable' };
}
try {
const result = await bridge.setSkillEnabled(skillId, enabled);
return {
ok: result?.ok !== false,
skill_id: result?.skill_id ?? skillId,
enabled: result?.enabled ?? enabled,
...(result?.error ? { error: result.error } : {}),
};
} catch (error) {
return {
ok: false,
skill_id: skillId,
enabled,
error: error instanceof Error ? error.message : String(error),
};
}
}
function dedupeSkills(skills: SkillSpec[]): SkillSpec[] {
@@ -2808,12 +2836,16 @@ async function handleIngressRoute(
const idSource = body.idempotency_key || correlationId;
const contextRefs = buildIngressContextRefs(body);
const classification = classifyIngressRoute(body, contextRefs, snapshot);
const filteredCandidates = await filterRouteCandidateSkills(classification.candidateSkills);
const reasonCodes = filteredCandidates.allCandidatesDisabled
? [...classification.reasonCodes, 'candidate_skills_disabled']
: classification.reasonCodes;
const decision: RouteDecisionRecord = {
id: `route-decision-${slugifyIdPart(idSource)}`,
route_kind: classification.routeKind,
intent_kind: classification.intentKind,
reason_codes: classification.reasonCodes,
candidate_skills: classification.candidateSkills,
reason_codes: reasonCodes,
candidate_skills: filteredCandidates.candidateSkills,
context_refs: contextRefs,
created_command_id: null,
correlation_id: correlationId,
@@ -2950,12 +2982,43 @@ function routeCandidateSkills(routeKind: string, contextRefs: Record<string, unk
stringValue(payload.skill_id),
]);
if (explicit.length > 0) return explicit;
if (routeKind === 'PlanControl') return ['qimingclaw-task-agent'];
if (routeKind === 'ProjectionQuery') return ['qimingclaw-artifact-report'];
if (routeKind === 'PlanControl') return ['qimingclaw-acp-session'];
if (routeKind === 'ProjectionQuery') return ['qimingclaw-artifact-reporting'];
if (routeKind === 'PlanExecution') return ['qimingclaw-computer-control'];
return [];
}
async function filterRouteCandidateSkills(
candidateSkills: string[],
): Promise<{ candidateSkills: string[]; allCandidatesDisabled: boolean }> {
if (candidateSkills.length === 0) {
return { candidateSkills, allCandidatesDisabled: false };
}
const bridge = window.QimingClawBridge?.digital;
if (!bridge?.getSkillCapabilities) {
return { candidateSkills, allCandidatesDisabled: false };
}
try {
const result = await bridge.getSkillCapabilities();
if (!Array.isArray(result?.skills)) {
return { candidateSkills, allCandidatesDisabled: false };
}
const enabledBySkillId = new Map<string, boolean>();
for (const skill of result.skills) {
const skillId = stringValue(skill.skill_id);
if (skillId) enabledBySkillId.set(skillId, skill.enabled !== false);
}
const enabledCandidates = candidateSkills.filter((skillId) => enabledBySkillId.get(skillId) !== false);
return {
candidateSkills: enabledCandidates,
allCandidatesDisabled: candidateSkills.length > 0 && enabledCandidates.length === 0,
};
} catch (error) {
console.warn('[qimingclawAdapter] filter route candidate skills failed:', error);
return { candidateSkills, allCandidatesDisabled: false };
}
}
function uniqueStrings(values: unknown[]): string[] {
return [...new Set(values.map(stringValue).filter(Boolean))];
}