吸收数字员工管理端通知订阅策略预览
This commit is contained in:
@@ -88,6 +88,14 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
|
|||||||
private String notificationId;
|
private String notificationId;
|
||||||
@JsonProperty("subscription_policy_id")
|
@JsonProperty("subscription_policy_id")
|
||||||
private String subscriptionPolicyId;
|
private String subscriptionPolicyId;
|
||||||
|
@JsonProperty("notification_policy_id")
|
||||||
|
private String notificationPolicyId;
|
||||||
|
@JsonProperty("subscription_status")
|
||||||
|
private String subscriptionStatus;
|
||||||
|
@JsonProperty("desktop_enabled")
|
||||||
|
private Boolean desktopEnabled;
|
||||||
|
@JsonProperty("minimum_level")
|
||||||
|
private String minimumLevel;
|
||||||
private String channel;
|
private String channel;
|
||||||
@JsonProperty("delivery_status")
|
@JsonProperty("delivery_status")
|
||||||
private String deliveryStatus;
|
private String deliveryStatus;
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
"route_decision:mark_reviewed", "route_decision:dismiss", "route_decision:retry_policy_check",
|
"route_decision:mark_reviewed", "route_decision:dismiss", "route_decision:retry_policy_check",
|
||||||
"route:mark_reviewed", "route:dismiss", "route:retry_policy_check",
|
"route:mark_reviewed", "route:dismiss", "route:retry_policy_check",
|
||||||
"notification:mark_read", "notification:dismiss",
|
"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",
|
"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:mark_delivered", "daily_report:mark_confirmed", "daily_report:request_approval",
|
||||||
"memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed",
|
"memory:archive_memory", "memory:restore_memory", "memory:mark_reviewed",
|
||||||
@@ -1111,6 +1112,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
.batchCleanupDisabled(nullableBooleanValue(firstPresent(businessView.get("batch_cleanup_disabled"), businessView.get("batchCleanupDisabled"), payload.get("batch_cleanup_disabled"), payload.get("batchCleanupDisabled"))))
|
.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")))
|
.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")))
|
.subscriptionPolicyId(firstString(businessView.get("subscription_policy_id"), businessView.get("subscriptionPolicyId"), payload.get("subscription_policy_id"), payload.get("subscriptionPolicyId")))
|
||||||
|
.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")))
|
.channel(firstString(businessView.get("channel"), payload.get("channel")))
|
||||||
.deliveryStatus(firstString(businessView.get("delivery_status"), businessView.get("deliveryStatus"), payload.get("delivery_status"), payload.get("deliveryStatus")))
|
.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")))
|
.conflictStatus(firstString(businessView.get("conflict_status"), businessView.get("conflictStatus"), payload.get("conflict_status"), payload.get("conflictStatus")))
|
||||||
@@ -1185,6 +1190,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
case "service_operations" -> isServiceOperationsRow(row);
|
case "service_operations" -> isServiceOperationsRow(row);
|
||||||
case "artifact_lifecycle" -> isArtifactLifecycleRow(row);
|
case "artifact_lifecycle" -> isArtifactLifecycleRow(row);
|
||||||
case "notifications" -> isNotificationRow(row);
|
case "notifications" -> isNotificationRow(row);
|
||||||
|
case "notification_policies" -> isNotificationPolicyRow(row);
|
||||||
case "audits" -> isAuditRow(row);
|
case "audits" -> isAuditRow(row);
|
||||||
case "daily_reports" -> isDailyReportRow(row);
|
case "daily_reports" -> isDailyReportRow(row);
|
||||||
case "governance_commands" -> isGovernanceCommandRow(row);
|
case "governance_commands" -> isGovernanceCommandRow(row);
|
||||||
@@ -1276,6 +1282,20 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
|| StringUtils.isNotBlank(row.getNotificationId());
|
|| StringUtils.isNotBlank(row.getNotificationId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isNotificationPolicyRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||||
|
String category = firstString(row.getCategory());
|
||||||
|
String kind = firstString(row.getKind());
|
||||||
|
return Set.of("notification_policy", "notification_subscription_policy", "notification_preference_policy", "subscription_policy").contains(category)
|
||||||
|
|| contains(kind, "notification_policy")
|
||||||
|
|| contains(kind, "notification_subscription_policy")
|
||||||
|
|| contains(kind, "notification_preference_policy")
|
||||||
|
|| contains(kind, "subscription_policy")
|
||||||
|
|| StringUtils.isNotBlank(row.getNotificationPolicyId())
|
||||||
|
|| StringUtils.isNotBlank(row.getSubscriptionStatus())
|
||||||
|
|| row.getDesktopEnabled() != null
|
||||||
|
|| StringUtils.isNotBlank(row.getMinimumLevel());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
private boolean isAuditRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||||
return Set.of("sandbox_audit", "token_cost", "tool_call_audit", "governance", "sync_failure", "audit_archive", "billing_estimate").contains(row.getCategory())
|
return Set.of("sandbox_audit", "token_cost", "tool_call_audit", "governance", "sync_failure", "audit_archive", "billing_estimate").contains(row.getCategory())
|
||||||
|| isDiagnosticRepairRow(row)
|
|| isDiagnosticRepairRow(row)
|
||||||
@@ -1446,6 +1466,10 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
return contains(kind, "doctor") ? "service_health" : "service_operation";
|
return contains(kind, "doctor") ? "service_health" : "service_operation";
|
||||||
}
|
}
|
||||||
if (contains(kind, "risk_approval")) return "risk_approval";
|
if (contains(kind, "risk_approval")) return "risk_approval";
|
||||||
|
if (contains(kind, "notification_policy") || contains(kind, "subscription_policy") || contains(kind, "notification_preference_policy")
|
||||||
|
|| StringUtils.isNotBlank(firstString(businessView.get("notification_policy_id"), payload.get("notification_policy_id")))) {
|
||||||
|
return "notification_policy";
|
||||||
|
}
|
||||||
if (contains(kind, "notification") || StringUtils.isNotBlank(firstString(businessView.get("notification_id"), payload.get("notification_id")))) {
|
if (contains(kind, "notification") || StringUtils.isNotBlank(firstString(businessView.get("notification_id"), payload.get("notification_id")))) {
|
||||||
String level = firstString(businessView.get("level"), payload.get("level"));
|
String level = firstString(businessView.get("level"), payload.get("level"));
|
||||||
return StringUtils.equals(level, "action") ? "action_notification" : "notification";
|
return StringUtils.equals(level, "action") ? "action_notification" : "notification";
|
||||||
|
|||||||
@@ -1277,6 +1277,71 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
|||||||
assertThat(result.getRecords().get(2).getNotificationId()).isEqualTo("notification-preference");
|
assertThat(result.getRecords().get(2).getNotificationId()).isEqualTo("notification-preference");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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)))
|
||||||
|
.thenReturn(page(List.of(
|
||||||
|
record("outbox-subscription-policy", "event", "event-notification-policy-1", 101L, new Date(10_000),
|
||||||
|
Map.ofEntries(
|
||||||
|
Map.entry("entity_type", "event"),
|
||||||
|
Map.entry("category", "notification_policy"),
|
||||||
|
Map.entry("kind", "notification_subscription_policy_pulled"),
|
||||||
|
Map.entry("status", "enabled"),
|
||||||
|
Map.entry("title", "通知订阅策略"),
|
||||||
|
Map.entry("notification_policy_id", "notification-policy-1"),
|
||||||
|
Map.entry("subscription_policy_id", "subscription-policy-1"),
|
||||||
|
Map.entry("subscription_status", "enabled"),
|
||||||
|
Map.entry("desktop_enabled", true),
|
||||||
|
Map.entry("minimum_level", "warn"),
|
||||||
|
Map.entry("channel", "desktop"),
|
||||||
|
Map.entry("summary", "桌面通知 warn 以上启用")
|
||||||
|
),
|
||||||
|
Map.of("id", "event-notification-policy-1", "kind", "notification_subscription_policy_pulled")),
|
||||||
|
record("outbox-preference-policy", "event", "event-notification-policy-2", 101L, new Date(11_000),
|
||||||
|
Map.of(
|
||||||
|
"entity_type", "event",
|
||||||
|
"kind", "notification_preference_policy_updated",
|
||||||
|
"status", "recorded",
|
||||||
|
"title", "通知偏好策略更新",
|
||||||
|
"subscription_policy_id", "subscription-policy-2",
|
||||||
|
"subscription_status", "muted",
|
||||||
|
"desktop_enabled", false
|
||||||
|
),
|
||||||
|
Map.of("id", "event-notification-policy-2", "kind", "notification_preference_policy_updated")),
|
||||||
|
record("outbox-notification", "notification", "notification-1", 101L, new Date(12_000),
|
||||||
|
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread", "notification_id", "notification-1"),
|
||||||
|
Map.of("id", "notification-1", "status", "unread")),
|
||||||
|
record("outbox-action-notification", "event", "event-notification-action", 101L, new Date(13_000),
|
||||||
|
Map.of("entity_type", "event", "category", "action_notification", "kind", "digital_workday_notification_reply", "title", "通知回复", "notification_id", "notification-2"),
|
||||||
|
Map.of("id", "event-notification-action", "status", "recorded")),
|
||||||
|
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-audit", "event", "event-audit-1", 101L, new Date(15_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(
|
||||||
|
"notification_policies", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(result.getTotal()).isEqualTo(2);
|
||||||
|
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
|
||||||
|
.containsExactly("event-notification-policy-1", "event-notification-policy-2");
|
||||||
|
assertThat(result.getRecords().get(0).getView()).isEqualTo("notification_policies");
|
||||||
|
assertThat(result.getRecords().get(0).getCategory()).isEqualTo("notification_policy");
|
||||||
|
assertThat(result.getRecords().get(0).getNotificationPolicyId()).isEqualTo("notification-policy-1");
|
||||||
|
assertThat(result.getRecords().get(0).getSubscriptionPolicyId()).isEqualTo("subscription-policy-1");
|
||||||
|
assertThat(result.getRecords().get(0).getSubscriptionStatus()).isEqualTo("enabled");
|
||||||
|
assertThat(result.getRecords().get(0).getDesktopEnabled()).isTrue();
|
||||||
|
assertThat(result.getRecords().get(0).getMinimumLevel()).isEqualTo("warn");
|
||||||
|
assertThat(result.getRecords().get(0).getChannel()).isEqualTo("desktop");
|
||||||
|
assertThat(result.getRecords().get(0).getSummary()).isEqualTo("桌面通知 warn 以上启用");
|
||||||
|
assertThat(result.getRecords().get(1).getSubscriptionPolicyId()).isEqualTo("subscription-policy-2");
|
||||||
|
assertThat(result.getRecords().get(1).getDesktopEnabled()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void queryAdminWorkbenchArtifactLifecycleReturnsArtifactRowsOnly() {
|
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)))
|
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const WORKBENCH_TABS: Array<{
|
|||||||
{ key: 'service_operations', label: '服务运营' },
|
{ key: 'service_operations', label: '服务运营' },
|
||||||
{ key: 'artifact_lifecycle', label: '产物治理' },
|
{ key: 'artifact_lifecycle', label: '产物治理' },
|
||||||
{ key: 'notifications', label: '通知运营' },
|
{ key: 'notifications', label: '通知运营' },
|
||||||
|
{ key: 'notification_policies', label: '通知策略' },
|
||||||
{ key: 'audits', label: '审计诊断' },
|
{ key: 'audits', label: '审计诊断' },
|
||||||
{ key: 'daily_reports', label: '日报运营' },
|
{ key: 'daily_reports', label: '日报运营' },
|
||||||
{ key: 'governance_commands', label: '治理命令' },
|
{ key: 'governance_commands', label: '治理命令' },
|
||||||
@@ -209,6 +210,15 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp
|
|||||||
if (view === 'notifications' && record.notification_id) {
|
if (view === 'notifications' && record.notification_id) {
|
||||||
return { entityType: 'notification', entityId: record.notification_id };
|
return { entityType: 'notification', entityId: record.notification_id };
|
||||||
}
|
}
|
||||||
|
if (view === 'notification_policies') {
|
||||||
|
return {
|
||||||
|
entityType: 'notification_policy',
|
||||||
|
entityId:
|
||||||
|
record.notification_policy_id ||
|
||||||
|
record.subscription_policy_id ||
|
||||||
|
record.entity_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
if (view === 'service_operations') {
|
if (view === 'service_operations') {
|
||||||
return { entityType: 'event', entityId: record.entity_id };
|
return { entityType: 'event', entityId: record.entity_id };
|
||||||
}
|
}
|
||||||
@@ -303,6 +313,13 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE
|
|||||||
{ action: 'dismiss', label: '忽略' },
|
{ action: 'dismiss', label: '忽略' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if (view === 'notification_policies') {
|
||||||
|
return [
|
||||||
|
{ action: 'pull_policy', label: '拉取通知策略' },
|
||||||
|
{ action: 'mark_reviewed', label: '标记已看' },
|
||||||
|
{ action: 'dismiss', label: '忽略' },
|
||||||
|
];
|
||||||
|
}
|
||||||
if (view === 'service_operations') {
|
if (view === 'service_operations') {
|
||||||
return [
|
return [
|
||||||
{ action: 'mark_reviewed', label: '标记已看' },
|
{ action: 'mark_reviewed', label: '标记已看' },
|
||||||
@@ -884,6 +901,8 @@ const DigitalEmployeeOps: React.FC = () => {
|
|||||||
`Notification ${detailRecord.notification_id}`,
|
`Notification ${detailRecord.notification_id}`,
|
||||||
detailRecord.subscription_policy_id &&
|
detailRecord.subscription_policy_id &&
|
||||||
`Subscription ${detailRecord.subscription_policy_id}`,
|
`Subscription ${detailRecord.subscription_policy_id}`,
|
||||||
|
detailRecord.notification_policy_id &&
|
||||||
|
`NotificationPolicy ${detailRecord.notification_policy_id}`,
|
||||||
detailRecord.report_id && `Report ${detailRecord.report_id}`,
|
detailRecord.report_id && `Report ${detailRecord.report_id}`,
|
||||||
detailRecord.daily_report_fact_id &&
|
detailRecord.daily_report_fact_id &&
|
||||||
`ReportFact ${detailRecord.daily_report_fact_id}`,
|
`ReportFact ${detailRecord.daily_report_fact_id}`,
|
||||||
@@ -1054,6 +1073,27 @@ const DigitalEmployeeOps: React.FC = () => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
)}
|
)}
|
||||||
|
{activeView === 'notification_policies' && (
|
||||||
|
<Typography.Paragraph>
|
||||||
|
<Typography.Text strong>通知策略:</Typography.Text>
|
||||||
|
{detailRecord.notification_policy_id ||
|
||||||
|
detailRecord.subscription_policy_id ||
|
||||||
|
detailRecord.entity_id ||
|
||||||
|
'--'}
|
||||||
|
{detailRecord.subscription_policy_id &&
|
||||||
|
` · 订阅策略 ${detailRecord.subscription_policy_id}`}
|
||||||
|
{detailRecord.subscription_status &&
|
||||||
|
` · 订阅状态 ${detailRecord.subscription_status}`}
|
||||||
|
{detailRecord.desktop_enabled != null &&
|
||||||
|
` · 桌面通知 ${detailRecord.desktop_enabled ? '开启' : '关闭'}`}
|
||||||
|
{detailRecord.minimum_level && ` · 最低等级 ${detailRecord.minimum_level}`}
|
||||||
|
{detailRecord.channel && ` · 渠道 ${detailRecord.channel}`}
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
{' '}
|
||||||
|
· 展示复杂订阅策略和桌面通知阈值,只记录拉取/复核/忽略意图,不远程编辑策略、不触发真实推送。
|
||||||
|
</Typography.Text>
|
||||||
|
</Typography.Paragraph>
|
||||||
|
)}
|
||||||
{activeView === 'daily_reports' && (
|
{activeView === 'daily_reports' && (
|
||||||
<Typography.Paragraph>
|
<Typography.Paragraph>
|
||||||
<Typography.Text strong>日报协作:</Typography.Text>
|
<Typography.Text strong>日报协作:</Typography.Text>
|
||||||
|
|||||||
@@ -564,6 +564,7 @@ export type DigitalEmployeeAdminWorkbenchView =
|
|||||||
| 'service_operations'
|
| 'service_operations'
|
||||||
| 'artifact_lifecycle'
|
| 'artifact_lifecycle'
|
||||||
| 'notifications'
|
| 'notifications'
|
||||||
|
| 'notification_policies'
|
||||||
| 'audits'
|
| 'audits'
|
||||||
| 'daily_reports'
|
| 'daily_reports'
|
||||||
| 'governance_commands'
|
| 'governance_commands'
|
||||||
@@ -625,6 +626,10 @@ export interface DigitalEmployeeAdminWorkbenchRow {
|
|||||||
batch_cleanup_disabled?: boolean;
|
batch_cleanup_disabled?: boolean;
|
||||||
notification_id?: string;
|
notification_id?: string;
|
||||||
subscription_policy_id?: string;
|
subscription_policy_id?: string;
|
||||||
|
notification_policy_id?: string;
|
||||||
|
subscription_status?: string;
|
||||||
|
desktop_enabled?: boolean;
|
||||||
|
minimum_level?: string;
|
||||||
channel?: string;
|
channel?: string;
|
||||||
delivery_status?: string;
|
delivery_status?: string;
|
||||||
conflict_status?: string;
|
conflict_status?: string;
|
||||||
|
|||||||
@@ -2351,6 +2351,92 @@ function checkDigitalAdminNotificationWorkbench() {
|
|||||||
console.log("[DigitalEmployeeCheck] admin notification workbench hooks OK");
|
console.log("[DigitalEmployeeCheck] admin notification workbench hooks OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkDigitalAdminNotificationPoliciesWorkbench() {
|
||||||
|
console.log("\n[DigitalEmployeeCheck] Verify admin notification policies 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, "notification_policies", "backend notification policies view"],
|
||||||
|
[service, "isNotificationPolicyRow", "backend notification policies matcher"],
|
||||||
|
[dto, "notification_policy_id", "backend notification policy id row field"],
|
||||||
|
[dto, "subscription_status", "backend subscription status row field"],
|
||||||
|
[dto, "desktop_enabled", "backend desktop enabled row field"],
|
||||||
|
[dto, "minimum_level", "backend minimum level row field"],
|
||||||
|
[test, "queryAdminWorkbenchNotificationPoliciesReturnsSubscriptionPolicyRowsOnly", "backend notification policies test"],
|
||||||
|
[qimingTypes, "notification_policies", "qiming notification policies view type"],
|
||||||
|
[qimingOps, "通知策略", "qiming notification policies tab"],
|
||||||
|
[qimingOps, "拉取通知策略", "qiming notification policy pull action"],
|
||||||
|
[qimingOps, "不远程编辑策略", "qiming notification policy safety copy"],
|
||||||
|
[qimingOps, "不触发真实推送", "qiming notification push safety copy"],
|
||||||
|
[docs, "正式管理端通知订阅策略预览", "docs notification policies absorption"],
|
||||||
|
];
|
||||||
|
const missing = requiredSnippets
|
||||||
|
.filter(([content, snippet]) => !content.includes(snippet))
|
||||||
|
.map(([, , label]) => label);
|
||||||
|
if (missing.length > 0) {
|
||||||
|
throw new Error(`Missing admin notification policies workbench hooks: ${missing.join(", ")}`);
|
||||||
|
}
|
||||||
|
console.log("[DigitalEmployeeCheck] admin notification policies workbench hooks OK");
|
||||||
|
}
|
||||||
|
|
||||||
function checkDigitalAdminArtifactLifecycleWorkbench() {
|
function checkDigitalAdminArtifactLifecycleWorkbench() {
|
||||||
console.log("\n[DigitalEmployeeCheck] Verify admin artifact lifecycle workbench hooks");
|
console.log("\n[DigitalEmployeeCheck] Verify admin artifact lifecycle workbench hooks");
|
||||||
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
|
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
|
||||||
@@ -2862,6 +2948,7 @@ function main() {
|
|||||||
checkDigitalAdminServiceOperationsWorkbench();
|
checkDigitalAdminServiceOperationsWorkbench();
|
||||||
checkDigitalAdminRouteGovernanceWorkbench();
|
checkDigitalAdminRouteGovernanceWorkbench();
|
||||||
checkDigitalAdminNotificationWorkbench();
|
checkDigitalAdminNotificationWorkbench();
|
||||||
|
checkDigitalAdminNotificationPoliciesWorkbench();
|
||||||
checkDigitalAdminArtifactLifecycleWorkbench();
|
checkDigitalAdminArtifactLifecycleWorkbench();
|
||||||
checkDigitalAdminAuditDiagnosticsWorkbench();
|
checkDigitalAdminAuditDiagnosticsWorkbench();
|
||||||
checkDigitalAdminDiagnosticRepairPreview();
|
checkDigitalAdminDiagnosticRepairPreview();
|
||||||
|
|||||||
@@ -594,7 +594,7 @@ GET /api/digital-employee/approvals
|
|||||||
|
|
||||||
9. **通知 / Inbox / 用户消息**
|
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 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`,用于解释订阅策略和跨设备状态。仍缺少正式外部通知协作工作台、复杂订阅策略 UI、真实通知发送/推送通道和跨设备通知冲突合并。
|
- 缺口:正式管理端通知运营预览已开始吸收,embedded 新增“通知运营”入口,可筛选全部/未读/待处理/异常通知,支持批量已读、批量忽略、拉取远端通知、打开系统通知设置和单条回复;qiming“数字员工运营台”的“通知运营”tab 可只读聚合通知业务视图,并通过低风险 admin action 记录 `mark_read` / `dismiss` 意图,等待客户端拉取执行;正式管理端协作运营收口已开始吸收,通知运营详情会展示 `subscription_policy_id`、`channel`、`delivery_status` 和 `conflict_status`,用于解释订阅策略和跨设备状态。正式管理端通知订阅策略预览已开始吸收,qiming“数字员工运营台”的“通知策略”tab 可独立筛选订阅策略、桌面通知开关、最低通知等级和渠道策略,并只记录拉取通知策略、复核和忽略意图,不远程编辑策略、不触发真实推送。仍缺少正式外部通知协作工作台、复杂订阅策略编辑 UI、真实通知发送/推送通道和跨设备通知冲突合并。
|
||||||
- 建议:下一轮围绕正式管理端通知工作台,把 Inbox 从嵌入页投影推进到完整跨端协作入口。
|
- 建议:下一轮围绕正式管理端通知工作台,把 Inbox 从嵌入页投影推进到完整跨端协作入口。
|
||||||
|
|
||||||
10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)**
|
10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)**
|
||||||
|
|||||||
Reference in New Issue
Block a user