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 5af3497e..f34bd288 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 @@ -43,6 +43,8 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String reportId; @JsonProperty("route_decision_id") private String routeDecisionId; + @JsonProperty("service_id") + private String serviceId; @JsonProperty("occurred_at") private String occurredAt; @JsonProperty("synced_at") 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 238b60ab..83a1a554 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 @@ -979,6 +979,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .notificationId(firstString(businessView.get("notification_id"), payload.get("notification_id"))) .reportId(firstString(businessView.get("report_id"), payload.get("report_id"))) .routeDecisionId(firstString(businessView.get("route_decision_id"), payload.get("route_decision_id"))) + .serviceId(firstString(businessView.get("service_id"), businessView.get("serviceId"), payload.get("service_id"), payload.get("serviceId"))) .occurredAt(businessRow.getOccurredAt()) .syncedAt(record.getSyncedAt()) .clientKeyMasked(maskClientKey(record.getClientKey())) @@ -997,6 +998,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye return switch (view) { case "interventions" -> isInterventionRow(row); case "approval_governance" -> isApprovalGovernanceRow(row); + case "service_operations" -> isServiceOperationsRow(row); case "artifact_lifecycle" -> isArtifactLifecycleRow(row); case "notifications" -> isNotificationRow(row); case "audits" -> isAuditRow(row); @@ -1037,6 +1039,20 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.equals(row.getCategory(), "artifact_lifecycle"); } + private boolean isServiceOperationsRow(DigitalEmployeeAdminWorkbenchRowDto row) { + String kind = firstString(row.getKind()); + String category = firstString(row.getCategory()); + return StringUtils.equals(category, "managed_service") + || StringUtils.equals(category, "service_operation") + || StringUtils.equals(category, "service_health") + || contains(kind, "managed_service") + || contains(kind, "service_snapshot") + || contains(kind, "service_restart") + || contains(kind, "doctor_service") + || contains(kind, "doctor_issue") + || StringUtils.isNotBlank(row.getServiceId()); + } + private boolean isNotificationRow(DigitalEmployeeAdminWorkbenchRowDto row) { return StringUtils.equals(row.getCategory(), "notification") || StringUtils.equals(row.getCategory(), "action_notification") 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 15f98aef..43547fa8 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 @@ -349,6 +349,60 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(3).getApprovalId()).isEqualTo("approval-1"); } + @Test + void queryAdminWorkbenchServiceOperationsReturnsServiceHealthAndRestartRowsOnly() { + 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-snapshot", "event", "event-service-1", 101L, new Date(10_000), + Map.of( + "entity_type", "event", + "category", "managed_service", + "kind", "service_snapshot", + "status", "degraded", + "title", "File Server 异常", + "service_id", "file-server", + "level", "warn", + "summary", "端口不可用" + ), + Map.of("id", "event-service-1", "kind", "service_snapshot")), + record("outbox-service-restart", "event", "event-service-2", 101L, new Date(11_000), + Map.of( + "entity_type", "event", + "category", "service_operation", + "kind", "managed_service_restart", + "status", "recorded", + "title", "GUI Server 安全重启", + "service_id", "gui-server" + ), + Map.of("id", "event-service-2", "kind", "managed_service_restart")), + record("outbox-doctor-service", "event", "event-service-3", 101L, new Date(12_000), + Map.of( + "entity_type", "event", + "kind", "doctor_service_issue", + "status", "failed", + "title", "服务诊断失败", + "service_id", "agent-server", + "reason", "health_check_failed" + ), + Map.of("id", "event-service-3", "kind", "doctor_service_issue")), + record("outbox-approval", "approval", "approval-1", 101L, new Date(13_000), + Map.of("entity_type", "approval", "title", "审批", "status", "pending"), + Map.of("id", "approval-1", "status", "pending")) + ))); + + DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench( + "service_operations", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L + ); + + assertThat(result.getTotal()).isEqualTo(3); + assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId) + .containsExactly("event-service-1", "event-service-2", "event-service-3"); + assertThat(result.getRecords().get(0).getView()).isEqualTo("service_operations"); + assertThat(result.getRecords().get(0).getServiceId()).isEqualTo("file-server"); + assertThat(result.getRecords().get(1).getCategory()).isEqualTo("service_operation"); + assertThat(result.getRecords().get(2).getServiceId()).isEqualTo("agent-server"); + } + @Test void queryAdminWorkbenchAuditsRedactsSensitivePayloadFields() { when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) diff --git a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx index d4721e98..78197654 100644 --- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx @@ -39,6 +39,7 @@ const WORKBENCH_TABS: Array<{ { key: 'business', label: '业务视图' }, { key: 'interventions', label: '介入台' }, { key: 'approval_governance', label: '审批治理' }, + { key: 'service_operations', label: '服务运营' }, { key: 'artifact_lifecycle', label: '产物治理' }, { key: 'notifications', label: '通知运营' }, { key: 'audits', label: '审计诊断' }, @@ -101,6 +102,7 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) { record.task_id && `Task ${record.task_id}`, record.run_id && `Run ${record.run_id}`, record.approval_id && `Approval ${record.approval_id}`, + record.service_id && `Service ${record.service_id}`, record.artifact_id && `Artifact ${record.artifact_id}`, record.route_decision_id && `Route ${record.route_decision_id}`, record.policy_kind && `Policy ${record.policy_kind}`, @@ -154,6 +156,9 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp if (view === 'notifications' && record.notification_id) { return { entityType: 'notification', entityId: record.notification_id }; } + if (view === 'service_operations') { + return { entityType: 'event', entityId: record.entity_id }; + } if (view === 'artifact_lifecycle' && record.artifact_id) { return { entityType: 'artifact', entityId: record.artifact_id }; } @@ -211,6 +216,12 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE { action: 'dismiss', label: '忽略' }, ]; } + if (view === 'service_operations') { + return [ + { action: 'mark_reviewed', label: '标记已看' }, + { action: 'dismiss', label: '忽略' }, + ]; + } if (view === 'artifact_lifecycle' && record.artifact_id) { return [ { action: 'mark_keep', label: '标记保留' }, @@ -686,6 +697,7 @@ const DigitalEmployeeOps: React.FC = () => { detailRecord.task_id && `Task ${detailRecord.task_id}`, detailRecord.run_id && `Run ${detailRecord.run_id}`, detailRecord.approval_id && `Approval ${detailRecord.approval_id}`, + detailRecord.service_id && `Service ${detailRecord.service_id}`, detailRecord.artifact_id && `Artifact ${detailRecord.artifact_id}`, detailRecord.notification_id && `Notification ${detailRecord.notification_id}`, @@ -725,6 +737,18 @@ const DigitalEmployeeOps: React.FC = () => { )} + {activeView === 'service_operations' && ( + + 服务运营: + {detailRecord.service_id || detailRecord.entity_id || '--'} + {detailRecord.category && ` · ${detailRecord.category}`} + {detailRecord.level && ` · ${detailRecord.level}`} + + {' '} + · 只记录复核/忽略意图,不开放 start/stop。 + + + )} {allowedActions(detailRecord, activeView).length > 0 && (
低风险动作 diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 3b8abec2..679ac3d4 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -560,6 +560,7 @@ export type DigitalEmployeeAdminWorkbenchView = | 'business' | 'interventions' | 'approval_governance' + | 'service_operations' | 'artifact_lifecycle' | 'notifications' | 'audits' @@ -589,6 +590,7 @@ export interface DigitalEmployeeAdminWorkbenchRow { notification_id?: string; report_id?: string; route_decision_id?: string; + service_id?: string; policy_kind?: string; policy_key?: string; conflict_keys?: 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 6138484e..99e5ffe3 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -1449,6 +1449,91 @@ function checkDigitalAdminApprovalGovernanceWorkbench() { console.log("[DigitalEmployeeCheck] admin approval governance workbench hooks OK"); } +function checkDigitalAdminServiceOperationsWorkbench() { + console.log("\n[DigitalEmployeeCheck] Verify admin service operations workbench hooks"); + const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend"); + const qimingRoot = path.resolve(packageRoot, "..", "..", "..", "qiming"); + const servicePath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-application", + "src", + "main", + "java", + "com", + "xspaceagi", + "agent", + "core", + "application", + "service", + "DigitalEmployeeSyncApplicationServiceImpl.java", + ); + const 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, "service_operations", "backend service operations view"], + [service, "isServiceOperationsRow", "backend service operations matcher"], + [service, "managed_service", "backend managed service classifier"], + [service, "service_restart", "backend service restart classifier"], + [dto, "service_id", "backend service id row field"], + [test, "queryAdminWorkbenchServiceOperationsReturnsServiceHealthAndRestartRowsOnly", "backend service operations test"], + [qimingTypes, "service_operations", "qiming service operations view type"], + [qimingTypes, "service_id", "qiming service id field"], + [qimingOps, "服务运营", "qiming service operations tab"], + [qimingOps, "只记录复核/忽略意图", "qiming service operations safety copy"], + [qimingOps, "不开放 start/stop", "qiming service start stop guard copy"], + [docs, "正式外部管理端服务运营台预览", "docs service operations absorption"], + ]; + const missing = requiredSnippets + .filter(([content, snippet]) => !content.includes(snippet)) + .map(([, , label]) => label); + if (missing.length > 0) { + throw new Error(`Missing admin service operations workbench hooks: ${missing.join(", ")}`); + } + console.log("[DigitalEmployeeCheck] admin service operations workbench hooks OK"); +} + function checkLocalDatabaseSchema() { console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema"); const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db"); @@ -1512,6 +1597,7 @@ function main() { checkDigitalBackendMaterializedFacts(); checkDigitalAdminRecordBillingArchiveShell(); checkDigitalAdminApprovalGovernanceWorkbench(); + checkDigitalAdminServiceOperationsWorkbench(); 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 c566d56c..66668752 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -573,8 +573,8 @@ 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 新增“处置台”聚合服务异常、诊断风险、审计风险和策略拉取错误,支持批量标记已看、忽略、策略重拉和白名单服务安全重启。 - - 缺口:仍缺少 start / stop、lease 持久化、重启历史、自动恢复策略和正式外部管理端服务运营台;Agent、MCP、Computer Server、Lanproxy 等高风险服务不暴露数字员工重启动作。 + - 已吸收: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。 + - 缺口:仍缺少 start / stop、lease 持久化、重启历史、自动恢复策略和完整外部管理端服务运营台;Agent、MCP、Computer Server、Lanproxy 等高风险服务不暴露数字员工重启动作。 - 建议:先沉淀安全重启审计和人工确认体验,再评估更细粒度的服务恢复策略。 6. **技能生命周期与策略**