吸收数字员工诊断审计观测
This commit is contained in:
@@ -2044,7 +2044,13 @@
|
|||||||
}
|
}
|
||||||
.de-execution-status-strip em { color: #5b7590; font-size: 11px; font-style: normal; white-space: nowrap; }
|
.de-execution-status-strip em { color: #5b7590; font-size: 11px; font-style: normal; white-space: nowrap; }
|
||||||
.de-execution-status-strip strong { color: #12344f; font-size: 18px; line-height: 1; }
|
.de-execution-status-strip strong { color: #12344f; font-size: 18px; line-height: 1; }
|
||||||
.de-route-summary-strip {
|
.de-observation-strip-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.de-route-summary-strip,
|
||||||
|
.de-diagnostic-summary-strip {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(54px, 0.36fr)) minmax(0, 1fr);
|
grid-template-columns: repeat(3, minmax(54px, 0.36fr)) minmax(0, 1fr);
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
@@ -2054,11 +2060,16 @@
|
|||||||
background: rgba(247,251,255,0.88);
|
background: rgba(247,251,255,0.88);
|
||||||
border: 1px solid rgba(79,172,254,0.12);
|
border: 1px solid rgba(79,172,254,0.12);
|
||||||
}
|
}
|
||||||
.de-route-summary-strip span { min-width: 0; display: grid; gap: 2px; }
|
.de-route-summary-strip span,
|
||||||
.de-route-summary-strip em { color: #5b7590; font-size: 10px; font-style: normal; white-space: nowrap; }
|
.de-diagnostic-summary-strip span { min-width: 0; display: grid; gap: 2px; }
|
||||||
.de-route-summary-strip strong { color: #12344f; font-size: 14px; line-height: 1; }
|
.de-route-summary-strip em,
|
||||||
.de-route-summary-strip .is-warning strong { color: #a33b3b; }
|
.de-diagnostic-summary-strip em { color: #5b7590; font-size: 10px; font-style: normal; white-space: nowrap; }
|
||||||
.de-route-summary-strip small { min-width: 0; color: #41627f; font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.de-route-summary-strip strong,
|
||||||
|
.de-diagnostic-summary-strip strong { color: #12344f; font-size: 14px; line-height: 1; }
|
||||||
|
.de-route-summary-strip .is-warning strong,
|
||||||
|
.de-diagnostic-summary-strip .is-warning strong { color: #a33b3b; }
|
||||||
|
.de-route-summary-strip small,
|
||||||
|
.de-diagnostic-summary-strip small { min-width: 0; color: #41627f; font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.de-task-summary-scroll {
|
.de-task-summary-scroll {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -2996,7 +3007,7 @@
|
|||||||
.de-dashboard--compact .de-report-summary-title-row h3 { font-size: 15px; }
|
.de-dashboard--compact .de-report-summary-title-row h3 { font-size: 15px; }
|
||||||
.de-dashboard--compact .de-workday-execution-body {
|
.de-dashboard--compact .de-workday-execution-body {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
grid-template-rows: auto minmax(80px, 0.48fr) auto minmax(0, 1fr);
|
grid-template-rows: auto auto minmax(80px, 0.48fr) auto minmax(0, 1fr);
|
||||||
}
|
}
|
||||||
.de-dashboard--compact .de-execution-status-strip { gap: 5px; }
|
.de-dashboard--compact .de-execution-status-strip { gap: 5px; }
|
||||||
.de-dashboard--compact .de-execution-status-strip span { padding: 6px 7px; }
|
.de-dashboard--compact .de-execution-status-strip span { padding: 6px 7px; }
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type {
|
|||||||
DiagResult,
|
DiagResult,
|
||||||
DigitalEmployeeDailyReport,
|
DigitalEmployeeDailyReport,
|
||||||
DigitalEmployeeDecision,
|
DigitalEmployeeDecision,
|
||||||
|
DigitalEmployeeDiagnosticSummary,
|
||||||
DigitalEmployeeDuty,
|
DigitalEmployeeDuty,
|
||||||
DigitalEmployeeManagedService,
|
DigitalEmployeeManagedService,
|
||||||
DigitalEmployeePlanAgentState,
|
DigitalEmployeePlanAgentState,
|
||||||
@@ -672,7 +673,7 @@ const PLAN_SCHEDULES: Record<string, { expression: string; nextRun: string; sche
|
|||||||
|
|
||||||
const BUSINESS_VIEW_SOURCE = 'qimingclaw-runtime-projection';
|
const BUSINESS_VIEW_SOURCE = 'qimingclaw-runtime-projection';
|
||||||
|
|
||||||
type BusinessViewKind = 'plans' | 'tasks' | 'runs' | 'events' | 'artifacts' | 'approvals' | 'route-decisions';
|
type BusinessViewKind = 'plans' | 'tasks' | 'runs' | 'events' | 'artifacts' | 'approvals' | 'route-decisions' | 'audits';
|
||||||
type BusinessViewRow = Record<string, unknown>;
|
type BusinessViewRow = Record<string, unknown>;
|
||||||
|
|
||||||
export function isQimingclawDigitalApiEnabled(): boolean {
|
export function isQimingclawDigitalApiEnabled(): boolean {
|
||||||
@@ -756,7 +757,7 @@ export async function handleQimingclawDigitalApi<T>(
|
|||||||
const approvalId = decodeURIComponent(approvalActionMatch[1] || '');
|
const approvalId = decodeURIComponent(approvalActionMatch[1] || '');
|
||||||
return await handleApprovalAction(approvalId, parseOptionalJsonBody(options.body), await readQimingclawSnapshot()) as T;
|
return await handleApprovalAction(approvalId, parseOptionalJsonBody(options.body), await readQimingclawSnapshot()) as T;
|
||||||
}
|
}
|
||||||
const businessViewMatch = pathname.match(/^\/api\/digital-employee\/(plans|tasks|runs|events|artifacts|approvals|route-decisions)$/);
|
const businessViewMatch = pathname.match(/^\/api\/digital-employee\/(plans|tasks|runs|events|artifacts|approvals|route-decisions|audits)$/);
|
||||||
if (method === 'GET' && businessViewMatch) {
|
if (method === 'GET' && businessViewMatch) {
|
||||||
return buildBusinessViewResponse(businessViewMatch[1] as BusinessViewKind, await readQimingclawSnapshot(), url.searchParams) as T;
|
return buildBusinessViewResponse(businessViewMatch[1] as BusinessViewKind, await readQimingclawSnapshot(), url.searchParams) as T;
|
||||||
}
|
}
|
||||||
@@ -2365,6 +2366,106 @@ function buildAuditEvents(snapshot: QimingclawSnapshot | null, searchParams: URL
|
|||||||
.slice(0, limit);
|
.slice(0, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildDiagnosticSummary(snapshot: QimingclawSnapshot | null): DigitalEmployeeDiagnosticSummary {
|
||||||
|
const doctorResults = buildDoctor(snapshot).results;
|
||||||
|
const auditEvents = buildAuditEvents(snapshot, new URLSearchParams({ limit: '300' }));
|
||||||
|
const cost = buildCostSummary(snapshot);
|
||||||
|
const latestIssue = latestDiagnosticIssue(doctorResults, auditEvents);
|
||||||
|
return {
|
||||||
|
doctor: {
|
||||||
|
ok: doctorResults.filter((result) => result.severity === 'ok').length,
|
||||||
|
warn: doctorResults.filter((result) => result.severity === 'warn').length,
|
||||||
|
error: doctorResults.filter((result) => result.severity === 'error').length,
|
||||||
|
},
|
||||||
|
audit: {
|
||||||
|
info: auditEvents.filter((event) => stringValue(event.level) === 'info').length,
|
||||||
|
warn: auditEvents.filter((event) => stringValue(event.level) === 'warn').length,
|
||||||
|
error: auditEvents.filter((event) => stringValue(event.level) === 'error').length,
|
||||||
|
},
|
||||||
|
cost: {
|
||||||
|
request_count: cost.request_count,
|
||||||
|
estimated: Boolean(cost.estimated),
|
||||||
|
updated_at: cost.updated_at ?? null,
|
||||||
|
},
|
||||||
|
latest_issue: latestIssue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function latestDiagnosticIssue(
|
||||||
|
doctorResults: DiagResult[],
|
||||||
|
auditEvents: Array<Record<string, unknown>>,
|
||||||
|
): DigitalEmployeeDiagnosticSummary['latest_issue'] {
|
||||||
|
const doctorError = latestDoctorIssue(doctorResults, 'error');
|
||||||
|
if (doctorError) return doctorError;
|
||||||
|
const auditError = latestAuditIssue(auditEvents, 'error');
|
||||||
|
if (auditError) return auditError;
|
||||||
|
const doctorWarn = latestDoctorIssue(doctorResults, 'warn');
|
||||||
|
if (doctorWarn) return doctorWarn;
|
||||||
|
return latestAuditIssue(auditEvents, 'warn');
|
||||||
|
}
|
||||||
|
|
||||||
|
function latestDoctorIssue(
|
||||||
|
doctorResults: DiagResult[],
|
||||||
|
level: 'warn' | 'error',
|
||||||
|
): DigitalEmployeeDiagnosticSummary['latest_issue'] {
|
||||||
|
const result = doctorResults
|
||||||
|
.filter((item) => item.severity === level)
|
||||||
|
.sort((left, right) => stringValue(right.created_at).localeCompare(stringValue(left.created_at)))[0];
|
||||||
|
if (!result) return null;
|
||||||
|
return {
|
||||||
|
id: stringValue(result.id) || `${result.category}:${level}`,
|
||||||
|
source: 'doctor',
|
||||||
|
level,
|
||||||
|
category: stringValue(result.category) || 'diagnostic',
|
||||||
|
message: result.message,
|
||||||
|
occurred_at: result.created_at ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function latestAuditIssue(
|
||||||
|
auditEvents: Array<Record<string, unknown>>,
|
||||||
|
level: 'warn' | 'error',
|
||||||
|
): DigitalEmployeeDiagnosticSummary['latest_issue'] {
|
||||||
|
const event = auditEvents.find((item) => stringValue(item.level) === level);
|
||||||
|
if (!event) return null;
|
||||||
|
return {
|
||||||
|
id: stringValue(event.audit_id) || `${stringValue(event.category) || 'audit'}:${level}`,
|
||||||
|
source: 'audit',
|
||||||
|
level,
|
||||||
|
category: stringValue(event.category) || stringValue(event.entity_type) || 'audit',
|
||||||
|
message: stringValue(event.message) || stringValue(event.action) || '审计事件需要关注',
|
||||||
|
occurred_at: stringValue(event.occurred_at) || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function auditRows(snapshot: QimingclawSnapshot | null): BusinessViewRow[] {
|
||||||
|
return buildAuditEvents(snapshot, new URLSearchParams({ limit: '300' })).map((event) => ({
|
||||||
|
...event,
|
||||||
|
audit_id: stringValue(event.audit_id),
|
||||||
|
status: stringValue(event.level) || 'info',
|
||||||
|
status_label: auditLevelLabel(stringValue(event.level)),
|
||||||
|
entity_id: stringValue(event.entity_id) || stringValue(event.audit_id),
|
||||||
|
updated_at: stringValue(event.occurred_at),
|
||||||
|
source: BUSINESS_VIEW_SOURCE,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterAuditRows(rows: BusinessViewRow[], searchParams: URLSearchParams): BusinessViewRow[] {
|
||||||
|
const category = stringValue(searchParams.get('category')) || stringValue(searchParams.get('kind'));
|
||||||
|
const level = stringValue(searchParams.get('level'));
|
||||||
|
const id = stringValue(searchParams.get('id'));
|
||||||
|
return filterBusinessRows(rows, searchParams)
|
||||||
|
.filter((row) => !category || stringValue(row.category) === category || stringValue(row.entity_type) === category)
|
||||||
|
.filter((row) => !level || stringValue(row.level) === level)
|
||||||
|
.filter((row) => !id || stringValue(row.entity_id) === id || stringValue(row.audit_id).includes(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function auditLevelLabel(level: string): string {
|
||||||
|
if (level === 'error') return '异常';
|
||||||
|
if (level === 'warn') return '警告';
|
||||||
|
return '信息';
|
||||||
|
}
|
||||||
|
|
||||||
function buildBusinessViewResponse(kind: BusinessViewKind, snapshot: QimingclawSnapshot | null, searchParams: URLSearchParams): Record<string, unknown> {
|
function buildBusinessViewResponse(kind: BusinessViewKind, snapshot: QimingclawSnapshot | null, searchParams: URLSearchParams): Record<string, unknown> {
|
||||||
const rowsByKind: Record<BusinessViewKind, BusinessViewRow[]> = {
|
const rowsByKind: Record<BusinessViewKind, BusinessViewRow[]> = {
|
||||||
plans: businessPlanRows(snapshot),
|
plans: businessPlanRows(snapshot),
|
||||||
@@ -2374,9 +2475,12 @@ function buildBusinessViewResponse(kind: BusinessViewKind, snapshot: QimingclawS
|
|||||||
artifacts: businessArtifactRows(snapshot),
|
artifacts: businessArtifactRows(snapshot),
|
||||||
approvals: businessApprovalRows(snapshot),
|
approvals: businessApprovalRows(snapshot),
|
||||||
'route-decisions': routeDecisionRows(snapshot),
|
'route-decisions': routeDecisionRows(snapshot),
|
||||||
|
audits: auditRows(snapshot),
|
||||||
};
|
};
|
||||||
const rows = kind === 'route-decisions'
|
const rows = kind === 'route-decisions'
|
||||||
? filterRouteDecisionRows(rowsByKind[kind], searchParams)
|
? filterRouteDecisionRows(rowsByKind[kind], searchParams)
|
||||||
|
: kind === 'audits'
|
||||||
|
? filterAuditRows(rowsByKind[kind], searchParams)
|
||||||
: filterBusinessRows(rowsByKind[kind], searchParams);
|
: filterBusinessRows(rowsByKind[kind], searchParams);
|
||||||
return {
|
return {
|
||||||
...paginateBusinessRows(rows, searchParams),
|
...paginateBusinessRows(rows, searchParams),
|
||||||
@@ -3339,6 +3443,8 @@ function buildNotifications(snapshot: QimingclawSnapshot | null, state: AdapterS
|
|||||||
...managedServiceNotifications(snapshot),
|
...managedServiceNotifications(snapshot),
|
||||||
...planStepDispatchNotifications(snapshot),
|
...planStepDispatchNotifications(snapshot),
|
||||||
...routeDecisionNotifications(snapshot),
|
...routeDecisionNotifications(snapshot),
|
||||||
|
...diagnosticNotifications(snapshot),
|
||||||
|
...auditNotifications(snapshot),
|
||||||
...taskNotifications(snapshot),
|
...taskNotifications(snapshot),
|
||||||
]);
|
]);
|
||||||
const overlays = state.notificationStateById ?? {};
|
const overlays = state.notificationStateById ?? {};
|
||||||
@@ -3498,6 +3604,48 @@ function routeDecisionNotifications(snapshot: QimingclawSnapshot | null): UserNo
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function diagnosticNotifications(snapshot: QimingclawSnapshot | null): UserNotification[] {
|
||||||
|
return buildDoctor(snapshot).results
|
||||||
|
.filter((result) => result.severity === 'error' || result.severity === 'warn')
|
||||||
|
.map((result) => {
|
||||||
|
const level = result.severity === 'error' ? 'error' : 'warn';
|
||||||
|
const diagnosticId = stringValue(result.id) || `${stringValue(result.category) || 'diagnostic'}:${level}`;
|
||||||
|
return {
|
||||||
|
notification_id: `diagnostic:${diagnosticId}`,
|
||||||
|
plan_id: '',
|
||||||
|
task_id: '',
|
||||||
|
kind: level === 'error' ? 'task_failed' : 'need_input',
|
||||||
|
title: level === 'error' ? '诊断发现异常' : '诊断需要关注',
|
||||||
|
body: result.message,
|
||||||
|
level,
|
||||||
|
status: 'unread',
|
||||||
|
correlation_id: diagnosticId,
|
||||||
|
created_at: result.created_at ?? new Date().toISOString(),
|
||||||
|
} satisfies UserNotification;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function auditNotifications(snapshot: QimingclawSnapshot | null): UserNotification[] {
|
||||||
|
return auditRows(snapshot)
|
||||||
|
.filter((event) => ['error', 'warn'].includes(stringValue(event.level)))
|
||||||
|
.map((event) => {
|
||||||
|
const level = stringValue(event.level) === 'error' ? 'error' : 'warn';
|
||||||
|
const auditId = stringValue(event.audit_id) || stringValue(event.entity_id) || 'audit';
|
||||||
|
return {
|
||||||
|
notification_id: `audit:${auditId}`,
|
||||||
|
plan_id: stringValue(event.plan_id),
|
||||||
|
task_id: stringValue(event.task_id) || (stringValue(event.entity_type) === 'task' ? stringValue(event.entity_id) : ''),
|
||||||
|
kind: level === 'error' ? 'task_failed' : 'need_input',
|
||||||
|
title: level === 'error' ? '审计发现异常' : '审计需要关注',
|
||||||
|
body: stringValue(event.message) || stringValue(event.action) || auditId,
|
||||||
|
level,
|
||||||
|
status: 'unread',
|
||||||
|
correlation_id: auditId,
|
||||||
|
created_at: stringValue(event.occurred_at) || new Date().toISOString(),
|
||||||
|
} satisfies UserNotification;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function applyNotificationOverlay(notification: UserNotification, overlay?: NotificationState): UserNotification {
|
function applyNotificationOverlay(notification: UserNotification, overlay?: NotificationState): UserNotification {
|
||||||
if (!overlay) return notification;
|
if (!overlay) return notification;
|
||||||
return {
|
return {
|
||||||
@@ -4189,6 +4337,7 @@ function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): Digita
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
route_summary: buildRouteDecisionSummary(snapshot),
|
route_summary: buildRouteDecisionSummary(snapshot),
|
||||||
|
diagnostic_summary: buildDiagnosticSummary(snapshot),
|
||||||
daily_report: buildDailyReportProjection({ date, state, generatedAt, selectedCount, events, executionSummaries, runDetails, artifactSources, successCount, failureCount, runningCount, snapshot }),
|
daily_report: buildDailyReportProjection({ date, state, generatedAt, selectedCount, events, executionSummaries, runDetails, artifactSources, successCount, failureCount, runningCount, snapshot }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1082,6 +1082,10 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
|||||||
const actionableTaskAgents = taskAgents.filter((agent) => agent.can_retry || agent.can_skip || agent.can_cancel);
|
const actionableTaskAgents = taskAgents.filter((agent) => agent.can_retry || agent.can_skip || agent.can_cancel);
|
||||||
const routeSummary = isLiveProjection ? workdayProjection?.route_summary : null;
|
const routeSummary = isLiveProjection ? workdayProjection?.route_summary : null;
|
||||||
const latestRoute = routeSummary?.latest ?? null;
|
const latestRoute = routeSummary?.latest ?? null;
|
||||||
|
const diagnosticSummary = isLiveProjection ? workdayProjection?.diagnostic_summary : null;
|
||||||
|
const diagnosticIssueCount = diagnosticSummary ? diagnosticSummary.doctor.error + diagnosticSummary.doctor.warn : 0;
|
||||||
|
const auditRiskCount = diagnosticSummary ? diagnosticSummary.audit.error + diagnosticSummary.audit.warn : 0;
|
||||||
|
const latestDiagnosticIssue = diagnosticSummary?.latest_issue ?? null;
|
||||||
const dailyReport = isLiveProjection ? workdayProjection?.daily_report : undefined;
|
const dailyReport = isLiveProjection ? workdayProjection?.daily_report : undefined;
|
||||||
const runDetails = isLiveProjection
|
const runDetails = isLiveProjection
|
||||||
? (workdayProjection?.run_details?.length ? workdayProjection.run_details : allEvents.map(runDetailFromEvent))
|
? (workdayProjection?.run_details?.length ? workdayProjection.run_details : allEvents.map(runDetailFromEvent))
|
||||||
@@ -2006,6 +2010,8 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
|||||||
<span><em>异常</em><strong>{failedEvents.length}</strong></span>
|
<span><em>异常</em><strong>{failedEvents.length}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{(diagnosticSummary || (routeSummary && routeSummary.total > 0)) && (
|
||||||
|
<div className="de-observation-strip-stack">
|
||||||
{routeSummary && routeSummary.total > 0 && (
|
{routeSummary && routeSummary.total > 0 && (
|
||||||
<div className="de-route-summary-strip">
|
<div className="de-route-summary-strip">
|
||||||
<span><em>入口路由</em><strong>{routeSummary.total}</strong></span>
|
<span><em>入口路由</em><strong>{routeSummary.total}</strong></span>
|
||||||
@@ -2015,6 +2021,17 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{diagnosticSummary && (
|
||||||
|
<div className="de-diagnostic-summary-strip">
|
||||||
|
<span className={diagnosticIssueCount > 0 ? 'is-warning' : ''}><em>诊断异常</em><strong>{diagnosticIssueCount}</strong></span>
|
||||||
|
<span className={auditRiskCount > 0 ? 'is-warning' : ''}><em>审计风险</em><strong>{auditRiskCount}</strong></span>
|
||||||
|
<span><em>估算请求</em><strong>{diagnosticSummary.cost.request_count}</strong></span>
|
||||||
|
<small>{latestDiagnosticIssue ? `${latestDiagnosticIssue.category} · ${latestDiagnosticIssue.message}` : '诊断、审计和成本投影正常'}</small>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="de-task-summary-scroll">
|
<div className="de-task-summary-scroll">
|
||||||
<div className="de-task-summary-grid" aria-label="任务执行汇总">
|
<div className="de-task-summary-grid" aria-label="任务执行汇总">
|
||||||
{taskSummaryCards.length === 0 ? (
|
{taskSummaryCards.length === 0 ? (
|
||||||
|
|||||||
@@ -850,6 +850,32 @@ export interface DigitalEmployeeRouteSummary {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DigitalEmployeeDiagnosticSummary {
|
||||||
|
doctor: {
|
||||||
|
ok: number;
|
||||||
|
warn: number;
|
||||||
|
error: number;
|
||||||
|
};
|
||||||
|
audit: {
|
||||||
|
info: number;
|
||||||
|
warn: number;
|
||||||
|
error: number;
|
||||||
|
};
|
||||||
|
cost: {
|
||||||
|
request_count: number;
|
||||||
|
estimated: boolean;
|
||||||
|
updated_at?: string | null;
|
||||||
|
};
|
||||||
|
latest_issue?: {
|
||||||
|
id: string;
|
||||||
|
source: 'doctor' | 'audit';
|
||||||
|
level: 'warn' | 'error';
|
||||||
|
category: string;
|
||||||
|
message: string;
|
||||||
|
occurred_at?: string | null;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DigitalEmployeeWorkdayProjection {
|
export interface DigitalEmployeeWorkdayProjection {
|
||||||
source: DigitalEmployeeProjectionSource;
|
source: DigitalEmployeeProjectionSource;
|
||||||
demo_reason?: string | null;
|
demo_reason?: string | null;
|
||||||
@@ -867,6 +893,7 @@ export interface DigitalEmployeeWorkdayProjection {
|
|||||||
decisions: DigitalEmployeeDecision[];
|
decisions: DigitalEmployeeDecision[];
|
||||||
delivery: DigitalEmployeeDelivery;
|
delivery: DigitalEmployeeDelivery;
|
||||||
route_summary?: DigitalEmployeeRouteSummary;
|
route_summary?: DigitalEmployeeRouteSummary;
|
||||||
|
diagnostic_summary?: DigitalEmployeeDiagnosticSummary;
|
||||||
daily_report?: DigitalEmployeeDailyReport;
|
daily_report?: DigitalEmployeeDailyReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,8 +16,8 @@
|
|||||||
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="./assets/index-YhUdeF7L.js"></script>
|
<script type="module" crossorigin src="./assets/index-Cel_CqfI.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-DSHN6Btj.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-DLC8VZMk.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -168,6 +168,33 @@ function checkSgrobotDigitalAssets() {
|
|||||||
console.log(`[DigitalEmployeeCheck] ${assetRefs.length} asset references OK`);
|
console.log(`[DigitalEmployeeCheck] ${assetRefs.length} asset references OK`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkDigitalDiagnosticAuditProjection() {
|
||||||
|
console.log("\n[DigitalEmployeeCheck] Verify diagnostic/audit projection hooks");
|
||||||
|
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
|
||||||
|
const typesPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "types", "api.ts");
|
||||||
|
const pagePath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "CommandDeck.tsx");
|
||||||
|
const adapter = fs.readFileSync(adapterPath, "utf8");
|
||||||
|
const types = fs.readFileSync(typesPath, "utf8");
|
||||||
|
const page = fs.readFileSync(pagePath, "utf8");
|
||||||
|
const requiredSnippets = [
|
||||||
|
[adapter, "'audits'", "audit business view kind"],
|
||||||
|
[adapter, "buildDiagnosticSummary", "diagnostic summary builder"],
|
||||||
|
[adapter, "filterAuditRows", "audit filters"],
|
||||||
|
[adapter, "diagnosticNotifications", "diagnostic notifications"],
|
||||||
|
[adapter, "auditNotifications", "audit notifications"],
|
||||||
|
[types, "DigitalEmployeeDiagnosticSummary", "diagnostic summary type"],
|
||||||
|
[types, "diagnostic_summary?: DigitalEmployeeDiagnosticSummary", "workday diagnostic field"],
|
||||||
|
[page, "de-diagnostic-summary-strip", "dashboard diagnostic strip"],
|
||||||
|
];
|
||||||
|
const missing = requiredSnippets
|
||||||
|
.filter(([content, snippet]) => !content.includes(snippet))
|
||||||
|
.map(([, , label]) => label);
|
||||||
|
if (missing.length > 0) {
|
||||||
|
throw new Error(`Missing diagnostic/audit projection hooks: ${missing.join(", ")}`);
|
||||||
|
}
|
||||||
|
console.log("[DigitalEmployeeCheck] diagnostic/audit projection hooks OK");
|
||||||
|
}
|
||||||
|
|
||||||
function checkLocalDatabaseSchema() {
|
function checkLocalDatabaseSchema() {
|
||||||
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
|
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
|
||||||
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
|
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
|
||||||
@@ -210,6 +237,7 @@ function main() {
|
|||||||
runStep("Build Electron main process", "npm", ["run", "build:main:dev"]);
|
runStep("Build Electron main process", "npm", ["run", "build:main:dev"]);
|
||||||
runStep("Build embedded digital employee", "npm", ["run", "build:sgrobot-digital"]);
|
runStep("Build embedded digital employee", "npm", ["run", "build:sgrobot-digital"]);
|
||||||
checkSgrobotDigitalAssets();
|
checkSgrobotDigitalAssets();
|
||||||
|
checkDigitalDiagnosticAuditProjection();
|
||||||
checkLocalDatabaseSchema();
|
checkLocalDatabaseSchema();
|
||||||
console.log("\n[DigitalEmployeeCheck] All checks passed");
|
console.log("\n[DigitalEmployeeCheck] All checks passed");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -594,8 +594,8 @@ GET /api/digital-employee/approvals
|
|||||||
- 建议:下一轮围绕管理端正式路由 UI 和策略下发,把入口路由从本地可观测推进到跨端治理策略。
|
- 建议:下一轮围绕管理端正式路由 UI 和策略下发,把入口路由从本地可观测推进到跨端治理策略。
|
||||||
|
|
||||||
11. **诊断 / 成本 / 审计视图**
|
11. **诊断 / 成本 / 审计视图**
|
||||||
- 已吸收:同步失败诊断较完整,部分 sandbox / memory / service 日志在 qimingclaw 其他模块中存在;embedded adapter 已接管 `/api/doctor`、`/api/cost`、`/api/audit`,从 snapshot、runtime records、sync status、managed services 和 artifact delivery facts 生成只读诊断、估算成本和审计投影,只读投影已开始闭环。
|
- 已吸收:同步失败诊断较完整,部分 sandbox / memory / service 日志在 qimingclaw 其他模块中存在;embedded adapter 已接管 `/api/doctor`、`/api/cost`、`/api/audit`,从 snapshot、runtime records、sync status、managed services 和 artifact delivery facts 生成只读诊断、估算成本和审计投影;数字员工首页已展示诊断/审计/成本摘要,`/api/digital-employee/audits` 已提供本地审计业务视图,诊断与审计风险通知联动已开始闭环。
|
||||||
- 缺口:sandbox audit 文件、真实 token/cost 采集、CLI/tool 调用审计、诊断修复动作和管理端审计视图尚未统一进入数字员工视图和 outbox。
|
- 缺口:sandbox audit 文件、真实 token/cost 采集、CLI/tool 调用审计、诊断修复动作和管理端正式审计视图尚未统一进入数字员工视图和 outbox。
|
||||||
- 建议:下一轮围绕 sandbox audit 与 token/cost 采集,把只读投影升级为可追溯的真实审计与成本统计。
|
- 建议:下一轮围绕 sandbox audit 与 token/cost 采集,把只读投影升级为可追溯的真实审计与成本统计。
|
||||||
|
|
||||||
12. **管理端业务查询模型**
|
12. **管理端业务查询模型**
|
||||||
|
|||||||
Reference in New Issue
Block a user