吸收数字员工通知回复任务输入

This commit is contained in:
baiyanyun
2026-06-10 17:49:14 +08:00
parent 1271e4967b
commit 5b78189c9c
8 changed files with 164 additions and 50 deletions

View File

@@ -3819,11 +3819,14 @@ async function handleNotificationAction(
action: 'read' | 'dismiss' | 'reply',
body: Record<string, unknown>,
snapshot: QimingclawSnapshot | null,
): Promise<{ ok: boolean; task_id?: string; status?: string }> {
): Promise<{ ok: boolean; task_id?: string; status?: string; task_input_recorded?: boolean; governance_command_id?: string; governance_event_id?: string }> {
const state = snapshot?.uiState ?? await readBridgeUiState() ?? readState();
const current = buildNotifications(snapshot, state).find((notification) => notification.notification_id === notificationId);
const now = new Date().toISOString();
const overlay: NotificationState = { ...(state.notificationStateById?.[notificationId] ?? {}) };
const previousOverlay = state.notificationStateById?.[notificationId];
const alreadyReplied = Boolean(previousOverlay?.replied_at);
const overlay: NotificationState = { ...(previousOverlay ?? {}) };
let replyContent = '';
if (action === 'read') {
overlay.status = current?.status === 'resolved' ? 'resolved' : 'read';
overlay.read_at = overlay.read_at ?? now;
@@ -3833,7 +3836,8 @@ async function handleNotificationAction(
} else {
overlay.status = current?.status === 'resolved' ? 'resolved' : 'read';
overlay.read_at = overlay.read_at ?? now;
overlay.reply = stringValue(body.content ?? body.reply ?? body.message);
replyContent = stringValue(body.content ?? body.reply ?? body.message);
overlay.reply = replyContent;
overlay.replied_at = now;
}
const nextState: AdapterState = {
@@ -3844,7 +3848,51 @@ async function handleNotificationAction(
},
};
await saveState(nextState, `notification_${action}`, undefined, { notification_id: notificationId, action });
return { ok: true, task_id: current?.task_id, status: overlay.status };
const taskInput = action === 'reply' && current?.kind === 'need_input' && current.task_id && replyContent && !alreadyReplied
? await recordNotificationTaskInput(current, replyContent, snapshot)
: null;
return {
ok: true,
task_id: current?.task_id,
status: overlay.status,
task_input_recorded: Boolean(taskInput?.accepted),
governance_command_id: taskInput?.command_id,
governance_event_id: governanceEventIdFromCommand(taskInput),
};
}
async function recordNotificationTaskInput(
notification: UserNotification,
replyContent: string,
snapshot: QimingclawSnapshot | null,
): Promise<GovernanceCommandResponse> {
const stamp = Date.now();
return handleGovernanceCommand({
command_kind: 'provide_task_input',
context_refs: {
plan_id: notification.plan_id || undefined,
task_id: notification.task_id,
},
payload: {
input: replyContent,
source: 'notification_reply',
notification_id: notification.notification_id,
plan_id: notification.plan_id || undefined,
task_id: notification.task_id,
},
correlation_id: `qimingclaw-notification-reply-${slugifyIdPart(notification.notification_id)}-${stamp}`,
idempotency_key: `qimingclaw-notification-reply-${notification.notification_id}`,
}, snapshot);
}
function governanceEventIdFromCommand(response: GovernanceCommandResponse | null): string | undefined {
const runId = stringFromUnknown(response?.result?.run_id);
if (!response?.accepted || !runId || !response.command_id) return undefined;
return `${runId}:provide_task_input:${safeEventIdPart(response.command_id)}`;
}
function safeEventIdPart(value: string): string {
return value.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '') || 'unknown';
}
function buildWorkdayDecisions(