From ff7576f33dab48b9997d87192e5bbc6253be1610 Mon Sep 17 00:00:00 2001 From: baiyanyun Date: Mon, 15 Jun 2026 11:05:02 +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=E6=9C=8D=E5=8A=A1=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E5=AE=89=E5=85=A8=E5=A3=B3=E9=A2=84=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DigitalEmployeeAdminWorkbenchRowDto.java | 10 ++++ ...talEmployeeSyncApplicationServiceImpl.java | 16 +++++- ...mployeeSyncApplicationServiceImplTest.java | 56 +++++++++++++++++++ .../Content/DigitalEmployeeOps/index.tsx | 7 ++- qiming/src/types/interfaces/systemManage.ts | 5 ++ .../scripts/check-digital-employee.js | 13 +++++ ...ital-employee-frontend-integration-plan.md | 2 +- 7 files changed, 106 insertions(+), 3 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 5a82a5c8..ce0dbaca 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 @@ -139,6 +139,16 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String recoveryPolicyId; @JsonProperty("auto_recovery_status") private String autoRecoveryStatus; + @JsonProperty("service_start_disabled") + private Boolean serviceStartDisabled; + @JsonProperty("service_stop_disabled") + private Boolean serviceStopDisabled; + @JsonProperty("lease_takeover_disabled") + private Boolean leaseTakeoverDisabled; + @JsonProperty("auto_recovery_disabled") + private Boolean autoRecoveryDisabled; + @JsonProperty("recovery_action_request_id") + private String recoveryActionRequestId; @JsonProperty("memory_id") private String memoryId; @JsonProperty("memory_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 83b678ff..ba55b03b 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 @@ -1029,6 +1029,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye 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 "start_service" -> List.of("start_service_disabled"); + 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 "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"); @@ -1139,6 +1143,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .lastRestartAt(firstString(businessView.get("last_restart_at"), businessView.get("lastRestartAt"), payload.get("last_restart_at"), payload.get("lastRestartAt"))) .recoveryPolicyId(firstString(businessView.get("recovery_policy_id"), businessView.get("recoveryPolicyId"), payload.get("recovery_policy_id"), payload.get("recoveryPolicyId"))) .autoRecoveryStatus(firstString(businessView.get("auto_recovery_status"), businessView.get("autoRecoveryStatus"), payload.get("auto_recovery_status"), payload.get("autoRecoveryStatus"))) + .serviceStartDisabled(nullableBooleanValue(firstPresent(businessView.get("service_start_disabled"), businessView.get("serviceStartDisabled"), payload.get("service_start_disabled"), payload.get("serviceStartDisabled")))) + .serviceStopDisabled(nullableBooleanValue(firstPresent(businessView.get("service_stop_disabled"), businessView.get("serviceStopDisabled"), payload.get("service_stop_disabled"), payload.get("serviceStopDisabled")))) + .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"))) .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"))) @@ -1295,7 +1304,12 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.isNotBlank(row.getServiceId()) || StringUtils.isNotBlank(row.getServiceLeaseId()) || StringUtils.isNotBlank(row.getRestartRecordId()) - || StringUtils.isNotBlank(row.getRecoveryPolicyId()); + || StringUtils.isNotBlank(row.getRecoveryPolicyId()) + || row.getServiceStartDisabled() != null + || row.getServiceStopDisabled() != null + || row.getLeaseTakeoverDisabled() != null + || row.getAutoRecoveryDisabled() != null + || StringUtils.isNotBlank(row.getRecoveryActionRequestId()); } private boolean isNotificationRow(DigitalEmployeeAdminWorkbenchRowDto row) { 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 4dcf6d26..fceb7da4 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 @@ -1157,6 +1157,40 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(3).getSummary()).isEqualTo("health_check_failed"); } + @Test + void queryAdminWorkbenchServiceOperationsReturnsSafetyShellFields() { + 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.of("id", "event-service-shell-1", "kind", "service_recovery_shell_snapshot")) + ))); + + DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench( + "service_operations", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L + ); + + assertThat(result.getTotal()).isEqualTo(1); + DigitalEmployeeAdminWorkbenchRowDto row = result.getRecords().get(0); + assertThat(row.getServiceStartDisabled()).isTrue(); + assertThat(row.getServiceStopDisabled()).isTrue(); + assertThat(row.getLeaseTakeoverDisabled()).isTrue(); + assertThat(row.getAutoRecoveryDisabled()).isTrue(); + assertThat(row.getRecoveryActionRequestId()).isEqualTo("recovery-req-1"); + assertThat(row.getTitle()).isEqualTo("File Server safety shell"); + } + @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))) @@ -1993,6 +2027,28 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(remotePolicyEdit.getReasonCodes()).contains("remote_policy_edit_disabled"); } + @Test + void createAdminActionRejectsHighRiskServiceRecoveryExecution() { + 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") + ); + + assertThat(startService.getOk()).isFalse(); + 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"); + } + @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 d6e8feb7..80a4f4d4 100644 --- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx @@ -1050,9 +1050,14 @@ const DigitalEmployeeOps: React.FC = () => { {detailRecord.last_restart_at && ` · 最近重启 ${detailRecord.last_restart_at}`} {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.service_start_disabled && ' · 启动禁用'} + {detailRecord.service_stop_disabled && ' · 停止禁用'} + {detailRecord.lease_takeover_disabled && ' · lease 接管禁用'} + {detailRecord.auto_recovery_disabled && ' · 自动恢复执行禁用'} {' '} - · 只记录复核/忽略意图,不开放 start/stop、不接管 lease、不自动恢复服务。 + · 只记录复核/忽略意图和恢复请求状态,不开放 start/stop、不接管 lease、不自动恢复服务。 )} diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 2f72d262..344c3025 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -653,6 +653,11 @@ export interface DigitalEmployeeAdminWorkbenchRow { last_restart_at?: string; recovery_policy_id?: string; auto_recovery_status?: string; + service_start_disabled?: boolean; + service_stop_disabled?: boolean; + lease_takeover_disabled?: boolean; + auto_recovery_disabled?: boolean; + recovery_action_request_id?: string; memory_id?: string; memory_key?: string; governance_status?: 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 77e4826c..78912522 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -2231,20 +2231,33 @@ function checkDigitalAdminServiceOperationsWorkbench() { [dto, "restart_record_id", "backend service restart record row field"], [dto, "recovery_policy_id", "backend service recovery policy row field"], [dto, "auto_recovery_status", "backend auto recovery status row field"], + [dto, "service_start_disabled", "backend service start disabled row field"], + [dto, "service_stop_disabled", "backend service stop disabled row field"], + [dto, "lease_takeover_disabled", "backend lease takeover disabled row field"], + [dto, "auto_recovery_disabled", "backend auto recovery disabled row field"], + [dto, "recovery_action_request_id", "backend recovery action request row field"], [test, "queryAdminWorkbenchServiceOperationsReturnsServiceHealthAndRestartRowsOnly", "backend service operations test"], [test, "queryAdminWorkbenchServiceOperationsReturnsLeaseRestartAndRecoveryRowsOnly", "backend service recovery operations test"], + [test, "queryAdminWorkbenchServiceOperationsReturnsSafetyShellFields", "backend service recovery safety shell test"], [qimingTypes, "service_operations", "qiming service operations view type"], [qimingTypes, "service_id", "qiming service id field"], [qimingTypes, "service_lease_id", "qiming service lease id field"], [qimingTypes, "restart_record_id", "qiming restart record id field"], [qimingTypes, "recovery_policy_id", "qiming recovery policy id field"], [qimingTypes, "auto_recovery_status", "qiming auto recovery status field"], + [qimingTypes, "service_start_disabled", "qiming service start disabled field"], + [qimingTypes, "service_stop_disabled", "qiming service stop disabled field"], + [qimingTypes, "lease_takeover_disabled", "qiming lease takeover disabled field"], + [qimingTypes, "auto_recovery_disabled", "qiming auto recovery disabled field"], + [qimingTypes, "recovery_action_request_id", "qiming recovery action request field"], [qimingOps, "服务运营", "qiming service operations tab"], [qimingOps, "只记录复核/忽略意图", "qiming service operations safety copy"], [qimingOps, "不开放 start/stop", "qiming service start stop guard copy"], [qimingOps, "不接管 lease", "qiming service lease takeover guard copy"], [qimingOps, "不自动恢复服务", "qiming service auto recovery guard copy"], + [qimingOps, "恢复请求", "qiming service recovery request copy"], [docs, "正式管理端服务恢复运营预览", "docs service recovery absorption"], + [docs, "恢复请求状态", "docs service recovery request absorption"], ]; const missing = requiredSnippets .filter(([content, snippet]) => !content.includes(snippet)) diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index d8fd4bb6..b02cc96a 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -573,7 +573,7 @@ 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、不自动恢复服务。 + - 已吸收: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,不进入客户端待执行队列。 - 缺口:仍缺少真实 start / stop、lease 接管执行、自动恢复执行和完整外部管理端服务运营台;Agent、MCP、Computer Server、Lanproxy 等高风险服务不暴露数字员工重启动作。 - 建议:先沉淀安全重启审计和人工确认体验,再评估更细粒度的服务恢复策略。