吸收数字员工管理端路由治理预览

This commit is contained in:
baiyanyun
2026-06-12 23:47:20 +08:00
parent ca746366f6
commit 479e2e209d
4 changed files with 138 additions and 3 deletions

View File

@@ -1084,7 +1084,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict"; if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict";
String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind")); String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind"));
if (contains(kind, "route_decision_blocked") || contains(kind, "route_decision_approval_required") if (contains(kind, "route_decision_blocked") || contains(kind, "route_decision_approval_required")
|| contains(kind, "route_action_missing_context") || contains(kind, "route_action_not_mapped")) { || contains(kind, "route_action_policy_blocked") || contains(kind, "route_action_missing_context")
|| contains(kind, "route_action_not_mapped")) {
return "route_intervention"; return "route_intervention";
} }
String category = firstString(businessView.get("category"), payload.get("category")); String category = firstString(businessView.get("category"), payload.get("category"));

View File

@@ -403,6 +403,71 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(2).getServiceId()).isEqualTo("agent-server"); assertThat(result.getRecords().get(2).getServiceId()).isEqualTo("agent-server");
} }
@Test
void queryAdminWorkbenchRouteGovernanceReturnsRouteAndGovernanceRowsOnly() {
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-route-blocked", "event", "event-route-1", 101L, new Date(10_000),
Map.of(
"entity_type", "event",
"category", "route_governance",
"kind", "route_decision_blocked",
"status", "blocked",
"title", "路由策略阻断",
"route_decision_id", "route-1",
"reason", "route_action_policy_blocked",
"summary", "控制动作被策略阻断"
),
Map.of("id", "event-route-1", "kind", "route_decision_blocked")),
record("outbox-route-intervention", "event", "event-route-2", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"category", "route_intervention",
"kind", "route_action_missing_context",
"status", "pending",
"title", "路由缺少上下文",
"route_decision_id", "route-2",
"reason", "route_action_missing_context"
),
Map.of("id", "event-route-2", "kind", "route_action_missing_context")),
record("outbox-governance", "event", "event-route-3", 101L, new Date(12_000),
Map.of(
"entity_type", "event",
"category", "governance",
"kind", "governance_retry_task",
"status", "recorded",
"title", "治理命令已映射",
"route_decision_id", "route-3",
"command_id", "command-1"
),
Map.of("id", "event-route-3", "kind", "governance_retry_task")),
record("outbox-task", "task", "task-1", 101L, new Date(13_000),
Map.of("entity_type", "task", "title", "普通任务", "status", "running"),
Map.of("id", "task-1", "status", "running")),
record("outbox-approval", "approval", "approval-1", 101L, new Date(14_000),
Map.of("entity_type", "approval", "title", "普通审批", "status", "pending"),
Map.of("id", "approval-1", "status", "pending")),
record("outbox-notification", "notification", "notification-1", 101L, new Date(15_000),
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
Map.of("id", "notification-1", "status", "unread"))
)));
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
"route_governance", 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-route-1", "event-route-2", "event-route-3");
assertThat(result.getRecords().get(0).getView()).isEqualTo("route_governance");
assertThat(result.getRecords().get(0).getRouteDecisionId()).isEqualTo("route-1");
assertThat(result.getRecords().get(0).getCategory()).isEqualTo("route_governance");
assertThat(result.getRecords().get(0).getStatus()).isEqualTo("blocked");
assertThat(result.getRecords().get(0).getSummary()).isEqualTo("控制动作被策略阻断");
assertThat(result.getRecords().get(1).getCategory()).isEqualTo("route_intervention");
assertThat(result.getRecords().get(2).getRouteDecisionId()).isEqualTo("route-3");
}
@Test @Test
void queryAdminWorkbenchAuditsRedactsSensitivePayloadFields() { 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))) when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))

View File

