From d86f82a4c526b71e883ee96a35f07119222e4c8f Mon Sep 17 00:00:00 2001 From: baiyanyun Date: Sat, 13 Jun 2026 13:54:18 +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=E5=AE=A1=E8=AE=A1=E8=AF=8A?= =?UTF-8?q?=E6=96=AD=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...mployeeSyncApplicationServiceImplTest.java | 95 +++++++++++++++++++ .../scripts/check-digital-employee.js | 70 ++++++++++++++ ...ital-employee-frontend-integration-plan.md | 2 +- 3 files changed, 166 insertions(+), 1 deletion(-) 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 c18b0b20..34644fc1 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 @@ -604,6 +604,101 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("deleted_at", "2026-06-13T00:00:00+08:00"); } + @Test + void queryAdminWorkbenchAuditsReturnsAuditRowsOnly() { + 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-sandbox-audit", "event", "event-audit-1", 101L, new Date(10_000), + Map.of( + "entity_type", "event", + "category", "sandbox_audit", + "kind", "sandbox_command_observed", + "status", "warn", + "level", "warn", + "title", "Sandbox 命令观察", + "operation", "shell_exec", + "summary", "命令参数已脱敏" + ), + Map.of("id", "event-audit-1", "kind", "sandbox_command_observed")), + record("outbox-token-cost", "event", "event-audit-2", 101L, new Date(11_000), + Map.of( + "entity_type", "event", + "category", "token_cost", + "kind", "token_cost_recorded", + "status", "recorded", + "title", "Token 成本估算", + "model", "gpt-4.1", + "input_tokens", 120, + "output_tokens", 60, + "total_tokens", 180, + "estimated_cost_usd", 0.012 + ), + Map.of("id", "event-audit-2", "kind", "token_cost_recorded")), + record("outbox-tool-audit", "event", "event-audit-3", 101L, new Date(12_000), + Map.of( + "entity_type", "event", + "category", "tool_call_audit", + "kind", "tool_call_audit", + "status", "rejected", + "level", "error", + "title", "工具调用被拒绝", + "tool_name", "shell", + "server_id", "local" + ), + Map.of("id", "event-audit-3", "kind", "tool_call_audit")), + record("outbox-sync-failure", "event", "event-audit-4", 101L, new Date(13_000), + Map.of( + "entity_type", "event", + "category", "sync_failure", + "kind", "digital_sync_failed", + "status", "failed", + "title", "同步失败", + "reason", "remote_timeout" + ), + Map.of("id", "event-audit-4", "kind", "digital_sync_failed")), + record("outbox-governance", "event", "event-audit-5", 101L, new Date(14_000), + Map.of( + "entity_type", "event", + "category", "governance", + "kind", "governance_skip_task", + "status", "recorded", + "title", "治理命令审计", + "command_id", "command-1" + ), + Map.of("id", "event-audit-5", "kind", "governance_skip_task")), + record("outbox-task", "task", "task-1", 101L, new Date(15_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(16_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(17_000), + Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"), + Map.of("id", "notification-1", "status", "unread")), + record("outbox-artifact", "artifact", "artifact-1", 101L, new Date(18_000), + Map.of("entity_type", "artifact", "title", "普通产物", "status", "available"), + Map.of("id", "artifact-1", "status", "available")) + ))); + + DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench( + "audits", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L + ); + + assertThat(result.getTotal()).isEqualTo(5); + assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId) + .containsExactly("event-audit-1", "event-audit-2", "event-audit-3", "event-audit-4", "event-audit-5"); + assertThat(result.getRecords().get(0).getView()).isEqualTo("audits"); + assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getCategory) + .containsExactly("sandbox_audit", "token_cost", "tool_call_audit", "sync_failure", "governance"); + assertThat(result.getRecords().get(0).getLevel()).isEqualTo("warn"); + assertThat(result.getRecords().get(0).getSummary()).isEqualTo("命令参数已脱敏"); + assertThat(result.getRecords().get(1).getBusinessView()).containsEntry("total_tokens", 180); + assertThat(result.getRecords().get(1).getBusinessView()).containsEntry("estimated_cost_usd", 0.012); + assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("tool_name", "shell"); + assertThat(result.getRecords().get(3).getSummary()).isEqualTo("remote_timeout"); + assertThat(result.getRecords().get(4).getBusinessView()).containsEntry("command_id", "command-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/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js index 063bb72a..d17de0cc 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -1770,6 +1770,75 @@ function checkDigitalAdminArtifactLifecycleWorkbench() { console.log("[DigitalEmployeeCheck] admin artifact lifecycle workbench hooks OK"); } +function checkDigitalAdminAuditDiagnosticsWorkbench() { + console.log("\n[DigitalEmployeeCheck] Verify admin audit diagnostics 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, "audits", "backend audits view"], + [service, "isAuditRow", "backend audit matcher"], + [service, "sandbox_audit", "backend sandbox audit category"], + [service, "token_cost", "backend token cost category"], + [service, "tool_call_audit", "backend tool call audit category"], + [service, "sync_failure", "backend sync failure audit category"], + [test, "queryAdminWorkbenchAuditsReturnsAuditRowsOnly", "backend audit diagnostics test"], + [test, "doesNotContainKeys(\"token\", \"authorization\", \"raw_input\", \"command\", \"prompt\")", "backend audit redaction assertion"], + [qimingTypes, "audits", "qiming audits view type"], + [qimingOps, "审计诊断", "qiming audit diagnostics tab"], + [qimingOps, "标记复核", "qiming audit reviewed action"], + [qimingOps, "忽略", "qiming audit dismiss action"], + [qimingOps, "标记归档", "qiming audit archive action"], + [docs, "正式管理端审计诊断预览", "docs audit diagnostics absorption"], + ]; + const missing = requiredSnippets + .filter(([content, snippet]) => !content.includes(snippet)) + .map(([, , label]) => label); + if (missing.length > 0) { + throw new Error(`Missing admin audit diagnostics workbench hooks: ${missing.join(", ")}`); + } + console.log("[DigitalEmployeeCheck] admin audit diagnostics workbench hooks OK"); +} + function checkLocalDatabaseSchema() { console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema"); const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db"); @@ -1837,6 +1906,7 @@ function main() { checkDigitalAdminRouteGovernanceWorkbench(); checkDigitalAdminNotificationWorkbench(); checkDigitalAdminArtifactLifecycleWorkbench(); + checkDigitalAdminAuditDiagnosticsWorkbench(); 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 c7ed6886..053d139d 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -604,7 +604,7 @@ GET /api/digital-employee/approvals - 建议:下一轮围绕管理端正式路由 UI 和跨设备冲突可视化,把 embedded 批量干预入口推进到管理端运营台。 11. **诊断 / 成本 / 审计视图** - - 已吸收:同步失败诊断较完整,部分 sandbox / memory / service 日志在 qimingclaw 其他模块中存在;embedded adapter 已接管 `/api/doctor`、`/api/cost`、`/api/audit`,从 snapshot、runtime records、sync status、managed services 和 artifact delivery facts 生成诊断、估算成本和审计投影;sandbox audit、token/cost 和 CLI/tool 调用事件已开始通过 `digital_events` 与 outbox `business_view` 提炼为 `sandbox_audit`、`token_cost`、`tool_call_audit` 分类,并只同步 session、tool、server、operation、status、exit code、duration、model、token 计数和估算成本等安全字段;数字员工首页已展示诊断/审计/Token/估算成本摘要,`/api/digital-employee/audits` 已提供本地审计业务视图,诊断与审计风险通知联动已开始闭环。管理端正式审计视图预览已开始吸收,embedded “技能审计”会消费 sandbox/tool/cost 风险来源并保留安全 payload preview。诊断修复动作预览已开始吸收,“处置台”可把 doctor/audit/service/policy 风险转为可筛选、可标记和可安全重试的运营事项。发送账单审计外壳已开始吸收,后端提供估算账单查询和审计归档记录接口,qiming 运营台可显示 Token/估算成本并标记脱敏归档;归档记录会剔除 token、authorization、raw_input、prompt 和 command 等敏感字段。 + - 已吸收:同步失败诊断较完整,部分 sandbox / memory / service 日志在 qimingclaw 其他模块中存在;embedded adapter 已接管 `/api/doctor`、`/api/cost`、`/api/audit`,从 snapshot、runtime records、sync status、managed services 和 artifact delivery facts 生成诊断、估算成本和审计投影;sandbox audit、token/cost 和 CLI/tool 调用事件已开始通过 `digital_events` 与 outbox `business_view` 提炼为 `sandbox_audit`、`token_cost`、`tool_call_audit` 分类,并只同步 session、tool、server、operation、status、exit code、duration、model、token 计数和估算成本等安全字段;数字员工首页已展示诊断/审计/Token/估算成本摘要,`/api/digital-employee/audits` 已提供本地审计业务视图,诊断与审计风险通知联动已开始闭环。管理端正式审计视图预览和正式管理端审计诊断预览已开始吸收,qiming-backend 的 `audits` 运营台视图会聚合 sandbox / token cost / tool call / sync failure / governance 审计业务视图,qiming“数字员工运营台”的“审计诊断”tab 可标记复核、忽略和脱敏归档。诊断修复动作预览已开始吸收,“处置台”可把 doctor/audit/service/policy 风险转为可筛选、可标记和可安全重试的运营事项。发送账单审计外壳已开始吸收,后端提供估算账单查询和审计归档记录接口,qiming 运营台可显示 Token/估算成本并标记脱敏归档;归档记录会剔除 token、authorization、raw_input、prompt 和 command 等敏感字段。 - 缺口:真实计费账单、独立 sandbox audit 文件归档、自动诊断修复动作和正式外部管理端审计视图尚未统一吸收;当前账单仍是估算,审计归档只记录脱敏摘要和归档位置。 - 建议:下一轮围绕管理端正式审计视图和诊断修复动作,把本地可追溯审计继续推进到可运营处置。