吸收数字员工技能策略开关
This commit is contained in:
@@ -102,6 +102,22 @@ const mockAgent = vi.hoisted(() => ({
|
||||
],
|
||||
}));
|
||||
|
||||
const mockSettings = vi.hoisted(() => ({
|
||||
values: new Map<string, unknown>(),
|
||||
}));
|
||||
|
||||
vi.mock("../../db", () => ({
|
||||
readSetting: (key: string) => mockSettings.values.get(key) ?? null,
|
||||
writeSetting: (key: string, value: unknown) => {
|
||||
if (value === null || value === undefined) {
|
||||
mockSettings.values.delete(key);
|
||||
} else {
|
||||
mockSettings.values.set(key, value);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../packages/mcp", () => ({
|
||||
mcpProxyManager: {
|
||||
getStatus: () => mockMcp.status,
|
||||
@@ -140,6 +156,7 @@ vi.mock("../startupPorts", () => ({
|
||||
describe("digital employee skill catalog service", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
mockSettings.values = new Map<string, unknown>();
|
||||
});
|
||||
|
||||
it("projects MCP servers and tools with governance and recent calls", async () => {
|
||||
@@ -263,4 +280,35 @@ describe("digital employee skill catalog service", () => {
|
||||
}),
|
||||
]));
|
||||
});
|
||||
|
||||
it("persists local skill enabled policy and applies it to catalog governance", async () => {
|
||||
const {
|
||||
listDigitalEmployeeSkillCapabilities,
|
||||
readDigitalEmployeeSkillPolicies,
|
||||
setDigitalEmployeeSkillEnabled,
|
||||
} = await import("./skillCatalogService");
|
||||
|
||||
const update = setDigitalEmployeeSkillEnabled("qimingclaw-computer-control", false);
|
||||
|
||||
expect(update).toEqual(expect.objectContaining({
|
||||
ok: true,
|
||||
skill_id: "qimingclaw-computer-control",
|
||||
enabled: false,
|
||||
}));
|
||||
expect(readDigitalEmployeeSkillPolicies().skills["qimingclaw-computer-control"]).toEqual(expect.objectContaining({
|
||||
enabled: false,
|
||||
updatedAt: expect.any(String),
|
||||
}));
|
||||
|
||||
const result = listDigitalEmployeeSkillCapabilities();
|
||||
expect(result.skills).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
skill_id: "qimingclaw-computer-control",
|
||||
enabled: false,
|
||||
governance: expect.objectContaining({
|
||||
effective_policy: "disabled",
|
||||
}),
|
||||
}),
|
||||
]));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getGuiAgentServerStatus } from "../packages/guiAgentServer";
|
||||
import { getWindowsMcpStatus } from "../packages/windowsMcp";
|
||||
import { isWindows } from "../system/shellEnv";
|
||||
import { getConfiguredPorts } from "../startupPorts";
|
||||
import { readSetting, writeSetting } from "../../db";
|
||||
import { readDigitalEmployeeRuntimeRecords } from "./stateService";
|
||||
|
||||
export interface DigitalEmployeeSkillRecentCall {
|
||||
@@ -84,6 +85,25 @@ export interface DigitalEmployeeSkillCatalogOptions {
|
||||
guiServerStatus?: DigitalEmployeeLocalServiceStatus | null;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeSkillPolicyRecord {
|
||||
enabled: boolean;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeSkillPolicies {
|
||||
version: 1;
|
||||
skills: Record<string, DigitalEmployeeSkillPolicyRecord>;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeSkillPolicyUpdateResult {
|
||||
ok: boolean;
|
||||
skill_id: string;
|
||||
enabled: boolean;
|
||||
policies: DigitalEmployeeSkillPolicies;
|
||||
}
|
||||
|
||||
const SKILL_POLICIES_SETTING_KEY = "digital_employee_skill_policies_v1";
|
||||
|
||||
const ACP_SKILLS: AcpSkillDefinition[] = [
|
||||
{
|
||||
skill_id: "qimingclaw-acp-session",
|
||||
@@ -168,6 +188,7 @@ export function listDigitalEmployeeSkillCapabilities(
|
||||
generatedAt: string;
|
||||
skills: DigitalEmployeeSkillCapability[];
|
||||
} {
|
||||
const policies = readDigitalEmployeeSkillPolicies();
|
||||
const status = mcpProxyManager.getStatus();
|
||||
const config = mcpProxyManager.getConfig();
|
||||
const runtime = readDigitalEmployeeRuntimeRecords(100);
|
||||
@@ -259,10 +280,86 @@ export function listDigitalEmployeeSkillCapabilities(
|
||||
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
skills,
|
||||
skills: applySkillPolicies(skills, policies),
|
||||
};
|
||||
}
|
||||
|
||||
export function readDigitalEmployeeSkillPolicies(): DigitalEmployeeSkillPolicies {
|
||||
return normalizeSkillPolicies(readSetting(SKILL_POLICIES_SETTING_KEY));
|
||||
}
|
||||
|
||||
export function setDigitalEmployeeSkillEnabled(
|
||||
skillId: string,
|
||||
enabled: boolean,
|
||||
): DigitalEmployeeSkillPolicyUpdateResult {
|
||||
const normalizedSkillId = normalizeSkillId(skillId);
|
||||
if (!normalizedSkillId) {
|
||||
throw new Error("skill_id is required");
|
||||
}
|
||||
const current = readDigitalEmployeeSkillPolicies();
|
||||
const next: DigitalEmployeeSkillPolicies = {
|
||||
version: 1,
|
||||
skills: {
|
||||
...current.skills,
|
||||
[normalizedSkillId]: {
|
||||
enabled: enabled === true,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
};
|
||||
writeSetting(SKILL_POLICIES_SETTING_KEY, next);
|
||||
return {
|
||||
ok: true,
|
||||
skill_id: normalizedSkillId,
|
||||
enabled: enabled === true,
|
||||
policies: next,
|
||||
};
|
||||
}
|
||||
|
||||
function applySkillPolicies(
|
||||
skills: DigitalEmployeeSkillCapability[],
|
||||
policies: DigitalEmployeeSkillPolicies,
|
||||
): DigitalEmployeeSkillCapability[] {
|
||||
return skills.map((skill) => {
|
||||
const policy = policies.skills[skill.skill_id];
|
||||
if (!policy) return skill;
|
||||
const enabled = skill.enabled && policy.enabled !== false;
|
||||
return {
|
||||
...skill,
|
||||
enabled,
|
||||
governance: {
|
||||
...skill.governance,
|
||||
effective_policy: enabled ? skill.governance.effective_policy : "disabled",
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeSkillPolicies(value: unknown): DigitalEmployeeSkillPolicies {
|
||||
const record = objectRecord(value);
|
||||
const rawSkills = objectRecord(record?.skills) ?? record;
|
||||
const skills: Record<string, DigitalEmployeeSkillPolicyRecord> = {};
|
||||
for (const [rawSkillId, rawPolicy] of Object.entries(rawSkills ?? {})) {
|
||||
const skillId = normalizeSkillId(rawSkillId);
|
||||
if (!skillId) continue;
|
||||
if (typeof rawPolicy === "boolean") {
|
||||
skills[skillId] = { enabled: rawPolicy, updatedAt: "" };
|
||||
continue;
|
||||
}
|
||||
const policy = objectRecord(rawPolicy);
|
||||
if (!policy || typeof policy.enabled !== "boolean") continue;
|
||||
skills[skillId] = {
|
||||
enabled: policy.enabled,
|
||||
updatedAt: stringValue(policy.updatedAt ?? policy.updated_at) || "",
|
||||
};
|
||||
}
|
||||
return { version: 1, skills };
|
||||
}
|
||||
|
||||
function normalizeSkillId(value: unknown): string {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
function buildGuiAgentSkills(
|
||||
events: DigitalEmployeeRuntimeEventLike[],
|
||||
inputStatus?: DigitalEmployeeLocalServiceStatus | null,
|
||||
|
||||
Reference in New Issue
Block a user