@@ -1534,6 +1534,74 @@ function checkDigitalAdminServiceOperationsWorkbench() {
console.log("[DigitalEmployeeCheck] admin service operations workbench hooks OK"); console.log("[DigitalEmployeeCheck] admin service operations workbench hooks OK");
} }
function checkDigitalAdminRouteGovernanceWorkbench() {
console.log("\n[DigitalEmployeeCheck] Verify admin route governance workbench hooks");
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
const qimingRoot = path.resolve(packageRoot, "..", "..", "..", "qiming");
const servicePath = path.join(
backendRoot,
"app-platform-modules",
"app-platform-agent",
"app-platform-agent-core-application",
"src",
"main",
"java",
"com",
"xspaceagi",
"agent",
"core",
"application",
"service",
"DigitalEmployeeSyncApplicationServiceImpl.java",
);
const testPath = path.join(
backendRoot,
"app-platform-modules",
"app-platform-agent",
"app-platform-agent-core-application",
"src",
"test",
"java",
"com",
"xspaceagi",
"agent",
"core",
"application",
"service",
"DigitalEmployeeSyncApplicationServiceImplTest.java",
);
const qimingOpsPath = path.join(qimingRoot, "src", "pages", "SystemManagement", "Content", "DigitalEmployeeOps", "index.tsx");
const qimingTypesPath = path.join(qimingRoot, "src", "types", "interfaces", "systemManage.ts");
const docsPath = path.resolve(packageRoot, "..", "..", "docs", "digital-employee-frontend-integration-plan.md");
const service = fs.readFileSync(servicePath, "utf8");
const test = fs.readFileSync(testPath, "utf8");
const qimingOps = fs.readFileSync(qimingOpsPath, "utf8");
const qimingTypes = fs.readFileSync(qimingTypesPath, "utf8");
const docs = fs.readFileSync(docsPath, "utf8");
const requiredSnippets = [
[service, "route_governance", "backend route governance view"],
[service, "isRouteGovernanceRow", "backend route governance matcher"],
[service, "route_decision_blocked", "backend route blocked classifier"],
[service, "route_action_policy_blocked", "backend route action policy blocked classifier"],
[service, "route_action_missing_context", "backend route action missing context classifier"],
[service, "governance_", "backend governance event classifier"],
[test, "queryAdminWorkbenchRouteGovernanceReturnsRouteAndGovernanceRowsOnly", "backend route governance test"],
[qimingTypes, "route_governance", "qiming route governance view type"],
[qimingOps, "路由治理", "qiming route governance tab"],
[qimingOps, "重试策略检查", "qiming route policy retry action"],
[qimingOps, "标记已看", "qiming route mark reviewed action"],
[qimingOps, "忽略", "qiming route dismiss action"],
[docs, "正式管理端路由治理预览", "docs route governance absorption"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing admin route governance workbench hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] admin route governance workbench hooks OK");
}
function checkLocalDatabaseSchema() { function checkLocalDatabaseSchema() {
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema"); console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db"); const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
@@ -1598,6 +1666,7 @@ function main() {
checkDigitalAdminRecordBillingArchiveShell(); checkDigitalAdminRecordBillingArchiveShell();
checkDigitalAdminApprovalGovernanceWorkbench(); checkDigitalAdminApprovalGovernanceWorkbench();
checkDigitalAdminServiceOperationsWorkbench(); checkDigitalAdminServiceOperationsWorkbench();
checkDigitalAdminRouteGovernanceWorkbench();
checkLocalDatabaseSchema(); checkLocalDatabaseSchema();
console.log("\n[DigitalEmployeeCheck] All checks passed"); console.log("\n[DigitalEmployeeCheck] All checks passed");
} catch (error) { } catch (error) {

View File

@@ -599,8 +599,8 @@ GET /api/digital-employee/approvals
10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)** 10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)**
- 已吸收:管理端通过 `/computer/chat` 进入 qimingclaw 后会生成本地任务事实embedded `/api/ingress/route``/api/route-decisions``/api/digital-employee/route-decisions` 已由 qimingclaw adapter 接管。 - 已吸收:管理端通过 `/computer/chat` 进入 qimingclaw 后会生成本地任务事实embedded `/api/ingress/route``/api/route-decisions``/api/digital-employee/route-decisions` 已由 qimingclaw adapter 接管。
- 当前能力入口请求会按上下文、ready PlanStep、调度、查询和控制动作生成 route decision并写入 `digital_events.kind = route_decision` 与现有 event outbox路由时间线优先从 qimingclaw 本地事件读取;低风险 route decision 已开始映射为明确的本地 governance command并把 `created_command_id` 回填到 route decision自动映射前会执行低风险/受保护高风险 action guard未允许的控制动作会记录 `route_action_policy_blocked``route_action_missing_context`,避免静默创建命令;入口路由策略可从管理端远端下发,支持按 intent 阻断、按 intent 要求审批、限制自动创建 governance command并在策略干预时先记录 route decision 再停止本地命令执行;首页已展示路由干预队列和治理审计摘要,审批通过/拒绝会写入本地 approval decision 并回写管理端,管理端审批更新拉取后会自动扫描已批准 route approval 并按幂等规则恢复执行scheduler check 已接入 ready PlanStep 安全派发,首页立即执行后会触发一次派发 sweep 并展示派发结果;路由决策本地业务视图、首页摘要、治理审计、策略来源/拉取错误、阻断/待审/待干预/已恢复计数和通知联动已开始闭环。管理端正式路由 UI 预览已开始吸收,“策略中心”会把入口路由策略纳入统一拉取与状态展示。 - 当前能力入口请求会按上下文、ready PlanStep、调度、查询和控制动作生成 route decision并写入 `digital_events.kind = route_decision` 与现有 event outbox路由时间线优先从 qimingclaw 本地事件读取;低风险 route decision 已开始映射为明确的本地 governance command并把 `created_command_id` 回填到 route decision自动映射前会执行低风险/受保护高风险 action guard未允许的控制动作会记录 `route_action_policy_blocked``route_action_missing_context`,避免静默创建命令;入口路由策略可从管理端远端下发,支持按 intent 阻断、按 intent 要求审批、限制自动创建 governance command并在策略干预时先记录 route decision 再停止本地命令执行;首页已展示路由干预队列和治理审计摘要,审批通过/拒绝会写入本地 approval decision 并回写管理端,管理端审批更新拉取后会自动扫描已批准 route approval 并按幂等规则恢复执行scheduler check 已接入 ready PlanStep 安全派发,首页立即执行后会触发一次派发 sweep 并展示派发结果;路由决策本地业务视图、首页摘要、治理审计、策略来源/拉取错误、阻断/待审/待干预/已恢复计数和通知联动已开始闭环。管理端正式路由 UI 预览已开始吸收,“策略中心”会把入口路由策略纳入统一拉取与状态展示。正式管理端路由治理预览已开始吸收qiming-backend 的 `route_governance` 运营台视图会聚合 route blocked / approval required / missing context / not mapped / governance auditqiming“数字员工运营台”的“路由治理”tab 只开放标记已看、忽略和重试策略检查等低风险动作意图。
- 后续缺口:正式外部管理端路由 UI、路由策略编辑跨设备路由干预冲突可视化仍待吸收。 - 后续缺口:路由策略编辑跨设备路由干预冲突可视化和高风险恢复执行策略 UI 仍待吸收。
- 建议:下一轮围绕管理端正式路由 UI 和跨设备冲突可视化,把 embedded 批量干预入口推进到管理端运营台。 - 建议:下一轮围绕管理端正式路由 UI 和跨设备冲突可视化,把 embedded 批量干预入口推进到管理端运营台。
11. **诊断 / 成本 / 审计视图** 11. **诊断 / 成本 / 审计视图**