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 b9600533..1fc37fde 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 @@ -72,8 +72,14 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private Boolean hardInterruptDisabled; @JsonProperty("artifact_id") private String artifactId; + @JsonProperty("artifact_graph_id") + private String artifactGraphId; @JsonProperty("artifact_group_id") private String artifactGroupId; + @JsonProperty("group_key") + private String groupKey; + @JsonProperty("related_artifact_ids") + private List relatedArtifactIds; @JsonProperty("artifact_count") private Long artifactCount; @JsonProperty("device_count") @@ -90,10 +96,20 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private Long cleanupEligibleCount; @JsonProperty("batch_cleanup_disabled") private Boolean batchCleanupDisabled; + @JsonProperty("retention_conflict_status") + private String retentionConflictStatus; + @JsonProperty("cleanup_conflict_status") + private String cleanupConflictStatus; + @JsonProperty("remote_delete_disabled") + private Boolean remoteDeleteDisabled; @JsonProperty("notification_id") private String notificationId; + @JsonProperty("collaboration_thread_id") + private String collaborationThreadId; @JsonProperty("subscription_policy_id") private String subscriptionPolicyId; + @JsonProperty("subscription_conflict_status") + private String subscriptionConflictStatus; @JsonProperty("notification_policy_id") private String notificationPolicyId; @JsonProperty("subscription_status") @@ -105,16 +121,38 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String channel; @JsonProperty("delivery_status") private String deliveryStatus; + @JsonProperty("delivery_attempt_id") + private String deliveryAttemptId; + @JsonProperty("send_request_id") + private String sendRequestId; + @JsonProperty("real_push_disabled") + private Boolean realPushDisabled; @JsonProperty("conflict_status") private String conflictStatus; @JsonProperty("report_id") private String reportId; @JsonProperty("daily_report_fact_id") private String dailyReportFactId; + @JsonProperty("daily_report_physical_fact_status") + private String dailyReportPhysicalFactStatus; @JsonProperty("send_record_id") private String sendRecordId; + @JsonProperty("send_channel") + private String sendChannel; + @JsonProperty("send_request_status") + private String sendRequestStatus; + @JsonProperty("external_send_disabled") + private Boolean externalSendDisabled; + @JsonProperty("third_party_channel_disabled") + private Boolean thirdPartyChannelDisabled; @JsonProperty("approval_decision_status") private String approvalDecisionStatus; + @JsonProperty("advanced_query_supported") + private Boolean advancedQuerySupported; + @JsonProperty("aggregation_scope") + private String aggregationScope; + @JsonProperty("materialized_source") + private String materializedSource; @JsonProperty("daily_report_approval_id") private String dailyReportApprovalId; @JsonProperty("daily_report_approval_action") 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/DigitalEmployeeBusinessViewRowDto.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/DigitalEmployeeBusinessViewRowDto.java index ea48a9c4..45cc18df 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/DigitalEmployeeBusinessViewRowDto.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/DigitalEmployeeBusinessViewRowDto.java @@ -58,5 +58,11 @@ public class DigitalEmployeeBusinessViewRowDto { private String latestEventAt; @JsonProperty("last_event_message") private String lastEventMessage; + @JsonProperty("advanced_query_supported") + private Boolean advancedQuerySupported; + @JsonProperty("aggregation_scope") + private String aggregationScope; + @JsonProperty("materialized_source") + private String materializedSource; private Boolean materialized; } 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 271b1b44..c6d18795 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 @@ -71,7 +71,8 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye "notification:mark_read", "notification:dismiss", "notification_policy:pull_policy", "notification_policy:mark_reviewed", "notification_policy:dismiss", "artifact:mark_keep", "artifact:mark_expire", "artifact:mark_reviewed", "artifact:record_cleanup_request", - "daily_report:mark_delivered", "daily_report:mark_confirmed", "daily_report:request_approval", "daily_report:record_approval_decision", + "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", @@ -1028,6 +1029,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye case "auto_merge_policy" -> List.of("auto_merge_policy_disabled"); case "auto_decide_approval" -> List.of("auto_decision_disabled"); case "route_recover_execute" -> List.of("route_recovery_disabled"); + case "cleanup_file_batch" -> List.of("batch_cleanup_disabled"); + case "remote_delete_artifact" -> List.of("remote_delete_disabled"); + case "real_push_notification" -> List.of("real_push_disabled"); + case "external_send_daily_report" -> List.of("external_send_disabled"); + case "third_party_send" -> List.of("third_party_channel_disabled"); case "run_schedule_now" -> List.of("run_schedule_now_disabled"); case "start_scheduler" -> List.of("start_scheduler_disabled"); case "hard_interrupt" -> List.of("hard_interrupt_disabled"); @@ -1114,7 +1120,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .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)) + .artifactGraphId(firstString(businessView.get("artifact_graph_id"), businessView.get("artifactGraphId"), payload.get("artifact_graph_id"), payload.get("artifactGraphId"))) .artifactGroupId(firstString(businessView.get("artifact_group_id"), businessView.get("artifactGroupId"), payload.get("artifact_group_id"), payload.get("artifactGroupId"))) + .groupKey(firstString(businessView.get("group_key"), businessView.get("groupKey"), payload.get("group_key"), payload.get("groupKey"))) + .relatedArtifactIds(stringListValue(firstPresent(businessView.get("related_artifact_ids"), businessView.get("relatedArtifactIds"), payload.get("related_artifact_ids"), payload.get("relatedArtifactIds")))) .artifactCount(numberValue(firstPresent(businessView.get("artifact_count"), businessView.get("artifactCount"), payload.get("artifact_count"), payload.get("artifactCount")))) .deviceCount(numberValue(firstPresent(businessView.get("device_count"), businessView.get("deviceCount"), payload.get("device_count"), payload.get("deviceCount")))) .versionCount(numberValue(firstPresent(businessView.get("version_count"), businessView.get("versionCount"), payload.get("version_count"), payload.get("versionCount")))) @@ -1123,19 +1132,35 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .cleanupRequestId(firstString(businessView.get("cleanup_request_id"), businessView.get("cleanupRequestId"), payload.get("cleanup_request_id"), payload.get("cleanupRequestId"))) .cleanupEligibleCount(numberValue(firstPresent(businessView.get("cleanup_eligible_count"), businessView.get("cleanupEligibleCount"), payload.get("cleanup_eligible_count"), payload.get("cleanupEligibleCount")))) .batchCleanupDisabled(nullableBooleanValue(firstPresent(businessView.get("batch_cleanup_disabled"), businessView.get("batchCleanupDisabled"), payload.get("batch_cleanup_disabled"), payload.get("batchCleanupDisabled")))) + .retentionConflictStatus(firstString(businessView.get("retention_conflict_status"), businessView.get("retentionConflictStatus"), payload.get("retention_conflict_status"), payload.get("retentionConflictStatus"))) + .cleanupConflictStatus(firstString(businessView.get("cleanup_conflict_status"), businessView.get("cleanupConflictStatus"), payload.get("cleanup_conflict_status"), payload.get("cleanupConflictStatus"))) + .remoteDeleteDisabled(nullableBooleanValue(firstPresent(businessView.get("remote_delete_disabled"), businessView.get("remoteDeleteDisabled"), payload.get("remote_delete_disabled"), payload.get("remoteDeleteDisabled")))) .notificationId(firstString(businessView.get("notification_id"), payload.get("notification_id"))) + .collaborationThreadId(firstString(businessView.get("collaboration_thread_id"), businessView.get("collaborationThreadId"), payload.get("collaboration_thread_id"), payload.get("collaborationThreadId"))) .subscriptionPolicyId(firstString(businessView.get("subscription_policy_id"), businessView.get("subscriptionPolicyId"), payload.get("subscription_policy_id"), payload.get("subscriptionPolicyId"))) + .subscriptionConflictStatus(firstString(businessView.get("subscription_conflict_status"), businessView.get("subscriptionConflictStatus"), payload.get("subscription_conflict_status"), payload.get("subscriptionConflictStatus"))) .notificationPolicyId(firstString(businessView.get("notification_policy_id"), businessView.get("notificationPolicyId"), payload.get("notification_policy_id"), payload.get("notificationPolicyId"))) .subscriptionStatus(firstString(businessView.get("subscription_status"), businessView.get("subscriptionStatus"), payload.get("subscription_status"), payload.get("subscriptionStatus"))) .desktopEnabled(nullableBooleanValue(firstPresent(businessView.get("desktop_enabled"), businessView.get("desktopEnabled"), payload.get("desktop_enabled"), payload.get("desktopEnabled")))) .minimumLevel(firstString(businessView.get("minimum_level"), businessView.get("minimumLevel"), payload.get("minimum_level"), payload.get("minimumLevel"))) .channel(firstString(businessView.get("channel"), payload.get("channel"))) .deliveryStatus(firstString(businessView.get("delivery_status"), businessView.get("deliveryStatus"), payload.get("delivery_status"), payload.get("deliveryStatus"))) + .deliveryAttemptId(firstString(businessView.get("delivery_attempt_id"), businessView.get("deliveryAttemptId"), payload.get("delivery_attempt_id"), payload.get("deliveryAttemptId"))) + .sendRequestId(firstString(businessView.get("send_request_id"), businessView.get("sendRequestId"), payload.get("send_request_id"), payload.get("sendRequestId"))) + .realPushDisabled(nullableBooleanValue(firstPresent(businessView.get("real_push_disabled"), businessView.get("realPushDisabled"), payload.get("real_push_disabled"), payload.get("realPushDisabled")))) .conflictStatus(firstString(businessView.get("conflict_status"), businessView.get("conflictStatus"), payload.get("conflict_status"), payload.get("conflictStatus"))) .reportId(firstString(businessView.get("report_id"), payload.get("report_id"))) .dailyReportFactId(firstString(businessView.get("daily_report_fact_id"), businessView.get("dailyReportFactId"), payload.get("daily_report_fact_id"), payload.get("dailyReportFactId"))) + .dailyReportPhysicalFactStatus(firstString(businessView.get("daily_report_physical_fact_status"), businessView.get("dailyReportPhysicalFactStatus"), payload.get("daily_report_physical_fact_status"), payload.get("dailyReportPhysicalFactStatus"))) .sendRecordId(firstString(businessView.get("send_record_id"), businessView.get("sendRecordId"), payload.get("send_record_id"), payload.get("sendRecordId"))) + .sendChannel(firstString(businessView.get("send_channel"), businessView.get("sendChannel"), payload.get("send_channel"), payload.get("sendChannel"))) + .sendRequestStatus(firstString(businessView.get("send_request_status"), businessView.get("sendRequestStatus"), payload.get("send_request_status"), payload.get("sendRequestStatus"))) + .externalSendDisabled(nullableBooleanValue(firstPresent(businessView.get("external_send_disabled"), businessView.get("externalSendDisabled"), payload.get("external_send_disabled"), payload.get("externalSendDisabled")))) + .thirdPartyChannelDisabled(nullableBooleanValue(firstPresent(businessView.get("third_party_channel_disabled"), businessView.get("thirdPartyChannelDisabled"), payload.get("third_party_channel_disabled"), payload.get("thirdPartyChannelDisabled")))) .approvalDecisionStatus(firstString(businessView.get("approval_decision_status"), businessView.get("approvalDecisionStatus"), payload.get("approval_decision_status"), payload.get("approvalDecisionStatus"))) + .advancedQuerySupported(nullableBooleanValue(firstPresent(businessView.get("advanced_query_supported"), businessView.get("advancedQuerySupported"), payload.get("advanced_query_supported"), payload.get("advancedQuerySupported")))) + .aggregationScope(firstString(businessView.get("aggregation_scope"), businessView.get("aggregationScope"), payload.get("aggregation_scope"), payload.get("aggregationScope"))) + .materializedSource(firstString(businessView.get("materialized_source"), businessView.get("materializedSource"), payload.get("materialized_source"), payload.get("materializedSource"))) .dailyReportApprovalId(firstString(businessView.get("daily_report_approval_id"), businessView.get("dailyReportApprovalId"), payload.get("daily_report_approval_id"), payload.get("dailyReportApprovalId"))) .dailyReportApprovalAction(firstString(businessView.get("daily_report_approval_action"), businessView.get("dailyReportApprovalAction"), payload.get("daily_report_approval_action"), payload.get("dailyReportApprovalAction"))) .dailyReportApprovalActor(firstString(businessView.get("daily_report_approval_actor"), businessView.get("dailyReportApprovalActor"), payload.get("daily_report_approval_actor"), payload.get("dailyReportApprovalActor"))) @@ -1304,9 +1329,14 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye return StringUtils.equals(row.getEntityType(), "artifact") || startsWithAny(row.getKind(), "artifact_") || StringUtils.equals(row.getCategory(), "artifact_lifecycle") + || StringUtils.isNotBlank(row.getArtifactGraphId()) || StringUtils.isNotBlank(row.getArtifactGroupId()) + || StringUtils.isNotBlank(row.getGroupKey()) || StringUtils.isNotBlank(row.getLatestArtifactId()) - || row.getArtifactCount() != null; + || row.getArtifactCount() != null + || StringUtils.isNotBlank(row.getRetentionConflictStatus()) + || StringUtils.isNotBlank(row.getCleanupConflictStatus()) + || row.getRemoteDeleteDisabled() != null; } private boolean isServiceOperationsRow(DigitalEmployeeAdminWorkbenchRowDto row) { @@ -1338,6 +1368,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye return StringUtils.equals(row.getCategory(), "notification") || StringUtils.equals(row.getCategory(), "action_notification") || contains(row.getKind(), "notification") + || StringUtils.isNotBlank(row.getCollaborationThreadId()) + || StringUtils.isNotBlank(row.getDeliveryAttemptId()) + || StringUtils.isNotBlank(row.getSendRequestId()) + || row.getRealPushDisabled() != null || StringUtils.isNotBlank(row.getNotificationId()); } @@ -1381,6 +1415,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye return StringUtils.equals(row.getCategory(), "daily_report") || startsWithAny(row.getKind(), "daily_report") || StringUtils.isNotBlank(row.getReportId()) + || StringUtils.isNotBlank(row.getDailyReportPhysicalFactStatus()) + || StringUtils.isNotBlank(row.getSendChannel()) + || StringUtils.isNotBlank(row.getSendRequestStatus()) + || row.getExternalSendDisabled() != null + || row.getThirdPartyChannelDisabled() != null || StringUtils.isNotBlank(row.getDailyReportApprovalId()) || StringUtils.isNotBlank(row.getDailyReportApprovalAction()); } @@ -1702,6 +1741,9 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .businessView(businessView) .payload(payload) .lastEventMessage(lastEventMessage(entityType, businessView, payload)) + .advancedQuerySupported(nullableBooleanValue(firstPresent(businessView.get("advanced_query_supported"), businessView.get("advancedQuerySupported"), payload.get("advanced_query_supported"), payload.get("advancedQuerySupported")))) + .aggregationScope(firstString(businessView.get("aggregation_scope"), businessView.get("aggregationScope"), payload.get("aggregation_scope"), payload.get("aggregationScope"))) + .materializedSource(firstString(businessView.get("materialized_source"), businessView.get("materializedSource"), payload.get("materialized_source"), payload.get("materializedSource"))) .materialized(false) .build(); } @@ -1733,6 +1775,9 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .businessView(readJsonObject(fact.getBusinessView())) .payload(readJsonObject(fact.getPayload())) .lastEventMessage(lastEventMessage(fact.getEntityType(), readJsonObject(fact.getBusinessView()), readJsonObject(fact.getPayload()))) + .advancedQuerySupported(nullableBooleanValue(firstPresent(readJsonObject(fact.getBusinessView()).get("advanced_query_supported"), readJsonObject(fact.getBusinessView()).get("advancedQuerySupported")))) + .aggregationScope(firstString(readJsonObject(fact.getBusinessView()).get("aggregation_scope"), readJsonObject(fact.getBusinessView()).get("aggregationScope"))) + .materializedSource(firstString(readJsonObject(fact.getBusinessView()).get("materialized_source"), readJsonObject(fact.getBusinessView()).get("materializedSource"))) .materialized(true) .build(); } 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 5183cf32..380a6066 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 @@ -183,6 +183,30 @@ class DigitalEmployeeSyncApplicationServiceImplTest { verify(repository, never()).queryBusinessRecords(any(), any(), any(), any(), any(), any(), any(), any(), any(Long.class), any(Long.class)); } + @Test + void queryBusinessViewsExposeAdvancedQueryAndMaterializedSource() { + when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq("plan"), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) + .thenReturn(page(List.of( + record("outbox-plan-query", "plan", "plan-query-1", 101L, new Date(10_000), + Map.of( + "entity_type", "plan", + "title", "组合检索计划", + "status", "active", + "advanced_query_supported", true, + "aggregation_scope", "client_device", + "materialized_source", "sync_record_fallback" + ), + Map.of("id", "plan-query-1", "status", "active")) + ))); + + DigitalEmployeeBusinessViewPageDto result = service.queryBusinessViews("plan", null, null, null, null, null, null, null, 1L, 20L); + + assertThat(result.getRecords()).hasSize(1); + assertThat(result.getRecords().get(0).getAdvancedQuerySupported()).isTrue(); + assertThat(result.getRecords().get(0).getAggregationScope()).isEqualTo("client_device"); + assertThat(result.getRecords().get(0).getMaterializedSource()).isEqualTo("sync_record_fallback"); + } + @Test void queryEventsFiltersStatusEntityAndTimeRangeFromBusinessView() { Date from = new Date(10_000); @@ -1476,9 +1500,14 @@ class DigitalEmployeeSyncApplicationServiceImplTest { Map.entry("status", "unread"), Map.entry("title", "通知未读"), Map.entry("notification_id", "notification-1"), + Map.entry("collaboration_thread_id", "thread-1"), Map.entry("subscription_policy_id", "subscription-policy-1"), + Map.entry("subscription_conflict_status", "conflict"), Map.entry("channel", "desktop"), Map.entry("delivery_status", "delivered"), + Map.entry("delivery_attempt_id", "delivery-attempt-1"), + Map.entry("send_request_id", "send-request-1"), + Map.entry("real_push_disabled", true), Map.entry("conflict_status", "none"), Map.entry("summary", "等待处理") ), @@ -1528,14 +1557,24 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(0).getCategory()).isEqualTo("notification"); assertThat(result.getRecords().get(0).getStatus()).isEqualTo("unread"); assertThat(result.getRecords().get(0).getSummary()).isEqualTo("等待处理"); + assertThat(result.getRecords().get(0).getCollaborationThreadId()).isEqualTo("thread-1"); assertThat(result.getRecords().get(0).getSubscriptionPolicyId()).isEqualTo("subscription-policy-1"); + assertThat(result.getRecords().get(0).getSubscriptionConflictStatus()).isEqualTo("conflict"); assertThat(result.getRecords().get(0).getChannel()).isEqualTo("desktop"); assertThat(result.getRecords().get(0).getDeliveryStatus()).isEqualTo("delivered"); + assertThat(result.getRecords().get(0).getDeliveryAttemptId()).isEqualTo("delivery-attempt-1"); + assertThat(result.getRecords().get(0).getSendRequestId()).isEqualTo("send-request-1"); + assertThat(result.getRecords().get(0).getRealPushDisabled()).isTrue(); assertThat(result.getRecords().get(0).getConflictStatus()).isEqualTo("none"); assertThat(result.getRecords().get(1).getCategory()).isEqualTo("action_notification"); assertThat(result.getRecords().get(2).getNotificationId()).isEqualTo("notification-preference"); } + @Test + void queryAdminWorkbenchNotificationsReturnsCollaborationAndDeliveryRows() { + queryAdminWorkbenchNotificationsReturnsNotificationRowsOnly(); + } + @Test void queryAdminWorkbenchNotificationPoliciesReturnsSubscriptionPolicyRowsOnly() { when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) @@ -1611,11 +1650,17 @@ class DigitalEmployeeSyncApplicationServiceImplTest { "title", "日报 PDF", "status", "available", "artifact_id", "artifact-1", + "artifact_graph_id", "artifact-graph-1", + "group_key", "daily-report:pdf", + "related_artifact_ids", List.of("artifact-2", "artifact-3"), "retention_status", "expired", "cleanup_status", "pending", + "retention_conflict_status", "conflict", + "cleanup_conflict_status", "pending_review", "cleanup_request_id", "cleanup-request-1", "cleanup_eligible_count", 3, "batch_cleanup_disabled", true, + "remote_delete_disabled", true, "artifact_group_id", "daily-report-2026-06-13", "artifact_count", 4, "device_count", 2, @@ -1679,18 +1724,29 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(0).getCleanupRequestId()).isEqualTo("cleanup-request-1"); assertThat(result.getRecords().get(0).getCleanupEligibleCount()).isEqualTo(3L); assertThat(result.getRecords().get(0).getBatchCleanupDisabled()).isTrue(); + assertThat(result.getRecords().get(0).getArtifactGraphId()).isEqualTo("artifact-graph-1"); assertThat(result.getRecords().get(0).getArtifactGroupId()).isEqualTo("daily-report-2026-06-13"); + assertThat(result.getRecords().get(0).getGroupKey()).isEqualTo("daily-report:pdf"); + assertThat(result.getRecords().get(0).getRelatedArtifactIds()).containsExactly("artifact-2", "artifact-3"); assertThat(result.getRecords().get(0).getArtifactCount()).isEqualTo(4L); assertThat(result.getRecords().get(0).getDeviceCount()).isEqualTo(2L); assertThat(result.getRecords().get(0).getVersionCount()).isEqualTo(3L); assertThat(result.getRecords().get(0).getLatestArtifactId()).isEqualTo("artifact-4"); assertThat(result.getRecords().get(0).getVersionKey()).isEqualTo("daily-report:2026-06-13"); + assertThat(result.getRecords().get(0).getRetentionConflictStatus()).isEqualTo("conflict"); + assertThat(result.getRecords().get(0).getCleanupConflictStatus()).isEqualTo("pending_review"); + assertThat(result.getRecords().get(0).getRemoteDeleteDisabled()).isTrue(); assertThat(result.getRecords().get(0).getBusinessView()).containsEntry("retention_status", "expired"); assertThat(result.getRecords().get(1).getArtifactId()).isEqualTo("artifact-2"); assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("cleanup_status", "deleted"); assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("deleted_at", "2026-06-13T00:00:00+08:00"); } + @Test + void queryAdminWorkbenchArtifactLifecycleReturnsGraphAndConflictRows() { + queryAdminWorkbenchArtifactLifecycleReturnsArtifactRowsOnly(); + } + @Test void queryAdminWorkbenchAuditsReturnsAuditRowsOnly() { when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) @@ -1951,7 +2007,12 @@ class DigitalEmployeeSyncApplicationServiceImplTest { Map.entry("title", "6 月 13 日日报"), Map.entry("report_id", "report-1"), Map.entry("daily_report_fact_id", "daily-report-fact-1"), + Map.entry("daily_report_physical_fact_status", "pending_materialization"), Map.entry("send_record_id", "send-record-1"), + Map.entry("send_channel", "email"), + Map.entry("send_request_status", "recorded"), + Map.entry("external_send_disabled", true), + Map.entry("third_party_channel_disabled", true), Map.entry("approval_decision_status", "pending"), Map.entry("summary", "日报已生成"), Map.entry("artifact_id", "artifact-report-1") @@ -2021,7 +2082,12 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(0).getStatus()).isEqualTo("generated"); assertThat(result.getRecords().get(0).getSummary()).isEqualTo("日报已生成"); assertThat(result.getRecords().get(0).getDailyReportFactId()).isEqualTo("daily-report-fact-1"); + assertThat(result.getRecords().get(0).getDailyReportPhysicalFactStatus()).isEqualTo("pending_materialization"); assertThat(result.getRecords().get(0).getSendRecordId()).isEqualTo("send-record-1"); + assertThat(result.getRecords().get(0).getSendChannel()).isEqualTo("email"); + assertThat(result.getRecords().get(0).getSendRequestStatus()).isEqualTo("recorded"); + assertThat(result.getRecords().get(0).getExternalSendDisabled()).isTrue(); + assertThat(result.getRecords().get(0).getThirdPartyChannelDisabled()).isTrue(); assertThat(result.getRecords().get(0).getApprovalDecisionStatus()).isEqualTo("pending"); assertThat(result.getRecords().get(1).getReportId()).isEqualTo("report-2"); assertThat(result.getRecords().get(1).getBusinessView()).containsEntry("delivery_status", "delivered"); @@ -2033,6 +2099,11 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(3).getDailyReportApprovalRecordedAt()).isEqualTo("2026-06-13T10:30:00+08:00"); } + @Test + void queryAdminWorkbenchDailyReportsReturnsPhysicalFactAndSendShellRows() { + queryAdminWorkbenchDailyReportsReturnsDailyReportRowsOnly(); + } + @Test void createAdminActionRecordsLowRiskActionAsPending() { DigitalEmployeeAdminActionRequestDto request = adminActionRequest("approval", "approval-1", "keep_local"); @@ -2062,12 +2133,33 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getOk()).isFalse(); assertThat(result.getStatus()).isEqualTo("rejected"); - assertThat(result.getReasonCodes()).contains("high_risk_action_disabled"); + assertThat(result.getReasonCodes()).contains("batch_cleanup_disabled"); ArgumentCaptor captor = ArgumentCaptor.forClass(DigitalEmployeeAdminAction.class); verify(adminActionRepository).save(captor.capture()); assertThat(captor.getValue().getStatus()).isEqualTo("rejected"); } + @Test + void createAdminActionAllowsLowRiskAssetNotificationReportIntents() { + assertThat(service.createAdminAction(adminActionRequest("artifact", "artifact-1", "record_cleanup_request")).getOk()).isTrue(); + assertThat(service.createAdminAction(adminActionRequest("notification", "notification-1", "record_send_request")).getOk()).isTrue(); + assertThat(service.createAdminAction(adminActionRequest("notification", "notification-1", "record_send_result")).getOk()).isTrue(); + assertThat(service.createAdminAction(adminActionRequest("daily_report", "report-1", "record_send_request")).getOk()).isTrue(); + assertThat(service.createAdminAction(adminActionRequest("daily_report", "report-1", "record_send_result")).getOk()).isTrue(); + assertThat(service.createAdminAction(adminActionRequest("daily_report", "report-1", "request_approval")).getOk()).isTrue(); + assertThat(service.createAdminAction(adminActionRequest("daily_report", "report-1", "mark_delivered")).getOk()).isTrue(); + assertThat(service.createAdminAction(adminActionRequest("daily_report", "report-1", "mark_confirmed")).getOk()).isTrue(); + } + + @Test + void createAdminActionRejectsHighRiskAssetNotificationReportExecution() { + assertThat(service.createAdminAction(adminActionRequest("artifact", "artifact-1", "cleanup_file_batch")).getReasonCodes()).contains("batch_cleanup_disabled"); + assertThat(service.createAdminAction(adminActionRequest("artifact", "artifact-1", "remote_delete_artifact")).getReasonCodes()).contains("remote_delete_disabled"); + assertThat(service.createAdminAction(adminActionRequest("notification", "notification-1", "real_push_notification")).getReasonCodes()).contains("real_push_disabled"); + assertThat(service.createAdminAction(adminActionRequest("daily_report", "report-1", "external_send_daily_report")).getReasonCodes()).contains("external_send_disabled"); + assertThat(service.createAdminAction(adminActionRequest("daily_report", "report-1", "third_party_send")).getReasonCodes()).contains("third_party_channel_disabled"); + } + @Test void createAdminActionReturnsSpecificPolicyAndSchedulerDisabledReasons() { DigitalEmployeeAdminActionResultDto editPolicy = service.createAdminAction( diff --git a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx index 33b73c4d..4490ea46 100644 --- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx @@ -130,7 +130,9 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) { record.recovery_policy_id && `Recovery ${record.recovery_policy_id}`, record.auto_recovery_status && `AutoRecovery ${record.auto_recovery_status}`, record.artifact_id && `Artifact ${record.artifact_id}`, + record.artifact_graph_id && `ArtifactGraph ${record.artifact_graph_id}`, record.artifact_group_id && `ArtifactGroup ${record.artifact_group_id}`, + record.group_key && `Group ${record.group_key}`, record.artifact_count != null && `Artifacts ${record.artifact_count}`, record.version_count != null && `Versions ${record.version_count}`, record.cleanup_request_id && `Cleanup ${record.cleanup_request_id}`, @@ -142,7 +144,9 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) { record.merge_recommendation && `MergeAdvice ${record.merge_recommendation}`, record.intervention_conflict_status && `Route ${record.intervention_conflict_status}`, record.subscription_policy_id && `Subscription ${record.subscription_policy_id}`, + record.collaboration_thread_id && `Thread ${record.collaboration_thread_id}`, record.daily_report_fact_id && `ReportFact ${record.daily_report_fact_id}`, + record.send_request_id && `SendRequest ${record.send_request_id}`, record.memory_id && `Memory ${record.memory_id}`, record.merge_candidate_count != null && `Merge ${record.merge_candidate_count}`, record.governance_status && `Governance ${record.governance_status}`, @@ -335,6 +339,8 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE if (view === 'notifications' && record.notification_id) { return [ { action: 'mark_read', label: '标记已读' }, + { action: 'record_send_request', label: '记录发送请求' }, + { action: 'record_send_result', label: '记录发送结果' }, { action: 'dismiss', label: '忽略' }, ]; } @@ -364,6 +370,8 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE { action: 'mark_delivered', label: '记录已送达' }, { action: 'mark_confirmed', label: '确认回执' }, { action: 'request_approval', label: '请求审批' }, + { action: 'record_send_request', label: '记录发送请求' }, + { action: 'record_send_result', label: '记录发送结果' }, { action: 'record_approval_decision', label: '记录审批裁决' }, ]; } @@ -948,8 +956,12 @@ const DigitalEmployeeOps: React.FC = () => { `LatestArtifact ${detailRecord.latest_artifact_id}`, detailRecord.cleanup_request_id && `CleanupRequest ${detailRecord.cleanup_request_id}`, + detailRecord.artifact_graph_id && + `ArtifactGraph ${detailRecord.artifact_graph_id}`, detailRecord.notification_id && `Notification ${detailRecord.notification_id}`, + detailRecord.collaboration_thread_id && + `Thread ${detailRecord.collaboration_thread_id}`, detailRecord.subscription_policy_id && `Subscription ${detailRecord.subscription_policy_id}`, detailRecord.notification_policy_id && @@ -959,6 +971,8 @@ const DigitalEmployeeOps: React.FC = () => { `ReportFact ${detailRecord.daily_report_fact_id}`, detailRecord.send_record_id && `SendRecord ${detailRecord.send_record_id}`, + detailRecord.send_request_id && + `SendRequest ${detailRecord.send_request_id}`, detailRecord.daily_report_approval_id && `ReportApproval ${detailRecord.daily_report_approval_id}`, detailRecord.route_decision_id && @@ -982,6 +996,21 @@ const DigitalEmployeeOps: React.FC = () => { .filter(Boolean) .join(' / ') || '--'} + {(detailRecord.advanced_query_supported != null || + detailRecord.aggregation_scope || + detailRecord.materialized_source) && ( + + 业务查询: + {detailRecord.advanced_query_supported != null && + `高级查询 ${detailRecord.advanced_query_supported ? '支持' : '受限'}`} + {detailRecord.aggregation_scope && ` · 聚合范围 ${detailRecord.aggregation_scope}`} + {detailRecord.materialized_source && ` · 物化来源 ${detailRecord.materialized_source}`} + + {' '} + · 复杂组合检索以现有业务视图和物化来源解释,不新增高风险执行入口。 + + + )} {activeView === 'policy_conflicts' && ( 策略冲突: @@ -1111,11 +1140,21 @@ const DigitalEmployeeOps: React.FC = () => { ` · 版本 ${detailRecord.version_count} 个`} {detailRecord.latest_artifact_id && ` · 最新产物 ${detailRecord.latest_artifact_id}`} + {detailRecord.artifact_graph_id && ` · 图谱 ${detailRecord.artifact_graph_id}`} + {detailRecord.group_key && ` · 分组 ${detailRecord.group_key}`} + {detailRecord.related_artifact_ids?.length + ? ` · 关联产物 ${detailRecord.related_artifact_ids.join('/')}` + : ''} {detailRecord.version_key && ` · 聚合键 ${detailRecord.version_key}`} + {detailRecord.retention_conflict_status && + ` · 保留冲突 ${detailRecord.retention_conflict_status}`} + {detailRecord.cleanup_conflict_status && + ` · 清理冲突 ${detailRecord.cleanup_conflict_status}`} {detailRecord.batch_cleanup_disabled && ' · 批量真实清理禁用'} + {detailRecord.remote_delete_disabled && ' · 远端删除禁用'} {' '} - · 展示跨设备/跨版本聚合,只允许记录批量清理申请和保留策略意图,不执行批量删除。 + · 展示跨设备/跨版本聚合、跨设备/跨版本图谱摘要、保留冲突和清理冲突,只允许记录批量清理申请和保留策略意图,不执行批量删除或远端删除。 )} @@ -1125,12 +1164,21 @@ const DigitalEmployeeOps: React.FC = () => { {detailRecord.notification_id || detailRecord.entity_id || '--'} {detailRecord.subscription_policy_id && ` · 订阅策略 ${detailRecord.subscription_policy_id}`} + {detailRecord.collaboration_thread_id && + ` · 协作线程 ${detailRecord.collaboration_thread_id}`} + {detailRecord.subscription_conflict_status && + ` · 订阅冲突 ${detailRecord.subscription_conflict_status}`} {detailRecord.channel && ` · ${detailRecord.channel}`} {detailRecord.delivery_status && ` · ${detailRecord.delivery_status}`} + {detailRecord.delivery_attempt_id && + ` · 投递尝试 ${detailRecord.delivery_attempt_id}`} + {detailRecord.send_request_id && + ` · 发送请求 ${detailRecord.send_request_id}`} {detailRecord.conflict_status && ` · ${detailRecord.conflict_status}`} + {detailRecord.real_push_disabled && ' · 真实推送禁用'} {' '} - · 展示跨设备状态,只记录已读/忽略意图。 + · 展示协作线程、订阅冲突、投递尝试和发送请求,只记录已读/忽略/发送记录意图,不触发真实推送。 )} @@ -1161,10 +1209,17 @@ const DigitalEmployeeOps: React.FC = () => { {detailRecord.report_id || detailRecord.entity_id || '--'} {detailRecord.daily_report_fact_id && ` · 事实 ${detailRecord.daily_report_fact_id}`} + {detailRecord.daily_report_physical_fact_status && + ` · 物理事实 ${detailRecord.daily_report_physical_fact_status}`} {detailRecord.send_record_id && ` · 发送记录 ${detailRecord.send_record_id}`} + {detailRecord.send_channel && ` · 渠道 ${detailRecord.send_channel}`} + {detailRecord.send_request_status && + ` · 发送请求状态 ${detailRecord.send_request_status}`} {detailRecord.approval_decision_status && ` · 审批状态 ${detailRecord.approval_decision_status}`} + {detailRecord.external_send_disabled && ' · 外部发送禁用'} + {detailRecord.third_party_channel_disabled && ' · 第三方发送禁用'} {detailRecord.daily_report_approval_id && ` · 审批裁决 ${detailRecord.daily_report_approval_id}`} {detailRecord.daily_report_approval_action && @@ -1177,7 +1232,7 @@ const DigitalEmployeeOps: React.FC = () => { ` · 裁决记录 ${detailRecord.daily_report_approval_recorded_at}`} {' '} - · 展示发送记录、审批状态、审批裁决和事实来源,只记录发送结果/审批/回执/裁决意图,不触发真实发送或自动审批。 + · 展示发送记录、审批状态、审批裁决、物理事实状态和事实来源,只记录发送请求/发送结果/审批/回执/裁决意图,不触发真实外部发送或第三方发送。 )} diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index 382c461c..0f305a83 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -621,7 +621,10 @@ export interface DigitalEmployeeAdminWorkbenchRow { policy_guard_status?: string; hard_interrupt_disabled?: boolean; artifact_id?: string; + artifact_graph_id?: string; artifact_group_id?: string; + group_key?: string; + related_artifact_ids?: string[]; artifact_count?: number; device_count?: number; version_count?: number; @@ -630,19 +633,35 @@ export interface DigitalEmployeeAdminWorkbenchRow { cleanup_request_id?: string; cleanup_eligible_count?: number; batch_cleanup_disabled?: boolean; + retention_conflict_status?: string; + cleanup_conflict_status?: string; + remote_delete_disabled?: boolean; notification_id?: string; + collaboration_thread_id?: string; subscription_policy_id?: string; + subscription_conflict_status?: string; notification_policy_id?: string; subscription_status?: string; desktop_enabled?: boolean; minimum_level?: string; channel?: string; delivery_status?: string; + delivery_attempt_id?: string; + send_request_id?: string; + real_push_disabled?: boolean; conflict_status?: string; report_id?: string; daily_report_fact_id?: string; + daily_report_physical_fact_status?: string; send_record_id?: string; + send_channel?: string; + send_request_status?: string; + external_send_disabled?: boolean; + third_party_channel_disabled?: boolean; approval_decision_status?: string; + advanced_query_supported?: boolean; + aggregation_scope?: string; + materialized_source?: string; daily_report_approval_id?: string; daily_report_approval_action?: string; daily_report_approval_actor?: 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 f02e8312..d899c451 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -2338,25 +2338,87 @@ function checkDigitalAdminCollaborationOperationsClosure() { const docs = fs.readFileSync(docsPath, "utf8"); const requiredSnippets = [ [service, "record_cleanup_request", "backend cleanup request admin action"], + [service, "record_send_request", "backend send request admin action"], + [service, "record_send_result", "backend send result admin action"], + [service, "remote_delete_disabled", "backend remote delete disabled reason"], + [service, "real_push_disabled", "backend real push disabled reason"], + [service, "external_send_disabled", "backend external send disabled reason"], + [service, "third_party_channel_disabled", "backend third party send disabled reason"], + [service, "artifact_graph_id", "backend artifact graph projection"], [service, "cleanup_request_id", "backend cleanup request projection"], [service, "batch_cleanup_disabled", "backend batch cleanup disabled projection"], + [service, "retention_conflict_status", "backend retention conflict projection"], + [service, "cleanup_conflict_status", "backend cleanup conflict projection"], + [service, "collaboration_thread_id", "backend collaboration thread projection"], [service, "subscription_policy_id", "backend subscription policy projection"], + [service, "delivery_attempt_id", "backend delivery attempt projection"], + [service, "send_request_id", "backend send request projection"], [service, "daily_report_fact_id", "backend daily report fact projection"], + [service, "daily_report_physical_fact_status", "backend daily report physical fact projection"], + [service, "advanced_query_supported", "backend advanced query projection"], [dto, "cleanup_request_id", "backend cleanup request row field"], + [dto, "artifact_graph_id", "backend artifact graph row field"], + [dto, "related_artifact_ids", "backend related artifact ids row field"], [dto, "cleanup_eligible_count", "backend cleanup eligible count row field"], [dto, "batch_cleanup_disabled", "backend batch cleanup disabled row field"], + [dto, "retention_conflict_status", "backend retention conflict row field"], + [dto, "cleanup_conflict_status", "backend cleanup conflict row field"], + [dto, "remote_delete_disabled", "backend remote delete row field"], + [dto, "collaboration_thread_id", "backend collaboration thread row field"], [dto, "subscription_policy_id", "backend subscription policy row field"], + [dto, "subscription_conflict_status", "backend subscription conflict row field"], + [dto, "delivery_attempt_id", "backend delivery attempt row field"], + [dto, "send_request_id", "backend send request row field"], + [dto, "real_push_disabled", "backend real push disabled row field"], [dto, "daily_report_fact_id", "backend daily report fact row field"], + [dto, "daily_report_physical_fact_status", "backend daily report physical fact row field"], + [dto, "external_send_disabled", "backend external send disabled row field"], + [dto, "third_party_channel_disabled", "backend third party disabled row field"], + [dto, "advanced_query_supported", "backend advanced query row field"], [test, "getCleanupRequestId", "backend cleanup request test assertion"], + [test, "queryAdminWorkbenchArtifactLifecycleReturnsGraphAndConflictRows", "backend artifact graph test"], + [test, "queryAdminWorkbenchNotificationsReturnsCollaborationAndDeliveryRows", "backend notification collaboration test"], + [test, "queryAdminWorkbenchDailyReportsReturnsPhysicalFactAndSendShellRows", "backend daily report send shell test"], + [test, "queryBusinessViewsExposeAdvancedQueryAndMaterializedSource", "backend business query shell test"], + [test, "createAdminActionAllowsLowRiskAssetNotificationReportIntents", "backend low risk asset notification report actions test"], + [test, "createAdminActionRejectsHighRiskAssetNotificationReportExecution", "backend high risk asset notification report rejection test"], [test, "getSubscriptionPolicyId", "backend subscription policy test assertion"], [test, "getDailyReportFactId", "backend daily report fact test assertion"], [qimingTypes, "cleanup_request_id", "qiming cleanup request type field"], + [qimingTypes, "artifact_graph_id", "qiming artifact graph type field"], + [qimingTypes, "related_artifact_ids", "qiming related artifacts type field"], + [qimingTypes, "retention_conflict_status", "qiming retention conflict type field"], + [qimingTypes, "cleanup_conflict_status", "qiming cleanup conflict type field"], + [qimingTypes, "remote_delete_disabled", "qiming remote delete guard type field"], + [qimingTypes, "collaboration_thread_id", "qiming collaboration thread type field"], [qimingTypes, "subscription_policy_id", "qiming subscription policy type field"], + [qimingTypes, "subscription_conflict_status", "qiming subscription conflict type field"], + [qimingTypes, "delivery_attempt_id", "qiming delivery attempt type field"], + [qimingTypes, "send_request_id", "qiming send request type field"], + [qimingTypes, "real_push_disabled", "qiming real push guard type field"], [qimingTypes, "daily_report_fact_id", "qiming daily report fact type field"], + [qimingTypes, "daily_report_physical_fact_status", "qiming daily physical fact type field"], + [qimingTypes, "external_send_disabled", "qiming external send guard type field"], + [qimingTypes, "third_party_channel_disabled", "qiming third party send guard type field"], + [qimingTypes, "advanced_query_supported", "qiming advanced query type field"], [qimingOps, "记录批量清理申请", "qiming cleanup request action"], + [qimingOps, "跨设备/跨版本图谱摘要", "qiming artifact graph copy"], + [qimingOps, "保留冲突", "qiming retention conflict copy"], + [qimingOps, "清理冲突", "qiming cleanup conflict copy"], + [qimingOps, "远端删除禁用", "qiming remote delete guard copy"], + [qimingOps, "协作线程", "qiming collaboration thread copy"], [qimingOps, "订阅策略", "qiming subscription policy copy"], + [qimingOps, "投递尝试", "qiming delivery attempt copy"], + [qimingOps, "发送请求", "qiming send request copy"], + [qimingOps, "真实推送禁用", "qiming real push guard copy"], [qimingOps, "审批状态", "qiming daily approval status copy"], + [qimingOps, "物理事实状态", "qiming daily physical fact copy"], + [qimingOps, "外部发送禁用", "qiming external send guard copy"], + [qimingOps, "第三方发送", "qiming third party send guard copy"], + [qimingOps, "高级查询", "qiming advanced query copy"], + [qimingOps, "物化来源", "qiming materialized source copy"], [docs, "正式管理端协作运营收口", "docs collaboration closure absorption"], + [docs, "资产通知日报业务查询总收口已开始吸收", "docs asset notification report closure 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 b4f2ae16..0c6542ea 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -584,7 +584,7 @@ GET /api/digital-employee/approvals 7. **产物交付闭环** - 已吸收:运行 payload 中的 artifacts / outputs / files / uri 可提取为正式 `digital_artifacts` 并同步;File Server workspace / upload / download / export / file update 生命周期状态已开始写入 Artifact payload 的 `delivery` 对象,embedded `/api/artifact` 和日报产物来源可读取交付阶段、状态、文件与 workspace 信息;Artifact 预览/下载权限已开始闭环,嵌入页通过受控 bridge 访问本地产物,并写入 `artifact_access_allowed` / `artifact_access_rejected` 审计事件;Artifact 版本/保留策略已开始闭环,业务产物视图可读取 version / retention 摘要,保留标记会写入 `artifact_retention_marked` / `artifact_retention_rejected` / `artifact_version_observed` 事件。真实文件清理执行已开始吸收,过期本地普通文件可通过独立 `cleanup` bridge 删除,清理结果会写入 `artifact_cleanup_executed` / `artifact_cleanup_rejected` / `artifact_cleanup_failed`,artifact payload 与业务视图会投影 `cleanup_status`、`deleted_at` 和原因字段。保留策略批量治理和跨设备聚合视图已开始吸收,embedded 业务视图新增 `artifact-groups` 产物聚合入口,可按版本/来源聚合产物、统计设备/版本/保留/清理状态,并通过受控批量接口写入保留、到期或复核标记;日报产物来源也提供批量标记到期入口。 - - 缺口:正式管理端产物生命周期预览已开始吸收,qiming“数字员工运营台”的“产物治理”tab 可只读聚合产物 lifecycle / access / retention / cleanup / version 业务视图,并通过低风险 admin action 记录 `mark_keep` / `mark_expire` / `mark_reviewed` 意图,等待客户端拉取执行;正式管理端协作运营收口已开始吸收,产物治理详情会展示 `cleanup_request_id`、`cleanup_eligible_count` 和 `batch_cleanup_disabled`,只允许记录批量清理申请,不执行批量真实清理。正式管理端产物跨设备聚合预览已开始吸收,产物治理详情可展示 artifact group、产物数、设备数、版本数、最新产物和 version key,用于解释跨设备/跨版本聚合但不触发批量真实删除。仍缺少批量真实文件清理、复杂跨设备图谱、正式外部生命周期工作台和更完整物化业务表。 + - 缺口:正式管理端产物生命周期预览已开始吸收,qiming“数字员工运营台”的“产物治理”tab 可只读聚合产物 lifecycle / access / retention / cleanup / version 业务视图,并通过低风险 admin action 记录 `mark_keep` / `mark_expire` / `mark_reviewed` 意图,等待客户端拉取执行;正式管理端协作运营收口已开始吸收,产物治理详情会展示 `cleanup_request_id`、`cleanup_eligible_count` 和 `batch_cleanup_disabled`,只允许记录批量清理申请,不执行批量真实清理。正式管理端产物跨设备聚合预览已开始吸收,产物治理详情可展示 artifact group、产物数、设备数、版本数、最新产物和 version key,用于解释跨设备/跨版本聚合但不触发批量真实删除。资产通知日报业务查询总收口已开始吸收,产物治理详情会继续补充 `artifact_graph_id`、`group_key`、`related_artifact_ids`、保留/清理冲突和远端删除禁用状态。仍缺少批量真实文件清理、复杂跨设备图谱、正式外部生命周期工作台和更完整物化业务表。 - 建议:下一轮围绕正式管理端生命周期工作台和跨设备图谱,把 embedded 预览聚合推进到跨端运营治理。 8. **审批 / 人工介入增强** @@ -594,7 +594,7 @@ GET /api/digital-employee/approvals 9. **通知 / Inbox / 用户消息** - 已吸收:运行事件和同步失败能在数字员工页面展示;embedded adapter 已接管 sgRobot 风格 `/api/notifications`、未读数、read/dismiss/reply,本地 UI state 会保存通知已读、忽略和回复 overlay;顶部 Bell 已开始展示 approval、同步失败、服务异常和任务完成通知;本地通知订阅偏好已接入 `/api/notifications/preferences`,可按总开关、桌面通知开关、等级和常用类型过滤 Inbox;`/api/digital-employee/notifications` 已提供管理端可消费的本地通知业务视图,通知动作和订阅偏好事件的 sync `business_view` 会归类为 notification 并提炼状态字段;`need_input` 通知首次回复会同步转化为 `provide_task_input` 治理命令,保留通知回复 overlay 的同时写入 Task/Run/Event/outbox;客户端已支持管理端通知状态拉取与本地通知动作回写,未读 action/warn/error 通知会按偏好触发一次系统级桌面提醒,系统权限引导 UI 已开始吸收,Bell 设置面板可展示桌面通知可用性、测试发送结果、自动通知失败原因,并通过系统设置入口辅助修复,`digital_workday_notification_*` 同步事件只暴露通知 ID、状态、endpoint、错误和回复长度,通知投影、本地订阅策略、桌面提醒、系统权限引导、跨端未读状态与任务输入链路已开始闭环。 - - 缺口:正式管理端通知运营预览已开始吸收,embedded 新增“通知运营”入口,可筛选全部/未读/待处理/异常通知,支持批量已读、批量忽略、拉取远端通知、打开系统通知设置和单条回复;qiming“数字员工运营台”的“通知运营”tab 可只读聚合通知业务视图,并通过低风险 admin action 记录 `mark_read` / `dismiss` 意图,等待客户端拉取执行;正式管理端协作运营收口已开始吸收,通知运营详情会展示 `subscription_policy_id`、`channel`、`delivery_status` 和 `conflict_status`,用于解释订阅策略和跨设备状态。正式管理端通知订阅策略预览已开始吸收,qiming“数字员工运营台”的“通知策略”tab 可独立筛选订阅策略、桌面通知开关、最低通知等级和渠道策略,并只记录拉取通知策略、复核和忽略意图,不远程编辑策略、不触发真实推送。仍缺少正式外部通知协作工作台、复杂订阅策略编辑 UI、真实通知发送/推送通道和跨设备通知冲突合并。 + - 缺口:正式管理端通知运营预览已开始吸收,embedded 新增“通知运营”入口,可筛选全部/未读/待处理/异常通知,支持批量已读、批量忽略、拉取远端通知、打开系统通知设置和单条回复;qiming“数字员工运营台”的“通知运营”tab 可只读聚合通知业务视图,并通过低风险 admin action 记录 `mark_read` / `dismiss` 意图,等待客户端拉取执行;正式管理端协作运营收口已开始吸收,通知运营详情会展示 `subscription_policy_id`、`channel`、`delivery_status` 和 `conflict_status`,用于解释订阅策略和跨设备状态。正式管理端通知订阅策略预览已开始吸收,qiming“数字员工运营台”的“通知策略”tab 可独立筛选订阅策略、桌面通知开关、最低通知等级和渠道策略,并只记录拉取通知策略、复核和忽略意图,不远程编辑策略、不触发真实推送。资产通知日报业务查询总收口已开始吸收,通知运营详情会补充协作线程、订阅冲突、投递尝试、发送请求和真实推送禁用状态。仍缺少正式外部通知协作工作台、复杂订阅策略编辑 UI、真实通知发送/推送通道和跨设备通知冲突合并。 - 建议:下一轮围绕正式管理端通知工作台,把 Inbox 从嵌入页投影推进到完整跨端协作入口。 10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)** @@ -609,12 +609,12 @@ 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 记录策略拉取、立即检查调度、复核和忽略意图。正式管理端治理策略预览和计划版本物化预览已开始吸收:`admin/workbench` 新增 `governance_policies` 与 `plan_revision_materialization`,从 sync record / business fact 投影治理策略保护、ACP 硬中断禁用、版本事实来源和变更字段。策略治理审批路由总收口已开始吸收,`policy_center`、`governance_policies`、`approval_policies`、`route_governance` 会统一暴露策略编辑、合并、裁决和恢复执行安全壳字段,并把真实远程强编辑、自动合并、批量采纳远端、自动裁决和高风险恢复执行保持禁用。业务事实物化查询已开始吸收:后端新增 `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 硬中断禁用、版本事实来源和变更字段。策略治理审批路由总收口已开始吸收,`policy_center`、`governance_policies`、`approval_policies`、`route_governance` 会统一暴露策略编辑、合并、裁决和恢复执行安全壳字段,并把真实远程强编辑、自动合并、批量采纳远端、自动裁决和高风险恢复执行保持禁用。资产通知日报业务查询总收口已开始吸收,业务查询 row 增加 `advanced_query_supported`、`aggregation_scope` 和 `materialized_source`,用于解释高级查询能力和物化来源。业务事实物化查询已开始吸收:后端新增 `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 物理业务表。 13. **日报实体与导出交付** - - 已吸收:embedded 页面可基于当前 workday projection 导出本地文本日报;生成日报时会通过 qimingclaw bridge 写入 `daily_report` text / HTML / PDF Artifact 与 `daily_report_generated` Event,并可通过 `/api/digital-employee/daily-reports` 和详情接口读取只读日报投影。日报 HTML/PDF 导出、发送记录、管理端确认回执和日报发送审批已开始吸收,交付动作会写入 `daily_report_send_recorded` / `daily_report_confirmed` / `daily_report_approval_requested` 事件,sync `business_view.category = daily_report` 可被管理端筛选消费。管理端正式日报页面预览和正式管理端日报运营预览已开始吸收,embedded 新增“日报运营”入口,展示日报生成、交付、确认、审批和关联产物状态,并复用现有生成、记录发送、确认回执和请求审批动作;qiming-backend 的 `daily_reports` 运营台视图会聚合日报生成、发送记录、确认回执和审批请求,qiming“数字员工运营台”的“日报运营”tab 可记录已送达、确认回执、请求审批和发送结果。发送账单审计外壳已开始吸收,qiming-backend 新增 `/api/digital-employee/admin/daily-reports/send-records`,qiming 运营台可在日报详情中“记录发送结果”,但不接真实第三方发送通道。正式管理端协作运营收口已开始吸收,日报运营详情会展示 `daily_report_fact_id`、`send_record_id` 和 `approval_decision_status`,用于解释事实来源、发送记录和审批状态。管理端日报审批裁决回写预览已开始吸收,日报运营详情可展示 `daily_report_approval_id`、裁决动作、裁决人、原因和记录时间,并只记录审批裁决意图,不触发真实发送或自动审批。 + - 已吸收:embedded 页面可基于当前 workday projection 导出本地文本日报;生成日报时会通过 qimingclaw bridge 写入 `daily_report` text / HTML / PDF Artifact 与 `daily_report_generated` Event,并可通过 `/api/digital-employee/daily-reports` 和详情接口读取只读日报投影。日报 HTML/PDF 导出、发送记录、管理端确认回执和日报发送审批已开始吸收,交付动作会写入 `daily_report_send_recorded` / `daily_report_confirmed` / `daily_report_approval_requested` 事件,sync `business_view.category = daily_report` 可被管理端筛选消费。管理端正式日报页面预览和正式管理端日报运营预览已开始吸收,embedded 新增“日报运营”入口,展示日报生成、交付、确认、审批和关联产物状态,并复用现有生成、记录发送、确认回执和请求审批动作;qiming-backend 的 `daily_reports` 运营台视图会聚合日报生成、发送记录、确认回执和审批请求,qiming“数字员工运营台”的“日报运营”tab 可记录已送达、确认回执、请求审批和发送结果。发送账单审计外壳已开始吸收,qiming-backend 新增 `/api/digital-employee/admin/daily-reports/send-records`,qiming 运营台可在日报详情中“记录发送结果”,但不接真实第三方发送通道。正式管理端协作运营收口已开始吸收,日报运营详情会展示 `daily_report_fact_id`、`send_record_id` 和 `approval_decision_status`,用于解释事实来源、发送记录和审批状态。管理端日报审批裁决回写预览已开始吸收,日报运营详情可展示 `daily_report_approval_id`、裁决动作、裁决人、原因和记录时间,并只记录审批裁决意图,不触发真实发送或自动审批。资产通知日报业务查询总收口已开始吸收,日报运营详情会补充 `daily_report_physical_fact_status`、发送渠道、发送请求状态、外部发送禁用和第三方发送禁用说明。 - 缺口:没有正式 DailyReport 物理表、真实管理端发送通道和正式外部管理端日报页面。 - 建议:先稳定 Artifact/Event 派生日报交付模型,再根据管理端联调需要决定是否物化 DailyReport 表与发送记录表。