From a62b749ef2ee79aea2486cf1679b169c38e73818 Mon Sep 17 00:00:00 2001 From: baiyanyun Date: Mon, 15 Jun 2026 09:14:32 +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=AF=8A=E6=96=AD=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DigitalEmployeeAdminWorkbenchRowDto.java | 16 ++ ...talEmployeeSyncApplicationServiceImpl.java | 28 ++++ ...mployeeSyncApplicationServiceImplTest.java | 154 ++++++++++++++++++ .../Content/DigitalEmployeeOps/index.tsx | 26 ++- qiming/src/types/interfaces/systemManage.ts | 8 + .../scripts/check-digital-employee.js | 95 +++++++++++ ...ital-employee-frontend-integration-plan.md | 4 +- 7 files changed, 325 insertions(+), 6 deletions(-) diff --git a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/src/main/java/com/xspaceagi/agent/core/adapter/dto/digital/DigitalEmployeeAdminWorkbenchRowDto.java b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/src/main/java/com/xspaceagi/agent/core/adapter/dto/digital/DigitalEmployeeAdminWorkbenchRowDto.java index 4c74cd4b..5585eefd 100644 --- a/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/src/main/java/com/xspaceagi/agent/core/adapter/dto/digital/DigitalEmployeeAdminWorkbenchRowDto.java +++ b/qiming-backend/app-platform-modules/app-platform-agent/app-platform-agent-core-adapter/src/main/java/com/xspaceagi/agent/core/adapter/dto/digital/DigitalEmployeeAdminWorkbenchRowDto.java @@ -164,6 +164,22 @@ public class DigitalEmployeeAdminWorkbenchRowDto { @JsonProperty("billing_source") private String billingSource; private Boolean estimated; + @JsonProperty("diagnostic_id") + private String diagnosticId; + @JsonProperty("diagnostic_kind") + private String diagnosticKind; + @JsonProperty("issue_code") + private String issueCode; + @JsonProperty("repair_action") + private String repairAction; + @JsonProperty("repair_status") + private String repairStatus; + @JsonProperty("repair_reason") + private String repairReason; + @JsonProperty("safe_repair_disabled") + private Boolean safeRepairDisabled; + @JsonProperty("recommended_action") + private String recommendedAction; @JsonProperty("policy_kind") private String policyKind; @JsonProperty("policy_key") 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 738c2099..2f3e239f 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 @@ -78,6 +78,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye "plan_step:mark_reviewed", "plan_step:dismiss", "plan_step:retry_policy_check", "plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation", "revision:mark_reviewed", "revision:dismiss", "revision:request_revision_review", + "diagnostic:mark_reviewed", "diagnostic:dismiss", "diagnostic:retry_check", "diagnostic:record_repair_intent", "audit:mark_reviewed", "audit:dismiss", "event:mark_reviewed", "event:dismiss" ); @@ -1023,6 +1024,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye case "run_schedule_now" -> List.of("run_schedule_now_disabled"); case "start_scheduler" -> List.of("start_scheduler_disabled"); case "hard_interrupt" -> List.of("hard_interrupt_disabled"); + case "run_shell_repair" -> List.of("run_shell_repair_disabled"); + case "modify_system_config" -> List.of("modify_system_config_disabled"); + case "restart_unlisted_service" -> List.of("restart_unlisted_service_disabled"); + case "delete_file_repair" -> List.of("delete_file_repair_disabled"); + case "remote_policy_edit" -> List.of("remote_policy_edit_disabled"); case "activate_plan", "cancel_run", "cancel_task", "accept_remote_approval_batch" -> List.of("high_risk_governance_action_disabled"); default -> List.of(); }; @@ -1144,6 +1150,14 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .billingEstimateId(firstString(businessView.get("billing_estimate_id"), businessView.get("billingEstimateId"), payload.get("billing_estimate_id"), payload.get("billingEstimateId"))) .billingSource(firstString(businessView.get("billing_source"), businessView.get("billingSource"), payload.get("billing_source"), payload.get("billingSource"))) .estimated(nullableBooleanValue(firstPresent(businessView.get("estimated"), payload.get("estimated")))) + .diagnosticId(firstString(businessView.get("diagnostic_id"), businessView.get("diagnosticId"), payload.get("diagnostic_id"), payload.get("diagnosticId"))) + .diagnosticKind(firstString(businessView.get("diagnostic_kind"), businessView.get("diagnosticKind"), payload.get("diagnostic_kind"), payload.get("diagnosticKind"))) + .issueCode(firstString(businessView.get("issue_code"), businessView.get("issueCode"), payload.get("issue_code"), payload.get("issueCode"))) + .repairAction(firstString(businessView.get("repair_action"), businessView.get("repairAction"), payload.get("repair_action"), payload.get("repairAction"))) + .repairStatus(firstString(businessView.get("repair_status"), businessView.get("repairStatus"), payload.get("repair_status"), payload.get("repairStatus"))) + .repairReason(firstString(businessView.get("repair_reason"), businessView.get("repairReason"), payload.get("repair_reason"), payload.get("repairReason"))) + .safeRepairDisabled(nullableBooleanValue(firstPresent(businessView.get("safe_repair_disabled"), businessView.get("safeRepairDisabled"), payload.get("safe_repair_disabled"), payload.get("safeRepairDisabled")))) + .recommendedAction(firstString(businessView.get("recommended_action"), businessView.get("recommendedAction"), payload.get("recommended_action"), payload.get("recommendedAction"))) .policyKind(firstString(businessView.get("policy_kind"), businessView.get("policyKind"), payload.get("policy_kind"), payload.get("policyKind"))) .policyKey(firstString(businessView.get("policy_key"), businessView.get("policyKey"), payload.get("policy_key"), payload.get("policyKey"))) .scheduleId(firstString(businessView.get("schedule_id"), businessView.get("scheduleId"), payload.get("schedule_id"), payload.get("scheduleId"))) @@ -1264,6 +1278,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye private boolean isAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) { return Set.of("sandbox_audit", "token_cost", "tool_call_audit", "governance", "sync_failure", "audit_archive", "billing_estimate").contains(row.getCategory()) + || isDiagnosticRepairRow(row) || contains(row.getKind(), "audit") || contains(row.getKind(), "cost") || contains(row.getKind(), "billing") @@ -1271,6 +1286,18 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.isNotBlank(row.getBillingEstimateId()); } + private boolean isDiagnosticRepairRow(DigitalEmployeeAdminWorkbenchRowDto row) { + String category = firstString(row.getCategory()); + String kind = firstString(row.getKind()); + return Set.of("diagnostic_repair", "diagnostic", "doctor_issue", "policy_pull_error").contains(category) + || startsWithAny(kind, "doctor_", "diagnostic_", "repair_") + || contains(kind, "policy_pull_error") + || StringUtils.isNotBlank(row.getDiagnosticId()) + || StringUtils.isNotBlank(row.getIssueCode()) + || StringUtils.isNotBlank(row.getRepairAction()) + || StringUtils.isNotBlank(row.getRecommendedAction()); + } + private boolean isDailyReportRow(DigitalEmployeeAdminWorkbenchRowDto row) { return StringUtils.equals(row.getCategory(), "daily_report") || startsWithAny(row.getKind(), "daily_report") @@ -1427,6 +1454,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye 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, "doctor_", "diagnostic_", "repair_") || contains(kind, "policy_pull_error")) return "diagnostic_repair"; if (contains(kind, "policy")) return "policy_snapshot"; if (contains(kind, "scheduler") || contains(kind, "cron") || contains(kind, "lease")) return "scheduler_operation"; if (startsWithAny(kind, "plan_revision", "governance_revision")) return "plan_revision"; 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 a0786f5c..5d27f358 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 @@ -1474,6 +1474,110 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(4).getBusinessView()).containsEntry("command_id", "command-1"); } + @Test + void queryAdminWorkbenchAuditsReturnsDiagnosticRepairRowsOnly() { + 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-doctor-issue", "event", "diagnostic-1", 101L, new Date(10_000), + Map.ofEntries( + Map.entry("entity_type", "event"), + Map.entry("category", "diagnostic_repair"), + Map.entry("kind", "doctor_issue_detected"), + Map.entry("status", "warn"), + Map.entry("title", "File Server 异常"), + Map.entry("summary", "建议复核服务状态"), + Map.entry("diagnostic_id", "diagnostic-1"), + Map.entry("diagnostic_kind", "doctor_service"), + Map.entry("issue_code", "file_server_unavailable"), + Map.entry("recommended_action", "safe_restart_service"), + Map.entry("safe_repair_disabled", true) + ), + Map.of("id", "diagnostic-1", "kind", "doctor_issue_detected")), + record("outbox-repair-intent", "event", "repair-1", 101L, new Date(11_000), + Map.ofEntries( + Map.entry("entity_type", "event"), + Map.entry("category", "diagnostic_repair"), + Map.entry("kind", "repair_intent_recorded"), + Map.entry("status", "recorded"), + Map.entry("title", "记录修复意图"), + Map.entry("diagnostic_id", "diagnostic-1"), + Map.entry("issue_code", "file_server_unavailable"), + Map.entry("repair_action", "record_repair_intent"), + Map.entry("repair_status", "recorded"), + Map.entry("repair_reason", "operator_reviewed") + ), + Map.of("id", "repair-1", "kind", "repair_intent_recorded")), + record("outbox-policy-pull-error", "event", "diagnostic-2", 101L, new Date(12_000), + Map.of( + "entity_type", "event", + "kind", "policy_pull_error", + "status", "failed", + "title", "策略拉取失败", + "diagnostic_id", "diagnostic-2", + "issue_code", "policy_pull_error", + "recommended_action", "retry_check" + ), + Map.of("id", "diagnostic-2", "kind", "policy_pull_error")), + record("outbox-sandbox-audit", "event", "audit-1", 101L, new Date(13_000), + Map.of( + "entity_type", "event", + "category", "sandbox_audit", + "kind", "sandbox_audit", + "status", "warn", + "title", "Sandbox 风险", + "diagnostic_id", "diagnostic-3", + "issue_code", "sandbox_warn" + ), + Map.of("id", "audit-1", "kind", "sandbox_audit")), + record("outbox-tool-audit", "event", "audit-2", 101L, new Date(14_000), + Map.of( + "entity_type", "event", + "category", "tool_call_audit", + "kind", "tool_call_audit", + "status", "rejected", + "title", "工具调用被拒绝", + "diagnostic_id", "diagnostic-4", + "issue_code", "tool_rejected" + ), + Map.of("id", "audit-2", "kind", "tool_call_audit")), + 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")), + record("outbox-route", "event", "route-1", 101L, new Date(19_000), + Map.of("entity_type", "event", "category", "route_intervention", "title", "普通路由", "status", "blocked"), + Map.of("id", "route-1", "status", "blocked")) + ))); + + 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("diagnostic-1", "repair-1", "diagnostic-2", "audit-1", "audit-2"); + DigitalEmployeeAdminWorkbenchRowDto row = result.getRecords().get(0); + assertThat(row.getView()).isEqualTo("audits"); + assertThat(row.getDiagnosticId()).isEqualTo("diagnostic-1"); + assertThat(row.getDiagnosticKind()).isEqualTo("doctor_service"); + assertThat(row.getIssueCode()).isEqualTo("file_server_unavailable"); + assertThat(row.getRecommendedAction()).isEqualTo("safe_restart_service"); + assertThat(row.getSafeRepairDisabled()).isTrue(); + assertThat(row.getSummary()).isEqualTo("建议复核服务状态"); + assertThat(result.getRecords().get(1).getRepairAction()).isEqualTo("record_repair_intent"); + assertThat(result.getRecords().get(1).getRepairStatus()).isEqualTo("recorded"); + assertThat(result.getRecords().get(1).getRepairReason()).isEqualTo("operator_reviewed"); + assertThat(result.getRecords().get(2).getIssueCode()).isEqualTo("policy_pull_error"); + } + @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))) @@ -1666,6 +1770,56 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(takeoverLease.getReasonCodes()).contains("unsupported_admin_action"); } + @Test + void createAdminActionAllowsLowRiskDiagnosticRepairIntent() { + DigitalEmployeeAdminActionResultDto markReviewed = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "mark_reviewed") + ); + DigitalEmployeeAdminActionResultDto dismiss = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "dismiss") + ); + DigitalEmployeeAdminActionResultDto retryCheck = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "retry_check") + ); + DigitalEmployeeAdminActionResultDto recordRepairIntent = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "record_repair_intent") + ); + + assertThat(markReviewed.getOk()).isTrue(); + assertThat(dismiss.getOk()).isTrue(); + assertThat(retryCheck.getOk()).isTrue(); + assertThat(recordRepairIntent.getOk()).isTrue(); + assertThat(recordRepairIntent.getStatus()).isEqualTo("pending"); + assertThat(recordRepairIntent.getReasonCodes()).contains("action_recorded"); + } + + @Test + void createAdminActionRejectsHighRiskDiagnosticRepair() { + DigitalEmployeeAdminActionResultDto shellRepair = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "run_shell_repair") + ); + DigitalEmployeeAdminActionResultDto modifyConfig = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "modify_system_config") + ); + DigitalEmployeeAdminActionResultDto restartService = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "restart_unlisted_service") + ); + DigitalEmployeeAdminActionResultDto deleteFileRepair = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "delete_file_repair") + ); + DigitalEmployeeAdminActionResultDto remotePolicyEdit = service.createAdminAction( + adminActionRequest("diagnostic", "diagnostic-1", "remote_policy_edit") + ); + + assertThat(shellRepair.getOk()).isFalse(); + assertThat(shellRepair.getStatus()).isEqualTo("rejected"); + assertThat(shellRepair.getReasonCodes()).contains("run_shell_repair_disabled"); + assertThat(modifyConfig.getReasonCodes()).contains("modify_system_config_disabled"); + assertThat(restartService.getReasonCodes()).contains("restart_unlisted_service_disabled"); + assertThat(deleteFileRepair.getReasonCodes()).contains("delete_file_repair_disabled"); + assertThat(remotePolicyEdit.getReasonCodes()).contains("remote_policy_edit_disabled"); + } + @Test void queryPendingAdminActionsFiltersTenantClientDeviceAndMasksKey() { when(adminActionRepository.queryActions(eq(101L), eq("client-key-001"), eq("device-001"), eq("pending"), 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 31625544..b7caf6ab 100644 --- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx @@ -219,6 +219,9 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp return { entityType: 'daily_report', entityId: record.report_id }; } if (view === 'audits') { + if (record.diagnostic_id || record.issue_code || record.repair_action) { + return { entityType: 'diagnostic', entityId: record.diagnostic_id || record.entity_id }; + } return { entityType: 'audit', entityId: record.entity_id }; } if (view === 'governance_commands') { @@ -322,10 +325,18 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE ]; } if (view === 'audits') { - return [ + const actions = [ { action: 'mark_reviewed', label: '标记复核' }, { action: 'dismiss', label: '忽略' }, ]; + if (record.diagnostic_id || record.issue_code || record.repair_action) { + return [ + ...actions, + { action: 'retry_check', label: '重试诊断' }, + { action: 'record_repair_intent', label: '记录修复意图' }, + ]; + } + return actions; } if (view === 'governance_commands') { return [ @@ -1061,15 +1072,22 @@ const DigitalEmployeeOps: React.FC = () => { )} {activeView === 'audits' && ( - 审计归档/账单: - {detailRecord.audit_archive_id || detailRecord.billing_estimate_id || detailRecord.entity_id || '--'} + 审计归档/账单/诊断修复: + {detailRecord.diagnostic_id || detailRecord.audit_archive_id || detailRecord.billing_estimate_id || detailRecord.entity_id || '--'} + {detailRecord.diagnostic_kind && ` · 诊断类型 ${detailRecord.diagnostic_kind}`} + {detailRecord.issue_code && ` · 问题码 ${detailRecord.issue_code}`} + {detailRecord.recommended_action && ` · 建议动作 ${detailRecord.recommended_action}`} + {detailRecord.repair_action && ` · 修复动作 ${detailRecord.repair_action}`} + {detailRecord.repair_status && ` · 修复状态 ${detailRecord.repair_status}`} + {detailRecord.repair_reason && ` · 修复原因 ${detailRecord.repair_reason}`} + {detailRecord.safe_repair_disabled && ' · 安全修复禁用'} {detailRecord.archive_status && ` · 归档状态 ${detailRecord.archive_status}`} {detailRecord.archive_location && ` · 归档位置 ${detailRecord.archive_location}`} {detailRecord.billing_source && ` · 估算来源 ${detailRecord.billing_source}`} {detailRecord.estimated != null && ` · ${detailRecord.estimated ? '估算费用' : '真实费用'}`} {' '} - · 只展示脱敏归档摘要和账单估算来源,不保存 prompt、raw command、authorization 或 token。 + · 只展示脱敏归档摘要、账单估算来源和诊断修复建议,只记录修复意图,不执行 shell、不修改系统配置、不自动修复,不保存 prompt、raw command、authorization 或 token。 )} diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 1878da85..08506f62 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -664,6 +664,14 @@ export interface DigitalEmployeeAdminWorkbenchRow { billing_estimate_id?: string; billing_source?: string; estimated?: boolean; + diagnostic_id?: string; + diagnostic_kind?: string; + issue_code?: string; + repair_action?: string; + repair_status?: string; + repair_reason?: string; + safe_repair_disabled?: boolean; + recommended_action?: string; policy_kind?: string; policy_key?: string; schedule_id?: string; 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 dd53fde6..00a8010d 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -2545,6 +2545,100 @@ function checkDigitalAdminAuditDiagnosticsWorkbench() { console.log("[DigitalEmployeeCheck] admin audit diagnostics workbench hooks OK"); } +function checkDigitalAdminDiagnosticRepairPreview() { + console.log("\n[DigitalEmployeeCheck] Verify admin diagnostic repair preview 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 dtoPath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-adapter", + "src", + "main", + "java", + "com", + "xspaceagi", + "agent", + "core", + "adapter", + "dto", + "digital", + "DigitalEmployeeAdminWorkbenchRowDto.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 dto = fs.readFileSync(dtoPath, "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, "isDiagnosticRepairRow", "backend diagnostic repair matcher"], + [service, "diagnostic:retry_check", "backend diagnostic retry action"], + [service, "diagnostic:record_repair_intent", "backend diagnostic repair intent action"], + [service, "run_shell_repair_disabled", "backend shell repair disabled reason"], + [service, "modify_system_config_disabled", "backend system config disabled reason"], + [dto, "diagnostic_id", "backend diagnostic id field"], + [dto, "issue_code", "backend issue code field"], + [dto, "repair_action", "backend repair action field"], + [dto, "repair_status", "backend repair status field"], + [dto, "safe_repair_disabled", "backend safe repair disabled field"], + [test, "queryAdminWorkbenchAuditsReturnsDiagnosticRepairRowsOnly", "backend diagnostic repair workbench test"], + [test, "createAdminActionAllowsLowRiskDiagnosticRepairIntent", "backend diagnostic low risk action test"], + [test, "createAdminActionRejectsHighRiskDiagnosticRepair", "backend diagnostic high risk action test"], + [qimingTypes, "diagnostic_id", "qiming diagnostic id field"], + [qimingTypes, "repair_action", "qiming repair action field"], + [qimingTypes, "safe_repair_disabled", "qiming safe repair disabled field"], + [qimingOps, "记录修复意图", "qiming repair intent action"], + [qimingOps, "重试诊断", "qiming retry diagnostic action"], + [qimingOps, "不执行 shell", "qiming shell repair guard copy"], + [qimingOps, "不修改系统配置", "qiming system config guard copy"], + [docs, "正式管理端诊断修复预览", "docs diagnostic repair absorption"], + ]; + const missing = requiredSnippets + .filter(([content, snippet]) => !content.includes(snippet)) + .map(([, , label]) => label); + if (missing.length > 0) { + throw new Error(`Missing admin diagnostic repair preview hooks: ${missing.join(", ")}`); + } + console.log("[DigitalEmployeeCheck] admin diagnostic repair preview hooks OK"); +} + function checkDigitalAdminDailyReportWorkbench() { console.log("\n[DigitalEmployeeCheck] Verify admin daily report workbench hooks"); const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend"); @@ -2770,6 +2864,7 @@ function main() { checkDigitalAdminNotificationWorkbench(); checkDigitalAdminArtifactLifecycleWorkbench(); checkDigitalAdminAuditDiagnosticsWorkbench(); + checkDigitalAdminDiagnosticRepairPreview(); checkDigitalAdminDailyReportWorkbench(); checkDigitalAdminGovernanceCommandWorkbench(); checkLocalDatabaseSchema(); diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index 05ba1f02..5e5a0d40 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -604,8 +604,8 @@ 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` 已提供本地审计业务视图,诊断与审计风险通知联动已开始闭环。管理端正式审计视图预览和正式管理端审计诊断预览已开始吸收,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 等敏感字段。正式管理端审计归档账单预览已开始吸收,审计诊断详情可展示 `audit_archive_id`、`archive_status`、`archive_location`、`billing_estimate_id`、`billing_source` 和 `estimated`,用于解释脱敏归档位置与账单估算来源。 - - 缺口:真实计费账单、独立 sandbox audit 文件归档、自动诊断修复动作和正式外部管理端审计视图尚未统一吸收;当前账单仍是估算,审计归档只记录脱敏摘要和归档位置。 + - 已吸收:同步失败诊断较完整,部分 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 风险转为可筛选、可标记和可安全重试的运营事项。正式管理端诊断修复预览已开始吸收,审计诊断详情可展示诊断 ID、问题码、建议动作、修复动作、修复状态和安全修复禁用状态,并只记录重试诊断、复核、忽略和修复意图,不执行 shell、不修改系统配置、不自动修复。发送账单审计外壳已开始吸收,后端提供估算账单查询和审计归档记录接口,qiming 运营台可显示 Token/估算成本并标记脱敏归档;归档记录会剔除 token、authorization、raw_input、prompt 和 command 等敏感字段。正式管理端审计归档账单预览已开始吸收,审计诊断详情可展示 `audit_archive_id`、`archive_status`、`archive_location`、`billing_estimate_id`、`billing_source` 和 `estimated`,用于解释脱敏归档位置与账单估算来源。 + - 缺口:真实计费账单、独立 sandbox audit 文件归档、真实自动诊断修复执行器和高风险修复审批流尚未统一吸收;当前账单仍是估算,审计归档只记录脱敏摘要和归档位置。 - 建议:下一轮围绕管理端正式审计视图和诊断修复动作,把本地可追溯审计继续推进到可运营处置。 12. **管理端业务查询模型**