吸收数字员工管理端通知运营预览

This commit is contained in:
baiyanyun
2026-06-13 00:03:42 +08:00
parent 479e2e209d
commit 1daf58a94c
3 changed files with 151 additions and 2 deletions

View File

@@ -468,6 +468,70 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
assertThat(result.getRecords().get(2).getRouteDecisionId()).isEqualTo("route-3");
}
@Test
void queryAdminWorkbenchNotificationsReturnsNotificationRowsOnly() {
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.of("id", "notification-1", "kind", "notification_received")),
record("outbox-action-notification", "event", "event-notification-1", 101L, new Date(11_000),
Map.of(
"entity_type", "event",
"category", "action_notification",
"kind", "digital_workday_notification_reply",
"status", "recorded",
"title", "通知回复已记录",
"notification_id", "notification-2"
),
Map.of("id", "event-notification-1", "kind", "digital_workday_notification_reply")),
record("outbox-preference", "event", "event-notification-2", 101L, new Date(12_000),
Map.of(
"entity_type", "event",
"kind", "notification_preference_updated",
"status", "recorded",
"title", "通知订阅偏好更新",
"notification_id", "notification-preference"
),
Map.of("id", "event-notification-2", "kind", "notification_preference_updated")),
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-route", "event", "event-route-1", 101L, new Date(15_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(16_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(
"notifications", 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("notification-1", "event-notification-1", "event-notification-2");
assertThat(result.getRecords().get(0).getView()).isEqualTo("notifications");
assertThat(result.getRecords().get(0).getNotificationId()).isEqualTo("notification-1");
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(1).getCategory()).isEqualTo("action_notification");
assertThat(result.getRecords().get(2).getNotificationId()).isEqualTo("notification-preference");
}
@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)))

View File

@@ -1083,7 +1083,7 @@ function checkDigitalOpsNotificationReportWorkbenches() {
[dailyReportWorkbench, "请求审批", "daily report approval UI"],
[dailyReportWorkbench, "确认回执", "daily report confirmation UI"],
[docs, "完整人机介入入口", "docs managed service ops absorption"],
[docs, "真实管理端通知页面预览", "docs notification workbench absorption"],
[docs, "正式管理端通知运营预览", "docs notification workbench absorption"],
[docs, "诊断修复动作预览", "docs diagnostic repair absorption"],
[docs, "管理端正式日报页面预览", "docs daily report workbench absorption"],
];
@@ -1602,6 +1602,90 @@ function checkDigitalAdminRouteGovernanceWorkbench() {
console.log("[DigitalEmployeeCheck] admin route governance workbench hooks OK");
}
function checkDigitalAdminNotificationWorkbench() {
console.log("\n[DigitalEmployeeCheck] Verify admin notification 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, "notifications", "backend notification view"],
[service, "isNotificationRow", "backend notification matcher"],
[dto, "notification_id", "backend notification id row field"],
[service, "action_notification", "backend action notification classifier"],
[service, "notification", "backend notification kind classifier"],
[test, "queryAdminWorkbenchNotificationsReturnsNotificationRowsOnly", "backend notification workbench test"],
[qimingTypes, "notifications", "qiming notification view type"],
[qimingOps, "通知运营", "qiming notification workbench tab"],
[qimingOps, "标记已读", "qiming notification read action"],
[qimingOps, "忽略", "qiming notification dismiss action"],
[docs, "正式管理端通知运营预览", "docs notification workbench absorption"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing admin notification workbench hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] admin notification workbench hooks OK");
}
function checkLocalDatabaseSchema() {
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
@@ -1667,6 +1751,7 @@ function main() {
checkDigitalAdminApprovalGovernanceWorkbench();
checkDigitalAdminServiceOperationsWorkbench();
checkDigitalAdminRouteGovernanceWorkbench();
checkDigitalAdminNotificationWorkbench();
checkLocalDatabaseSchema();
console.log("\n[DigitalEmployeeCheck] All checks passed");
} catch (error) {

View File

@@ -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 新增“通知运营”入口,可筛选全部/未读/待处理/异常通知,支持批量已读、批量忽略、拉取远端通知、打开系统通知设置和单条回复;仍缺少正式外部管理端通知页面
- 缺口:正式管理端通知运营预览已开始吸收embedded 新增“通知运营”入口,可筛选全部/未读/待处理/异常通知,支持批量已读、批量忽略、拉取远端通知、打开系统通知设置和单条回复;qiming“数字员工运营台”的“通知运营”tab 可只读聚合通知业务视图,并通过低风险 admin action 记录 `mark_read` / `dismiss` 意图,等待客户端拉取执行;仍缺少正式外部通知协作工作台、复杂订阅策略 UI、真实通知发送/推送通道和跨设备通知冲突合并
- 建议:下一轮围绕正式管理端通知工作台,把 Inbox 从嵌入页投影推进到完整跨端协作入口。
10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)**