吸收数字员工审批时间线
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
ApprovalEntry,
|
||||
ApprovalTimelineResponse,
|
||||
ArtifactEntry,
|
||||
CanonicalEvent,
|
||||
CronJob,
|
||||
@@ -639,6 +640,10 @@ export async function handleQimingclawDigitalApi<T>(
|
||||
if (method === 'GET' && pathname === '/api/artifact') {
|
||||
return { artifacts: buildArtifacts(await readQimingclawSnapshot()) } as T;
|
||||
}
|
||||
const approvalTimelineMatch = pathname.match(/^\/api\/approval\/([^/]+)\/timeline$/);
|
||||
if (method === 'GET' && approvalTimelineMatch) {
|
||||
return buildApprovalTimeline(decodeURIComponent(approvalTimelineMatch[1] || ''), await readQimingclawSnapshot()) as T;
|
||||
}
|
||||
if (method === 'GET' && pathname === '/api/memory') {
|
||||
return { entries: await readMemoryEntries(url.searchParams.get('query'), url.searchParams.get('category')) } as T;
|
||||
}
|
||||
@@ -1963,6 +1968,107 @@ function buildApprovals(snapshot: QimingclawSnapshot | null): ApprovalEntry[] {
|
||||
return [...runtimeApprovalEntries, ...syncReviewEntries];
|
||||
}
|
||||
|
||||
function buildApprovalTimeline(approvalId: string, snapshot: QimingclawSnapshot | null): ApprovalTimelineResponse {
|
||||
const approval = buildApprovals(snapshot).find((candidate) => stringValue(candidate.approval_id) === approvalId) ?? null;
|
||||
const runtimeApproval = runtimeApprovals(snapshot).find((candidate) => candidate.id === approvalId) ?? null;
|
||||
const approvalPayload = asRecord(approval?.payload ?? runtimeApproval?.payload);
|
||||
const approvalAcpPermission = acpPermissionFromPayload(approvalPayload);
|
||||
const refs = {
|
||||
approvalId,
|
||||
planId: stringValue(approval?.plan_id) || runtimeApproval?.planId || '',
|
||||
taskId: stringValue(approval?.task_id) || runtimeApproval?.taskId || '',
|
||||
runId: stringValue(approval?.run_id) || runtimeApproval?.runId || '',
|
||||
permissionId: approvalAcpPermission.permissionId,
|
||||
sessionId: approvalAcpPermission.sessionId,
|
||||
};
|
||||
const approvalCreatedAt = stringValue(approval?.created_at) || runtimeApproval?.createdAt || runtimeApproval?.updatedAt || '';
|
||||
const entries: ApprovalTimelineResponse['events'] = [];
|
||||
|
||||
if (approval || runtimeApproval) {
|
||||
const title = stringValue(approval?.title) || runtimeApproval?.title || '待确认事项';
|
||||
const status = stringValue(approval?.status) || runtimeApproval?.status || 'pending';
|
||||
entries.push({
|
||||
event_id: `${approvalId}:approval_record`,
|
||||
kind: 'approval_record',
|
||||
status,
|
||||
message: `审批记录:${title} -> ${status}`,
|
||||
occurred_at: approvalCreatedAt || new Date().toISOString(),
|
||||
payload: approvalPayload,
|
||||
source_type: 'approval',
|
||||
});
|
||||
}
|
||||
|
||||
for (const event of runtimeEvents(snapshot)) {
|
||||
if (!isApprovalTimelineEvent(event, refs)) continue;
|
||||
const payload = asRecord(event.payload);
|
||||
entries.push({
|
||||
event_id: event.id,
|
||||
kind: event.kind,
|
||||
status: approvalTimelineEventStatus(event, payload),
|
||||
message: event.message || event.kind,
|
||||
occurred_at: event.occurredAt,
|
||||
payload,
|
||||
source_type: stringValue(payload?.source) || 'runtime_event',
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
approval_id: approvalId,
|
||||
approval,
|
||||
events: dedupeApprovalTimelineEvents(entries)
|
||||
.sort((left, right) => left.occurred_at.localeCompare(right.occurred_at) || left.event_id.localeCompare(right.event_id)),
|
||||
};
|
||||
}
|
||||
|
||||
function acpPermissionFromPayload(payload: Record<string, unknown> | null): { permissionId: string; sessionId: string } {
|
||||
const nested = asRecord(payload?.acpPermission) ?? asRecord(payload?.acp_permission) ?? asRecord(payload?.permission);
|
||||
return {
|
||||
permissionId: stringValue(nested?.permission_id) || stringValue(nested?.permissionId) || stringValue(payload?.permission_id) || stringValue(payload?.permissionId),
|
||||
sessionId: stringValue(nested?.session_id) || stringValue(nested?.sessionId) || stringValue(payload?.session_id) || stringValue(payload?.sessionId),
|
||||
};
|
||||
}
|
||||
|
||||
function isApprovalTimelineEvent(
|
||||
event: QimingclawEventRecord,
|
||||
refs: { approvalId: string; planId: string; taskId: string; runId: string; permissionId: string; sessionId: string },
|
||||
): boolean {
|
||||
const payload = asRecord(event.payload);
|
||||
const payloadApprovalId = stringValue(payload?.approvalId)
|
||||
|| stringValue(payload?.approval_id)
|
||||
|| stringValue(payload?.decision_id)
|
||||
|| stringValue(payload?.decisionId);
|
||||
if (payloadApprovalId === refs.approvalId) return true;
|
||||
if (event.id.startsWith(`${refs.approvalId}:`)) return true;
|
||||
|
||||
const acpPermission = acpPermissionFromPayload(payload);
|
||||
if (refs.permissionId && acpPermission.permissionId === refs.permissionId) return true;
|
||||
if (refs.sessionId && acpPermission.sessionId === refs.sessionId) return true;
|
||||
|
||||
const approvalLikeKind = event.kind.includes('approval') || event.kind.includes('permission');
|
||||
if (!approvalLikeKind) return false;
|
||||
if (refs.runId && event.runId === refs.runId) return true;
|
||||
if (refs.taskId && event.taskId === refs.taskId) return true;
|
||||
if (refs.planId && event.planId === refs.planId) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function approvalTimelineEventStatus(event: QimingclawEventRecord, payload: Record<string, unknown> | null): string | null {
|
||||
return stringValue(payload?.nextStatus)
|
||||
|| stringValue(payload?.next_status)
|
||||
|| stringValue(payload?.status)
|
||||
|| stringValue(payload?.decision)
|
||||
|| (event.kind === 'approval_decision' ? 'completed' : null);
|
||||
}
|
||||
|
||||
function dedupeApprovalTimelineEvents(events: ApprovalTimelineResponse['events']): ApprovalTimelineResponse['events'] {
|
||||
const seen = new Set<string>();
|
||||
return events.filter((event) => {
|
||||
if (seen.has(event.event_id)) return false;
|
||||
seen.add(event.event_id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function buildWorkdayDecisions(
|
||||
state: AdapterState,
|
||||
date: string,
|
||||
|
||||
@@ -443,6 +443,22 @@ export interface ApprovalEntry {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface ApprovalTimelineEvent {
|
||||
event_id: string;
|
||||
kind: string;
|
||||
status?: string | null;
|
||||
message: string;
|
||||
occurred_at: string;
|
||||
payload?: Record<string, unknown> | null;
|
||||
source_type: string;
|
||||
}
|
||||
|
||||
export interface ApprovalTimelineResponse {
|
||||
approval_id: string;
|
||||
approval: ApprovalEntry | null;
|
||||
events: ApprovalTimelineEvent[];
|
||||
}
|
||||
|
||||
export interface ArtifactEntry {
|
||||
artifact_id?: string;
|
||||
subject_id: string;
|
||||
|
||||
Reference in New Issue
Block a user