吸收数字员工多步审批跨端回写
This commit is contained in:
@@ -861,7 +861,7 @@ export function setArtifactRetention(
|
||||
|
||||
export function recordApprovalAction(
|
||||
approvalId: string,
|
||||
action: 'comment' | 'delegate' | 'mark_timeout' | 'mark_risk' | 'clear_risk',
|
||||
action: 'comment' | 'delegate' | 'mark_timeout' | 'mark_risk' | 'clear_risk' | 'approve' | 'reject',
|
||||
options: Record<string, string | null | undefined> = {},
|
||||
): Promise<any> {
|
||||
return apiFetch(`/api/digital-employee/approvals/${encodeURIComponent(approvalId)}/actions`, {
|
||||
@@ -870,6 +870,13 @@ export function recordApprovalAction(
|
||||
});
|
||||
}
|
||||
|
||||
export function pullApprovalUpdates(): Promise<{ ok: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }> {
|
||||
return apiFetch<{ ok: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }>('/api/approval/updates/pull', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
}
|
||||
|
||||
export function getDebugConversations(): Promise<any[]> {
|
||||
return apiFetch<{ conversations: any[] }>('/api/debug/conversations')
|
||||
.then((data) => data.conversations ?? []);
|
||||
|
||||
@@ -56,6 +56,8 @@ declare global {
|
||||
setSkillEnabled?: (skillId: string, enabled: boolean) => Promise<{ ok?: boolean; skill_id?: string; enabled?: boolean; error?: string }>;
|
||||
pullSkillPolicies?: () => Promise<{ ok?: boolean; skipped?: boolean; error?: string | null }>;
|
||||
pullRiskApprovalPolicy?: () => Promise<{ ok?: boolean; skipped?: boolean; error?: string | null }>;
|
||||
pullApprovalUpdates?: () => Promise<{ ok?: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }>;
|
||||
submitApprovalDecision?: (input: Record<string, unknown>) => Promise<{ ok?: boolean; error?: string | null }>;
|
||||
evaluateRiskPolicy?: (input: QimingclawRiskPolicyInput) => Promise<QimingclawRiskPolicyResult>;
|
||||
getPlanTemplates?: () => Promise<QimingclawPlanTemplateList>;
|
||||
listMemories?: (options?: { query?: string; category?: string; limit?: number }) => Promise<QimingclawMemoryRecord[]>;
|
||||
@@ -323,6 +325,16 @@ interface QimingclawApprovalActionResult {
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface ApprovalWorkflowSummary {
|
||||
workflow_id: string | null;
|
||||
current_step_id: string | null;
|
||||
current_step_label: string | null;
|
||||
current_participant: string | null;
|
||||
step_index: number | null;
|
||||
step_count: number | null;
|
||||
next_step_label: string | null;
|
||||
}
|
||||
|
||||
interface QimingclawManagedServiceActionResponse {
|
||||
ok: boolean;
|
||||
service_id: string;
|
||||
@@ -1010,6 +1022,9 @@ export async function handleQimingclawDigitalApi<T>(
|
||||
if (method === 'POST' && pathname === '/api/risk-approval/policy/pull') {
|
||||
return await pullBridgeRiskApprovalPolicy() as T;
|
||||
}
|
||||
if (method === 'POST' && pathname === '/api/approval/updates/pull') {
|
||||
return await pullBridgeApprovalUpdates() as T;
|
||||
}
|
||||
if (method === 'POST' && pathname.startsWith('/api/skill/') && pathname.endsWith('/enabled')) {
|
||||
const skillId = decodeURIComponent(pathname.split('/')[3] || '');
|
||||
const body = parseJsonBody<{ enabled?: boolean }>(options.body);
|
||||
@@ -1162,6 +1177,25 @@ async function pullBridgeRiskApprovalPolicy(): Promise<{ ok: boolean; skipped?:
|
||||
}
|
||||
}
|
||||
|
||||
async function pullBridgeApprovalUpdates(): Promise<{ ok: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }> {
|
||||
const bridge = window.QimingClawBridge?.digital;
|
||||
if (!bridge?.pullApprovalUpdates) {
|
||||
return { ok: false, error: 'qimingclaw_bridge_unavailable' };
|
||||
}
|
||||
try {
|
||||
const result = await bridge.pullApprovalUpdates();
|
||||
return {
|
||||
ok: result?.ok !== false,
|
||||
applied: Number(result?.applied ?? 0),
|
||||
ignored: Number(result?.ignored ?? 0),
|
||||
...(result?.skipped ? { skipped: true } : {}),
|
||||
...(result?.error ? { error: result.error } : {}),
|
||||
};
|
||||
} catch (error) {
|
||||
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
async function evaluateBridgeRiskPolicy(input: QimingclawRiskPolicyInput): Promise<QimingclawRiskPolicyResult> {
|
||||
const bridge = window.QimingClawBridge?.digital;
|
||||
if (!bridge?.evaluateRiskPolicy) {
|
||||
@@ -3281,6 +3315,7 @@ function businessApprovalRows(snapshot: QimingclawSnapshot | null): BusinessView
|
||||
const approvalId = stringValue(approval.approval_id);
|
||||
const runtime = runtimeById.get(approvalId);
|
||||
const summary = approvalActionSummary(snapshot, approvalId, approval);
|
||||
const workflow = approvalWorkflowSummary(approval);
|
||||
const entity = businessEntityFromRefs({
|
||||
plan_id: approval.plan_id,
|
||||
task_id: approval.task_id,
|
||||
@@ -3302,6 +3337,13 @@ function businessApprovalRows(snapshot: QimingclawSnapshot | null): BusinessView
|
||||
last_actor: summary.last_actor,
|
||||
comment_count: summary.comment_count,
|
||||
delegate_to: summary.delegate_to,
|
||||
workflow_id: workflow.workflow_id,
|
||||
current_step_id: workflow.current_step_id,
|
||||
current_step_label: workflow.current_step_label,
|
||||
current_participant: workflow.current_participant,
|
||||
approval_step_index: workflow.step_index,
|
||||
approval_step_count: workflow.step_count,
|
||||
next_step_label: workflow.next_step_label,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -3423,6 +3465,25 @@ function filterApprovalHistoryRows(rows: BusinessViewRow[], searchParams: URLSea
|
||||
.filter((row) => !riskLevel || stringValue(row.risk_level) === riskLevel);
|
||||
}
|
||||
|
||||
function approvalWorkflowSummary(approval: Record<string, unknown>): ApprovalWorkflowSummary {
|
||||
const payload = asRecord(approval.payload) ?? {};
|
||||
const workflow = asRecord(payload.workflow) ?? {};
|
||||
const steps = Array.isArray(workflow.steps) ? workflow.steps.map((step) => asRecord(step) ?? {}) : [];
|
||||
const currentStepId = stringValue(workflow.current_step_id ?? workflow.currentStepId ?? payload.current_step_id ?? payload.currentStepId);
|
||||
const currentIndex = Math.max(0, steps.findIndex((step) => stringValue(step.step_id ?? step.stepId ?? step.id) === currentStepId));
|
||||
const current = steps[currentIndex] ?? null;
|
||||
const next = steps.slice(currentIndex + 1).find((step) => ['pending', 'delegated', 'timeout'].includes(stringValue(step.status) || 'pending')) ?? null;
|
||||
return {
|
||||
workflow_id: stringValue(workflow.workflow_id ?? workflow.workflowId) || null,
|
||||
current_step_id: currentStepId || null,
|
||||
current_step_label: stringValue(current?.label ?? current?.title ?? current?.name) || null,
|
||||
current_participant: stringValue(current?.participant_label ?? current?.participantLabel ?? current?.participant_id ?? current?.participantId) || null,
|
||||
step_index: steps.length > 0 ? currentIndex + 1 : null,
|
||||
step_count: steps.length || null,
|
||||
next_step_label: stringValue(next?.label ?? next?.title ?? next?.name) || null,
|
||||
};
|
||||
}
|
||||
|
||||
function approvalActionSummary(
|
||||
snapshot: QimingclawSnapshot | null,
|
||||
approvalId: string,
|
||||
@@ -4581,6 +4642,13 @@ function buildWorkdayDecisions(
|
||||
last_actor: stringValue(summary?.last_actor) || null,
|
||||
comment_count: numberValue(summary?.comment_count) ?? 0,
|
||||
delegate_to: stringValue(summary?.delegate_to) || null,
|
||||
workflow_id: stringValue(summary?.workflow_id) || null,
|
||||
current_step_id: stringValue(summary?.current_step_id) || null,
|
||||
current_step_label: stringValue(summary?.current_step_label) || null,
|
||||
current_participant: stringValue(summary?.current_participant) || null,
|
||||
approval_step_index: numberValue(summary?.approval_step_index),
|
||||
approval_step_count: numberValue(summary?.approval_step_count),
|
||||
next_step_label: stringValue(summary?.next_step_label) || null,
|
||||
actions: isDecisionOpenStatus(status)
|
||||
? [
|
||||
{ id: 'approved', label: '同意', tone: 'primary' },
|
||||
@@ -5375,6 +5443,16 @@ async function handleWorkdayAction(body: DigitalEmployeeWorkdayActionRequest, sn
|
||||
: undefined,
|
||||
);
|
||||
const nextSnapshot = { ...(snapshot ?? { generatedAt: new Date().toISOString(), services: {}, sessions: { total: 0, active: 0, items: [] } }), uiState: savedState };
|
||||
if (body.action === 'decide_approval') {
|
||||
await window.QimingClawBridge?.digital?.submitApprovalDecision?.({
|
||||
approval_id: body.decision_id,
|
||||
decision: body.decision,
|
||||
date,
|
||||
actor: 'digital_employee_operator',
|
||||
source: 'sgrobot-digital-adapter',
|
||||
acp_permission: acpPermissionResult,
|
||||
}).catch(() => null);
|
||||
}
|
||||
const projection = buildWorkday(date, nextSnapshot);
|
||||
if (body.action === 'generate_daily_report' && projection.daily_report) {
|
||||
await recordDailyReportArtifact(projection.daily_report);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getSchedulerJobs,
|
||||
governanceCommand,
|
||||
postDigitalEmployeeWorkdayAction,
|
||||
pullApprovalUpdates,
|
||||
pullPlanStepDispatchPolicy,
|
||||
pullRiskApprovalPolicy,
|
||||
recordApprovalAction,
|
||||
@@ -1413,6 +1414,27 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
}
|
||||
}, [fetchProjection]);
|
||||
|
||||
const refreshApprovalUpdates = useCallback(async () => {
|
||||
setActionBusy('pull_approval_updates');
|
||||
setActionError(null);
|
||||
setActionNotice(null);
|
||||
try {
|
||||
const result = await pullApprovalUpdates();
|
||||
if (!mountedRef.current) return;
|
||||
await fetchProjection();
|
||||
if (!mountedRef.current) return;
|
||||
setActionNotice(result.ok
|
||||
? `审批更新已拉取:应用 ${result.applied ?? 0} 条,忽略 ${result.ignored ?? 0} 条。`
|
||||
: `审批更新拉取失败:${result.error || '未知错误'}。`);
|
||||
} catch (error) {
|
||||
if (mountedRef.current) {
|
||||
setActionError(error instanceof Error ? error.message : '拉取审批更新失败');
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current) setActionBusy(null);
|
||||
}
|
||||
}, [fetchProjection]);
|
||||
|
||||
const copySyncFailureDiagnostic = useCallback(async (failure: ManagementSyncFailure) => {
|
||||
const diagnostic = buildSyncFailureDiagnostic(failure, managementSyncStatus);
|
||||
try {
|
||||
@@ -2081,6 +2103,16 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
<ShieldAlert size={13} aria-hidden="true" />
|
||||
<span>{actionBusy === 'pull_risk_approval_policy' ? '拉取中...' : '风险策略'}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="de-btn-secondary de-btn-sm"
|
||||
disabled={projectionLoading || actionBusy === 'pull_approval_updates'}
|
||||
onClick={refreshApprovalUpdates}
|
||||
title="从管理端拉取审批更新"
|
||||
>
|
||||
<RefreshCw size={13} aria-hidden="true" />
|
||||
<span>{actionBusy === 'pull_approval_updates' ? '拉取中...' : '审批更新'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={`de-management-sync-strip de-management-sync-strip--${managementSyncTone}`} title={managementSyncError || managementSyncSummary}>
|
||||
@@ -3227,6 +3259,10 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
<span>{decision.comment_count ? `评论 ${decision.comment_count} 条` : '暂无评论'}</span>
|
||||
<span>{decision.delegate_to ? `转交 ${decision.delegate_to}` : '未转交'}</span>
|
||||
<span>{decision.last_action ? `最近 ${approvalActionLabel(decision.last_action)} · ${decision.last_actor || '未知'}` : '暂无审批动作'}</span>
|
||||
<span>{decision.approval_step_count ? `步骤 ${decision.approval_step_index || 1}/${decision.approval_step_count}` : '单步审批'}</span>
|
||||
<span>{decision.current_step_label ? `当前 ${decision.current_step_label}` : '当前步骤待确认'}</span>
|
||||
<span>{decision.current_participant ? `处理人 ${decision.current_participant}` : '未指定处理人'}</span>
|
||||
<span>{decision.next_step_label ? `下一步 ${decision.next_step_label}` : '无后续步骤'}</span>
|
||||
</div>
|
||||
<div className="de-approval-action-fields">
|
||||
<input
|
||||
|
||||
@@ -874,6 +874,13 @@ export interface DigitalEmployeeDecision {
|
||||
last_actor?: string | null;
|
||||
comment_count?: number | null;
|
||||
delegate_to?: string | null;
|
||||
workflow_id?: string | null;
|
||||
current_step_id?: string | null;
|
||||
current_step_label?: string | null;
|
||||
current_participant?: string | null;
|
||||
approval_step_index?: number | null;
|
||||
approval_step_count?: number | null;
|
||||
next_step_label?: string | null;
|
||||
actions: DigitalEmployeeDecisionAction[];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user