From 479e2e209dd37397f56692067f85c5c74623000a Mon Sep 17 00:00:00 2001 From: baiyanyun Date: Fri, 12 Jun 2026 23:47:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=B8=E6=94=B6=E6=95=B0=E5=AD=97=E5=91=98?= =?UTF-8?q?=E5=B7=A5=E7=AE=A1=E7=90=86=E7=AB=AF=E8=B7=AF=E7=94=B1=E6=B2=BB?= =?UTF-8?q?=E7=90=86=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...talEmployeeSyncApplicationServiceImpl.java | 3 +- ...mployeeSyncApplicationServiceImplTest.java | 65 +++++++++++++++++ .../scripts/check-digital-employee.js | 69 +++++++++++++++++++ ...ital-employee-frontend-integration-plan.md | 4 +- 4 files changed, 138 insertions(+), 3 deletions(-) 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 83a1a554..44dc490c 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 @@ -1084,7 +1084,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict"; String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind")); 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"; } String category = firstString(businessView.get("category"), payload.get("category")); 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 43547fa8..0c5e5935 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 @@ -403,6 +403,71 @@ class DigitalEmployeeSyncApplicationServiceImplTest { 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 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/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js index 99e5ffe3..69e0cc3a 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -1534,6 +1534,74 @@ function checkDigitalAdminServiceOperationsWorkbench() { 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() { console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema"); const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db"); @@ -1598,6 +1666,7 @@ function main() { checkDigitalAdminRecordBillingArchiveShell(); checkDigitalAdminApprovalGovernanceWorkbench(); checkDigitalAdminServiceOperationsWorkbench(); + checkDigitalAdminRouteGovernanceWorkbench(); checkLocalDatabaseSchema(); console.log("\n[DigitalEmployeeCheck] All checks passed"); } catch (error) { diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index 66668752..d3db202d 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -599,8 +599,8 @@ GET /api/digital-employee/approvals 10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)** - 已吸收:管理端通过 `/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 预览已开始吸收,“策略中心”会把入口路由策略纳入统一拉取与状态展示。 - - 后续缺口:正式外部管理端路由 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 audit,qiming“数字员工运营台”的“路由治理”tab 只开放标记已看、忽略和重试策略检查等低风险动作意图。 + - 后续缺口:路由策略编辑、跨设备路由干预冲突可视化和高风险恢复执行策略 UI 仍待吸收。 - 建议:下一轮围绕管理端正式路由 UI 和跨设备冲突可视化,把 embedded 批量干预入口推进到管理端运营台。 11. **诊断 / 成本 / 审计视图**