吸收数字员工管理端通知视图

This commit is contained in:
baiyanyun
2026-06-10 17:05:57 +08:00
parent 5b2f14123e
commit 1271e4967b
7 changed files with 234 additions and 46 deletions

View File

@@ -533,6 +533,68 @@ describe("digital employee sync service", () => {
status: "failed",
reason: "computer chat failed",
});
insertEventEntity("event-notification-read", "digital_workday_notification_read", {
metadata: {
notification_id: "task:task-1",
action: "read",
},
state: {
notificationStateById: {
"task:task-1": {
status: "read",
read_at: "2026-06-07T08:01:00.000Z",
},
},
},
});
insertEventEntity("event-notification-dismiss", "digital_workday_notification_dismiss", {
metadata: {
notification_id: "task:task-2",
action: "dismiss",
},
state: {
notificationStateById: {
"task:task-2": {
status: "dismissed",
dismissed_at: "2026-06-07T08:02:00.000Z",
},
},
},
});
insertEventEntity("event-notification-reply", "digital_workday_notification_reply", {
metadata: {
notification_id: "input:task-3",
action: "reply",
},
state: {
notificationStateById: {
"input:task-3": {
status: "read",
read_at: "2026-06-07T08:03:00.000Z",
reply: "请改用客户备用手机号",
replied_at: "2026-06-07T08:03:30.000Z",
},
},
},
});
insertEventEntity("event-notification-preferences", "digital_workday_update_notification_preferences", {
metadata: {
notification_preferences: {
enabled: false,
muted_kinds: ["task_finished", "need_input"],
muted_levels: ["info"],
updated_at: "2026-06-07T08:04:00.000Z",
},
},
state: {
notificationPreferences: {
enabled: false,
muted_kinds: ["task_finished", "need_input"],
muted_levels: ["info"],
updated_at: "2026-06-07T08:04:00.000Z",
},
},
});
mockState.fetch.mockResolvedValue(
jsonResponse({
success: true,
@@ -542,6 +604,10 @@ describe("digital employee sync service", () => {
{ entity_type: "event", entity_id: "event-artifact" },
{ entity_type: "event", entity_id: "event-skill" },
{ entity_type: "event", entity_id: "event-dispatch" },
{ entity_type: "event", entity_id: "event-notification-read" },
{ entity_type: "event", entity_id: "event-notification-dismiss" },
{ entity_type: "event", entity_id: "event-notification-reply" },
{ entity_type: "event", entity_id: "event-notification-preferences" },
],
}),
);
@@ -602,6 +668,36 @@ describe("digital employee sync service", () => {
status: "failed",
reason: "computer chat failed",
});
expect(businessView("event-notification-read")).toMatchObject({
category: "notification",
notification_id: "task:task-1",
action: "read",
status: "read",
read_at: "2026-06-07T08:01:00.000Z",
});
expect(businessView("event-notification-dismiss")).toMatchObject({
category: "notification",
notification_id: "task:task-2",
action: "dismiss",
status: "dismissed",
dismissed_at: "2026-06-07T08:02:00.000Z",
});
expect(businessView("event-notification-reply")).toMatchObject({
category: "notification",
notification_id: "input:task-3",
action: "reply",
status: "read",
read_at: "2026-06-07T08:03:00.000Z",
replied_at: "2026-06-07T08:03:30.000Z",
reply_length: 10,
});
expect(businessView("event-notification-reply")).not.toHaveProperty("reply");
expect(businessView("event-notification-preferences")).toMatchObject({
category: "notification",
preferences_enabled: false,
muted_kinds: ["task_finished", "need_input"],
muted_levels: ["info"],
});
expect(readEntity("event", "event-route")).toMatchObject({
sync_status: "synced",
sync_error: null,

View File

@@ -568,6 +568,7 @@ function eventSpecificBusinessView(
}
if (kind.startsWith("skill_call_")) return skillCallBusinessView(payload);
if (kind.startsWith("plan_step_dispatch_")) return planStepDispatchBusinessView(payload);
if (isNotificationEvent(kind)) return notificationBusinessView(payload);
return {};
}
@@ -637,16 +638,46 @@ function planStepDispatchBusinessView(payload: Record<string, unknown>): Record<
};
}
function notificationBusinessView(payload: Record<string, unknown>): Record<string, unknown> {
const metadata = objectRecord(payload.metadata) ?? {};
const state = objectRecord(payload.state) ?? {};
const preferences = objectRecord(metadata.notification_preferences) ?? objectRecord(state.notificationPreferences) ?? {};
const overlays = objectRecord(state.notificationStateById) ?? {};
const notificationId = stringField(metadata.notification_id ?? payload.notification_id ?? payload.notificationId) || null;
const overlay = notificationId ? objectRecord(overlays[notificationId]) ?? {} : {};
const reply = stringField(overlay.reply ?? metadata.reply ?? payload.reply);
return {
notification_id: notificationId,
action: stringField(metadata.action ?? payload.action) || null,
status: stringField(overlay.status ?? payload.status) || null,
read_at: stringField(overlay.read_at ?? payload.read_at ?? payload.readAt) || null,
dismissed_at: stringField(overlay.dismissed_at ?? payload.dismissed_at ?? payload.dismissedAt) || null,
replied_at: stringField(overlay.replied_at ?? payload.replied_at ?? payload.repliedAt) || null,
reply_length: reply ? reply.length : null,
preferences_enabled: typeof preferences.enabled === "boolean" ? preferences.enabled : null,
muted_kinds: stringArrayField(preferences.muted_kinds ?? preferences.mutedKinds),
muted_levels: stringArrayField(preferences.muted_levels ?? preferences.mutedLevels),
};
}
function eventBusinessCategory(kind: string): string {
if (kind === "route_decision") return "route";
if (kind.startsWith("approval_")) return "approval";
if (kind.startsWith("artifact_")) return "artifact";
if (kind.startsWith("skill_call_")) return "skill";
if (kind.startsWith("plan_step_dispatch_")) return "dispatch";
if (isNotificationEvent(kind)) return "notification";
if (kind.startsWith("governance_")) return "governance";
return "runtime";
}
function isNotificationEvent(kind: string): boolean {
return kind === "digital_workday_update_notification_preferences"
|| kind === "digital_workday_notification_read"
|| kind === "digital_workday_notification_dismiss"
|| kind === "digital_workday_notification_reply";
}
function routeDecisionAttentionLevel(policyResult: string, reasonCodes: string[], createdCommandId: string | null): "info" | "action" | "warn" | "error" {
if (policyResult && policyResult !== "accepted") return "error";
if (reasonCodes.includes("candidate_skills_disabled") || reasonCodes.includes("route_action_missing_context")) return "warn";