吸收数字员工管理端记忆技能审计预览
This commit is contained in:
@@ -45,6 +45,16 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
|
||||
private String routeDecisionId;
|
||||
@JsonProperty("service_id")
|
||||
private String serviceId;
|
||||
@JsonProperty("memory_id")
|
||||
private String memoryId;
|
||||
@JsonProperty("memory_key")
|
||||
private String memoryKey;
|
||||
@JsonProperty("skill_id")
|
||||
private String skillId;
|
||||
@JsonProperty("tool_name")
|
||||
private String toolName;
|
||||
@JsonProperty("server_id")
|
||||
private String serverId;
|
||||
@JsonProperty("occurred_at")
|
||||
private String occurredAt;
|
||||
@JsonProperty("synced_at")
|
||||
|
||||
@@ -70,6 +70,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
"notification:mark_read", "notification:dismiss",
|
||||
"artifact:mark_keep", "artifact:mark_expire", "artifact:mark_reviewed",
|
||||
"daily_report:mark_delivered", "daily_report:mark_confirmed", "daily_report:request_approval",
|
||||
"memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed",
|
||||
"skill:mark_reviewed", "skill:dismiss",
|
||||
"audit:mark_reviewed", "audit:dismiss",
|
||||
"event:mark_reviewed", "event:dismiss"
|
||||
);
|
||||
@@ -980,6 +982,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
.reportId(firstString(businessView.get("report_id"), payload.get("report_id")))
|
||||
.routeDecisionId(firstString(businessView.get("route_decision_id"), payload.get("route_decision_id")))
|
||||
.serviceId(firstString(businessView.get("service_id"), businessView.get("serviceId"), payload.get("service_id"), payload.get("serviceId")))
|
||||
.memoryId(firstString(businessView.get("memory_id"), businessView.get("memoryId"), payload.get("memory_id"), payload.get("memoryId"), "memory".equals(record.getEntityType()) ? record.getEntityId() : null))
|
||||
.memoryKey(firstString(businessView.get("memory_key"), businessView.get("memoryKey"), businessView.get("key"), payload.get("memory_key"), payload.get("memoryKey"), payload.get("key")))
|
||||
.skillId(firstString(businessView.get("skill_id"), businessView.get("skillId"), payload.get("skill_id"), payload.get("skillId")))
|
||||
.toolName(firstString(businessView.get("tool_name"), businessView.get("toolName"), payload.get("tool_name"), payload.get("toolName")))
|
||||
.serverId(firstString(businessView.get("server_id"), businessView.get("serverId"), payload.get("server_id"), payload.get("serverId")))
|
||||
.occurredAt(businessRow.getOccurredAt())
|
||||
.syncedAt(record.getSyncedAt())
|
||||
.clientKeyMasked(maskClientKey(record.getClientKey()))
|
||||
@@ -1005,6 +1012,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
case "daily_reports" -> isDailyReportRow(row);
|
||||
case "governance_commands" -> isGovernanceCommandRow(row);
|
||||
case "route_governance" -> isRouteGovernanceRow(row);
|
||||
case "memory_governance" -> isMemoryGovernanceRow(row);
|
||||
case "skill_audits" -> isSkillAuditRow(row);
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
@@ -1087,6 +1096,31 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
|| StringUtils.isNotBlank(row.getRouteDecisionId());
|
||||
}
|
||||
|
||||
private boolean isMemoryGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||
String category = firstString(row.getCategory());
|
||||
String kind = firstString(row.getKind());
|
||||
return StringUtils.equals(row.getEntityType(), "memory")
|
||||
|| StringUtils.equals(category, "memory")
|
||||
|| StringUtils.equals(category, "memory_governance")
|
||||
|| startsWithAny(kind, "memory_")
|
||||
|| StringUtils.isNotBlank(row.getMemoryId())
|
||||
|| StringUtils.isNotBlank(row.getMemoryKey());
|
||||
}
|
||||
|
||||
private boolean isSkillAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||
String category = firstString(row.getCategory());
|
||||
String kind = firstString(row.getKind());
|
||||
return StringUtils.equals(category, "skill")
|
||||
|| StringUtils.equals(category, "skill_call_audit")
|
||||
|| startsWithAny(kind, "skill_call_")
|
||||
|| StringUtils.equals(category, "tool_call_audit")
|
||||
|| StringUtils.equals(category, "sandbox_audit")
|
||||
|| StringUtils.equals(category, "token_cost")
|
||||
|| StringUtils.isNotBlank(row.getSkillId())
|
||||
|| StringUtils.isNotBlank(row.getToolName())
|
||||
|| StringUtils.isNotBlank(row.getServerId());
|
||||
}
|
||||
|
||||
private String adminCategory(DigitalEmployeeBusinessViewRowDto row, Map<String, Object> businessView, Map<String, Object> payload) {
|
||||
if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict";
|
||||
String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind"));
|
||||
@@ -1104,6 +1138,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
}
|
||||
if (startsWithAny(kind, "artifact_")) return "artifact_lifecycle";
|
||||
if (startsWithAny(kind, "daily_report")) return "daily_report";
|
||||
if (startsWithAny(kind, "memory_")) return "memory_governance";
|
||||
if (startsWithAny(kind, "skill_call_")) return "skill_call_audit";
|
||||
if (startsWithAny(kind, "governance_")) return "governance";
|
||||
if (contains(kind, "audit")) return kind;
|
||||
return row.getEntityType();
|
||||
|
||||
@@ -349,6 +349,118 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
assertThat(result.getRecords().get(3).getApprovalId()).isEqualTo("approval-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchMemoryGovernanceReturnsMemoryRowsOnly() {
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
.thenReturn(page(List.of(
|
||||
record("outbox-memory", "memory", "memory-1", 101L, new Date(10_000),
|
||||
Map.of(
|
||||
"entity_type", "memory",
|
||||
"title", "客户偏好记忆",
|
||||
"status", "active",
|
||||
"memory_id", "memory-1",
|
||||
"memory_key", "customer.preference",
|
||||
"governance_status", "active",
|
||||
"merge_candidate_count", 2,
|
||||
"duplicate_memory_ids", List.of("memory-2")
|
||||
),
|
||||
Map.of("id", "memory-1", "key", "customer.preference")),
|
||||
record("outbox-memory-archive", "event", "event-memory-1", 101L, new Date(11_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "memory_governance",
|
||||
"kind", "memory_archived",
|
||||
"status", "recorded",
|
||||
"title", "记忆已归档",
|
||||
"memory_id", "memory-3",
|
||||
"memory_key", "customer.old",
|
||||
"last_governance_action", "archive"
|
||||
),
|
||||
Map.of("id", "event-memory-1", "kind", "memory_archived")),
|
||||
record("outbox-task", "task", "task-1", 101L, new Date(12_000),
|
||||
Map.of("entity_type", "task", "title", "普通任务", "status", "running"),
|
||||
Map.of("id", "task-1", "status", "running")),
|
||||
record("outbox-audit", "event", "event-audit-1", 101L, new Date(13_000),
|
||||
Map.of("entity_type", "event", "category", "tool_call_audit", "title", "工具审计", "status", "warn"),
|
||||
Map.of("id", "event-audit-1", "status", "warn"))
|
||||
)));
|
||||
|
||||
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
|
||||
"memory_governance", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
|
||||
);
|
||||
|
||||
assertThat(result.getTotal()).isEqualTo(2);
|
||||
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
|
||||
.containsExactly("memory-1", "event-memory-1");
|
||||
assertThat(result.getRecords().get(0).getView()).isEqualTo("memory_governance");
|
||||
assertThat(result.getRecords().get(0).getMemoryId()).isEqualTo("memory-1");
|
||||
assertThat(result.getRecords().get(0).getMemoryKey()).isEqualTo("customer.preference");
|
||||
assertThat(result.getRecords().get(0).getBusinessView()).containsEntry("merge_candidate_count", 2);
|
||||
assertThat(result.getRecords().get(1).getCategory()).isEqualTo("memory_governance");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchSkillAuditsReturnsSkillAndToolAuditRowsOnly() {
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
.thenReturn(page(List.of(
|
||||
record("outbox-skill-allowed", "event", "event-skill-1", 101L, new Date(10_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "skill",
|
||||
"kind", "skill_call_allowed",
|
||||
"status", "allowed",
|
||||
"title", "技能调用允许",
|
||||
"skill_id", "skill-web",
|
||||
"tool_name", "browser.open",
|
||||
"server_id", "mcp-browser",
|
||||
"decision", "allowed",
|
||||
"risk_level", "low"
|
||||
),
|
||||
Map.of("id", "event-skill-1", "kind", "skill_call_allowed")),
|
||||
record("outbox-tool-audit", "event", "event-skill-2", 101L, new Date(11_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "tool_call_audit",
|
||||
"kind", "tool_call_audit",
|
||||
"status", "rejected",
|
||||
"title", "工具调用拒绝",
|
||||
"tool_name", "shell",
|
||||
"server_id", "local",
|
||||
"decision", "rejected"
|
||||
),
|
||||
Map.of("id", "event-skill-2", "kind", "tool_call_audit")),
|
||||
record("outbox-token-cost", "event", "event-skill-3", 101L, new Date(12_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "token_cost",
|
||||
"kind", "token_cost_recorded",
|
||||
"status", "recorded",
|
||||
"title", "Token 成本估算",
|
||||
"model", "gpt-4.1",
|
||||
"total_tokens", 180,
|
||||
"estimated_cost_usd", 0.012
|
||||
),
|
||||
Map.of("id", "event-skill-3", "kind", "token_cost_recorded")),
|
||||
record("outbox-notification", "notification", "notification-1", 101L, new Date(13_000),
|
||||
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
|
||||
Map.of("id", "notification-1", "status", "unread"))
|
||||
)));
|
||||
|
||||
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
|
||||
"skill_audits", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
|
||||
);
|
||||
|
||||
assertThat(result.getTotal()).isEqualTo(3);
|
||||
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
|
||||
.containsExactly("event-skill-1", "event-skill-2", "event-skill-3");
|
||||
assertThat(result.getRecords().get(0).getView()).isEqualTo("skill_audits");
|
||||
assertThat(result.getRecords().get(0).getSkillId()).isEqualTo("skill-web");
|
||||
assertThat(result.getRecords().get(0).getToolName()).isEqualTo("browser.open");
|
||||
assertThat(result.getRecords().get(0).getServerId()).isEqualTo("mcp-browser");
|
||||
assertThat(result.getRecords().get(1).getCategory()).isEqualTo("tool_call_audit");
|
||||
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("total_tokens", 180);
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchServiceOperationsReturnsServiceHealthAndRestartRowsOnly() {
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
|
||||
Reference in New Issue
Block a user