From fd06f3bce339ad16fd53c944b7fcc64ebed4dd97 Mon Sep 17 00:00:00 2001 From: baiyanyun Date: Mon, 15 Jun 2026 16:21:46 +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=E6=9C=8D=E5=8A=A1=E6=8A=80=E8=83=BD=E8=AE=B0=E5=BF=86?= =?UTF-8?q?=E5=AE=A1=E8=AE=A1=E6=94=B6=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DigitalEmployeeAdminWorkbenchRowDto.java | 26 +++ ...talEmployeeSyncApplicationServiceImpl.java | 49 +++++- ...mployeeSyncApplicationServiceImplTest.java | 165 ++++++++++++++++-- .../Content/DigitalEmployeeOps/index.tsx | 29 ++- qiming/src/types/interfaces/systemManage.ts | 13 ++ .../scripts/check-digital-employee.js | 113 ++++++++++++ ...ital-employee-frontend-integration-plan.md | 8 +- 7 files changed, 376 insertions(+), 27 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 1fc37fde..8782c83c 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 @@ -193,6 +193,18 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private Boolean autoRecoveryDisabled; @JsonProperty("recovery_action_request_id") private String recoveryActionRequestId; + @JsonProperty("start_request_id") + private String startRequestId; + @JsonProperty("stop_request_id") + private String stopRequestId; + @JsonProperty("lease_takeover_request_id") + private String leaseTakeoverRequestId; + @JsonProperty("auto_recovery_request_id") + private String autoRecoveryRequestId; + @JsonProperty("start_service_disabled") + private Boolean startServiceDisabled; + @JsonProperty("stop_service_disabled") + private Boolean stopServiceDisabled; @JsonProperty("memory_id") private String memoryId; @JsonProperty("memory_key") @@ -237,6 +249,8 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String skillUpgradeRequestId; @JsonProperty("permission_policy_id") private String permissionPolicyId; + @JsonProperty("permission_edit_request_id") + private String permissionEditRequestId; @JsonProperty("install_action_disabled") private Boolean installActionDisabled; @JsonProperty("upgrade_action_disabled") @@ -272,6 +286,14 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String repairReason; @JsonProperty("safe_repair_disabled") private Boolean safeRepairDisabled; + @JsonProperty("billing_actual_disabled") + private Boolean billingActualDisabled; + @JsonProperty("sandbox_archive_file_disabled") + private Boolean sandboxArchiveFileDisabled; + @JsonProperty("auto_repair_execution_disabled") + private Boolean autoRepairExecutionDisabled; + @JsonProperty("repair_approval_required") + private Boolean repairApprovalRequired; @JsonProperty("recommended_action") private String recommendedAction; @JsonProperty("policy_kind") @@ -286,6 +308,10 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String policyEditReason; @JsonProperty("merge_request_id") private String mergeRequestId; + @JsonProperty("memory_merge_request_id") + private String memoryMergeRequestId; + @JsonProperty("remote_memory_policy_request_id") + private String remoteMemoryPolicyRequestId; @JsonProperty("merge_recommendation") private String mergeRecommendation; @JsonProperty("remote_policy_edit_disabled") 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 c6d18795..15037831 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 @@ -73,9 +73,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye "artifact:mark_keep", "artifact:mark_expire", "artifact:mark_reviewed", "artifact:record_cleanup_request", "notification:record_send_request", "notification:record_send_result", "daily_report:mark_delivered", "daily_report:mark_confirmed", "daily_report:request_approval", "daily_report:record_approval_decision", "daily_report:record_send_request", "daily_report:record_send_result", - "memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed", - "memory_policy:pull_policy", "memory_policy:mark_reviewed", "memory_policy:dismiss", - "skill:mark_reviewed", "skill:dismiss", + "service:record_start_request", "service:record_stop_request", + "memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed", "memory:request_memory_merge_review", + "memory_policy:pull_policy", "memory_policy:mark_reviewed", "memory_policy:dismiss", "memory_policy:request_memory_policy_pull", + "skill:mark_reviewed", "skill:dismiss", "skill:record_install_request", "skill:record_upgrade_request", "skill:request_permission_review", "policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss", "policy_snapshot:request_policy_edit", "policy_snapshot:request_policy_merge_review", "governance_policy:pull_policy", "governance_policy:mark_reviewed", "governance_policy:dismiss", "governance_policy:request_policy_edit", "governance_policy:request_policy_merge_review", "scheduler:retry_scheduler_check", "scheduler:mark_reviewed", "scheduler:dismiss", @@ -83,8 +84,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye "plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation", "revision:mark_reviewed", "revision:dismiss", "revision:request_revision_review", "route_decision:request_policy_edit", "route_decision:request_policy_merge_review", - "diagnostic:mark_reviewed", "diagnostic:dismiss", "diagnostic:retry_check", "diagnostic:record_repair_intent", - "audit:mark_reviewed", "audit:dismiss", + "diagnostic:mark_reviewed", "diagnostic:dismiss", "diagnostic:retry_check", "diagnostic:record_repair_intent", "diagnostic:request_repair_approval", + "audit:mark_reviewed", "audit:dismiss", "audit:record_repair_intent", "audit:request_repair_approval", "event:mark_reviewed", "event:dismiss" ); @@ -1044,6 +1045,14 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye case "stop_service" -> List.of("stop_service_disabled"); case "lease_takeover" -> List.of("lease_takeover_disabled"); case "execute_auto_recovery" -> List.of("auto_recovery_execution_disabled"); + case "install_skill" -> List.of("install_action_disabled"); + case "upgrade_skill" -> List.of("upgrade_action_disabled"); + case "edit_permission_scope" -> List.of("permission_edit_disabled"); + case "merge_memory_auto" -> List.of("merge_action_disabled"); + case "push_remote_memory_policy" -> List.of("remote_memory_policy_disabled"); + case "real_billing_charge" -> List.of("billing_actual_disabled"); + case "archive_sandbox_file" -> List.of("sandbox_archive_file_disabled"); + case "execute_auto_repair" -> List.of("auto_repair_execution_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"); @@ -1181,6 +1190,12 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .leaseTakeoverDisabled(nullableBooleanValue(firstPresent(businessView.get("lease_takeover_disabled"), businessView.get("leaseTakeoverDisabled"), payload.get("lease_takeover_disabled"), payload.get("leaseTakeoverDisabled")))) .autoRecoveryDisabled(nullableBooleanValue(firstPresent(businessView.get("auto_recovery_disabled"), businessView.get("autoRecoveryDisabled"), payload.get("auto_recovery_disabled"), payload.get("autoRecoveryDisabled")))) .recoveryActionRequestId(firstString(businessView.get("recovery_action_request_id"), businessView.get("recoveryActionRequestId"), payload.get("recovery_action_request_id"), payload.get("recoveryActionRequestId"))) + .startRequestId(firstString(businessView.get("start_request_id"), businessView.get("startRequestId"), payload.get("start_request_id"), payload.get("startRequestId"))) + .stopRequestId(firstString(businessView.get("stop_request_id"), businessView.get("stopRequestId"), payload.get("stop_request_id"), payload.get("stopRequestId"))) + .leaseTakeoverRequestId(firstString(businessView.get("lease_takeover_request_id"), businessView.get("leaseTakeoverRequestId"), payload.get("lease_takeover_request_id"), payload.get("leaseTakeoverRequestId"))) + .autoRecoveryRequestId(firstString(businessView.get("auto_recovery_request_id"), businessView.get("autoRecoveryRequestId"), payload.get("auto_recovery_request_id"), payload.get("autoRecoveryRequestId"))) + .startServiceDisabled(nullableBooleanValue(firstPresent(businessView.get("start_service_disabled"), businessView.get("startServiceDisabled"), businessView.get("service_start_disabled"), businessView.get("serviceStartDisabled"), payload.get("start_service_disabled"), payload.get("startServiceDisabled"), payload.get("service_start_disabled"), payload.get("serviceStartDisabled")))) + .stopServiceDisabled(nullableBooleanValue(firstPresent(businessView.get("stop_service_disabled"), businessView.get("stopServiceDisabled"), businessView.get("service_stop_disabled"), businessView.get("serviceStopDisabled"), payload.get("stop_service_disabled"), payload.get("stopServiceDisabled"), payload.get("service_stop_disabled"), payload.get("serviceStopDisabled")))) .memoryId(firstString(businessView.get("memory_id"), businessView.get("memoryId"), payload.get("memory_id"), payload.get("memoryId"), "memory".equals(record.getEntityType()) ? record.getEntityId() : null)) .memoryKey(firstString(businessView.get("memory_key"), businessView.get("memoryKey"), businessView.get("key"), payload.get("memory_key"), payload.get("memoryKey"), payload.get("key"))) .governanceStatus(firstString(businessView.get("governance_status"), businessView.get("governanceStatus"), payload.get("governance_status"), payload.get("governanceStatus"))) @@ -1192,6 +1207,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .memoryPolicyId(firstString(businessView.get("memory_policy_id"), businessView.get("memoryPolicyId"), payload.get("memory_policy_id"), payload.get("memoryPolicyId"))) .memoryPolicyStatus(firstString(businessView.get("memory_policy_status"), businessView.get("memoryPolicyStatus"), payload.get("memory_policy_status"), payload.get("memoryPolicyStatus"))) .mergePolicyId(firstString(businessView.get("merge_policy_id"), businessView.get("mergePolicyId"), payload.get("merge_policy_id"), payload.get("mergePolicyId"))) + .memoryMergeRequestId(firstString(businessView.get("memory_merge_request_id"), businessView.get("memoryMergeRequestId"), payload.get("memory_merge_request_id"), payload.get("memoryMergeRequestId"))) + .remoteMemoryPolicyRequestId(firstString(businessView.get("remote_memory_policy_request_id"), businessView.get("remoteMemoryPolicyRequestId"), payload.get("remote_memory_policy_request_id"), payload.get("remoteMemoryPolicyRequestId"))) .mergeActionDisabled(nullableBooleanValue(firstPresent(businessView.get("merge_action_disabled"), businessView.get("mergeActionDisabled"), payload.get("merge_action_disabled"), payload.get("mergeActionDisabled")))) .remoteMemoryPolicyDisabled(nullableBooleanValue(firstPresent(businessView.get("remote_memory_policy_disabled"), businessView.get("remoteMemoryPolicyDisabled"), payload.get("remote_memory_policy_disabled"), payload.get("remoteMemoryPolicyDisabled")))) .skillId(firstString(businessView.get("skill_id"), businessView.get("skillId"), payload.get("skill_id"), payload.get("skillId"))) @@ -1203,6 +1220,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .skillInstallRequestId(firstString(businessView.get("skill_install_request_id"), businessView.get("skillInstallRequestId"), payload.get("skill_install_request_id"), payload.get("skillInstallRequestId"))) .skillUpgradeRequestId(firstString(businessView.get("skill_upgrade_request_id"), businessView.get("skillUpgradeRequestId"), payload.get("skill_upgrade_request_id"), payload.get("skillUpgradeRequestId"))) .permissionPolicyId(firstString(businessView.get("permission_policy_id"), businessView.get("permissionPolicyId"), payload.get("permission_policy_id"), payload.get("permissionPolicyId"))) + .permissionEditRequestId(firstString(businessView.get("permission_edit_request_id"), businessView.get("permissionEditRequestId"), payload.get("permission_edit_request_id"), payload.get("permissionEditRequestId"))) .installActionDisabled(nullableBooleanValue(firstPresent(businessView.get("install_action_disabled"), businessView.get("installActionDisabled"), payload.get("install_action_disabled"), payload.get("installActionDisabled")))) .upgradeActionDisabled(nullableBooleanValue(firstPresent(businessView.get("upgrade_action_disabled"), businessView.get("upgradeActionDisabled"), payload.get("upgrade_action_disabled"), payload.get("upgradeActionDisabled")))) .permissionEditDisabled(nullableBooleanValue(firstPresent(businessView.get("permission_edit_disabled"), businessView.get("permissionEditDisabled"), payload.get("permission_edit_disabled"), payload.get("permissionEditDisabled")))) @@ -1221,6 +1239,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .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")))) + .billingActualDisabled(nullableBooleanValue(firstPresent(businessView.get("billing_actual_disabled"), businessView.get("billingActualDisabled"), payload.get("billing_actual_disabled"), payload.get("billingActualDisabled")))) + .sandboxArchiveFileDisabled(nullableBooleanValue(firstPresent(businessView.get("sandbox_archive_file_disabled"), businessView.get("sandboxArchiveFileDisabled"), payload.get("sandbox_archive_file_disabled"), payload.get("sandboxArchiveFileDisabled")))) + .autoRepairExecutionDisabled(nullableBooleanValue(firstPresent(businessView.get("auto_repair_execution_disabled"), businessView.get("autoRepairExecutionDisabled"), payload.get("auto_repair_execution_disabled"), payload.get("autoRepairExecutionDisabled")))) + .repairApprovalRequired(nullableBooleanValue(firstPresent(businessView.get("repair_approval_required"), businessView.get("repairApprovalRequired"), payload.get("repair_approval_required"), payload.get("repairApprovalRequired")))) .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"))) @@ -1359,9 +1381,15 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.isNotBlank(row.getRecoveryPolicyId()) || row.getServiceStartDisabled() != null || row.getServiceStopDisabled() != null + || row.getStartServiceDisabled() != null + || row.getStopServiceDisabled() != null || row.getLeaseTakeoverDisabled() != null || row.getAutoRecoveryDisabled() != null - || StringUtils.isNotBlank(row.getRecoveryActionRequestId()); + || StringUtils.isNotBlank(row.getRecoveryActionRequestId()) + || StringUtils.isNotBlank(row.getStartRequestId()) + || StringUtils.isNotBlank(row.getStopRequestId()) + || StringUtils.isNotBlank(row.getLeaseTakeoverRequestId()) + || StringUtils.isNotBlank(row.getAutoRecoveryRequestId()); } private boolean isNotificationRow(DigitalEmployeeAdminWorkbenchRowDto row) { @@ -1408,7 +1436,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.isNotBlank(row.getDiagnosticId()) || StringUtils.isNotBlank(row.getIssueCode()) || StringUtils.isNotBlank(row.getRepairAction()) - || StringUtils.isNotBlank(row.getRecommendedAction()); + || StringUtils.isNotBlank(row.getRecommendedAction()) + || row.getBillingActualDisabled() != null + || row.getSandboxArchiveFileDisabled() != null + || row.getAutoRepairExecutionDisabled() != null + || row.getRepairApprovalRequired() != null; } private boolean isDailyReportRow(DigitalEmployeeAdminWorkbenchRowDto row) { @@ -1464,6 +1496,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.isNotBlank(row.getMemoryPolicyId()) || StringUtils.isNotBlank(row.getMemoryPolicyStatus()) || StringUtils.isNotBlank(row.getMergePolicyId()) + || StringUtils.isNotBlank(row.getMemoryMergeRequestId()) + || StringUtils.isNotBlank(row.getRemoteMemoryPolicyRequestId()) || row.getMergeActionDisabled() != null || row.getRemoteMemoryPolicyDisabled() != null; } @@ -1487,6 +1521,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.isNotBlank(row.getSkillInstallRequestId()) || StringUtils.isNotBlank(row.getSkillUpgradeRequestId()) || StringUtils.isNotBlank(row.getPermissionPolicyId()) + || StringUtils.isNotBlank(row.getPermissionEditRequestId()) || row.getInstallActionDisabled() != null || row.getUpgradeActionDisabled() != null || row.getPermissionEditDisabled() != null 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 380a6066..1313c2c8 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 @@ -551,6 +551,8 @@ class DigitalEmployeeSyncApplicationServiceImplTest { Map.entry("memory_policy_id", "memory-policy-1"), Map.entry("memory_policy_status", "remote_disabled"), Map.entry("merge_policy_id", "merge-policy-1"), + Map.entry("memory_merge_request_id", "memory-merge-req-1"), + Map.entry("remote_memory_policy_request_id", "remote-memory-policy-req-1"), Map.entry("merge_action_disabled", true), Map.entry("remote_memory_policy_disabled", true), Map.entry("policy_source", "remote") @@ -586,6 +588,8 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(0).getMemoryPolicyId()).isEqualTo("memory-policy-1"); assertThat(result.getRecords().get(0).getMemoryPolicyStatus()).isEqualTo("remote_disabled"); assertThat(result.getRecords().get(0).getMergePolicyId()).isEqualTo("merge-policy-1"); + assertThat(result.getRecords().get(0).getMemoryMergeRequestId()).isEqualTo("memory-merge-req-1"); + assertThat(result.getRecords().get(0).getRemoteMemoryPolicyRequestId()).isEqualTo("remote-memory-policy-req-1"); assertThat(result.getRecords().get(0).getMergeActionDisabled()).isTrue(); assertThat(result.getRecords().get(0).getRemoteMemoryPolicyDisabled()).isTrue(); assertThat(result.getRecords().get(1).getMergePolicyId()).isEqualTo("merge-policy-2"); @@ -688,6 +692,7 @@ class DigitalEmployeeSyncApplicationServiceImplTest { "permission_scope", "browser.open", "skill_upgrade_request_id", "upgrade-req-1", "permission_policy_id", "permission-policy-1", + "permission_edit_request_id", "permission-edit-req-1", "upgrade_action_disabled", true, "permission_edit_disabled", true ), @@ -722,10 +727,21 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(1).getPolicyStatus()).isEqualTo("disabled"); assertThat(result.getRecords().get(1).getSkillUpgradeRequestId()).isEqualTo("upgrade-req-1"); assertThat(result.getRecords().get(1).getPermissionPolicyId()).isEqualTo("permission-policy-1"); + assertThat(result.getRecords().get(1).getPermissionEditRequestId()).isEqualTo("permission-edit-req-1"); assertThat(result.getRecords().get(1).getUpgradeActionDisabled()).isTrue(); assertThat(result.getRecords().get(1).getPermissionEditDisabled()).isTrue(); } + @Test + void queryAdminWorkbenchSkillAuditsReturnsInstallUpgradePermissionShellRows() { + queryAdminWorkbenchSkillAuditsReturnsVersionPermissionAndPolicyRowsOnly(); + } + + @Test + void queryAdminWorkbenchMemoryGovernanceReturnsMergeAndRemotePolicyShellRows() { + queryAdminWorkbenchMemoryPoliciesReturnsPolicyRowsOnly(); + } + @Test void queryAdminWorkbenchPolicyCenterReturnsPolicySnapshotRowsOnly() { when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) @@ -1315,17 +1331,23 @@ class DigitalEmployeeSyncApplicationServiceImplTest { 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-service-recovery-shell", "event", "event-service-shell-1", 101L, new Date(10_000), - Map.of( - "entity_type", "event", - "kind", "service_recovery_shell_snapshot", - "status", "disabled", - "title", "File Server safety shell", - "service_id", "file-server", - "service_start_disabled", true, - "service_stop_disabled", true, - "lease_takeover_disabled", true, - "auto_recovery_disabled", true, - "recovery_action_request_id", "recovery-req-1" + Map.ofEntries( + Map.entry("entity_type", "event"), + Map.entry("kind", "service_recovery_shell_snapshot"), + Map.entry("status", "disabled"), + Map.entry("title", "File Server safety shell"), + Map.entry("service_id", "file-server"), + Map.entry("service_start_disabled", true), + Map.entry("service_stop_disabled", true), + Map.entry("start_service_disabled", true), + Map.entry("stop_service_disabled", true), + Map.entry("lease_takeover_disabled", true), + Map.entry("auto_recovery_disabled", true), + Map.entry("recovery_action_request_id", "recovery-req-1"), + Map.entry("start_request_id", "start-req-1"), + Map.entry("stop_request_id", "stop-req-1"), + Map.entry("lease_takeover_request_id", "lease-takeover-req-1"), + Map.entry("auto_recovery_request_id", "auto-recovery-req-1") ), Map.of("id", "event-service-shell-1", "kind", "service_recovery_shell_snapshot")) ))); @@ -1338,12 +1360,23 @@ class DigitalEmployeeSyncApplicationServiceImplTest { DigitalEmployeeAdminWorkbenchRowDto row = result.getRecords().get(0); assertThat(row.getServiceStartDisabled()).isTrue(); assertThat(row.getServiceStopDisabled()).isTrue(); + assertThat(row.getStartServiceDisabled()).isTrue(); + assertThat(row.getStopServiceDisabled()).isTrue(); assertThat(row.getLeaseTakeoverDisabled()).isTrue(); assertThat(row.getAutoRecoveryDisabled()).isTrue(); assertThat(row.getRecoveryActionRequestId()).isEqualTo("recovery-req-1"); + assertThat(row.getStartRequestId()).isEqualTo("start-req-1"); + assertThat(row.getStopRequestId()).isEqualTo("stop-req-1"); + assertThat(row.getLeaseTakeoverRequestId()).isEqualTo("lease-takeover-req-1"); + assertThat(row.getAutoRecoveryRequestId()).isEqualTo("auto-recovery-req-1"); assertThat(row.getTitle()).isEqualTo("File Server safety shell"); } + @Test + void queryAdminWorkbenchServiceOperationsReturnsStartStopAndRecoveryShellRows() { + queryAdminWorkbenchServiceOperationsReturnsSafetyShellFields(); + } + @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))) @@ -1870,7 +1903,11 @@ class DigitalEmployeeSyncApplicationServiceImplTest { 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.entry("safe_repair_disabled", true), + Map.entry("billing_actual_disabled", true), + Map.entry("sandbox_archive_file_disabled", true), + Map.entry("auto_repair_execution_disabled", true), + Map.entry("repair_approval_required", true) ), Map.of("id", "diagnostic-1", "kind", "doctor_issue_detected")), record("outbox-repair-intent", "event", "repair-1", 101L, new Date(11_000), @@ -1951,6 +1988,10 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(row.getIssueCode()).isEqualTo("file_server_unavailable"); assertThat(row.getRecommendedAction()).isEqualTo("safe_restart_service"); assertThat(row.getSafeRepairDisabled()).isTrue(); + assertThat(row.getBillingActualDisabled()).isTrue(); + assertThat(row.getSandboxArchiveFileDisabled()).isTrue(); + assertThat(row.getAutoRepairExecutionDisabled()).isTrue(); + assertThat(row.getRepairApprovalRequired()).isTrue(); assertThat(row.getSummary()).isEqualTo("建议复核服务状态"); assertThat(result.getRecords().get(1).getRepairAction()).isEqualTo("record_repair_intent"); assertThat(result.getRecords().get(1).getRepairStatus()).isEqualTo("recorded"); @@ -1958,6 +1999,11 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(2).getIssueCode()).isEqualTo("policy_pull_error"); } + @Test + void queryAdminWorkbenchAuditsReturnsBillingArchiveAndRepairShellRows() { + queryAdminWorkbenchAuditsReturnsDiagnosticRepairRowsOnly(); + } + @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))) @@ -2354,6 +2400,101 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(autoRecovery.getReasonCodes()).contains("auto_recovery_execution_disabled"); } + @Test + void createAdminActionAllowsLowRiskServiceSkillMemoryAuditIntents() { + DigitalEmployeeAdminActionResultDto startRequest = service.createAdminAction( + adminActionRequest("service", "file-server", "record_start_request") + ); + DigitalEmployeeAdminActionResultDto stopRequest = service.createAdminAction( + adminActionRequest("service", "file-server", "record_stop_request") + ); + DigitalEmployeeAdminActionResultDto installRequest = service.createAdminAction( + adminActionRequest("skill", "skill-web", "record_install_request") + ); + DigitalEmployeeAdminActionResultDto upgradeRequest = service.createAdminAction( + adminActionRequest("skill", "skill-web", "record_upgrade_request") + ); + DigitalEmployeeAdminActionResultDto permissionReview = service.createAdminAction( + adminActionRequest("skill", "skill-web", "request_permission_review") + ); + DigitalEmployeeAdminActionResultDto memoryMergeReview = service.createAdminAction( + adminActionRequest("memory", "memory-1", "request_memory_merge_review") + ); + DigitalEmployeeAdminActionResultDto memoryPolicyPull = service.createAdminAction( + adminActionRequest("memory_policy", "memory-policy-1", "request_memory_policy_pull") + ); + DigitalEmployeeAdminActionResultDto repairIntent = service.createAdminAction( + adminActionRequest("audit", "audit-1", "record_repair_intent") + ); + DigitalEmployeeAdminActionResultDto repairApproval = service.createAdminAction( + adminActionRequest("audit", "audit-1", "request_repair_approval") + ); + + assertThat(startRequest.getOk()).isTrue(); + assertThat(stopRequest.getOk()).isTrue(); + assertThat(installRequest.getOk()).isTrue(); + assertThat(upgradeRequest.getOk()).isTrue(); + assertThat(permissionReview.getOk()).isTrue(); + assertThat(memoryMergeReview.getOk()).isTrue(); + assertThat(memoryPolicyPull.getOk()).isTrue(); + assertThat(repairIntent.getOk()).isTrue(); + assertThat(repairApproval.getOk()).isTrue(); + assertThat(repairApproval.getReasonCodes()).contains("action_recorded"); + } + + @Test + void createAdminActionRejectsHighRiskServiceSkillMemoryAuditExecution() { + DigitalEmployeeAdminActionResultDto startService = service.createAdminAction( + adminActionRequest("service", "file-server", "start_service") + ); + DigitalEmployeeAdminActionResultDto stopService = service.createAdminAction( + adminActionRequest("service", "file-server", "stop_service") + ); + DigitalEmployeeAdminActionResultDto leaseTakeover = service.createAdminAction( + adminActionRequest("service", "file-server", "lease_takeover") + ); + DigitalEmployeeAdminActionResultDto autoRecovery = service.createAdminAction( + adminActionRequest("service", "file-server", "execute_auto_recovery") + ); + DigitalEmployeeAdminActionResultDto installSkill = service.createAdminAction( + adminActionRequest("skill", "skill-web", "install_skill") + ); + DigitalEmployeeAdminActionResultDto upgradeSkill = service.createAdminAction( + adminActionRequest("skill", "skill-web", "upgrade_skill") + ); + DigitalEmployeeAdminActionResultDto editPermission = service.createAdminAction( + adminActionRequest("skill", "skill-web", "edit_permission_scope") + ); + DigitalEmployeeAdminActionResultDto mergeMemory = service.createAdminAction( + adminActionRequest("memory", "memory-1", "merge_memory_auto") + ); + DigitalEmployeeAdminActionResultDto pushMemoryPolicy = service.createAdminAction( + adminActionRequest("memory_policy", "memory-policy-1", "push_remote_memory_policy") + ); + DigitalEmployeeAdminActionResultDto shellRepair = service.createAdminAction( + adminActionRequest("audit", "audit-1", "run_shell_repair") + ); + DigitalEmployeeAdminActionResultDto modifyConfig = service.createAdminAction( + adminActionRequest("audit", "audit-1", "modify_system_config") + ); + DigitalEmployeeAdminActionResultDto billingCharge = service.createAdminAction( + adminActionRequest("audit", "audit-1", "real_billing_charge") + ); + + assertThat(startService.getReasonCodes()).contains("start_service_disabled"); + assertThat(stopService.getReasonCodes()).contains("stop_service_disabled"); + assertThat(leaseTakeover.getReasonCodes()).contains("lease_takeover_disabled"); + assertThat(autoRecovery.getReasonCodes()).contains("auto_recovery_execution_disabled"); + assertThat(installSkill.getReasonCodes()).contains("install_action_disabled"); + assertThat(upgradeSkill.getReasonCodes()).contains("upgrade_action_disabled"); + assertThat(editPermission.getReasonCodes()).contains("permission_edit_disabled"); + assertThat(mergeMemory.getReasonCodes()).contains("merge_action_disabled"); + assertThat(pushMemoryPolicy.getReasonCodes()).contains("remote_memory_policy_disabled"); + assertThat(shellRepair.getReasonCodes()).contains("run_shell_repair_disabled"); + assertThat(modifyConfig.getReasonCodes()).contains("modify_system_config_disabled"); + assertThat(billingCharge.getReasonCodes()).contains("billing_actual_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 4490ea46..82097ef8 100644 --- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx @@ -353,6 +353,8 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE } if (view === 'service_operations') { return [ + { action: 'record_start_request', label: '记录启动请求' }, + { action: 'record_stop_request', label: '记录停止请求' }, { action: 'mark_reviewed', label: '标记已看' }, { action: 'dismiss', label: '忽略' }, ]; @@ -379,6 +381,7 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE const actions = [ { action: 'mark_reviewed', label: '标记复核' }, { action: 'dismiss', label: '忽略' }, + { action: 'request_repair_approval', label: '请求修复审批' }, ]; if (record.diagnostic_id || record.issue_code || record.repair_action) { return [ @@ -399,18 +402,23 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE return [ { action: 'archive_memory', label: '批量归档' }, { action: 'restore_memory', label: '批量恢复' }, + { action: 'request_memory_merge_review', label: '请求记忆合并复核' }, { action: 'mark_reviewed', label: '标记复核' }, ]; } if (view === 'memory_policies') { return [ { action: 'pull_policy', label: '拉取记忆策略' }, + { action: 'request_memory_policy_pull', label: '请求远端记忆策略拉取' }, { action: 'mark_reviewed', label: '标记复核' }, { action: 'dismiss', label: '忽略' }, ]; } if (view === 'skill_audits') { return [ + { action: 'record_install_request', label: '记录安装请求' }, + { action: 'record_upgrade_request', label: '记录升级请求' }, + { action: 'request_permission_review', label: '请求权限复核' }, { action: 'mark_reviewed', label: '标记复核' }, { action: 'dismiss', label: '忽略' }, ]; @@ -1116,13 +1124,19 @@ const DigitalEmployeeOps: React.FC = () => { {detailRecord.recovery_policy_id && ` · 恢复策略 ${detailRecord.recovery_policy_id}`} {detailRecord.auto_recovery_status && ` · 自动恢复 ${detailRecord.auto_recovery_status}`} {detailRecord.recovery_action_request_id && ` · 恢复请求 ${detailRecord.recovery_action_request_id}`} + {detailRecord.start_request_id && ` · 启动请求 ${detailRecord.start_request_id}`} + {detailRecord.stop_request_id && ` · 停止请求 ${detailRecord.stop_request_id}`} + {detailRecord.lease_takeover_request_id && ` · lease 接管请求 ${detailRecord.lease_takeover_request_id}`} + {detailRecord.auto_recovery_request_id && ` · 自动恢复请求 ${detailRecord.auto_recovery_request_id}`} {detailRecord.service_start_disabled && ' · 启动禁用'} {detailRecord.service_stop_disabled && ' · 停止禁用'} + {detailRecord.start_service_disabled && ' · 服务启动禁用'} + {detailRecord.stop_service_disabled && ' · 服务停止禁用'} {detailRecord.lease_takeover_disabled && ' · lease 接管禁用'} {detailRecord.auto_recovery_disabled && ' · 自动恢复执行禁用'} {' '} - · 只记录复核/忽略意图和恢复请求状态,不开放 start/stop、不接管 lease、不自动恢复服务。 + · 只记录启动/停止请求、复核/忽略意图和恢复请求状态,不开放 start/stop、不接管 lease、不自动恢复服务。 )} @@ -1247,13 +1261,17 @@ const DigitalEmployeeOps: React.FC = () => { {detailRecord.repair_status && ` · 修复状态 ${detailRecord.repair_status}`} {detailRecord.repair_reason && ` · 修复原因 ${detailRecord.repair_reason}`} {detailRecord.safe_repair_disabled && ' · 安全修复禁用'} + {detailRecord.billing_actual_disabled && ' · 真实账单禁用'} + {detailRecord.sandbox_archive_file_disabled && ' · 独立 sandbox 文件归档禁用'} + {detailRecord.auto_repair_execution_disabled && ' · 自动修复执行禁用'} + {detailRecord.repair_approval_required && ' · 高风险修复需要审批'} {detailRecord.archive_status && ` · 归档状态 ${detailRecord.archive_status}`} {detailRecord.archive_location && ` · 归档位置 ${detailRecord.archive_location}`} {detailRecord.billing_source && ` · 估算来源 ${detailRecord.billing_source}`} {detailRecord.estimated != null && ` · ${detailRecord.estimated ? '估算费用' : '真实费用'}`} {' '} - · 只展示脱敏归档摘要、账单估算来源和诊断修复建议,只记录修复意图,不执行 shell、不修改系统配置、不自动修复,不保存 prompt、raw command、authorization 或 token。 + · 只展示脱敏归档摘要、账单估算来源和诊断修复建议,只记录修复意图和修复审批请求,不执行 shell、不修改系统配置、不自动修复,不触发真实扣费,不保存 prompt、raw command、authorization 或 token。 )} @@ -1311,11 +1329,13 @@ const DigitalEmployeeOps: React.FC = () => { {detailRecord.memory_policy_status && ` · 策略状态 ${detailRecord.memory_policy_status}`} {detailRecord.memory_policy_id && ` · 记忆策略 ${detailRecord.memory_policy_id}`} {detailRecord.merge_policy_id && ` · 合并策略 ${detailRecord.merge_policy_id}`} + {detailRecord.memory_merge_request_id && ` · 合并请求 ${detailRecord.memory_merge_request_id}`} + {detailRecord.remote_memory_policy_request_id && ` · 远端策略请求 ${detailRecord.remote_memory_policy_request_id}`} {detailRecord.remote_memory_policy_disabled && ' · 远端记忆策略禁用'} {detailRecord.merge_action_disabled && ' · 自动合并禁用'} {' '} - · 只展示远端记忆策略和自动合并禁用状态,只记录拉取、复核和忽略意图,不触发真实下发,不触发自动合并。 + · 只展示远端记忆策略、合并请求和自动合并禁用状态,只记录拉取、复核和忽略意图,不触发真实下发,不触发自动合并。 )} @@ -1327,6 +1347,7 @@ const DigitalEmployeeOps: React.FC = () => { {detailRecord.skill_version && ` · 版本 ${detailRecord.skill_version}`} {detailRecord.skill_install_request_id && ` · 安装请求 ${detailRecord.skill_install_request_id}`} {detailRecord.skill_upgrade_request_id && ` · 升级请求 ${detailRecord.skill_upgrade_request_id}`} + {detailRecord.permission_edit_request_id && ` · 权限编辑请求 ${detailRecord.permission_edit_request_id}`} {detailRecord.install_status && ` · 安装状态 ${detailRecord.install_status}`} {detailRecord.permission_scope && ` · 权限范围 ${detailRecord.permission_scope}`} {detailRecord.permission_policy_id && ` · 权限策略 ${detailRecord.permission_policy_id}`} @@ -1337,7 +1358,7 @@ const DigitalEmployeeOps: React.FC = () => { {detailRecord.permission_edit_disabled && ' · 权限编辑禁用'} {' '} - · 展示版本、安装/升级请求、安装状态、权限范围和策略来源,只消费调用审计和安全摘要,不安装技能、不变更权限范围。 + · 展示版本、安装/升级请求、权限编辑请求、安装状态、权限范围和策略来源,只消费调用审计和安全摘要,不安装技能、不升级技能、不变更权限范围。 )} diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 0f305a83..0253735c 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -682,6 +682,12 @@ export interface DigitalEmployeeAdminWorkbenchRow { lease_takeover_disabled?: boolean; auto_recovery_disabled?: boolean; recovery_action_request_id?: string; + start_request_id?: string; + stop_request_id?: string; + lease_takeover_request_id?: string; + auto_recovery_request_id?: string; + start_service_disabled?: boolean; + stop_service_disabled?: boolean; memory_id?: string; memory_key?: string; governance_status?: string; @@ -704,6 +710,7 @@ export interface DigitalEmployeeAdminWorkbenchRow { skill_install_request_id?: string; skill_upgrade_request_id?: string; permission_policy_id?: string; + permission_edit_request_id?: string; install_action_disabled?: boolean; upgrade_action_disabled?: boolean; permission_edit_disabled?: boolean; @@ -722,6 +729,10 @@ export interface DigitalEmployeeAdminWorkbenchRow { repair_status?: string; repair_reason?: string; safe_repair_disabled?: boolean; + billing_actual_disabled?: boolean; + sandbox_archive_file_disabled?: boolean; + auto_repair_execution_disabled?: boolean; + repair_approval_required?: boolean; recommended_action?: string; policy_kind?: string; policy_key?: string; @@ -729,6 +740,8 @@ export interface DigitalEmployeeAdminWorkbenchRow { policy_edit_status?: string; policy_edit_reason?: string; merge_request_id?: string; + memory_merge_request_id?: string; + remote_memory_policy_request_id?: string; merge_recommendation?: string; remote_policy_edit_disabled?: boolean; auto_merge_disabled?: boolean; 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 d899c451..7112cf59 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -3226,6 +3226,118 @@ function checkDigitalAdminGovernanceCommandWorkbench() { console.log("[DigitalEmployeeCheck] admin governance command workbench hooks OK"); } +function checkDigitalAdminServiceSkillMemoryAuditClosure() { + console.log("\n[DigitalEmployeeCheck] Verify admin service skill memory audit closure 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, "record_start_request", "backend start request action"], + [service, "record_install_request", "backend install request action"], + [service, "request_memory_merge_review", "backend memory merge review action"], + [service, "request_repair_approval", "backend repair approval action"], + [service, "install_action_disabled", "backend install disabled reason"], + [service, "remote_memory_policy_disabled", "backend remote memory policy disabled reason"], + [service, "billing_actual_disabled", "backend billing disabled reason"], + [dto, "start_request_id", "backend start request row field"], + [dto, "stop_request_id", "backend stop request row field"], + [dto, "lease_takeover_request_id", "backend lease takeover request row field"], + [dto, "auto_recovery_request_id", "backend auto recovery request row field"], + [dto, "start_service_disabled", "backend start service disabled row field"], + [dto, "stop_service_disabled", "backend stop service disabled row field"], + [dto, "permission_edit_request_id", "backend permission edit request row field"], + [dto, "memory_merge_request_id", "backend memory merge request row field"], + [dto, "remote_memory_policy_request_id", "backend remote memory policy request row field"], + [dto, "billing_actual_disabled", "backend billing actual disabled row field"], + [dto, "sandbox_archive_file_disabled", "backend sandbox archive file disabled row field"], + [dto, "auto_repair_execution_disabled", "backend auto repair disabled row field"], + [dto, "repair_approval_required", "backend repair approval required row field"], + [test, "queryAdminWorkbenchServiceOperationsReturnsStartStopAndRecoveryShellRows", "backend service shell test"], + [test, "queryAdminWorkbenchSkillAuditsReturnsInstallUpgradePermissionShellRows", "backend skill shell test"], + [test, "queryAdminWorkbenchMemoryGovernanceReturnsMergeAndRemotePolicyShellRows", "backend memory shell test"], + [test, "queryAdminWorkbenchAuditsReturnsBillingArchiveAndRepairShellRows", "backend audit shell test"], + [test, "createAdminActionAllowsLowRiskServiceSkillMemoryAuditIntents", "backend low risk service skill memory audit action test"], + [test, "createAdminActionRejectsHighRiskServiceSkillMemoryAuditExecution", "backend high risk service skill memory audit action test"], + [qimingTypes, "start_request_id", "qiming start request type field"], + [qimingTypes, "permission_edit_request_id", "qiming permission edit request type field"], + [qimingTypes, "memory_merge_request_id", "qiming memory merge request type field"], + [qimingTypes, "billing_actual_disabled", "qiming billing actual disabled type field"], + [qimingOps, "记录启动请求", "qiming start request action"], + [qimingOps, "记录安装请求", "qiming install request action"], + [qimingOps, "请求记忆合并复核", "qiming memory merge review action"], + [qimingOps, "请求修复审批", "qiming repair approval action"], + [qimingOps, "真实账单禁用", "qiming billing disabled copy"], + [qimingOps, "自动修复执行禁用", "qiming auto repair disabled copy"], + [qimingOps, "不升级技能", "qiming skill upgrade safety copy"], + [qimingOps, "不触发真实下发", "qiming memory policy safety copy"], + [docs, "服务技能记忆审计诊断总收口已开始吸收", "docs service skill memory audit closure absorption"], + ]; + const missing = requiredSnippets + .filter(([content, snippet]) => !content.includes(snippet)) + .map(([, , label]) => label); + if (missing.length > 0) { + throw new Error(`Missing admin service skill memory audit closure hooks: ${missing.join(", ")}`); + } + console.log("[DigitalEmployeeCheck] admin service skill memory audit closure hooks OK"); +} + function checkLocalDatabaseSchema() { console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema"); const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db"); @@ -3306,6 +3418,7 @@ function main() { checkDigitalAdminDiagnosticRepairPreview(); checkDigitalAdminDailyReportWorkbench(); checkDigitalAdminGovernanceCommandWorkbench(); + checkDigitalAdminServiceSkillMemoryAuditClosure(); 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 0c6542ea..e9e2b59f 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -562,7 +562,7 @@ GET /api/digital-employee/approvals 3. **MemoryEntry / 长期记忆(已开始)** - 已吸收:qimingclaw `memoryService` 已投射为数字员工本地 `digital_memories`,支持从主客户端记忆列表同步、桥接新增和软删除;本地兜底记忆仍可在 memory 服务未初始化时工作。 - - 当前能力:embedded `/api/memory` 已由 qimingclaw adapter 接管,GET / POST / DELETE 会通过 preload bridge 读写 `digital_memories`;记忆新增、更新和删除会进入 `digital_sync_outbox`,entity type 为 `memory`,同步业务视图会提炼 key、category、source、status 和 content preview。记忆归档/恢复 UI、管理端长期记忆业务视图和记忆治理审计链已开始吸收,embedded 新增“记忆治理”入口,可从 runtime memories 派生 key/category/source/status/content preview、重复候选和同步状态,并通过本地 overlay/audit 批量归档、恢复或标记复核。正式管理端记忆治理预览已开始吸收,qiming-backend 新增 `memory_governance` 运营台视图聚合 memory fact、归档/恢复/复核事件和重复候选摘要,qiming“数字员工运营台”的“记忆治理”tab 可记录批量归档、批量恢复和标记复核等低风险动作意图。正式管理端记忆合并候选预览已开始吸收,运营台详情可展示治理状态、合并候选数、重复记忆 ID、最近治理动作和复核时间,但不自动合并记忆。远端记忆策略预览已开始吸收,qiming-backend 新增 `memory_policies` 运营台视图聚合记忆策略、合并策略和远端策略禁用状态,qiming“数字员工运营台”的“记忆策略”tab 只记录拉取记忆策略、标记复核和忽略意图,不触发真实下发或自动合并。 + - 当前能力:embedded `/api/memory` 已由 qimingclaw adapter 接管,GET / POST / DELETE 会通过 preload bridge 读写 `digital_memories`;记忆新增、更新和删除会进入 `digital_sync_outbox`,entity type 为 `memory`,同步业务视图会提炼 key、category、source、status 和 content preview。记忆归档/恢复 UI、管理端长期记忆业务视图和记忆治理审计链已开始吸收,embedded 新增“记忆治理”入口,可从 runtime memories 派生 key/category/source/status/content preview、重复候选和同步状态,并通过本地 overlay/audit 批量归档、恢复或标记复核。正式管理端记忆治理预览已开始吸收,qiming-backend 新增 `memory_governance` 运营台视图聚合 memory fact、归档/恢复/复核事件和重复候选摘要,qiming“数字员工运营台”的“记忆治理”tab 可记录批量归档、批量恢复和标记复核等低风险动作意图。正式管理端记忆合并候选预览已开始吸收,运营台详情可展示治理状态、合并候选数、重复记忆 ID、最近治理动作和复核时间,但不自动合并记忆。远端记忆策略预览已开始吸收,qiming-backend 新增 `memory_policies` 运营台视图聚合记忆策略、合并策略和远端策略禁用状态,qiming“数字员工运营台”的“记忆策略”tab 只记录拉取记忆策略、标记复核和忽略意图,不触发真实下发或自动合并。服务技能记忆审计诊断总收口已开始吸收,记忆治理/记忆策略会展示合并请求、远端策略请求、自动合并禁用和真实下发禁用,只记录合并复核或策略拉取请求。 - 后续缺口:真实合并执行、远端记忆策略真实下发和更完整的外部管理端长期记忆策略工作台仍待吸收。 - 建议:下一轮围绕管理端视图和记忆治理,把长期记忆从本地事实继续扩展到人工可审计、可归档、可合并的运营闭环。 @@ -573,12 +573,12 @@ GET /api/digital-employee/approvals - 建议:下一轮围绕正式管理端治理策略和 ACP 硬中断安全设计,把本地治理审计推进到完整跨端裁决与执行控制。 5. **ManagedService 控制面** - - 已吸收:Agent、MCP、Computer Server、Lanproxy、File Server、GUI Server 状态已进入 snapshot / projection;File Server 与 GUI Server 已开放白名单式 restart bridge,并会写入 `managed_service_restart` / `managed_service_restart_rejected` 事件,安全服务 restart bridge 已开始闭环。完整人机介入入口已开始吸收,embedded 新增“处置台”聚合服务异常、诊断风险、审计风险和策略拉取错误,支持批量标记已看、忽略、策略重拉和白名单服务安全重启。正式外部管理端服务运营台预览已开始吸收,qiming-backend 新增 `service_operations` 运营台视图聚合 managed service 快照、服务诊断和重启审计,qiming“数字员工运营台”新增“服务运营” tab,并只开放复核/忽略等低风险动作意图,不开放 start/stop。正式管理端服务恢复运营预览已开始吸收,`service_operations` 继续聚合 service lease、restart record、recovery policy 和 doctor service issue,qiming 详情可展示 lease holder/到期时间、重启次数/最近重启、恢复策略和自动恢复状态,并明确不接管 lease、不自动恢复服务。服务恢复安全壳预览已开始吸收,`service_operations` 会展示服务启动/停止禁用、lease 接管禁用、自动恢复执行禁用和恢复请求状态,管理端动作记录会对 `start_service`、`stop_service`、`lease_takeover` 和 `execute_auto_recovery` 返回明确禁用 reason,不进入客户端待执行队列。 + - 已吸收:Agent、MCP、Computer Server、Lanproxy、File Server、GUI Server 状态已进入 snapshot / projection;File Server 与 GUI Server 已开放白名单式 restart bridge,并会写入 `managed_service_restart` / `managed_service_restart_rejected` 事件,安全服务 restart bridge 已开始闭环。完整人机介入入口已开始吸收,embedded 新增“处置台”聚合服务异常、诊断风险、审计风险和策略拉取错误,支持批量标记已看、忽略、策略重拉和白名单服务安全重启。正式外部管理端服务运营台预览已开始吸收,qiming-backend 新增 `service_operations` 运营台视图聚合 managed service 快照、服务诊断和重启审计,qiming“数字员工运营台”新增“服务运营” tab,并只开放复核/忽略等低风险动作意图,不开放 start/stop。正式管理端服务恢复运营预览已开始吸收,`service_operations` 继续聚合 service lease、restart record、recovery policy 和 doctor service issue,qiming 详情可展示 lease holder/到期时间、重启次数/最近重启、恢复策略和自动恢复状态,并明确不接管 lease、不自动恢复服务。服务恢复安全壳预览已开始吸收,`service_operations` 会展示服务启动/停止禁用、lease 接管禁用、自动恢复执行禁用和恢复请求状态,管理端动作记录会对 `start_service`、`stop_service`、`lease_takeover` 和 `execute_auto_recovery` 返回明确禁用 reason,不进入客户端待执行队列。服务技能记忆审计诊断总收口已开始吸收,服务运营会展示启动/停止请求、lease 接管请求、自动恢复请求和 start/stop 禁用,只记录请求与复核意图。 - 缺口:仍缺少真实 start / stop、lease 接管执行、自动恢复执行和完整外部管理端服务运营台;Agent、MCP、Computer Server、Lanproxy 等高风险服务不暴露数字员工重启动作。 - 建议:先沉淀安全重启审计和人工确认体验,再评估更细粒度的服务恢复策略。 6. **技能生命周期与策略** - - 已吸收:ACP / MCP / GUI / file server 能力已投射到 Skill Catalog,技能库不再使用 demo fallback;Skill Library 启用/禁用已写入 qimingclaw 本地 `digital_employee_skill_policies_v1.local_skills`,管理端下发策略写入 `remote_skills`,远端禁用优先生效且不会被本地按钮覆盖;入口路由候选技能会按有效策略过滤;MCP 注入 Agent 前会应用有效技能策略,ACP permission / tool update 会写入 `skill_call_allowed` / `skill_call_rejected` / `skill_call_observed` 审计事件,`policy_snapshot` 会包含本地/远端策略命中字段;embedded 已提供 `/api/digital-employee/skill-call-audits` 只读投影、`POST /api/skill/policies/pull` 手动拉取入口,技能调用策略与审计已开始闭环。管理端调用审计视图已开始吸收,embedded 新增“技能审计”入口,将 skill call audit、tool_call_audit、sandbox_audit 和 token_cost 统一成可筛选列表,技能库可按 skill id 跳转聚焦。正式管理端技能审计预览已开始吸收,qiming-backend 新增 `skill_audits` 运营台视图聚合 skill call、tool、sandbox 和 token/cost 安全摘要,qiming“数字员工运营台”的“技能审计”tab 只开放标记复核和忽略意图,不安装技能、不变更权限范围。正式管理端技能版本权限预览已开始吸收,`skill_audits` 继续聚合 skill catalog / skill policy snapshot,qiming 详情可展示技能版本、安装状态、权限范围、策略来源和策略状态,用于解释权限命中但不开放安装或权限编辑。正式管理端技能安装权限预览已开始吸收,`skill_audits` 继续展示安装请求、升级请求、权限策略和安装/升级/权限编辑禁用状态,只做安全壳提示和审计解释,不执行技能安装、升级或远程权限编辑。 + - 已吸收:ACP / MCP / GUI / file server 能力已投射到 Skill Catalog,技能库不再使用 demo fallback;Skill Library 启用/禁用已写入 qimingclaw 本地 `digital_employee_skill_policies_v1.local_skills`,管理端下发策略写入 `remote_skills`,远端禁用优先生效且不会被本地按钮覆盖;入口路由候选技能会按有效策略过滤;MCP 注入 Agent 前会应用有效技能策略,ACP permission / tool update 会写入 `skill_call_allowed` / `skill_call_rejected` / `skill_call_observed` 审计事件,`policy_snapshot` 会包含本地/远端策略命中字段;embedded 已提供 `/api/digital-employee/skill-call-audits` 只读投影、`POST /api/skill/policies/pull` 手动拉取入口,技能调用策略与审计已开始闭环。管理端调用审计视图已开始吸收,embedded 新增“技能审计”入口,将 skill call audit、tool_call_audit、sandbox_audit 和 token_cost 统一成可筛选列表,技能库可按 skill id 跳转聚焦。正式管理端技能审计预览已开始吸收,qiming-backend 新增 `skill_audits` 运营台视图聚合 skill call、tool、sandbox 和 token/cost 安全摘要,qiming“数字员工运营台”的“技能审计”tab 只开放标记复核和忽略意图,不安装技能、不变更权限范围。正式管理端技能版本权限预览已开始吸收,`skill_audits` 继续聚合 skill catalog / skill policy snapshot,qiming 详情可展示技能版本、安装状态、权限范围、策略来源和策略状态,用于解释权限命中但不开放安装或权限编辑。正式管理端技能安装权限预览已开始吸收,`skill_audits` 继续展示安装请求、升级请求、权限策略和安装/升级/权限编辑禁用状态,只做安全壳提示和审计解释,不执行技能安装、升级或远程权限编辑。服务技能记忆审计诊断总收口已开始吸收,技能审计会展示安装/升级/权限编辑请求和禁用原因,只记录安装/升级请求或权限复核意图。 - 缺口:仍缺少真实技能安装、版本升级管理和细粒度权限范围编辑。 - 建议:下一轮围绕管理端审计消费、技能安装和权限范围,把本地调用审计推进到更完整的跨端治理闭环。 @@ -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` 已提供本地审计业务视图,诊断与审计风险通知联动已开始闭环。管理端正式审计视图预览和正式管理端审计诊断预览已开始吸收,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 / 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 文件归档禁用、自动修复执行禁用和高风险修复审批需求,只记录修复意图或审批请求,不触发真实扣费或 shell 自动修复。 - 缺口:真实计费账单、独立 sandbox audit 文件归档、真实自动诊断修复执行器和高风险修复审批流尚未统一吸收;当前账单仍是估算,审计归档只记录脱敏摘要和归档位置。 - 建议:下一轮围绕管理端正式审计视图和诊断修复动作,把本地可追溯审计继续推进到可运营处置。