吸收数字员工计划步骤依赖图
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user