吸收数字员工技能远端策略下发
This commit is contained in:
@@ -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]) => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user