吸收数字员工跨设备策略冲突视图
This commit is contained in:
@@ -1181,6 +1181,66 @@ function checkDigitalAdminActionLoop() {
|
||||
console.log("[DigitalEmployeeCheck] admin action pull/apply loop hooks OK");
|
||||
}
|
||||
|
||||
function checkDigitalAdminPolicySnapshots() {
|
||||
console.log("\n[DigitalEmployeeCheck] Verify admin policy snapshot conflict view hooks");
|
||||
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
|
||||
const syncTestPath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.test.ts");
|
||||
const docsPath = path.resolve(packageRoot, "..", "..", "docs", "digital-employee-frontend-integration-plan.md");
|
||||
const qimingRoot = path.resolve(packageRoot, "..", "..", "..", "qiming");
|
||||
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
|
||||
const qimingOpsPath = path.join(qimingRoot, "src", "pages", "SystemManagement", "Content", "DigitalEmployeeOps", "index.tsx");
|
||||
const qimingServicePath = path.join(qimingRoot, "src", "services", "systemManage.ts");
|
||||
const qimingTypesPath = path.join(qimingRoot, "src", "types", "interfaces", "systemManage.ts");
|
||||
const backendControllerPath = path.join(
|
||||
backendRoot,
|
||||
"app-platform-modules",
|
||||
"app-platform-agent",
|
||||
"app-platform-agent-core-ui",
|
||||
"src",
|
||||
"main",
|
||||
"java",
|
||||
"com",
|
||||
"xspaceagi",
|
||||
"agent",
|
||||
"web",
|
||||
"ui",
|
||||
"controller",
|
||||
"DigitalEmployeeAdminPolicyController.java",
|
||||
);
|
||||
const syncService = fs.readFileSync(syncServicePath, "utf8");
|
||||
const syncTest = fs.readFileSync(syncTestPath, "utf8");
|
||||
const docs = fs.readFileSync(docsPath, "utf8");
|
||||
const qimingOps = fs.readFileSync(qimingOpsPath, "utf8");
|
||||
const qimingService = fs.readFileSync(qimingServicePath, "utf8");
|
||||
const qimingTypes = fs.readFileSync(qimingTypesPath, "utf8");
|
||||
const backendController = fs.readFileSync(backendControllerPath, "utf8");
|
||||
const requiredSnippets = [
|
||||
[syncService, "policySnapshotBusinessView", "client policy snapshot business view"],
|
||||
[syncService, "policy_snapshot", "client policy snapshot category"],
|
||||
[syncService, "policy_kind", "client policy kind field"],
|
||||
[syncService, "auto_create_governance_commands", "route policy conflict field"],
|
||||
[syncService, "max_per_sweep", "dispatch policy conflict field"],
|
||||
[syncService, "catch_up_on_startup", "scheduler policy conflict field"],
|
||||
[syncTest, "event-route-policy-pull", "route policy snapshot sync test"],
|
||||
[syncTest, "event-risk-policy-pull", "risk policy snapshot sync test"],
|
||||
[backendController, "/snapshots", "backend policy snapshot endpoint"],
|
||||
[qimingService, "apiDigitalEmployeeAdminPolicySnapshots", "qiming policy snapshot service"],
|
||||
[qimingService, "/api/digital-employee/admin/policies/snapshots", "qiming policy snapshot endpoint"],
|
||||
[qimingTypes, "DigitalEmployeePolicySnapshotRow", "qiming policy snapshot row type"],
|
||||
[qimingTypes, "policy_conflicts", "qiming policy conflicts view type"],
|
||||
[qimingOps, "策略冲突", "qiming policy conflict tab text"],
|
||||
[qimingOps, "conflict_keys", "qiming conflict key display"],
|
||||
[docs, "跨设备策略冲突视图", "docs policy conflict absorption"],
|
||||
];
|
||||
const missing = requiredSnippets
|
||||
.filter(([content, snippet]) => !content.includes(snippet))
|
||||
.map(([, , label]) => label);
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Missing admin policy snapshot hooks: ${missing.join(", ")}`);
|
||||
}
|
||||
console.log("[DigitalEmployeeCheck] admin policy snapshot conflict view hooks OK");
|
||||
}
|
||||
|
||||
function checkLocalDatabaseSchema() {
|
||||
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
|
||||
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
|
||||
@@ -1240,6 +1300,7 @@ function main() {
|
||||
checkDigitalOpsNotificationReportWorkbenches();
|
||||
checkDigitalPolicySchedulerWorkbenches();
|
||||
checkDigitalAdminActionLoop();
|
||||
checkDigitalAdminPolicySnapshots();
|
||||
checkLocalDatabaseSchema();
|
||||
console.log("\n[DigitalEmployeeCheck] All checks passed");
|
||||
} catch (error) {
|
||||
|
||||
@@ -1655,6 +1655,41 @@ describe("digital employee sync service", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
insertEventEntity("event-risk-policy-pull", "digital_workday_pull_risk_approval_policy", {
|
||||
metadata: {
|
||||
action: "pull_risk_approval_policy",
|
||||
source: "management-api",
|
||||
ok: true,
|
||||
risk_approval_policy: {
|
||||
enabled: true,
|
||||
source: "remote",
|
||||
version: "risk-policy-v2",
|
||||
approval_required_risk_levels: ["high", "critical"],
|
||||
approval_required_actions: ["managed_service.restart.*"],
|
||||
auto_reject_actions: ["service.stop.*"],
|
||||
remote_updated_at: "2026-06-07T08:10:00.000Z",
|
||||
last_pulled_at: "2026-06-07T08:11:00.000Z",
|
||||
},
|
||||
},
|
||||
});
|
||||
insertEventEntity("event-route-policy-pull", "digital_workday_pull_route_decision_policy", {
|
||||
metadata: {
|
||||
action: "pull_route_decision_policy",
|
||||
source: "management-api",
|
||||
ok: true,
|
||||
route_decision_policy: {
|
||||
enabled: true,
|
||||
source: "remote",
|
||||
version: "route-policy-v3",
|
||||
auto_create_governance_commands: false,
|
||||
blocked_intents: ["delete"],
|
||||
approval_required_intents: ["restart_service"],
|
||||
preferred_route_kinds: ["PlanStepDispatch"],
|
||||
remote_updated_at: "2026-06-07T08:12:00.000Z",
|
||||
last_pulled_at: "2026-06-07T08:13:00.000Z",
|
||||
},
|
||||
},
|
||||
});
|
||||
insertEventEntity("event-risk-approval", "risk_approval_required", {
|
||||
action_kind: "managed_service.restart.crm-sync",
|
||||
action_label: "重启 CRM 同步服务",
|
||||
@@ -1701,6 +1736,8 @@ describe("digital employee sync service", () => {
|
||||
{ entity_type: "event", entity_id: "event-approval-subscriptions" },
|
||||
{ entity_type: "event", entity_id: "event-plan-step-policy" },
|
||||
{ entity_type: "event", entity_id: "event-plan-step-policy-pull" },
|
||||
{ entity_type: "event", entity_id: "event-risk-policy-pull" },
|
||||
{ entity_type: "event", entity_id: "event-route-policy-pull" },
|
||||
{ entity_type: "event", entity_id: "event-risk-approval" },
|
||||
],
|
||||
}),
|
||||
@@ -1990,27 +2027,58 @@ describe("digital employee sync service", () => {
|
||||
expect(businessView("event-approval-subscriptions")).not.toHaveProperty("state");
|
||||
expect(businessView("event-approval-subscriptions")).not.toHaveProperty("approvalSubscriptionPreferences");
|
||||
expect(businessView("event-plan-step-policy")).toMatchObject({
|
||||
category: "dispatch",
|
||||
category: "policy_snapshot",
|
||||
policy_kind: "dispatch",
|
||||
policy_key: "dispatch",
|
||||
action: "update_plan_step_dispatch_policy",
|
||||
policy_enabled: false,
|
||||
enabled: false,
|
||||
max_per_sweep: 1,
|
||||
auto_dispatch_ready_steps: false,
|
||||
updated_at: "2026-06-07T08:07:00.000Z",
|
||||
policy_version: "2026-06-07T08:07:00.000Z",
|
||||
});
|
||||
expect(businessView("event-plan-step-policy")).not.toHaveProperty("state");
|
||||
expect(businessView("event-plan-step-policy")).not.toHaveProperty("planStepDispatchPolicy");
|
||||
expect(businessView("event-plan-step-policy-pull")).toMatchObject({
|
||||
category: "dispatch",
|
||||
category: "policy_snapshot",
|
||||
policy_kind: "dispatch",
|
||||
policy_key: "dispatch",
|
||||
action: "pull_plan_step_dispatch_policy",
|
||||
source: "management-api",
|
||||
pull_ok: false,
|
||||
pull_error: "policy service unavailable",
|
||||
policy_enabled: true,
|
||||
enabled: true,
|
||||
max_per_sweep: 2,
|
||||
auto_dispatch_ready_steps: true,
|
||||
remote_updated_at: "2026-06-07T08:08:00.000Z",
|
||||
last_pulled_at: "2026-06-07T08:09:00.000Z",
|
||||
});
|
||||
expect(businessView("event-risk-policy-pull")).toMatchObject({
|
||||
category: "policy_snapshot",
|
||||
policy_kind: "risk_approval",
|
||||
policy_key: "risk_approval",
|
||||
policy_version: "risk-policy-v2",
|
||||
policy_source: "management-api",
|
||||
enabled: true,
|
||||
approval_required_risk_levels: ["high", "critical"],
|
||||
approval_required_actions: ["managed_service.restart.*"],
|
||||
auto_reject_actions: ["service.stop.*"],
|
||||
remote_updated_at: "2026-06-07T08:10:00.000Z",
|
||||
last_pulled_at: "2026-06-07T08:11:00.000Z",
|
||||
});
|
||||
expect(businessView("event-route-policy-pull")).toMatchObject({
|
||||
category: "policy_snapshot",
|
||||
policy_kind: "route_decision",
|
||||
policy_key: "route_decision",
|
||||
policy_version: "route-policy-v3",
|
||||
policy_source: "management-api",
|
||||
enabled: true,
|
||||
auto_create_governance_commands: false,
|
||||
blocked_intents: ["delete"],
|
||||
approval_required_intents: ["restart_service"],
|
||||
preferred_route_kinds: ["PlanStepDispatch"],
|
||||
remote_updated_at: "2026-06-07T08:12:00.000Z",
|
||||
last_pulled_at: "2026-06-07T08:13:00.000Z",
|
||||
});
|
||||
expect(businessView("event-risk-approval")).toMatchObject({
|
||||
category: "approval",
|
||||
action_kind: "managed_service.restart.crm-sync",
|
||||
|
||||
@@ -1979,7 +1979,7 @@ function eventSpecificBusinessView(
|
||||
if (isPlanStepDependencyEvent(kind)) return planStepDependencyBusinessView(payload);
|
||||
if (kind.startsWith("plan_step_dispatch_")) return planStepDispatchBusinessView(payload);
|
||||
if (isPlanStepLeaseEvent(kind)) return planStepLeaseBusinessView(payload);
|
||||
if (isPlanStepDispatchPolicyEvent(kind)) return planStepDispatchPolicyBusinessView(payload);
|
||||
if (isPolicySnapshotEvent(kind)) return policySnapshotBusinessView(kind, payload);
|
||||
if (kind.startsWith("governance_")) return governanceCommandBusinessView(kind, payload);
|
||||
if (kind === "sandbox_audit") return sandboxAuditBusinessView(payload);
|
||||
if (kind === "token_cost") return tokenCostBusinessView(payload);
|
||||
@@ -2268,6 +2268,48 @@ function planStepDispatchPolicyBusinessView(payload: Record<string, unknown>): R
|
||||
};
|
||||
}
|
||||
|
||||
function policySnapshotBusinessView(kind: string, payload: Record<string, unknown>): Record<string, unknown> {
|
||||
const metadata = objectRecord(payload.metadata) ?? {};
|
||||
const state = objectRecord(payload.state) ?? {};
|
||||
const policyKind = kind.includes("risk_approval")
|
||||
? "risk_approval"
|
||||
: kind.includes("route_decision")
|
||||
? "route_decision"
|
||||
: "dispatch";
|
||||
const policy = policyKind === "risk_approval"
|
||||
? objectRecord(metadata.risk_approval_policy) ?? objectRecord(state.riskApprovalPolicy) ?? {}
|
||||
: policyKind === "route_decision"
|
||||
? objectRecord(metadata.route_decision_policy) ?? objectRecord(state.routeDecisionPolicy) ?? {}
|
||||
: objectRecord(metadata.plan_step_dispatch_policy) ?? objectRecord(state.planStepDispatchPolicy) ?? {};
|
||||
return {
|
||||
category: "policy_snapshot",
|
||||
policy_kind: policyKind,
|
||||
policy_key: policyKind,
|
||||
policy_version: stringField(policy.version) || stringField(policy.updated_at ?? policy.updatedAt) || null,
|
||||
policy_source: stringField(metadata.source ?? policy.source) || null,
|
||||
source: stringField(metadata.source ?? policy.source) || null,
|
||||
action: stringField(metadata.action ?? payload.action) || kind.replace(/^digital_workday_/, ""),
|
||||
status: metadata.ok === false ? "pull_failed" : "snapshot",
|
||||
enabled: typeof policy.enabled === "boolean" ? policy.enabled : null,
|
||||
pull_ok: typeof metadata.ok === "boolean" ? metadata.ok : null,
|
||||
pull_error: stringField(metadata.error ?? policy.last_pull_error) || null,
|
||||
pull_endpoint: stringField(metadata.endpoint) || null,
|
||||
max_per_sweep: numberField(policy.max_per_sweep ?? policy.maxPerSweep),
|
||||
auto_dispatch_ready_steps: typeof policy.auto_dispatch_ready_steps === "boolean" ? policy.auto_dispatch_ready_steps : typeof policy.autoDispatchReadySteps === "boolean" ? policy.autoDispatchReadySteps : null,
|
||||
auto_create_governance_commands: typeof policy.auto_create_governance_commands === "boolean" ? policy.auto_create_governance_commands : typeof policy.autoCreateGovernanceCommands === "boolean" ? policy.autoCreateGovernanceCommands : null,
|
||||
catch_up_on_startup: typeof policy.catch_up_on_startup === "boolean" ? policy.catch_up_on_startup : typeof policy.catchUpOnStartup === "boolean" ? policy.catchUpOnStartup : null,
|
||||
approval_required_risk_levels: stringArrayField(policy.approval_required_risk_levels ?? policy.approvalRequiredRiskLevels),
|
||||
approval_required_actions: stringArrayField(policy.approval_required_actions ?? policy.approvalRequiredActions),
|
||||
auto_reject_actions: stringArrayField(policy.auto_reject_actions ?? policy.autoRejectActions),
|
||||
blocked_intents: stringArrayField(policy.blocked_intents ?? policy.blockedIntents),
|
||||
approval_required_intents: stringArrayField(policy.approval_required_intents ?? policy.approvalRequiredIntents),
|
||||
preferred_route_kinds: stringArrayField(policy.preferred_route_kinds ?? policy.preferredRouteKinds),
|
||||
remote_updated_at: stringField(policy.remote_updated_at ?? policy.remoteUpdatedAt) || null,
|
||||
last_pulled_at: stringField(policy.last_pulled_at ?? policy.lastPulledAt) || null,
|
||||
summary: `${policyKind} policy snapshot`,
|
||||
};
|
||||
}
|
||||
|
||||
function governanceCommandBusinessView(kind: string, payload: Record<string, unknown>): Record<string, unknown> {
|
||||
const commandPayload = objectRecord(payload.payload) ?? {};
|
||||
const result = objectRecord(payload.result) ?? {};
|
||||
@@ -2383,7 +2425,7 @@ function eventBusinessCategory(kind: string): string {
|
||||
if (isPlanStepDependencyEvent(kind)) return "dispatch";
|
||||
if (kind.startsWith("plan_step_dispatch_")) return "dispatch";
|
||||
if (isPlanStepLeaseEvent(kind)) return "dispatch";
|
||||
if (isPlanStepDispatchPolicyEvent(kind)) return "dispatch";
|
||||
if (isPolicySnapshotEvent(kind)) return "policy_snapshot";
|
||||
if (isNotificationEvent(kind)) return "notification";
|
||||
if (kind.startsWith("governance_")) return "governance";
|
||||
if (kind === "sandbox_audit") return "sandbox_audit";
|
||||
@@ -2419,6 +2461,14 @@ function isPlanStepDispatchPolicyEvent(kind: string): boolean {
|
||||
|| kind === "digital_workday_pull_plan_step_dispatch_policy";
|
||||
}
|
||||
|
||||
function isPolicySnapshotEvent(kind: string): boolean {
|
||||
return isPlanStepDispatchPolicyEvent(kind)
|
||||
|| kind === "digital_workday_pull_risk_approval_policy"
|
||||
|| kind === "digital_workday_pull_route_decision_policy"
|
||||
|| kind === "digital_workday_update_risk_approval_policy"
|
||||
|| kind === "digital_workday_update_route_decision_policy";
|
||||
}
|
||||
|
||||
function isPlanStepLeaseEvent(kind: string): boolean {
|
||||
return kind === "plan_step_lease_acquired" || kind === "plan_step_lease_released" || kind === "plan_step_lease_skipped";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user