吸收数字员工技能远端策略下发

This commit is contained in:
baiyanyun
2026-06-11 09:04:08 +08:00
parent 751ce45619
commit 8ab3d73fb1
18 changed files with 876 additions and 207 deletions

View File

@@ -619,6 +619,12 @@ export function pullPlanStepDispatchPolicy(): Promise<PlanStepDispatchPolicyPull
});
}
export function pullSkillPolicies(): Promise<{ ok: boolean; skipped?: boolean; error?: string | null }> {
return apiFetch<{ ok: boolean; skipped?: boolean; error?: string | null }>('/api/skill/policies/pull', {
method: 'POST',
});
}
export function getPlanSteps(params: Record<string, string | number | null | undefined> = {}): Promise<BusinessViewResponse<PlanStepBusinessRow>> {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {

View File

@@ -78,6 +78,9 @@ export interface SkillSummary {
allowTools?: string[];
denyTools?: string[];
governancePolicy?: 'allow' | 'deny' | 'open' | 'disabled' | null;
policySource?: 'remote' | 'local' | 'open' | null;
remotePolicy?: Record<string, unknown> | null;
localPolicy?: Record<string, unknown> | null;
recentCalls?: Array<{
eventId: string;
message: string;
@@ -496,6 +499,9 @@ export function adaptSkillSummaries(skillsData: Obj[] | null): SkillSummary[] {
allowTools: stringList(s.allow_tools ?? governance?.server_allow_tools ?? governance?.global_allow_tools),
denyTools: stringList(s.deny_tools ?? governance?.server_deny_tools ?? governance?.global_deny_tools),
governancePolicy: governancePolicy(s.governance_policy ?? governance?.effective_policy),
policySource: policySource(s.policy_source),
remotePolicy: obj(s.remote_policy),
localPolicy: obj(s.local_policy),
recentCalls: arr(s.recent_calls).map((call) => {
const c = obj(call) ?? {};
return {
@@ -533,6 +539,12 @@ function governancePolicy(value: unknown): SkillSummary['governancePolicy'] {
: null;
}
function policySource(value: unknown): SkillSummary['policySource'] {
return value === 'remote' || value === 'local' || value === 'open'
? value
: null;
}
export function adaptJournalEntries(storeData: Obj | null): JournalEntry[] {
if (!storeData) return [];
const tables = (storeData.tables ?? {}) as Obj;

View File

@@ -54,6 +54,7 @@ declare global {
getUiState?: () => Promise<AdapterState>;
getSkillCapabilities?: () => Promise<QimingclawSkillCapabilities>;
setSkillEnabled?: (skillId: string, enabled: boolean) => Promise<{ ok?: boolean; skill_id?: string; enabled?: boolean; error?: string }>;
pullSkillPolicies?: () => Promise<{ ok?: boolean; skipped?: boolean; error?: string | null }>;
getPlanTemplates?: () => Promise<QimingclawPlanTemplateList>;
listMemories?: (options?: { query?: string; category?: string; limit?: number }) => Promise<QimingclawMemoryRecord[]>;
addMemory?: (input: QimingclawMemoryUpsertInput) => Promise<QimingclawMemoryRecord | null>;
@@ -975,6 +976,9 @@ export async function handleQimingclawDigitalApi<T>(
if (method === 'GET' && pathname === '/api/skill') {
return { skills: await buildSkills() } as T;
}
if (method === 'POST' && pathname === '/api/skill/policies/pull') {
return await pullBridgeSkillPolicies() as T;
}
if (method === 'POST' && pathname.startsWith('/api/skill/') && pathname.endsWith('/enabled')) {
const skillId = decodeURIComponent(pathname.split('/')[3] || '');
const body = parseJsonBody<{ enabled?: boolean }>(options.body);
@@ -1093,6 +1097,23 @@ async function setBridgeSkillEnabled(
}
}
async function pullBridgeSkillPolicies(): Promise<{ ok: boolean; skipped?: boolean; error?: string | null }> {
const bridge = window.QimingClawBridge?.digital;
if (!bridge?.pullSkillPolicies) {
return { ok: false, error: 'qimingclaw_bridge_unavailable' };
}
try {
const result = await bridge.pullSkillPolicies();
return {
ok: result?.ok !== false,
...(result?.skipped ? { skipped: true } : {}),
...(result?.error ? { error: result.error } : {}),
};
} catch (error) {
return { ok: false, error: error instanceof Error ? error.message : String(error) };
}
}
function dedupeSkills(skills: SkillSpec[]): SkillSpec[] {
const seen = new Set<string>();
const result: SkillSpec[] = [];

View File

@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react';
import GlassCard from '@/components/digital/GlassCard';
import CardTitleBar from '@/components/digital/CardTitleBar';
import { adaptSkillSummaries, type SkillSummary } from '@/lib/consoleDataAdapter';
import { getSkills, setSkillEnabled } from '@/lib/api';
import { getSkills, pullSkillPolicies, setSkillEnabled } from '@/lib/api';
import { domSafeId } from './navigation';
type TaskCadence = 'daily' | 'weekly' | 'monthly' | 'adhoc';
@@ -81,6 +81,8 @@ function skillMetaBadges(skill: SkillSummary): string[] {
const badges: string[] = [];
if (skill.serverId) badges.push(skill.serverId);
if (skill.running !== null && skill.running !== undefined) badges.push(skill.running ? '运行中' : '未运行');
if (skill.policySource === 'remote') badges.push('远端策略');
if (skill.remotePolicy && skill.remotePolicy.enabled === false) badges.push('管理端禁用');
if (skill.governancePolicy) badges.push(policyLabels[skill.governancePolicy]);
if (typeof skill.toolCount === 'number') badges.push(`${skill.toolCount} 工具`);
if (skill.allowTools?.length) badges.push(`allow ${skill.allowTools.length}`);
@@ -174,7 +176,9 @@ export default function SkillLibrary({ focusSkillId }: { focusSkillId?: string |
const [cadenceFilter, setCadenceFilter] = useState<'all' | TaskCadence>('all');
const [selectedSkillId, setSelectedSkillId] = useState<string | null>(focusSkillId ?? null);
const [updatingSkillId, setUpdatingSkillId] = useState<string | null>(null);
const [pullingPolicies, setPullingPolicies] = useState(false);
const [actionError, setActionError] = useState<string | null>(null);
const [actionNotice, setActionNotice] = useState<string | null>(null);
const mountedRef = useRef(false);
useEffect(() => {
@@ -217,6 +221,7 @@ export default function SkillLibrary({ focusSkillId }: { focusSkillId?: string |
const nextEnabled = !skill.enabled;
setUpdatingSkillId(skill.id);
setActionError(null);
setActionNotice(null);
try {
const result = await setSkillEnabled(skill.id, nextEnabled);
if (!result.ok) {
@@ -233,6 +238,22 @@ export default function SkillLibrary({ focusSkillId }: { focusSkillId?: string |
}
}, []);
const pullPolicies = useCallback(async () => {
setPullingPolicies(true);
setActionError(null);
setActionNotice(null);
try {
const result = await pullSkillPolicies();
if (!result.ok) throw new Error(result.error || '技能策略拉取失败');
await fetchSkills();
setActionNotice(result.skipped ? '技能策略无需重复拉取。' : '技能策略已更新。');
} catch (err) {
setActionError(err instanceof Error ? err.message : '技能策略拉取失败');
} finally {
setPullingPolicies(false);
}
}, [fetchSkills]);
const filtered = skills
.filter(s => cadenceFilter === 'all' || getCadence(s) === cadenceFilter)
.filter(s => {
@@ -322,6 +343,9 @@ export default function SkillLibrary({ focusSkillId }: { focusSkillId?: string |
<div style={{ padding: '14px 18px' }}>
<GlassCard>
<CardTitleBar title="学习技能">
<button onClick={pullPolicies} disabled={pullingPolicies} className="de-btn-secondary de-btn-sm">
{pullingPolicies ? '拉取中...' : '拉取策略'}
</button>
<button onClick={fetchSkills} className="de-btn-secondary de-btn-sm"></button>
</CardTitleBar>
<div className="de-card-body">
@@ -330,6 +354,11 @@ export default function SkillLibrary({ focusSkillId }: { focusSkillId?: string |
{actionError}
</div>
)}
{actionNotice && (
<div className="de-schedule-result" style={{ marginBottom: 12 }}>
{actionNotice}
</div>
)}
<div className="de-skill-toolbar">
<input type="text" className="de-input" placeholder="搜索业务能力..." value={search} onChange={e => setSearch(e.target.value)} />
<div className="de-filter-row">