吸收数字员工治理审计补齐

This commit is contained in:
baiyanyun
2026-06-12 10:33:46 +08:00
parent e473b5c147
commit 3a159e2f5a
11 changed files with 230 additions and 57 deletions

View File

@@ -3387,7 +3387,12 @@ function routeDecisionFromRuntimePayload(payload: Record<string, unknown>, event
function routeDecisionAttentionLevel(decision: RouteDecisionRecord): 'info' | 'action' | 'warn' | 'error' {
if (decision.policy_result === 'approval_required') return 'action';
if (decision.policy_result && decision.policy_result !== 'accepted') return 'error';
if (decision.reason_codes.includes('candidate_skills_disabled') || decision.reason_codes.includes('route_action_missing_context')) return 'warn';
if (
decision.reason_codes.includes('candidate_skills_disabled')
|| decision.reason_codes.includes('route_action_missing_context')
|| decision.reason_codes.includes('route_action_policy_blocked')
|| decision.reason_codes.includes('route_action_not_mapped')
) return 'warn';
if (decision.created_command_id) return 'action';
return 'info';
}
@@ -3412,6 +3417,13 @@ function buildRouteDecisionSummary(snapshot: QimingclawSnapshot | null): Digital
return {
total: rows.length,
mapped_count: rows.filter((row) => stringValue(row.created_command_id)).length,
governance_mapped_count: rows.filter((row) => stringValue(row.created_command_id)).length,
governance_blocked_count: rows.filter((row) => {
const reasonCodes = arrayValue(row.reason_codes).map(stringValue);
return reasonCodes.includes('route_action_policy_blocked')
|| reasonCodes.includes('route_action_missing_context')
|| reasonCodes.includes('route_action_not_mapped');
}).length,
attention_count: rows.filter((row) => ['warn', 'error'].includes(stringValue(row.attention_level))).length,
blocked_count: rows.filter((row) => stringValue(row.policy_result) === 'blocked').length,
approval_required_count: rows.filter((row) => stringValue(row.policy_result) === 'approval_required').length,
@@ -6558,7 +6570,10 @@ function buildGovernanceCommandForRouteDecision(
if (decision.route_kind === 'PlanExecution' && decision.intent_kind === 'schedule') {
const scheduleId = stringValue(contextRefs.schedule_id) || stringValue(payload.schedule_id) || stringValue(payload.scheduleId);
if (!scheduleId) return { command: null, reasonCode: 'route_action_missing_context' };
return { command: baseCommand('run_schedule_now', { schedule_id: scheduleId }), reasonCode: 'route_action_mapped' };
const commandKind = 'run_schedule_now';
const policyReason = routeGovernanceActionPolicyReason(commandKind);
if (policyReason) return { command: null, reasonCode: policyReason };
return { command: baseCommand(commandKind, { schedule_id: scheduleId }), reasonCode: 'route_action_mapped' };
}
if (decision.route_kind === 'PlanExecution' && decision.intent_kind === 'execute') {
@@ -6566,8 +6581,11 @@ function buildGovernanceCommandForRouteDecision(
const taskId = stringValue(contextRefs.task_id) || stringValue(payload.task_id) || stringValue(payload.taskId) || taskIdForRouteStep(snapshot, stepId);
if (!taskId) return { command: null, reasonCode: 'route_action_missing_context' };
const task = runtimeTasks(snapshot).find((candidate) => candidate.id === taskId) ?? null;
const commandKind = 'start_run';
const policyReason = routeGovernanceActionPolicyReason(commandKind);
if (policyReason) return { command: null, reasonCode: policyReason };
return {
command: baseCommand('start_run', {
command: baseCommand(commandKind, {
plan_id: stringValue(contextRefs.plan_id) || task?.planId || stringValue(payload.plan_id) || stringValue(payload.planId),
task_id: taskId,
step_id: stepId || (task ? taskStepId(task) : ''),
@@ -6583,6 +6601,8 @@ function buildGovernanceCommandForRouteDecision(
const planId = stringValue(contextRefs.plan_id) || stringValue(payload.plan_id) || stringValue(payload.planId);
const commandKind = routeControlCommandKind(actionText, Boolean(taskId));
if (!commandKind || (!taskId && !planId)) return { command: null, reasonCode: 'route_action_missing_context' };
const policyReason = routeGovernanceActionPolicyReason(commandKind);
if (policyReason) return { command: null, reasonCode: policyReason };
return {
command: baseCommand(commandKind, {
plan_id: planId,
@@ -6777,6 +6797,32 @@ function compactRecord(record: Record<string, unknown>): Record<string, unknown>
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && value !== null && value !== ''));
}
const ROUTE_LOW_RISK_GOVERNANCE_COMMANDS = new Set<GovernanceCommandRequest['command_kind']>([
'submit_plan',
'approve_plan',
'amend_plan',
'resume_plan',
'pause_plan',
'retry_task',
'skip_task',
'resume_task',
'provide_task_input',
]);
const ROUTE_RISK_PROTECTED_GOVERNANCE_COMMANDS = new Set<GovernanceCommandRequest['command_kind']>([
'activate_plan',
'run_schedule_now',
'start_run',
'cancel_run',
'cancel_task',
'delete_schedule',
]);
function routeGovernanceActionPolicyReason(commandKind: GovernanceCommandRequest['command_kind']): string | null {
if (ROUTE_LOW_RISK_GOVERNANCE_COMMANDS.has(commandKind)) return null;
if (ROUTE_RISK_PROTECTED_GOVERNANCE_COMMANDS.has(commandKind)) return null;
return 'route_action_policy_blocked';
}
function governanceCommandRiskLevel(commandKind: string): string {
if (['activate_plan', 'run_schedule_now', 'start_run', 'cancel_run', 'cancel_task'].includes(commandKind)) return 'high';
if (['delete_schedule', 'pause_schedule', 'skip_task', 'retry_task', 'provide_task_input'].includes(commandKind)) return 'medium';

View File

@@ -2455,6 +2455,8 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
<div className="de-route-summary-strip">
<span><em></em><strong>{routeSummary.total}</strong></span>
<span><em></em><strong>{routeSummary.mapped_count}</strong></span>
<span><em></em><strong>{routeSummary.governance_mapped_count ?? routeSummary.mapped_count}</strong></span>
{(routeSummary.governance_blocked_count ?? 0) > 0 && <span className="is-warning"><em></em><strong>{routeSummary.governance_blocked_count}</strong></span>}
<span className={routeSummary.attention_count > 0 ? 'is-warning' : ''}><em></em><strong>{routeSummary.attention_count}</strong></span>
{(routeSummary.blocked_count ?? 0) > 0 && <span className="is-warning"><em></em><strong>{routeSummary.blocked_count}</strong></span>}
{(routeSummary.approval_required_count ?? 0) > 0 && <span className="is-warning"><em></em><strong>{routeSummary.approval_required_count}</strong></span>}
@@ -2463,6 +2465,8 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
<small>
{latestRoute ? `${latestRoute.route_kind} / ${latestRoute.intent_kind} · ${latestRoute.status_label}` : '暂无路由决策'}
{routeSummary.policy_source ? ` · 策略${routeSummary.policy_source === 'remote' ? '远端' : '本地'}` : ''}
{` · 治理审计已映射 ${routeSummary.governance_mapped_count ?? routeSummary.mapped_count}`}
{(routeSummary.governance_blocked_count ?? 0) > 0 ? ` · 拦截/失败 ${routeSummary.governance_blocked_count}` : ''}
{routeSummary.latest_intervention_status ? ` · 干预${routeSummary.latest_intervention_status}` : ''}
{routeSummary.policy_last_pull_error ? ` · 拉取失败` : ''}
</small>

View File

@@ -1084,6 +1084,8 @@ export interface DigitalEmployeeDailyReport {
export interface DigitalEmployeeRouteSummary {
total: number;
mapped_count: number;
governance_mapped_count?: number;
governance_blocked_count?: number;
attention_count: number;
blocked_count?: number;
approval_required_count?: number;