吸收数字员工计划步骤依赖图

This commit is contained in:
baiyanyun
2026-06-09 10:21:12 +08:00
parent e4715bbabb
commit f35d945d9c
10 changed files with 641 additions and 74 deletions

View File

@@ -22,6 +22,7 @@ import type {
GovernanceCommandRequest,
GovernanceCommandResponse,
PlanDispatchResponse,
PlanStepSummary,
SkillSpec,
} from '@/types/api';
@@ -178,6 +179,7 @@ interface QimingclawGovernanceCommandRecord {
interface QimingclawRuntimeRecords {
generatedAt: string;
plans: QimingclawPlanRecord[];
planSteps?: QimingclawPlanStepRecord[];
tasks: QimingclawTaskRecord[];
runs: QimingclawRunRecord[];
events: QimingclawEventRecord[];
@@ -200,6 +202,23 @@ interface QimingclawPlanRecord {
updatedAt: string;
}
interface QimingclawPlanStepRecord {
id: string;
remoteId?: string | null;
planId: string;
title: string;
status: string;
seq: number;
dependsOn: string[];
preferredSkillIds: string[];
payload: unknown;
syncStatus: string;
lastSyncedAt?: string | null;
syncError?: string | null;
createdAt: string;
updatedAt: string;
}
interface QimingclawTaskRecord {
id: string;
remoteId?: string | null;
@@ -774,6 +793,10 @@ function runtimePlans(snapshot: QimingclawSnapshot | null): QimingclawPlanRecord
return snapshot?.runtime?.plans ?? [];
}
function runtimePlanSteps(snapshot: QimingclawSnapshot | null): QimingclawPlanStepRecord[] {
return snapshot?.runtime?.planSteps ?? [];
}
function runtimeTasks(snapshot: QimingclawSnapshot | null): QimingclawTaskRecord[] {
return snapshot?.runtime?.tasks ?? [];
}
@@ -796,6 +819,7 @@ function runtimeApprovals(snapshot: QimingclawSnapshot | null): QimingclawApprov
function hasRuntimeRecords(snapshot: QimingclawSnapshot | null): boolean {
return runtimePlans(snapshot).length > 0
|| runtimePlanSteps(snapshot).length > 0
|| runtimeTasks(snapshot).length > 0
|| runtimeArtifacts(snapshot).length > 0
|| runtimeApprovals(snapshot).length > 0;
@@ -828,6 +852,17 @@ function runtimePlanTasks(snapshot: QimingclawSnapshot | null, planId: string):
return runtimeTasks(snapshot).filter((task) => task.planId === planId);
}
function runtimeStepsForPlan(snapshot: QimingclawSnapshot | null, planId: string): QimingclawPlanStepRecord[] {
return runtimePlanSteps(snapshot)
.filter((step) => step.planId === planId)
.sort((left, right) => left.seq - right.seq || left.createdAt.localeCompare(right.createdAt));
}
function taskStepId(task: QimingclawTaskRecord): string {
const record = asRecord(task.payload);
return stringValue(record?.stepId ?? record?.step_id) || (task.planId ? `${task.planId}:runtime` : 'qimingclaw-runtime');
}
function planRecordStatus(plan: QimingclawPlanRecord, tasks: QimingclawTaskRecord[]): string {
if (tasks.some((task) => taskStatusTone(task.status) === 'danger')) return 'failed';
if (tasks.some((task) => taskStatusTone(task.status) === 'running')) return 'running';
@@ -894,7 +929,16 @@ function latestRunForTask(runs: QimingclawRunRecord[], taskId: string): Qimingcl
function runtimePlanToDebugPlan(plan: QimingclawPlanRecord, snapshot: QimingclawSnapshot | null): DebugPlan {
const tasks = runtimePlanTasks(snapshot, plan.id);
const planSteps = runtimeStepsForPlan(snapshot, plan.id);
const status = planRecordStatus(plan, tasks);
const knownStepIds = new Set(planSteps.map((step) => step.id));
const orphanTasks = tasks.filter((task) => !knownStepIds.has(taskStepId(task)));
const steps = planSteps.length > 0
? [
...planSteps.map((step) => runtimePlanStepToSummary(step, tasks)),
...(orphanTasks.length > 0 ? [runtimeFallbackStepToSummary(plan.id, orphanTasks)] : []),
]
: [runtimeFallbackStepToSummary(plan.id, tasks)];
return {
plan_id: plan.id,
title: plan.title,
@@ -904,19 +948,43 @@ function runtimePlanToDebugPlan(plan: QimingclawPlanRecord, snapshot: Qimingclaw
updated_at: latestRuntimeTime(plan, tasks),
source: plan.source,
sync_status: plan.syncStatus,
steps: [
{
step_id: `${plan.id}:runtime`,
title: 'qimingclaw 执行记录',
depends_on: [],
preferred_skills: [...new Set(tasks.map((task) => task.assignedSkillId).filter((value): value is string => Boolean(value)))],
tasks: tasks.map((task) => ({
task_id: task.id,
title: task.title,
status: task.status,
})),
},
],
steps,
};
}
function runtimePlanStepToSummary(
step: QimingclawPlanStepRecord,
tasks: QimingclawTaskRecord[],
): PlanStepSummary {
return {
step_id: step.id,
title: step.title,
depends_on: step.dependsOn,
preferred_skills: step.preferredSkillIds,
tasks: tasks
.filter((task) => taskStepId(task) === step.id)
.map((task) => ({
task_id: task.id,
title: task.title,
status: task.status,
})),
};
}
function runtimeFallbackStepToSummary(
planId: string,
tasks: QimingclawTaskRecord[],
): PlanStepSummary {
return {
step_id: `${planId}:runtime`,
title: 'qimingclaw 执行记录',
depends_on: [],
preferred_skills: [...new Set(tasks.map((task) => task.assignedSkillId).filter((value): value is string => Boolean(value)))],
tasks: tasks.map((task) => ({
task_id: task.id,
title: task.title,
status: task.status,
})),
};
}
@@ -926,7 +994,7 @@ function runtimeTaskToDebugTask(task: QimingclawTaskRecord): DebugTask {
title: task.title,
status: task.status,
plan_id: task.planId ?? null,
step_id: task.planId ? `${task.planId}:runtime` : 'qimingclaw-runtime',
step_id: taskStepId(task),
assigned_skill_id: task.assignedSkillId ?? '',
description: task.syncError ?? '',
created_at: task.createdAt,
@@ -1260,13 +1328,16 @@ function buildDebugStore(snapshot: QimingclawSnapshot | null): DebugStoreRespons
const plans = filterPlans(null, snapshot);
const tasks = buildTasks(null, snapshot);
const runs = buildTasksResponse(null, snapshot).runs;
const realPlanSteps = runtimePlanSteps(snapshot);
return {
tables: {
canonical_events: buildCanonicalEvents(snapshot, tasks, runs),
tasks,
runs,
plans,
plan_steps: plans.flatMap((plan) => plan.steps ?? []),
plan_steps: realPlanSteps.length > 0
? realPlanSteps.map((step) => runtimePlanStepToSummary(step, runtimeTasks(snapshot)))
: plans.flatMap((plan) => plan.steps ?? []),
approvals: buildApprovals(snapshot),
artifacts: buildArtifacts(snapshot),
},
@@ -2274,10 +2345,45 @@ function buildPlanFlow(planId: string, snapshot: QimingclawSnapshot | null = nul
if (hasRuntimeRecords(snapshot)) {
const plan = runtimePlans(snapshot).find((candidate) => candidate.id === planId);
const tasks = buildTasks(planId, snapshot);
const planSteps = runtimeStepsForPlan(snapshot, planId);
const runs = buildTasksResponse(planId, snapshot).runs;
const events = runtimeEvents(snapshot).filter((event) => event.planId === planId || tasks.some((task) => task.task_id === event.taskId));
const title = plan?.title ?? tasks[0]?.title ?? planId;
const objective = plan?.objective ?? 'qimingclaw 本地运行记录';
const qimingclawFlowSteps: FlowResponse['steps'] = [];
let nextSeq = 2;
if (planSteps.length > 0) {
const knownStepIds = new Set(planSteps.map((step) => step.id));
for (const step of planSteps) {
qimingclawFlowSteps.push({
seq: nextSeq++,
lane: 'qimingclaw',
label: step.title,
sub_label: `${taskStatusLabel(step.status)} · 依赖 ${step.dependsOn.length} · 技能 ${step.preferredSkillIds.length}`,
timestamp: step.updatedAt,
color: '#22c55e',
source_type: 'plan_step',
});
for (const task of tasks.filter((candidate) => candidate.step_id === step.id)) {
qimingclawFlowSteps.push(runtimeTaskFlowStep(task, nextSeq++));
}
}
const orphanTasks = tasks.filter((task) => !knownStepIds.has(task.step_id || ''));
if (orphanTasks.length > 0) {
qimingclawFlowSteps.push({
seq: nextSeq++,
lane: 'qimingclaw',
label: '未归属步骤',
sub_label: `包含 ${orphanTasks.length} 个任务`,
timestamp: orphanTasks[0]?.updated_at ?? new Date().toISOString(),
color: '#22c55e',
source_type: 'plan_step',
});
for (const task of orphanTasks) qimingclawFlowSteps.push(runtimeTaskFlowStep(task, nextSeq++));
}
} else {
for (const task of tasks) qimingclawFlowSteps.push(runtimeTaskFlowStep(task, nextSeq++));
}
return {
plan_id: planId,
plan_title: title,
@@ -2298,17 +2404,9 @@ function buildPlanFlow(planId: string, snapshot: QimingclawSnapshot | null = nul
color: '#38bdf8',
source_type: 'plan',
},
...tasks.map((task, index) => ({
seq: index + 2,
lane: 'qimingclaw',
label: task.title,
sub_label: `${taskStatusLabel(task.status)} · ${task.assigned_skill_id || 'qimingclaw-runtime'}`,
timestamp: task.updated_at,
color: '#22c55e',
source_type: 'task',
})),
...qimingclawFlowSteps,
...runs.map((run, index) => ({
seq: tasks.length + index + 2,
seq: nextSeq + index,
lane: 'execution',
label: run.lifecycle || 'Run',
sub_label: run.run_id,
@@ -2377,6 +2475,18 @@ function buildPlanFlow(planId: string, snapshot: QimingclawSnapshot | null = nul
};
}
function runtimeTaskFlowStep(task: DebugTask, seq: number): FlowResponse['steps'][number] {
return {
seq,
lane: 'qimingclaw',
label: task.title,
sub_label: `${taskStatusLabel(task.status)} · ${task.assigned_skill_id || 'qimingclaw-runtime'}`,
timestamp: task.updated_at,
color: '#22c55e',
source_type: 'task',
};
}
function buildTaskFlow(taskId: string, snapshot: QimingclawSnapshot | null = null): FlowResponse {
const task = runtimeTasks(snapshot).find((candidate) => candidate.id === taskId);
if (!task) {