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 ce0dbaca..8505dd15 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 @@ -64,6 +64,12 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private Boolean autoDecisionDisabled; @JsonProperty("policy_reason") private String policyReason; + @JsonProperty("governance_policy_id") + private String governancePolicyId; + @JsonProperty("policy_guard_status") + private String policyGuardStatus; + @JsonProperty("hard_interrupt_disabled") + private Boolean hardInterruptDisabled; @JsonProperty("artifact_id") private String artifactId; @JsonProperty("artifact_group_id") @@ -250,6 +256,16 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String jobId; @JsonProperty("revision_id") private String revisionId; + @JsonProperty("revision_fact_id") + private String revisionFactId; + @JsonProperty("revision_status") + private String revisionStatus; + @JsonProperty("revision_materialized") + private Boolean revisionMaterialized; + @JsonProperty("base_revision_id") + private String baseRevisionId; + @JsonProperty("changed_fields") + private List changedFields; @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 ba55b03b..c3aee62f 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 @@ -76,6 +76,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye "memory_policy:pull_policy", "memory_policy:mark_reviewed", "memory_policy:dismiss", "skill:mark_reviewed", "skill:dismiss", "policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss", + "governance_policy:pull_policy", "governance_policy:mark_reviewed", "governance_policy:dismiss", "scheduler:retry_scheduler_check", "scheduler:mark_reviewed", "scheduler:dismiss", "plan_step:mark_reviewed", "plan_step:dismiss", "plan_step:retry_policy_check", "plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation", @@ -1105,6 +1106,9 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .batchAcceptRemoteDisabled(nullableBooleanValue(firstPresent(businessView.get("batch_accept_remote_disabled"), businessView.get("batchAcceptRemoteDisabled"), payload.get("batch_accept_remote_disabled"), payload.get("batchAcceptRemoteDisabled")))) .autoDecisionDisabled(nullableBooleanValue(firstPresent(businessView.get("auto_decision_disabled"), businessView.get("autoDecisionDisabled"), payload.get("auto_decision_disabled"), payload.get("autoDecisionDisabled")))) .policyReason(firstString(businessView.get("policy_reason"), businessView.get("policyReason"), payload.get("policy_reason"), payload.get("policyReason"))) + .governancePolicyId(firstString(businessView.get("governance_policy_id"), businessView.get("governancePolicyId"), payload.get("governance_policy_id"), payload.get("governancePolicyId"))) + .policyGuardStatus(firstString(businessView.get("policy_guard_status"), businessView.get("policyGuardStatus"), payload.get("policy_guard_status"), payload.get("policyGuardStatus"))) + .hardInterruptDisabled(nullableBooleanValue(firstPresent(businessView.get("hard_interrupt_disabled"), businessView.get("hardInterruptDisabled"), payload.get("hard_interrupt_disabled"), payload.get("hardInterruptDisabled")))) .artifactId(firstString(businessView.get("artifact_id"), payload.get("artifact_id"), "artifact".equals(record.getEntityType()) ? record.getEntityId() : null)) .artifactGroupId(firstString(businessView.get("artifact_group_id"), businessView.get("artifactGroupId"), payload.get("artifact_group_id"), payload.get("artifactGroupId"))) .artifactCount(numberValue(firstPresent(businessView.get("artifact_count"), businessView.get("artifactCount"), payload.get("artifact_count"), payload.get("artifactCount")))) @@ -1199,6 +1203,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .scheduleId(firstString(businessView.get("schedule_id"), businessView.get("scheduleId"), payload.get("schedule_id"), payload.get("scheduleId"))) .jobId(firstString(businessView.get("job_id"), businessView.get("jobId"), payload.get("job_id"), payload.get("jobId"))) .revisionId(firstString(businessView.get("revision_id"), businessView.get("revisionId"), payload.get("revision_id"), payload.get("revisionId"))) + .revisionFactId(firstString(businessView.get("revision_fact_id"), businessView.get("revisionFactId"), payload.get("revision_fact_id"), payload.get("revisionFactId"))) + .revisionStatus(firstString(businessView.get("revision_status"), businessView.get("revisionStatus"), payload.get("revision_status"), payload.get("revisionStatus"))) + .revisionMaterialized(nullableBooleanValue(firstPresent(businessView.get("revision_materialized"), businessView.get("revisionMaterialized"), payload.get("revision_materialized"), payload.get("revisionMaterialized")))) + .baseRevisionId(firstString(businessView.get("base_revision_id"), businessView.get("baseRevisionId"), payload.get("base_revision_id"), payload.get("baseRevisionId"))) + .changedFields(stringListValue(firstPresent(businessView.get("changed_fields"), businessView.get("changedFields"), payload.get("changed_fields"), payload.get("changedFields")))) .occurredAt(businessRow.getOccurredAt()) .syncedAt(record.getSyncedAt()) .clientKeyMasked(maskClientKey(record.getClientKey())) @@ -1234,6 +1243,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye case "plan_step_dispatch" -> isPlanStepDispatchRow(row); case "plan_governance" -> isPlanGovernanceRow(row); case "plan_revisions" -> isPlanRevisionRow(row); + case "governance_policies" -> isGovernancePolicyRow(row); + case "plan_revision_materialization" -> isPlanRevisionMaterializationRow(row); default -> true; }; } @@ -1499,6 +1510,18 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || contains(kind, "route_governance"); } + private boolean isGovernancePolicyRow(DigitalEmployeeAdminWorkbenchRowDto row) { + String category = firstString(row.getCategory()); + String kind = firstString(row.getKind()); + return Set.of("governance_policy", "plan_governance_policy", "manual_intervention_policy", "risk_guard_policy").contains(category) + || contains(kind, "governance_policy") + || contains(kind, "manual_intervention_policy") + || contains(kind, "risk_guard_policy") + || StringUtils.isNotBlank(row.getGovernancePolicyId()) + || StringUtils.isNotBlank(row.getPolicyGuardStatus()) + || row.getHardInterruptDisabled() != null; + } + private boolean isPlanRevisionRow(DigitalEmployeeAdminWorkbenchRowDto row) { String category = firstString(row.getCategory()); String kind = firstString(row.getKind()); @@ -1508,6 +1531,16 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye || StringUtils.isNotBlank(row.getRevisionId()); } + private boolean isPlanRevisionMaterializationRow(DigitalEmployeeAdminWorkbenchRowDto row) { + String category = firstString(row.getCategory()); + String kind = firstString(row.getKind()); + return Set.of("plan_revision_fact", "plan_revision_materialization", "plan_revision_review").contains(category) + || contains(kind, "plan_revision_materialized") + || contains(kind, "plan_revision_review") + || StringUtils.isNotBlank(row.getRevisionFactId()) + || row.getRevisionMaterialized() != null; + } + private String adminCategory(DigitalEmployeeBusinessViewRowDto row, Map businessView, Map payload) { if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict"; String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind")); @@ -1541,8 +1574,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye 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, "governance_policy") || contains(kind, "manual_intervention_policy") || contains(kind, "risk_guard_policy")) return "governance_policy"; if (contains(kind, "policy")) return "policy_snapshot"; if (contains(kind, "scheduler") || contains(kind, "cron") || contains(kind, "lease")) return "scheduler_operation"; + if (contains(kind, "plan_revision_materialized") || contains(kind, "plan_revision_review")) return "plan_revision_fact"; if (startsWithAny(kind, "plan_revision", "governance_revision")) return "plan_revision"; if (startsWithAny(kind, "plan_action_", "escalation_", "manual_intervention_")) return "plan_governance"; if (startsWithAny(kind, "governance_")) return "governance"; 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 fceb7da4..1e58cb9e 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 @@ -1019,6 +1019,112 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(1).getRevisionId()).isEqualTo("rev-2"); } + @Test + void queryAdminWorkbenchGovernancePoliciesReturnsPolicyRowsOnly() { + 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-governance-policy", "event", "event-governance-policy-1", 101L, new Date(10_000), + Map.of( + "entity_type", "event", + "category", "governance_policy", + "kind", "governance_policy_snapshot", + "status", "active", + "title", "治理策略快照", + "plan_id", "plan-1", + "governance_policy_id", "governance-policy-1", + "policy_guard_status", "guarded", + "policy_reason", "manual_review_required", + "hard_interrupt_disabled", true + ), + Map.of("id", "event-governance-policy-1", "kind", "governance_policy_snapshot")), + record("outbox-manual-policy", "event", "event-governance-policy-2", 101L, new Date(11_000), + Map.of( + "entity_type", "event", + "kind", "manual_intervention_policy_updated", + "status", "review_required", + "title", "人工介入策略", + "governance_policy_id", "manual-policy-1", + "policy_guard_status", "review_required" + ), + Map.of("id", "event-governance-policy-2", "kind", "manual_intervention_policy_updated")), + record("outbox-plan-governance", "event", "event-plan-governance", 101L, new Date(12_000), + Map.of("entity_type", "event", "category", "plan_governance", "kind", "plan_action_recorded", "title", "计划治理动作", "status", "recorded"), + Map.of("id", "event-plan-governance", "kind", "plan_action_recorded")), + record("outbox-task", "task", "task-1", 101L, new Date(13_000), + Map.of("entity_type", "task", "title", "普通任务", "status", "running"), + Map.of("id", "task-1", "status", "running")) + ))); + + DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench( + "governance_policies", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L + ); + + assertThat(result.getTotal()).isEqualTo(2); + assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId) + .containsExactly("event-governance-policy-1", "event-governance-policy-2"); + assertThat(result.getRecords().get(0).getView()).isEqualTo("governance_policies"); + assertThat(result.getRecords().get(0).getGovernancePolicyId()).isEqualTo("governance-policy-1"); + assertThat(result.getRecords().get(0).getPolicyGuardStatus()).isEqualTo("guarded"); + assertThat(result.getRecords().get(0).getPolicyReason()).isEqualTo("manual_review_required"); + assertThat(result.getRecords().get(0).getHardInterruptDisabled()).isTrue(); + assertThat(result.getRecords().get(1).getGovernancePolicyId()).isEqualTo("manual-policy-1"); + } + + @Test + void queryAdminWorkbenchPlanRevisionMaterializationReturnsRevisionRowsOnly() { + 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-revision-fact", "event", "event-revision-fact-1", 101L, new Date(10_000), + Map.of( + "entity_type", "event", + "category", "plan_revision_fact", + "kind", "plan_revision_materialized", + "status", "materialized", + "title", "计划版本已物化", + "plan_id", "plan-1", + "revision_id", "rev-1", + "revision_fact_id", "revision-fact-1", + "revision_materialized", true, + "base_revision_id", "rev-0", + "changed_fields", List.of("steps", "sla") + ), + Map.of("id", "event-revision-fact-1", "kind", "plan_revision_materialized")), + record("outbox-revision-review", "event", "event-revision-review-1", 101L, new Date(11_000), + Map.of( + "entity_type", "event", + "kind", "plan_revision_review_requested", + "status", "pending", + "title", "版本物化复核", + "plan_id", "plan-1", + "revision_id", "rev-2", + "revision_fact_id", "revision-fact-2", + "revision_materialized", false + ), + Map.of("id", "event-revision-review-1", "kind", "plan_revision_review_requested")), + record("outbox-plan", "plan", "plan-ordinary", 101L, new Date(12_000), + Map.of("entity_type", "plan", "title", "普通计划", "status", "running"), + Map.of("id", "plan-ordinary", "status", "running")), + record("outbox-notification", "notification", "notification-1", 101L, new Date(13_000), + Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"), + Map.of("id", "notification-1", "status", "unread")) + ))); + + DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench( + "plan_revision_materialization", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L + ); + + assertThat(result.getTotal()).isEqualTo(2); + assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId) + .containsExactly("event-revision-fact-1", "event-revision-review-1"); + assertThat(result.getRecords().get(0).getView()).isEqualTo("plan_revision_materialization"); + assertThat(result.getRecords().get(0).getRevisionId()).isEqualTo("rev-1"); + assertThat(result.getRecords().get(0).getRevisionFactId()).isEqualTo("revision-fact-1"); + assertThat(result.getRecords().get(0).getRevisionMaterialized()).isTrue(); + assertThat(result.getRecords().get(0).getBaseRevisionId()).isEqualTo("rev-0"); + assertThat(result.getRecords().get(0).getChangedFields()).containsExactly("steps", "sla"); + assertThat(result.getRecords().get(1).getRevisionFactId()).isEqualTo("revision-fact-2"); + } + @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))) @@ -1977,6 +2083,51 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(takeoverLease.getReasonCodes()).contains("unsupported_admin_action"); } + @Test + void createAdminActionAllowsGovernancePolicyRevisionLowRiskActions() { + DigitalEmployeeAdminActionResultDto pullGovernancePolicy = service.createAdminAction( + adminActionRequest("governance_policy", "governance-policy-1", "pull_policy") + ); + DigitalEmployeeAdminActionResultDto markPolicyReviewed = service.createAdminAction( + adminActionRequest("governance_policy", "governance-policy-1", "mark_reviewed") + ); + DigitalEmployeeAdminActionResultDto dismissPolicy = service.createAdminAction( + adminActionRequest("governance_policy", "governance-policy-1", "dismiss") + ); + DigitalEmployeeAdminActionResultDto requestRevisionReview = service.createAdminAction( + adminActionRequest("revision", "rev-1", "request_revision_review") + ); + + assertThat(pullGovernancePolicy.getOk()).isTrue(); + assertThat(markPolicyReviewed.getOk()).isTrue(); + assertThat(dismissPolicy.getOk()).isTrue(); + assertThat(requestRevisionReview.getOk()).isTrue(); + assertThat(requestRevisionReview.getStatus()).isEqualTo("pending"); + assertThat(requestRevisionReview.getReasonCodes()).contains("action_recorded"); + } + + @Test + void createAdminActionRejectsHighRiskGovernanceExecutionActions() { + DigitalEmployeeAdminActionResultDto hardInterrupt = service.createAdminAction( + adminActionRequest("governance_policy", "governance-policy-1", "hard_interrupt") + ); + DigitalEmployeeAdminActionResultDto activatePlan = service.createAdminAction( + adminActionRequest("governance_policy", "governance-policy-1", "activate_plan") + ); + DigitalEmployeeAdminActionResultDto cancelRun = service.createAdminAction( + adminActionRequest("governance_policy", "governance-policy-1", "cancel_run") + ); + DigitalEmployeeAdminActionResultDto cancelTask = service.createAdminAction( + adminActionRequest("governance_policy", "governance-policy-1", "cancel_task") + ); + + assertThat(hardInterrupt.getOk()).isFalse(); + assertThat(hardInterrupt.getReasonCodes()).contains("hard_interrupt_disabled"); + assertThat(activatePlan.getReasonCodes()).contains("high_risk_governance_action_disabled"); + assertThat(cancelRun.getReasonCodes()).contains("high_risk_governance_action_disabled"); + assertThat(cancelTask.getReasonCodes()).contains("high_risk_governance_action_disabled"); + } + @Test void createAdminActionAllowsLowRiskDiagnosticRepairIntent() { DigitalEmployeeAdminActionResultDto markReviewed = service.createAdminAction( diff --git a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx index 80a4f4d4..df47d4d1 100644 --- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx @@ -57,6 +57,8 @@ const WORKBENCH_TABS: Array<{ { key: 'plan_step_dispatch', label: '步骤调度' }, { key: 'plan_governance', label: '计划治理' }, { key: 'plan_revisions', label: '计划版本' }, + { key: 'governance_policies', label: '治理策略' }, + { key: 'plan_revision_materialization', label: '版本物化' }, ]; const ENTITY_TYPE_OPTIONS = { @@ -118,6 +120,8 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) { record.run_id && `Run ${record.run_id}`, record.approval_id && `Approval ${record.approval_id}`, record.approval_policy_id && `ApprovalPolicy ${record.approval_policy_id}`, + record.governance_policy_id && `GovernancePolicy ${record.governance_policy_id}`, + record.revision_fact_id && `RevisionFact ${record.revision_fact_id}`, record.conflict_policy_id && `ConflictPolicy ${record.conflict_policy_id}`, record.decision_guard_status && `DecisionGuard ${record.decision_guard_status}`, record.service_id && `Service ${record.service_id}`, @@ -273,6 +277,12 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp if (view === 'plan_revisions') { return { entityType: 'revision', entityId: record.revision_id || record.entity_id }; } + if (view === 'governance_policies') { + return { entityType: 'governance_policy', entityId: record.governance_policy_id || record.policy_key || record.entity_id }; + } + if (view === 'plan_revision_materialization') { + return { entityType: 'revision', entityId: record.revision_fact_id || record.revision_id || record.entity_id }; + } return { entityType: record.entity_type, entityId: record.entity_id }; } @@ -427,6 +437,20 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE { action: 'request_revision_review', label: '请求版本复核' }, ]; } + if (view === 'governance_policies') { + return [ + { action: 'pull_policy', label: '批量拉取策略' }, + { action: 'mark_reviewed', label: '标记已看' }, + { action: 'dismiss', label: '忽略' }, + ]; + } + if (view === 'plan_revision_materialization') { + return [ + { action: 'mark_reviewed', label: '标记已看' }, + { action: 'dismiss', label: '忽略' }, + { action: 'request_revision_review', label: '请求版本复核' }, + ]; + } return []; } @@ -1298,6 +1322,38 @@ const DigitalEmployeeOps: React.FC = () => { )} + {activeView === 'governance_policies' && ( + + 治理策略: + {detailRecord.governance_policy_id || detailRecord.policy_key || detailRecord.entity_id || '--'} + {detailRecord.policy_guard_status && ` · 策略保护 ${detailRecord.policy_guard_status}`} + {detailRecord.policy_source && ` · 来源 ${detailRecord.policy_source}`} + {detailRecord.policy_status && ` · 状态 ${detailRecord.policy_status}`} + {detailRecord.policy_reason && ` · 原因 ${detailRecord.policy_reason}`} + {detailRecord.hard_interrupt_disabled && ' · ACP 硬中断禁用'} + + {' '} + · 只记录拉取、复核和忽略意图,不开放 ACP 硬中断、不激活计划、不取消运行或任务。 + + + )} + {activeView === 'plan_revision_materialization' && ( + + 版本物化: + {detailRecord.revision_fact_id || detailRecord.revision_id || detailRecord.entity_id || '--'} + {detailRecord.revision_status && ` · 版本状态 ${detailRecord.revision_status}`} + {detailRecord.revision_materialized != null && + ` · 版本事实来源 ${detailRecord.revision_materialized ? 'materialized' : 'fallback'}`} + {detailRecord.base_revision_id && ` · 基线 ${detailRecord.base_revision_id}`} + {detailRecord.changed_fields?.length + ? ` · 变更字段 ${detailRecord.changed_fields.join('/')}` + : ''} + + {' '} + · 只展示计划版本物化状态并记录复核意图,不自动激活计划、不取消运行或任务。 + + + )} {allowedActions(detailRecord, activeView).length > 0 && (
低风险动作 diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 344c3025..d2546b3e 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -577,7 +577,9 @@ export type DigitalEmployeeAdminWorkbenchView = | 'scheduler_operations' | 'plan_step_dispatch' | 'plan_governance' - | 'plan_revisions'; + | 'plan_revisions' + | 'governance_policies' + | 'plan_revision_materialization'; export interface DigitalEmployeeAdminWorkbenchRow { view: DigitalEmployeeAdminWorkbenchView | string; @@ -615,6 +617,9 @@ export interface DigitalEmployeeAdminWorkbenchRow { batch_accept_remote_disabled?: boolean; auto_decision_disabled?: boolean; policy_reason?: string; + governance_policy_id?: string; + policy_guard_status?: string; + hard_interrupt_disabled?: boolean; artifact_id?: string; artifact_group_id?: string; artifact_count?: number; @@ -709,6 +714,11 @@ export interface DigitalEmployeeAdminWorkbenchRow { schedule_id?: string; job_id?: string; revision_id?: string; + revision_fact_id?: string; + revision_status?: string; + revision_materialized?: boolean; + base_revision_id?: string; + changed_fields?: string[]; conflict_keys?: string[]; recommended_resolution?: string; route_decision_count?: number; 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 78912522..cc6a883d 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -1969,6 +1969,104 @@ function checkDigitalAdminPlanGovernanceWorkbenches() { console.log("[DigitalEmployeeCheck] admin plan governance and revision hooks OK"); } +function checkDigitalAdminGovernancePolicyRevisionMaterialization() { + console.log("\n[DigitalEmployeeCheck] Verify admin governance policy and revision materialization 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, "governance_policies", "backend governance policies view"], + [service, "plan_revision_materialization", "backend plan revision materialization view"], + [service, "isGovernancePolicyRow", "backend governance policy matcher"], + [service, "isPlanRevisionMaterializationRow", "backend revision materialization matcher"], + [service, "governance_policy:pull_policy", "backend governance policy pull action"], + [service, "hard_interrupt_disabled", "backend hard interrupt disabled reason"], + [dto, "governance_policy_id", "backend governance policy id field"], + [dto, "policy_guard_status", "backend policy guard status field"], + [dto, "revision_fact_id", "backend revision fact id field"], + [dto, "revision_materialized", "backend revision materialized field"], + [dto, "changed_fields", "backend changed fields row field"], + [test, "queryAdminWorkbenchGovernancePoliciesReturnsPolicyRowsOnly", "backend governance policies test"], + [test, "queryAdminWorkbenchPlanRevisionMaterializationReturnsRevisionRowsOnly", "backend revision materialization test"], + [test, "createAdminActionAllowsGovernancePolicyRevisionLowRiskActions", "backend low risk action test"], + [qimingTypes, "governance_policies", "qiming governance policies view type"], + [qimingTypes, "plan_revision_materialization", "qiming revision materialization view type"], + [qimingTypes, "governance_policy_id", "qiming governance policy id field"], + [qimingTypes, "revision_fact_id", "qiming revision fact id field"], + [qimingOps, "治理策略", "qiming governance policies tab"], + [qimingOps, "版本物化", "qiming revision materialization tab"], + [qimingOps, "策略保护", "qiming policy guard copy"], + [qimingOps, "版本事实来源", "qiming revision fact source copy"], + [qimingOps, "不开放 ACP 硬中断", "qiming ACP hard interrupt guard copy"], + [docs, "正式管理端治理策略预览", "docs governance policy absorption"], + [docs, "计划版本物化预览", "docs revision materialization absorption"], + ]; + const missing = requiredSnippets + .filter(([content, snippet]) => !content.includes(snippet)) + .map(([, , label]) => label); + if (missing.length > 0) { + throw new Error(`Missing admin governance policy and revision materialization hooks: ${missing.join(", ")}`); + } + console.log("[DigitalEmployeeCheck] admin governance policy and revision materialization hooks OK"); +} + function checkDigitalAdminPlanStepDispatchWorkbench() { console.log("\n[DigitalEmployeeCheck] Verify admin plan step dispatch workbench hooks"); const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend"); @@ -3020,6 +3118,7 @@ function main() { checkDigitalAdminMemorySkillWorkbench(); checkDigitalAdminPolicySchedulerWorkbenches(); checkDigitalAdminPlanGovernanceWorkbenches(); + checkDigitalAdminGovernancePolicyRevisionMaterialization(); checkDigitalAdminPlanStepDispatchWorkbench(); checkDigitalAdminCollaborationOperationsClosure(); checkDigitalAdminServiceOperationsWorkbench(); diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index b02cc96a..3fa7bda3 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -568,8 +568,8 @@ GET /api/digital-employee/approvals 4. **治理命令完整生命周期** - 已吸收:`create_plan`、`submit_plan`、`approve_plan`、`activate_plan` 等基础命令可记录本地运行事实;任务级 `retry_task`、`skip_task`、`pause_task`、`resume_task`、`provide_task_input`、`cancel_task`、`start_run`、`cancel_run` 已具备本地事实闭环;embedded plan / task action endpoint 已开始映射为正式 governance command;治理审计链和低风险 action policy 已开始吸收,route decision 自动映射 governance command 前会先经过低风险/受保护高风险 action guard,未命中的动作写入 `route_action_policy_blocked`,所有 `governance_*` event 的同步 `business_view` 会提炼 command、route、实体目标、风险等级和结果字段,`provide_task_input` 只同步 `input_length`。 - - 当前能力:`/api/plan/:id/submit`、`/api/debug/plan/:id/:action`、`/api/task/:id/:action` 会进入 qimingclaw 本地治理记录;plan revision v1 可从 `governance_*` 事件投影只读时间线;数字员工首页会展示“治理审计/治理拦截”摘要,管理端通用 sync record 可通过 `business_view.category = governance` 消费治理命令审计字段。正式管理端治理命令预览已开始吸收,qiming-backend 的 `governance_commands` 运营台视图会聚合治理命令记录、拒绝和低风险输入审计,qiming“数字员工运营台”的“治理命令”tab 只开放标记已看和忽略意图,不开放 ACP 硬中断或高风险执行。正式管理端治理执行安全壳预览和正式管理端计划版本预览已开始吸收,qiming-backend 新增 `plan_governance` / `plan_revisions` 视图聚合治理动作、升级请求、人工介入和版本复核摘要,qiming“数字员工运营台”的“计划治理”“计划版本”tab 只记录标记已看、忽略、请求版本复核和记录升级等低风险意图;`hard_interrupt`、`activate_plan`、`cancel_run`、`cancel_task`、`accept_remote_approval_batch` 继续返回明确禁用 reason。 - - 缺口:正式计划 revision 物理表、治理策略编辑 UI 和对真实 ACP 执行流的硬中断仍不完整。 + - 当前能力:`/api/plan/:id/submit`、`/api/debug/plan/:id/:action`、`/api/task/:id/:action` 会进入 qimingclaw 本地治理记录;plan revision v1 可从 `governance_*` 事件投影只读时间线;数字员工首页会展示“治理审计/治理拦截”摘要,管理端通用 sync record 可通过 `business_view.category = governance` 消费治理命令审计字段。正式管理端治理命令预览已开始吸收,qiming-backend 的 `governance_commands` 运营台视图会聚合治理命令记录、拒绝和低风险输入审计,qiming“数字员工运营台”的“治理命令”tab 只开放标记已看和忽略意图,不开放 ACP 硬中断或高风险执行。正式管理端治理执行安全壳预览和正式管理端计划版本预览已开始吸收,qiming-backend 新增 `plan_governance` / `plan_revisions` 视图聚合治理动作、升级请求、人工介入和版本复核摘要,qiming“数字员工运营台”的“计划治理”“计划版本”tab 只记录标记已看、忽略、请求版本复核和记录升级等低风险意图;`hard_interrupt`、`activate_plan`、`cancel_run`、`cancel_task`、`accept_remote_approval_batch` 继续返回明确禁用 reason。正式管理端治理策略预览和计划版本物化预览已开始吸收,qiming-backend 新增 `governance_policies` / `plan_revision_materialization` 视图聚合治理策略、策略保护、版本事实来源、基线版本和变更字段,qiming“数字员工运营台”的“治理策略”“版本物化”tab 只记录策略拉取、复核、忽略和版本复核意图。 + - 缺口:正式计划 revision 物理表、治理策略编辑 UI 和对真实 ACP 执行流的硬中断仍不完整;计划版本物化预览不自动激活计划、不取消运行或任务。 - 建议:下一轮围绕正式管理端治理策略和 ACP 硬中断安全设计,把本地治理审计推进到完整跨端裁决与执行控制。 5. **ManagedService 控制面** @@ -609,7 +609,7 @@ GET /api/digital-employee/approvals - 建议:下一轮围绕管理端正式审计视图和诊断修复动作,把本地可追溯审计继续推进到可运营处置。 12. **管理端业务查询模型** - - 已吸收:管理端已有通用 sync record 接收、查询、payload 摘要和深链;后端 sync record 已开始保存客户端顶层 `contract_version`、`entity_summary` 和 `business_view`,并基于现有 `digital_employee_sync_record` 拆出 `/api/digital-employee/plans`、`/plans/{plan_id}`、`/tasks`、`/runs`、`/events`、`/artifacts`、`/approvals` 管理端业务查询模型;列表支持客户端 Key、设备、实体 ID、状态、同步状态和同步时间范围过滤,业务 row 会提炼 title/status/summary、Plan/Task/Run 关联、发生/开始/结束时间、sync record 深链和脱敏客户端 Key;Plan 视图会聚合 task/run 计数与最近事件,Plan 详情会返回关联 tasks/runs/events/artifacts/approvals 摘要。embedded adapter 已拆出同名只读业务查询接口,从 qimingclaw runtime、artifact、approval 和 canonical event 投影生成统一分页视图;embedded 数字员工页已新增“业务视图”入口,支持计划/任务/运行/事件/产物/审批分段、实体/状态/同步状态/时间筛选、分页和详情 JSON 查看;客户端 outbox 的 event `business_view` 已开始按 route decision、governance command、approval action、artifact lifecycle/access、skill call audit 和 ready PlanStep dispatch 细分常用字段,本地与管理端业务视图投影已开始闭环。正式外部管理端台面已开始吸收:qiming-backend 新增 `/api/digital-employee/admin/workbench/{view}`,qiming 管理端新增“数字员工运营台”,统一展示业务视图、介入台、产物治理、通知运营、审计诊断、日报运营和路由治理,只读消费 sync record `business_view` 并提供同步记录深链。管理端低风险动作闭环已开始吸收:后端新增 `/api/digital-employee/admin/actions` 记录动作意图和 `/pending` 拉取队列,qimingclaw 只应用保留本地、标记复核、通知已读/忽略、产物保留/到期、日报交付/确认/审批请求等低风险动作并写入 `admin_action_applied` / `admin_action_rejected` / `admin_action_failed`,qiming 运营台抽屉只提示“已记录,等待客户端拉取”。跨设备策略冲突视图已开始吸收:qimingclaw 将派发、风险审批和入口路由策略同步事件投影为 `policy_snapshot` business view,后端新增 `/api/digital-employee/admin/policies/snapshots` 聚合 client/device 维度差异并输出 `conflict_keys`,qiming 运营台新增“策略冲突” tab 展示 policy drift 与 sync record 深链。跨设备审批策略冲突预览已开始吸收:策略冲突页会把 `approval_policy_id`、`conflict_policy_id`、`decision_guard_status`、`batch_accept_remote_disabled`、`auto_decision_disabled` 和策略原因纳入冲突字段和详情解释,但不自动合并策略、不远程编辑策略。跨设备调度派发策略冲突预览已开始吸收:策略冲突页会把 `dispatch_policy_key`、`max_per_sweep`、`auto_dispatch_ready_steps`、`catch_up_on_startup` 和 `max_run_history` 纳入冲突字段和详情解释,但不立即运行、不接管租约、不远程改策略。正式管理端策略中心预览和正式管理端调度运营预览已开始吸收:`admin/workbench` 新增 `policy_center` 与 `scheduler_operations` 视图,统一消费策略快照、scheduler job、cron settings、lease、dispatch sweep 和 recent run 摘要,并通过低风险 admin action 记录策略拉取、立即检查调度、复核和忽略意图。业务事实物化查询已开始吸收:后端新增 `digital_employee_business_fact`、`digital_employee_artifact_lifecycle`、`digital_employee_daily_report_fact` 三张增量物化表,同步入库时 upsert 安全业务摘要,业务查询优先返回 `materialized: true` 行,旧记录继续回退 sync record。 + - 已吸收:管理端已有通用 sync record 接收、查询、payload 摘要和深链;后端 sync record 已开始保存客户端顶层 `contract_version`、`entity_summary` 和 `business_view`,并基于现有 `digital_employee_sync_record` 拆出 `/api/digital-employee/plans`、`/plans/{plan_id}`、`/tasks`、`/runs`、`/events`、`/artifacts`、`/approvals` 管理端业务查询模型;列表支持客户端 Key、设备、实体 ID、状态、同步状态和同步时间范围过滤,业务 row 会提炼 title/status/summary、Plan/Task/Run 关联、发生/开始/结束时间、sync record 深链和脱敏客户端 Key;Plan 视图会聚合 task/run 计数与最近事件,Plan 详情会返回关联 tasks/runs/events/artifacts/approvals 摘要。embedded adapter 已拆出同名只读业务查询接口,从 qimingclaw runtime、artifact、approval 和 canonical event 投影生成统一分页视图;embedded 数字员工页已新增“业务视图”入口,支持计划/任务/运行/事件/产物/审批分段、实体/状态/同步状态/时间筛选、分页和详情 JSON 查看;客户端 outbox 的 event `business_view` 已开始按 route decision、governance command、approval action、artifact lifecycle/access、skill call audit 和 ready PlanStep dispatch 细分常用字段,本地与管理端业务视图投影已开始闭环。正式外部管理端台面已开始吸收:qiming-backend 新增 `/api/digital-employee/admin/workbench/{view}`,qiming 管理端新增“数字员工运营台”,统一展示业务视图、介入台、产物治理、通知运营、审计诊断、日报运营和路由治理,只读消费 sync record `business_view` 并提供同步记录深链。管理端低风险动作闭环已开始吸收:后端新增 `/api/digital-employee/admin/actions` 记录动作意图和 `/pending` 拉取队列,qimingclaw 只应用保留本地、标记复核、通知已读/忽略、产物保留/到期、日报交付/确认/审批请求等低风险动作并写入 `admin_action_applied` / `admin_action_rejected` / `admin_action_failed`,qiming 运营台抽屉只提示“已记录,等待客户端拉取”。跨设备策略冲突视图已开始吸收:qimingclaw 将派发、风险审批和入口路由策略同步事件投影为 `policy_snapshot` business view,后端新增 `/api/digital-employee/admin/policies/snapshots` 聚合 client/device 维度差异并输出 `conflict_keys`,qiming 运营台新增“策略冲突” tab 展示 policy drift 与 sync record 深链。跨设备审批策略冲突预览已开始吸收:策略冲突页会把 `approval_policy_id`、`conflict_policy_id`、`decision_guard_status`、`batch_accept_remote_disabled`、`auto_decision_disabled` 和策略原因纳入冲突字段和详情解释,但不自动合并策略、不远程编辑策略。跨设备调度派发策略冲突预览已开始吸收:策略冲突页会把 `dispatch_policy_key`、`max_per_sweep`、`auto_dispatch_ready_steps`、`catch_up_on_startup` 和 `max_run_history` 纳入冲突字段和详情解释,但不立即运行、不接管租约、不远程改策略。正式管理端策略中心预览和正式管理端调度运营预览已开始吸收:`admin/workbench` 新增 `policy_center` 与 `scheduler_operations` 视图,统一消费策略快照、scheduler job、cron settings、lease、dispatch sweep 和 recent run 摘要,并通过低风险 admin action 记录策略拉取、立即检查调度、复核和忽略意图。正式管理端治理策略预览和计划版本物化预览已开始吸收:`admin/workbench` 新增 `governance_policies` 与 `plan_revision_materialization`,从 sync record / business fact 投影治理策略保护、ACP 硬中断禁用、版本事实来源和变更字段。业务事实物化查询已开始吸收:后端新增 `digital_employee_business_fact`、`digital_employee_artifact_lifecycle`、`digital_employee_daily_report_fact` 三张增量物化表,同步入库时 upsert 安全业务摘要,业务查询优先返回 `materialized: true` 行,旧记录继续回退 sync record。 - 缺口:复杂组合检索、跨设备聚合图、远程裁决/策略编辑动作和物化业务表仍待吸收。 - 建议:先使用 sync record 派生业务查询模型和 embedded 业务视图预览承接联调,再按容量和查询压力决定是否拆 Plan / Task / Run / Event 物理业务表。