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 149aad4f..c18b0b20 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 @@ -532,6 +532,78 @@ class DigitalEmployeeSyncApplicationServiceImplTest { assertThat(result.getRecords().get(2).getNotificationId()).isEqualTo("notification-preference"); } + @Test + void queryAdminWorkbenchArtifactLifecycleReturnsArtifactRowsOnly() { + 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-artifact", "artifact", "artifact-1", 101L, new Date(10_000), + Map.of( + "entity_type", "artifact", + "title", "日报 PDF", + "status", "available", + "artifact_id", "artifact-1", + "retention_status", "expired", + "cleanup_status", "pending", + "summary", "等待清理" + ), + Map.of("id", "artifact-1", "status", "available")), + record("outbox-artifact-retention", "event", "event-artifact-1", 101L, new Date(11_000), + Map.of( + "entity_type", "event", + "category", "artifact_lifecycle", + "kind", "artifact_retention_marked", + "status", "recorded", + "title", "产物标记到期", + "artifact_id", "artifact-2", + "retention_status", "expired" + ), + Map.of("id", "event-artifact-1", "kind", "artifact_retention_marked")), + record("outbox-artifact-cleanup", "event", "event-artifact-2", 101L, new Date(12_000), + Map.of( + "entity_type", "event", + "kind", "artifact_cleanup_executed", + "status", "deleted", + "title", "产物文件已清理", + "artifact_id", "artifact-3", + "cleanup_status", "deleted", + "deleted_at", "2026-06-13T00:00:00+08:00" + ), + Map.of("id", "event-artifact-2", "kind", "artifact_cleanup_executed")), + 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")), + record("outbox-approval", "approval", "approval-1", 101L, new Date(14_000), + Map.of("entity_type", "approval", "title", "普通审批", "status", "pending"), + Map.of("id", "approval-1", "status", "pending")), + record("outbox-notification", "notification", "notification-1", 101L, new Date(15_000), + Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"), + Map.of("id", "notification-1", "status", "unread")), + record("outbox-route", "event", "event-route-1", 101L, new Date(16_000), + Map.of("entity_type", "event", "category", "route_governance", "title", "路由", "status", "blocked"), + Map.of("id", "event-route-1", "status", "blocked")), + record("outbox-audit", "event", "event-audit-1", 101L, new Date(17_000), + Map.of("entity_type", "event", "category", "tool_call_audit", "title", "审计", "status", "warn"), + Map.of("id", "event-audit-1", "status", "warn")) + ))); + + DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench( + "artifact_lifecycle", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L + ); + + assertThat(result.getTotal()).isEqualTo(3); + assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId) + .containsExactly("artifact-1", "event-artifact-1", "event-artifact-2"); + assertThat(result.getRecords().get(0).getView()).isEqualTo("artifact_lifecycle"); + assertThat(result.getRecords().get(0).getArtifactId()).isEqualTo("artifact-1"); + 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).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 queryAdminWorkbenchAuditsRedactsSensitivePayloadFields() { when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L))) diff --git a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js index 206ab9d9..063bb72a 100644 --- a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -1686,6 +1686,90 @@ function checkDigitalAdminNotificationWorkbench() { console.log("[DigitalEmployeeCheck] admin notification workbench hooks OK"); } +function checkDigitalAdminArtifactLifecycleWorkbench() { + console.log("\n[DigitalEmployeeCheck] Verify admin artifact lifecycle workbench hooks"); + const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend"); + const qimingRoot = path.resolve(packageRoot, "..", "..", "..", "qiming"); + const servicePath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-application", + "src", + "main", + "java", + "com", + "xspaceagi", + "agent", + "core", + "application", + "service", + "DigitalEmployeeSyncApplicationServiceImpl.java", + ); + const dtoPath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-adapter", + "src", + "main", + "java", + "com", + "xspaceagi", + "agent", + "core", + "adapter", + "dto", + "digital", + "DigitalEmployeeAdminWorkbenchRowDto.java", + ); + const testPath = path.join( + backendRoot, + "app-platform-modules", + "app-platform-agent", + "app-platform-agent-core-application", + "src", + "test", + "java", + "com", + "xspaceagi", + "agent", + "core", + "application", + "service", + "DigitalEmployeeSyncApplicationServiceImplTest.java", + ); + const qimingOpsPath = path.join(qimingRoot, "src", "pages", "SystemManagement", "Content", "DigitalEmployeeOps", "index.tsx"); + const qimingTypesPath = path.join(qimingRoot, "src", "types", "interfaces", "systemManage.ts"); + const docsPath = path.resolve(packageRoot, "..", "..", "docs", "digital-employee-frontend-integration-plan.md"); + const service = fs.readFileSync(servicePath, "utf8"); + const dto = fs.readFileSync(dtoPath, "utf8"); + const test = fs.readFileSync(testPath, "utf8"); + const qimingOps = fs.readFileSync(qimingOpsPath, "utf8"); + const qimingTypes = fs.readFileSync(qimingTypesPath, "utf8"); + const docs = fs.readFileSync(docsPath, "utf8"); + const requiredSnippets = [ + [service, "artifact_lifecycle", "backend artifact lifecycle view"], + [service, "isArtifactLifecycleRow", "backend artifact lifecycle matcher"], + [service, "artifact_", "backend artifact event classifier"], + [dto, "artifact_id", "backend artifact id row field"], + [test, "queryAdminWorkbenchArtifactLifecycleReturnsArtifactRowsOnly", "backend artifact lifecycle test"], + [qimingTypes, "artifact_lifecycle", "qiming artifact lifecycle view type"], + [qimingOps, "产物治理", "qiming artifact lifecycle tab"], + [qimingOps, "标记保留", "qiming artifact keep action"], + [qimingOps, "标记到期", "qiming artifact expire action"], + [qimingOps, "标记复核", "qiming artifact reviewed action"], + [docs, "正式管理端产物生命周期预览", "docs artifact lifecycle absorption"], + ]; + const missing = requiredSnippets + .filter(([content, snippet]) => !content.includes(snippet)) + .map(([, , label]) => label); + if (missing.length > 0) { + throw new Error(`Missing admin artifact lifecycle workbench hooks: ${missing.join(", ")}`); + } + console.log("[DigitalEmployeeCheck] admin artifact lifecycle workbench hooks OK"); +} + function checkLocalDatabaseSchema() { console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema"); const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db"); @@ -1752,6 +1836,7 @@ function main() { checkDigitalAdminServiceOperationsWorkbench(); checkDigitalAdminRouteGovernanceWorkbench(); checkDigitalAdminNotificationWorkbench(); + checkDigitalAdminArtifactLifecycleWorkbench(); checkLocalDatabaseSchema(); console.log("\n[DigitalEmployeeCheck] All checks passed"); } catch (error) { diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index 139850a9..c7ed6886 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` 意图,等待客户端拉取执行;仍缺少批量真实文件清理、复杂跨设备图谱、正式外部生命周期工作台和更完整物化业务表。 - 建议:下一轮围绕正式管理端生命周期工作台和跨设备图谱,把 embedded 预览聚合推进到跨端运营治理。 8. **审批 / 人工介入增强**