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 b5e63a3b..064855d9 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 @@ -37,10 +37,29 @@ public class DigitalEmployeeAdminWorkbenchRowDto { private String approvalId; @JsonProperty("artifact_id") private String artifactId; + @JsonProperty("cleanup_request_id") + private String cleanupRequestId; + @JsonProperty("cleanup_eligible_count") + private Long cleanupEligibleCount; + @JsonProperty("batch_cleanup_disabled") + private Boolean batchCleanupDisabled; @JsonProperty("notification_id") private String notificationId; + @JsonProperty("subscription_policy_id") + private String subscriptionPolicyId; + private String channel; + @JsonProperty("delivery_status") + private String deliveryStatus; + @JsonProperty("conflict_status") + private String conflictStatus; @JsonProperty("report_id") private String reportId; + @JsonProperty("daily_report_fact_id") + private String dailyReportFactId; + @JsonProperty("send_record_id") + private String sendRecordId; + @JsonProperty("approval_decision_status") + private String approvalDecisionStatus; @JsonProperty("route_decision_id") private String routeDecisionId; @JsonProperty("service_id") 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 a47db905..aab7e815 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 @@ -68,7 +68,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye "route_decision:mark_reviewed", "route_decision:dismiss", "route_decision:retry_policy_check", "route:mark_reviewed", "route:dismiss", "route:retry_policy_check", "notification:mark_read", "notification:dismiss", - "artifact:mark_keep", "artifact:mark_expire", "artifact:mark_reviewed", + "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", "memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed", "skill:mark_reviewed", "skill:dismiss", @@ -997,8 +997,18 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye .runId(businessRow.getRunId()) .approvalId(firstString(businessView.get("approval_id"), payload.get("approval_id"), "approval".equals(record.getEntityType()) ? record.getEntityId() : null)) .artifactId(firstString(businessView.get("artifact_id"), payload.get("artifact_id"), "artifact".equals(record.getEntityType()) ? record.getEntityId() : null)) + .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")))) .notificationId(firstString(businessView.get("notification_id"), payload.get("notification_id"))) + .subscriptionPolicyId(firstString(businessView.get("subscription_policy_id"), businessView.get("subscriptionPolicyId"), payload.get("subscription_policy_id"), payload.get("subscriptionPolicyId"))) + .channel(firstString(businessView.get("channel"), payload.get("channel"))) + .deliveryStatus(firstString(businessView.get("delivery_status"), businessView.get("deliveryStatus"), payload.get("delivery_status"), payload.get("deliveryStatus"))) + .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"))) + .sendRecordId(firstString(businessView.get("send_record_id"), businessView.get("sendRecordId"), payload.get("send_record_id"), payload.get("sendRecordId"))) + .approvalDecisionStatus(firstString(businessView.get("approval_decision_status"), businessView.get("approvalDecisionStatus"), payload.get("approval_decision_status"), payload.get("approvalDecisionStatus"))) .routeDecisionId(firstString(businessView.get("route_decision_id"), payload.get("route_decision_id"))) .serviceId(firstString(businessView.get("service_id"), businessView.get("serviceId"), payload.get("service_id"), payload.get("serviceId"))) .memoryId(firstString(businessView.get("memory_id"), businessView.get("memoryId"), payload.get("memory_id"), payload.get("memoryId"), "memory".equals(record.getEntityType()) ? record.getEntityId() : null)) @@ -1377,6 +1387,13 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye return false; } + private Boolean nullableBooleanValue(Object value) { + if (value == null) return null; + if (value instanceof Boolean bool) return bool; + if (value instanceof String text && StringUtils.isNotBlank(text)) return Boolean.parseBoolean(text.trim()); + return null; + } + private boolean contains(String value, String fragment) { return StringUtils.containsIgnoreCase(value, fragment); } @@ -1447,6 +1464,13 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye return null; } + private Object firstPresent(Object... values) { + for (Object value : values) { + if (value != null) return value; + } + return null; + } + private String stringValue(Object value, String fallback) { if (value == null) return fallback; String text = Objects.toString(value, "").trim(); 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 065b1146..22c99b65 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 @@ -870,14 +870,18 @@ class DigitalEmployeeSyncApplicationServiceImplTest { when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) .thenReturn(page(List.of( record("outbox-notification", "notification", "notification-1", 101L, new Date(10_000), - Map.of( - "entity_type", "notification", - "category", "notification", - "kind", "notification_received", - "status", "unread", - "title", "通知未读", - "notification_id", "notification-1", - "summary", "等待处理" + Map.ofEntries( + Map.entry("entity_type", "notification"), + Map.entry("category", "notification"), + Map.entry("kind", "notification_received"), + Map.entry("status", "unread"), + Map.entry("title", "通知未读"), + Map.entry("notification_id", "notification-1"), + Map.entry("subscription_policy_id", "subscription-policy-1"), + Map.entry("channel", "desktop"), + Map.entry("delivery_status", "delivered"), + Map.entry("conflict_status", "none"), + Map.entry("summary", "等待处理") ), Map.of("id", "notification-1", "kind", "notification_received")), record("outbox-action-notification", "event", "event-notification-1", 101L, new Date(11_000), @@ -925,6 +929,10 @@ 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).getSubscriptionPolicyId()).isEqualTo("subscription-policy-1"); + assertThat(result.getRecords().get(0).getChannel()).isEqualTo("desktop"); + assertThat(result.getRecords().get(0).getDeliveryStatus()).isEqualTo("delivered"); + 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"); } @@ -941,6 +949,9 @@ class DigitalEmployeeSyncApplicationServiceImplTest { "artifact_id", "artifact-1", "retention_status", "expired", "cleanup_status", "pending", + "cleanup_request_id", "cleanup-request-1", + "cleanup_eligible_count", 3, + "batch_cleanup_disabled", true, "summary", "等待清理" ), Map.of("id", "artifact-1", "status", "available")), @@ -995,6 +1006,9 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(0).getCategory()).isEqualTo("artifact_lifecycle"); assertThat(result.getRecords().get(0).getStatus()).isEqualTo("available"); assertThat(result.getRecords().get(0).getSummary()).isEqualTo("等待清理"); + 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).getBusinessView()).containsEntry("retention_status", "expired"); assertThat(result.getRecords().get(1).getArtifactId()).isEqualTo("artifact-2"); assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("cleanup_status", "deleted"); @@ -1137,15 +1151,18 @@ class DigitalEmployeeSyncApplicationServiceImplTest { when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) .thenReturn(page(List.of( record("outbox-daily-report", "daily_report", "report-1", 101L, new Date(10_000), - Map.of( - "entity_type", "daily_report", - "category", "daily_report", - "kind", "daily_report_generated", - "status", "generated", - "title", "6 月 13 日日报", - "report_id", "report-1", - "summary", "日报已生成", - "artifact_id", "artifact-report-1" + Map.ofEntries( + Map.entry("entity_type", "daily_report"), + Map.entry("category", "daily_report"), + Map.entry("kind", "daily_report_generated"), + Map.entry("status", "generated"), + Map.entry("title", "6 月 13 日日报"), + Map.entry("report_id", "report-1"), + Map.entry("daily_report_fact_id", "daily-report-fact-1"), + Map.entry("send_record_id", "send-record-1"), + Map.entry("approval_decision_status", "pending"), + Map.entry("summary", "日报已生成"), + Map.entry("artifact_id", "artifact-report-1") ), Map.of("id", "report-1", "kind", "daily_report_generated")), record("outbox-daily-report-delivered", "event", "event-daily-report-1", 101L, new Date(11_000), @@ -1195,6 +1212,9 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(0).getCategory()).isEqualTo("daily_report"); 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).getSendRecordId()).isEqualTo("send-record-1"); + 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"); assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("confirmed_at", "2026-06-13T10:00:00+08:00"); diff --git a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx index fb6829c5..db00afc1 100644 --- a/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx +++ b/qiming/src/pages/SystemManagement/Content/DigitalEmployeeOps/index.tsx @@ -111,8 +111,11 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) { record.approval_id && `Approval ${record.approval_id}`, record.service_id && `Service ${record.service_id}`, record.artifact_id && `Artifact ${record.artifact_id}`, + record.cleanup_request_id && `Cleanup ${record.cleanup_request_id}`, record.route_decision_id && `Route ${record.route_decision_id}`, record.policy_kind && `Policy ${record.policy_kind}`, + record.subscription_policy_id && `Subscription ${record.subscription_policy_id}`, + record.daily_report_fact_id && `ReportFact ${record.daily_report_fact_id}`, record.memory_id && `Memory ${record.memory_id}`, record.skill_id && `Skill ${record.skill_id}`, record.tool_name && `Tool ${record.tool_name}`, @@ -261,6 +264,7 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE { action: 'mark_keep', label: '标记保留' }, { action: 'mark_expire', label: '标记到期' }, { action: 'mark_reviewed', label: '标记复核' }, + { action: 'record_cleanup_request', label: '记录批量清理申请' }, ]; } if (view === 'daily_reports' && record.report_id) { @@ -781,9 +785,17 @@ const DigitalEmployeeOps: React.FC = () => { detailRecord.approval_id && `Approval ${detailRecord.approval_id}`, detailRecord.service_id && `Service ${detailRecord.service_id}`, detailRecord.artifact_id && `Artifact ${detailRecord.artifact_id}`, + detailRecord.cleanup_request_id && + `CleanupRequest ${detailRecord.cleanup_request_id}`, detailRecord.notification_id && `Notification ${detailRecord.notification_id}`, + detailRecord.subscription_policy_id && + `Subscription ${detailRecord.subscription_policy_id}`, detailRecord.report_id && `Report ${detailRecord.report_id}`, + detailRecord.daily_report_fact_id && + `ReportFact ${detailRecord.daily_report_fact_id}`, + detailRecord.send_record_id && + `SendRecord ${detailRecord.send_record_id}`, detailRecord.route_decision_id && `Route ${detailRecord.route_decision_id}`, detailRecord.policy_kind && `Policy ${detailRecord.policy_kind}`, @@ -839,6 +851,50 @@ const DigitalEmployeeOps: React.FC = () => { )} + {activeView === 'artifact_lifecycle' && ( + + 产物协作: + {detailRecord.artifact_id || detailRecord.entity_id || '--'} + {detailRecord.cleanup_eligible_count != null && + ` · 可清理 ${detailRecord.cleanup_eligible_count} 项`} + {detailRecord.batch_cleanup_disabled && ' · 批量真实清理禁用'} + + {' '} + · 只允许记录批量清理申请和保留策略意图,不执行批量删除。 + + + )} + {activeView === 'notifications' && ( + + 通知协作: + {detailRecord.notification_id || detailRecord.entity_id || '--'} + {detailRecord.subscription_policy_id && + ` · 订阅策略 ${detailRecord.subscription_policy_id}`} + {detailRecord.channel && ` · ${detailRecord.channel}`} + {detailRecord.delivery_status && ` · ${detailRecord.delivery_status}`} + {detailRecord.conflict_status && ` · ${detailRecord.conflict_status}`} + + {' '} + · 展示跨设备状态,只记录已读/忽略意图。 + + + )} + {activeView === 'daily_reports' && ( + + 日报协作: + {detailRecord.report_id || detailRecord.entity_id || '--'} + {detailRecord.daily_report_fact_id && + ` · 事实 ${detailRecord.daily_report_fact_id}`} + {detailRecord.send_record_id && + ` · 发送记录 ${detailRecord.send_record_id}`} + {detailRecord.approval_decision_status && + ` · 审批状态 ${detailRecord.approval_decision_status}`} + + {' '} + · 展示发送记录、审批状态和事实来源,只记录发送结果/审批/回执意图。 + + + )} {activeView === 'governance_commands' && ( 治理命令: diff --git a/qiming/src/types/interfaces/systemManage.ts b/qiming/src/types/interfaces/systemManage.ts index e3cdfed7..f3406694 100644 --- a/qiming/src/types/interfaces/systemManage.ts +++ b/qiming/src/types/interfaces/systemManage.ts @@ -594,8 +594,18 @@ export interface DigitalEmployeeAdminWorkbenchRow { run_id?: string; approval_id?: string; artifact_id?: string; + cleanup_request_id?: string; + cleanup_eligible_count?: number; + batch_cleanup_disabled?: boolean; notification_id?: string; + subscription_policy_id?: string; + channel?: string; + delivery_status?: string; + conflict_status?: string; report_id?: string; + daily_report_fact_id?: string; + send_record_id?: string; + approval_decision_status?: string; route_decision_id?: string; service_id?: string; memory_id?: 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 be4b04c9..b756bec4 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -1737,6 +1737,99 @@ function checkDigitalAdminPlanGovernanceWorkbenches() { console.log("[DigitalEmployeeCheck] admin plan governance and revision hooks OK"); } +function checkDigitalAdminCollaborationOperationsClosure() { + console.log("\n[DigitalEmployeeCheck] Verify admin artifact, notification and daily report collaboration closure hooks"); + const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend"); + const qimingRoot = path.resolve(packageRoot, "..", "..", "..", "qiming"); + const servicePath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-application", + "src", + "main", + "java", + "com", + "xspaceagi", + "agent", + "core", + "application", + "service", + "DigitalEmployeeSyncApplicationServiceImpl.java", + ); + const dtoPath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-adapter", + "src", + "main", + "java", + "com", + "xspaceagi", + "agent", + "core", + "adapter", + "dto", + "digital", + "DigitalEmployeeAdminWorkbenchRowDto.java", + ); + const testPath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-application", + "src", + "test", + "java", + "com", + "xspaceagi", + "agent", + "core", + "application", + "service", + "DigitalEmployeeSyncApplicationServiceImplTest.java", + ); + const qimingOpsPath = path.join(qimingRoot, "src", "pages", "SystemManagement", "Content", "DigitalEmployeeOps", "index.tsx"); + const qimingTypesPath = path.join(qimingRoot, "src", "types", "interfaces", "systemManage.ts"); + const docsPath = path.resolve(packageRoot, "..", "..", "docs", "digital-employee-frontend-integration-plan.md"); + const service = fs.readFileSync(servicePath, "utf8"); + const dto = fs.readFileSync(dtoPath, "utf8"); + const test = fs.readFileSync(testPath, "utf8"); + const qimingOps = fs.readFileSync(qimingOpsPath, "utf8"); + const qimingTypes = fs.readFileSync(qimingTypesPath, "utf8"); + const docs = fs.readFileSync(docsPath, "utf8"); + const requiredSnippets = [ + [service, "record_cleanup_request", "backend cleanup request admin action"], + [service, "cleanup_request_id", "backend cleanup request projection"], + [service, "batch_cleanup_disabled", "backend batch cleanup disabled projection"], + [service, "subscription_policy_id", "backend subscription policy projection"], + [service, "daily_report_fact_id", "backend daily report fact projection"], + [dto, "cleanup_request_id", "backend cleanup request row field"], + [dto, "cleanup_eligible_count", "backend cleanup eligible count row field"], + [dto, "batch_cleanup_disabled", "backend batch cleanup disabled row field"], + [dto, "subscription_policy_id", "backend subscription policy row field"], + [dto, "daily_report_fact_id", "backend daily report fact row field"], + [test, "getCleanupRequestId", "backend cleanup request test assertion"], + [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, "subscription_policy_id", "qiming subscription policy type field"], + [qimingTypes, "daily_report_fact_id", "qiming daily report fact type field"], + [qimingOps, "记录批量清理申请", "qiming cleanup request action"], + [qimingOps, "订阅策略", "qiming subscription policy copy"], + [qimingOps, "审批状态", "qiming daily approval status copy"], + [docs, "正式管理端协作运营收口", "docs collaboration closure absorption"], + ]; + const missing = requiredSnippets + .filter(([content, snippet]) => !content.includes(snippet)) + .map(([, , label]) => label); + if (missing.length > 0) { + throw new Error(`Missing admin collaboration operations closure hooks: ${missing.join(", ")}`); + } + console.log("[DigitalEmployeeCheck] admin collaboration operations closure hooks OK"); +} + function checkDigitalAdminServiceOperationsWorkbench() { console.log("\n[DigitalEmployeeCheck] Verify admin service operations workbench hooks"); const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend"); @@ -2344,6 +2437,7 @@ function main() { checkDigitalAdminMemorySkillWorkbench(); checkDigitalAdminPolicySchedulerWorkbenches(); checkDigitalAdminPlanGovernanceWorkbenches(); + checkDigitalAdminCollaborationOperationsClosure(); checkDigitalAdminServiceOperationsWorkbench(); checkDigitalAdminRouteGovernanceWorkbench(); checkDigitalAdminNotificationWorkbench(); diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index df1782d5..a535f0ad 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` 意图,等待客户端拉取执行;仍缺少批量真实文件清理、复杂跨设备图谱、正式外部生命周期工作台和更完整物化业务表。 + - 缺口:正式管理端产物生命周期预览已开始吸收,qiming“数字员工运营台”的“产物治理”tab 可只读聚合产物 lifecycle / access / retention / cleanup / version 业务视图,并通过低风险 admin action 记录 `mark_keep` / `mark_expire` / `mark_reviewed` 意图,等待客户端拉取执行;正式管理端协作运营收口已开始吸收,产物治理详情会展示 `cleanup_request_id`、`cleanup_eligible_count` 和 `batch_cleanup_disabled`,只允许记录批量清理申请,不执行批量真实清理。仍缺少批量真实文件清理、复杂跨设备图谱、正式外部生命周期工作台和更完整物化业务表。 - 建议:下一轮围绕正式管理端生命周期工作台和跨设备图谱,把 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` 意图,等待客户端拉取执行;仍缺少正式外部通知协作工作台、复杂订阅策略 UI、真实通知发送/推送通道和跨设备通知冲突合并。 + - 缺口:正式管理端通知运营预览已开始吸收,embedded 新增“通知运营”入口,可筛选全部/未读/待处理/异常通知,支持批量已读、批量忽略、拉取远端通知、打开系统通知设置和单条回复;qiming“数字员工运营台”的“通知运营”tab 可只读聚合通知业务视图,并通过低风险 admin action 记录 `mark_read` / `dismiss` 意图,等待客户端拉取执行;正式管理端协作运营收口已开始吸收,通知运营详情会展示 `subscription_policy_id`、`channel`、`delivery_status` 和 `conflict_status`,用于解释订阅策略和跨设备状态。仍缺少正式外部通知协作工作台、复杂订阅策略 UI、真实通知发送/推送通道和跨设备通知冲突合并。 - 建议:下一轮围绕正式管理端通知工作台,把 Inbox 从嵌入页投影推进到完整跨端协作入口。 10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)** @@ -614,7 +614,7 @@ GET /api/digital-employee/approvals - 建议:先使用 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 运营台可在日报详情中“记录发送结果”,但不接真实第三方发送通道。 + - 已吸收: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`,用于解释事实来源、发送记录和审批状态。 - 缺口:没有正式 DailyReport 物理表、真实管理端发送通道、正式外部管理端日报页面和审批裁决回写闭环。 - 建议:先稳定 Artifact/Event 派生日报交付模型,再根据管理端联调需要决定是否物化 DailyReport 表与发送记录表。