吸收数字员工运营预览台

This commit is contained in:
baiyanyun
2026-06-12 19:57:38 +08:00
parent 871bd715c4
commit 1a8e0fb51f
23 changed files with 3344 additions and 277 deletions

View File

@@ -17,6 +17,17 @@ import type {
DigitalEmployeeBusinessViewKind,
DigitalEmployeeBusinessViewQuery,
DigitalEmployeeBusinessViewRow,
DigitalEmployeeBatchActionResult,
DigitalEmployeeInterventionWorkbenchResponse,
DigitalEmployeeMemoryGovernanceResponse,
DigitalEmployeeSkillAuditWorkbenchResponse,
DigitalEmployeeWorkbenchBatchResult,
DigitalEmployeeOpsWorkbenchResponse,
DigitalEmployeeNotificationWorkbenchResponse,
DigitalEmployeeDailyReportWorkbenchResponse,
DigitalEmployeePolicyCenterResponse,
DigitalEmployeePolicyPullBatchResult,
DigitalEmployeeSchedulerWorkbenchResponse,
PlanStepBusinessRow,
PlanStepDispatchPolicy,
PlanStepDispatchPolicyPullResult,
@@ -295,7 +306,7 @@ export function deleteCronJob(id: string): Promise<void> {
}
export function patchCronJob(
id: string,
patch: { name?: string; schedule?: string; command?: string },
patch: { name?: string; schedule?: string; command?: string; enabled?: boolean },
): Promise<CronJob> {
return apiFetch<CronJob | { status: string; job: CronJob }>(
`/api/cron/${encodeURIComponent(id)}`,
@@ -336,6 +347,40 @@ export function patchCronSettings(
});
}
export async function getDigitalEmployeeSchedulerWorkbench(): Promise<DigitalEmployeeSchedulerWorkbenchResponse> {
const [jobs, settings] = await Promise.all([
getSchedulerJobs(),
getCronSettings().catch(() => ({ enabled: false, catch_up_on_startup: false, max_run_history: 20 })),
]);
const runEntries = await Promise.all(jobs.slice(0, 30).map(async (job) => {
const runs = await getCronRuns(job.id, 5).catch(() => []);
return [job.id, runs] as const;
}));
const runsByJob = new Map(runEntries);
return {
items: jobs.map((job) => {
const runs = runsByJob.get(job.id) ?? [];
const latest = runs[0];
const schedule = job.schedule && typeof job.schedule === 'object' ? job.schedule as Record<string, unknown> : {};
return {
schedule_id: job.id,
name: job.name || job.id,
enabled: job.enabled !== false,
status: job.enabled === false ? 'paused' : String(job.last_status || schedule.status || 'enabled'),
cron_expr: job.expression || String(schedule.expression || ''),
next_run: job.next_run || null,
last_run: job.last_run || latest?.finished_at || latest?.started_at || null,
last_status: job.last_status || latest?.status || null,
latest_run_error: job.last_output || latest?.output || null,
run_count: runs.length,
payload: { job, runs },
};
}),
settings,
source: 'qimingclaw-adapter',
};
}
// ---------------------------------------------------------------------------
// Integrations
// ---------------------------------------------------------------------------
@@ -521,6 +566,117 @@ export function decideRouteIntervention(
});
}
export function decideRouteInterventionsBatch(
routeDecisionIds: string[],
input: { decision: 'mark_reviewed' | 'dismiss' | 'retry_policy_check'; actor?: string | null; comment?: string | null },
): Promise<DigitalEmployeeBatchActionResult> {
return apiFetch<DigitalEmployeeBatchActionResult>('/api/route-decisions/interventions/batch', {
method: 'POST',
body: JSON.stringify({ route_decision_ids: routeDecisionIds, ...input }),
});
}
export function getDigitalEmployeeInterventionWorkbench(
params: Record<string, string | number | null | undefined> = {},
): Promise<DigitalEmployeeInterventionWorkbenchResponse> {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && `${value}`.trim() !== '') query.set(key, `${value}`);
});
const suffix = query.toString();
return apiFetch<DigitalEmployeeInterventionWorkbenchResponse>(`/api/digital-employee/interventions/workbench${suffix ? `?${suffix}` : ''}`);
}
export function getDigitalEmployeeMemoryGovernance(
params: Record<string, string | number | null | undefined> = {},
): Promise<DigitalEmployeeMemoryGovernanceResponse> {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && `${value}`.trim() !== '') query.set(key, `${value}`);
});
const suffix = query.toString();
return apiFetch<DigitalEmployeeMemoryGovernanceResponse>(`/api/digital-employee/memory-governance${suffix ? `?${suffix}` : ''}`);
}
export function setMemoryGovernanceStatus(
memoryIds: string[],
action: 'archive' | 'restore' | 'mark_reviewed',
options: { actor?: string | null; reason?: string | null } = {},
): Promise<DigitalEmployeeBatchActionResult> {
return apiFetch<DigitalEmployeeBatchActionResult>('/api/digital-employee/memory-governance/status/batch', {
method: 'POST',
body: JSON.stringify({ memory_ids: memoryIds, action, ...options }),
});
}
export function getDigitalEmployeeSkillAuditWorkbench(
params: Record<string, string | number | null | undefined> = {},
): Promise<DigitalEmployeeSkillAuditWorkbenchResponse> {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && `${value}`.trim() !== '') query.set(key, `${value}`);
});
const suffix = query.toString();
return apiFetch<DigitalEmployeeSkillAuditWorkbenchResponse>(`/api/digital-employee/skill-audit-workbench${suffix ? `?${suffix}` : ''}`);
}
export function getDigitalEmployeeOpsWorkbench(
params: Record<string, string | number | null | undefined> = {},
): Promise<DigitalEmployeeOpsWorkbenchResponse> {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && `${value}`.trim() !== '') query.set(key, `${value}`);
});
const suffix = query.toString();
return apiFetch<DigitalEmployeeOpsWorkbenchResponse>(`/api/digital-employee/ops-workbench${suffix ? `?${suffix}` : ''}`);
}
export function runDigitalEmployeeOpsBatchAction(
opsIds: string[],
action: 'mark_reviewed' | 'dismiss' | 'open_related_view' | 'retry_policy_pull' | 'safe_restart_service',
options: { actor?: string | null; reason?: string | null } = {},
): Promise<DigitalEmployeeWorkbenchBatchResult> {
return apiFetch<DigitalEmployeeWorkbenchBatchResult>('/api/digital-employee/ops-workbench/actions/batch', {
method: 'POST',
body: JSON.stringify({ ops_ids: opsIds, action, ...options }),
timeoutMs: DIGITAL_EMPLOYEE_WORKDAY_TIMEOUT_MS,
});
}
export function getDigitalEmployeeNotificationWorkbench(
params: Record<string, string | number | null | undefined> = {},
): Promise<DigitalEmployeeNotificationWorkbenchResponse> {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && `${value}`.trim() !== '') query.set(key, `${value}`);
});
const suffix = query.toString();
return apiFetch<DigitalEmployeeNotificationWorkbenchResponse>(`/api/digital-employee/notifications/workbench${suffix ? `?${suffix}` : ''}`);
}
export function runDigitalEmployeeNotificationBatchAction(
notificationIds: string[],
action: 'mark_read' | 'dismiss' | 'pull_updates' | 'open_desktop_settings',
options: { actor?: string | null; reason?: string | null } = {},
): Promise<DigitalEmployeeWorkbenchBatchResult> {
return apiFetch<DigitalEmployeeWorkbenchBatchResult>('/api/digital-employee/notifications/actions/batch', {
method: 'POST',
body: JSON.stringify({ notification_ids: notificationIds, action, ...options }),
timeoutMs: DIGITAL_EMPLOYEE_WORKDAY_TIMEOUT_MS,
});
}
export function getDigitalEmployeeDailyReportWorkbench(
params: Record<string, string | number | null | undefined> = {},
): Promise<DigitalEmployeeDailyReportWorkbenchResponse> {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && `${value}`.trim() !== '') query.set(key, `${value}`);
});
const suffix = query.toString();
return apiFetch<DigitalEmployeeDailyReportWorkbenchResponse>(`/api/digital-employee/daily-reports/workbench${suffix ? `?${suffix}` : ''}`);
}
export function routeIngress(
body: IngressRouteRequest,
): Promise<IngressRouteResponse> {
@@ -698,6 +854,77 @@ export function pullRouteDecisionPolicy(): Promise<RouteDecisionPolicyPullResult
});
}
export async function getDigitalEmployeePolicyCenter(): Promise<DigitalEmployeePolicyCenterResponse> {
const [dispatchPolicy, riskPolicy] = await Promise.all([
getPlanStepDispatchPolicy(),
getRiskApprovalPolicy(),
]);
return {
plan_step_dispatch_policy: dispatchPolicy,
risk_approval_policy: riskPolicy,
source: 'qimingclaw-adapter',
items: [
{
policy_id: 'plan_step_dispatch',
title: 'PlanStep 派发策略',
status: dispatchPolicy.enabled ? 'enabled' : 'disabled',
source: dispatchPolicy.source,
updated_at: dispatchPolicy.updated_at,
last_pulled_at: dispatchPolicy.last_pulled_at,
last_pull_error: dispatchPolicy.last_pull_error,
summary: `max_per_sweep=${dispatchPolicy.max_per_sweep}; auto_dispatch_ready_steps=${dispatchPolicy.auto_dispatch_ready_steps}`,
payload: dispatchPolicy,
},
{
policy_id: 'risk_approval',
title: '风险审批策略',
status: riskPolicy.enabled ? 'enabled' : 'disabled',
source: riskPolicy.source,
updated_at: riskPolicy.updated_at,
last_pulled_at: riskPolicy.last_pulled_at,
last_pull_error: riskPolicy.last_pull_error,
summary: `approval_required=${riskPolicy.approval_required_risk_levels.join(',')}; auto_reject_actions=${riskPolicy.auto_reject_actions.length}`,
payload: riskPolicy,
},
{
policy_id: 'route_decision',
title: '入口路由策略',
status: 'pull_available',
source: 'remote_pull',
summary: '通过 /api/route-decision/policy/pull 获取管理端路由策略快照。',
},
{
policy_id: 'skill',
title: '技能策略',
status: 'pull_available',
source: 'remote_pull',
summary: '通过 /api/skill/policies/pull 同步远端技能启停策略。',
},
],
};
}
export async function pullDigitalEmployeePoliciesBatch(): Promise<DigitalEmployeePolicyPullBatchResult> {
const jobs: Array<{ policy_id: string; run: () => Promise<unknown> }> = [
{ policy_id: 'plan_step_dispatch', run: pullPlanStepDispatchPolicy },
{ policy_id: 'risk_approval', run: pullRiskApprovalPolicy },
{ policy_id: 'route_decision', run: pullRouteDecisionPolicy },
{ policy_id: 'skill', run: pullSkillPolicies },
];
const items = [] as DigitalEmployeePolicyPullBatchResult['items'];
for (const job of jobs) {
try {
const result = await job.run();
const record = result as { ok?: boolean; error?: string | null };
items.push({ policy_id: job.policy_id, ok: record.ok !== false, error: record.error ?? null, result });
} catch (error) {
items.push({ policy_id: job.policy_id, ok: false, error: error instanceof Error ? error.message : String(error) });
}
}
const pulled = items.filter((item) => item.ok).length;
return { ok: pulled > 0, total: items.length, pulled, failed: items.length - pulled, items };
}
export function getPlanSteps(params: Record<string, string | number | null | undefined> = {}): Promise<BusinessViewResponse<PlanStepBusinessRow>> {
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
@@ -726,6 +953,7 @@ export function getDigitalEmployeeBusinessView(
runs: '/api/digital-employee/runs',
events: '/api/digital-employee/events',
artifacts: '/api/digital-employee/artifacts',
'artifact-groups': '/api/digital-employee/artifact-groups',
approvals: '/api/digital-employee/approvals',
};
const query = new URLSearchParams();
@@ -961,6 +1189,17 @@ export function setArtifactRetention(
});
}
export function setArtifactRetentionBatch(
artifactIds: string[],
action: 'mark_keep' | 'mark_expire' | 'mark_review' | 'clear_retention',
options: Record<string, string | null | undefined> = {},
): Promise<any> {
return apiFetch('/api/digital-employee/artifacts/retention/batch', {
method: 'POST',
body: JSON.stringify({ artifact_ids: artifactIds, action, ...options }),
});
}
export function cleanupArtifact(
artifactId: string,
options: Record<string, string | null | undefined> = {},
@@ -992,6 +1231,17 @@ export function resolveApprovalConflict(
});
}
export function resolveApprovalConflictsBatch(
approvalIds: string[],
resolution: 'keep_local' | 'mark_reviewed',
options: { actor?: string | null; comment?: string | null } = {},
): Promise<DigitalEmployeeBatchActionResult> {
return apiFetch<DigitalEmployeeBatchActionResult>('/api/approval/conflicts/resolve/batch', {
method: 'POST',
body: JSON.stringify({ approval_ids: approvalIds, resolution, ...options }),
});
}
export function pullApprovalUpdates(): Promise<{ ok: boolean; applied?: number; ignored?: number; conflicted?: number; skipped?: boolean; error?: string | null }> {
return apiFetch<{ ok: boolean; applied?: number; ignored?: number; conflicted?: number; skipped?: boolean; error?: string | null }>('/api/approval/updates/pull', {
method: 'POST',