diff --git a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/main/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImpl.java b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/main/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImpl.java
index f46f35e4..238b60ab 100644
--- a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/main/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImpl.java
+++ b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/main/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImpl.java
@@ -996,6 +996,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
private boolean matchesAdminView(String view, DigitalEmployeeAdminWorkbenchRowDto row) {
return switch (view) {
case "interventions" -> isInterventionRow(row);
+ case "approval_governance" -> isApprovalGovernanceRow(row);
case "artifact_lifecycle" -> isArtifactLifecycleRow(row);
case "notifications" -> isNotificationRow(row);
case "audits" -> isAuditRow(row);
@@ -1012,6 +1013,24 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|| StringUtils.equals(row.getCategory(), "action_notification");
}
+ private boolean isApprovalGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) {
+ String kind = firstString(row.getKind());
+ String category = firstString(row.getCategory());
+ return StringUtils.equals(category, "approval_conflict")
+ || StringUtils.equals(category, "risk_approval")
+ || contains(kind, "risk_approval")
+ || contains(kind, "approval_action_recorded")
+ || contains(kind, "approval_decision")
+ || contains(kind, "approval_conflict_resolved")
+ || contains(kind, "approval_conflict_resolution")
+ || (StringUtils.isNotBlank(row.getApprovalId()) && (
+ StringUtils.equals(category, "route_intervention")
+ || StringUtils.equals(category, "route_governance")
+ || contains(kind, "route_decision_approval_required")
+ || contains(kind, "route_decision_blocked")
+ ));
+ }
+
private boolean isArtifactLifecycleRow(DigitalEmployeeAdminWorkbenchRowDto row) {
return StringUtils.equals(row.getEntityType(), "artifact")
|| startsWithAny(row.getKind(), "artifact_")
diff --git a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/test/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImplTest.java b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/test/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImplTest.java
index 1885f57a..15f98aef 100644
--- a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/test/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImplTest.java
+++ b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-application/src/test/java/com/xspaceagi/agent/core/application/service/DigitalEmployeeSyncApplicationServiceImplTest.java
@@ -278,6 +278,77 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(1).getRouteDecisionId()).isEqualTo("route-1");
}
+ @Test
+ void queryAdminWorkbenchApprovalGovernanceReturnsApprovalConflictRiskAndHistoryRowsOnly() {
+ 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-approval-conflict", "approval", "approval-1", 101L, new Date(10_000),
+ Map.of(
+ "entity_type", "approval",
+ "title", "远端审批冲突",
+ "status", "pending",
+ "approval_conflict_active", true,
+ "approval_conflict_reason", "terminal_status_mismatch",
+ "approval_id", "approval-1",
+ "plan_id", "plan-1",
+ "task_id", "task-1",
+ "priority", "high"
+ ),
+ Map.of("id", "approval-1", "status", "pending")),
+ record("outbox-risk-approval", "event", "event-risk-1", 101L, new Date(11_000),
+ Map.of(
+ "entity_type", "event",
+ "category", "risk_approval",
+ "kind", "risk_approval_required",
+ "status", "blocked",
+ "title", "高风险动作待审批",
+ "approval_id", "approval-risk-1",
+ "plan_id", "plan-1",
+ "risk_level", "high"
+ ),
+ Map.of("id", "event-risk-1", "kind", "risk_approval_required")),
+ record("outbox-route-approval", "event", "event-route-approval", 101L, new Date(12_000),
+ Map.of(
+ "entity_type", "event",
+ "category", "route_governance",
+ "kind", "route_decision_approval_required",
+ "status", "pending",
+ "title", "路由动作待审批",
+ "approval_id", "approval-route-1",
+ "route_decision_id", "route-1",
+ "reason", "route_decision_approval_required"
+ ),
+ Map.of("id", "event-route-approval", "kind", "route_decision_approval_required")),
+ record("outbox-approval-history", "event", "event-approval-history", 101L, new Date(13_000),
+ Map.of(
+ "entity_type", "event",
+ "kind", "approval_action_recorded",
+ "status", "recorded",
+ "title", "审批动作记录",
+ "approval_id", "approval-1",
+ "message", "已转交审批"
+ ),
+ Map.of("id", "event-approval-history", "kind", "approval_action_recorded")),
+ record("outbox-task", "task", "task-1", 101L, new Date(14_000),
+ Map.of("entity_type", "task", "title", "普通任务", "status", "running"),
+ Map.of("id", "task-1", "status", "running"))
+ )));
+
+ DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
+ "approval_governance", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
+ );
+
+ assertThat(result.getTotal()).isEqualTo(4);
+ assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
+ .containsExactly("approval-1", "event-risk-1", "event-route-approval", "event-approval-history");
+ assertThat(result.getRecords().get(0).getView()).isEqualTo("approval_governance");
+ assertThat(result.getRecords().get(0).getApprovalId()).isEqualTo("approval-1");
+ assertThat(result.getRecords().get(0).getCategory()).isEqualTo("approval_conflict");
+ assertThat(result.getRecords().get(1).getCategory()).isEqualTo("risk_approval");
+ assertThat(result.getRecords().get(2).getRouteDecisionId()).isEqualTo("route-1");
+ assertThat(result.getRecords().get(3).getApprovalId()).isEqualTo("approval-1");
+ }
+
@Test
void queryAdminWorkbenchAuditsRedactsSensitivePayloadFields() {
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
diff --git a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx
index 5801680d..d4721e98 100644
--- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx
+++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx
@@ -38,6 +38,7 @@ const WORKBENCH_TABS: Array<{
}> = [
{ key: 'business', label: '业务视图' },
{ key: 'interventions', label: '介入台' },
+ { key: 'approval_governance', label: '审批治理' },
{ key: 'artifact_lifecycle', label: '产物治理' },
{ key: 'notifications', label: '通知运营' },
{ key: 'audits', label: '审计诊断' },
@@ -135,6 +136,15 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) {
}
function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmployeeAdminWorkbenchView) {
+ if (view === 'approval_governance') {
+ if (record.category === 'approval_conflict' && record.approval_id) {
+ return { entityType: 'approval', entityId: record.approval_id };
+ }
+ if (record.route_decision_id) {
+ return { entityType: 'route_decision', entityId: record.route_decision_id };
+ }
+ return { entityType: 'event', entityId: record.entity_id };
+ }
if (view === 'interventions' && record.approval_id) {
return { entityType: 'approval', entityId: record.approval_id };
}
@@ -163,6 +173,25 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp
}
function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmployeeAdminWorkbenchView) {
+ if (view === 'approval_governance') {
+ if (record.category === 'approval_conflict' && record.approval_id) {
+ return [
+ { action: 'keep_local', label: '保留本地' },
+ { action: 'mark_reviewed', label: '标记复核' },
+ ];
+ }
+ if (record.route_decision_id) {
+ return [
+ { action: 'mark_reviewed', label: '标记已看' },
+ { action: 'dismiss', label: '忽略' },
+ { action: 'retry_policy_check', label: '重试策略检查' },
+ ];
+ }
+ return [
+ { action: 'mark_reviewed', label: '标记已看' },
+ { action: 'dismiss', label: '忽略' },
+ ];
+ }
if (view === 'interventions' && record.approval_id) {
return [
{ action: 'keep_local', label: '保留本地' },
@@ -682,6 +711,20 @@ const DigitalEmployeeOps: React.FC = () => {
)}
)}
+ {activeView === 'approval_governance' && (
+