吸收数字员工计划步骤依赖图
This commit is contained in:
@@ -88,6 +88,23 @@ export interface DigitalEmployeeTaskRecord {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeePlanStepRecord {
|
||||
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;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeRunRecord {
|
||||
id: string;
|
||||
remoteId?: string | null;
|
||||
@@ -155,6 +172,7 @@ export interface DigitalEmployeeApprovalRecord {
|
||||
export interface DigitalEmployeeRuntimeRecords {
|
||||
generatedAt: string;
|
||||
plans: DigitalEmployeePlanRecord[];
|
||||
planSteps: DigitalEmployeePlanStepRecord[];
|
||||
tasks: DigitalEmployeeTaskRecord[];
|
||||
runs: DigitalEmployeeRunRecord[];
|
||||
events: DigitalEmployeeEventRecord[];
|
||||
@@ -479,6 +497,7 @@ export function readDigitalEmployeeRuntimeRecords(
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
plans: [],
|
||||
planSteps: [],
|
||||
tasks: [],
|
||||
runs: [],
|
||||
events: [],
|
||||
@@ -494,6 +513,14 @@ export function readDigitalEmployeeRuntimeRecords(
|
||||
ORDER BY updated_at DESC, created_at DESC
|
||||
LIMIT ?
|
||||
`).all(safeLimit) as Array<Record<string, unknown>>;
|
||||
const planSteps = db.prepare(`
|
||||
SELECT id, remote_id, plan_id, title, status, seq, depends_on,
|
||||
preferred_skill_ids, payload, sync_status, last_synced_at,
|
||||
sync_error, created_at, updated_at
|
||||
FROM digital_plan_steps
|
||||
ORDER BY plan_id ASC, seq ASC, created_at ASC
|
||||
LIMIT ?
|
||||
`).all(safeLimit) as Array<Record<string, unknown>>;
|
||||
const tasks = db.prepare(`
|
||||
SELECT id, remote_id, plan_id, title, status, assigned_skill_id, payload,
|
||||
sync_status, last_synced_at, sync_error, created_at, updated_at
|
||||
@@ -533,6 +560,7 @@ export function readDigitalEmployeeRuntimeRecords(
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
plans: plans.map(toPlanRecord),
|
||||
planSteps: planSteps.map(toPlanStepRecord),
|
||||
tasks: tasks.map(toTaskRecord),
|
||||
runs: runs.map(toRunRecord),
|
||||
events: events.map(toEventRecord),
|
||||
@@ -802,6 +830,13 @@ export function recordDigitalEmployeeGovernanceCommand(
|
||||
now,
|
||||
});
|
||||
if (command.commandKind === "create_plan") {
|
||||
upsertCreatedPlanSteps({
|
||||
planId: ids.planId,
|
||||
payload,
|
||||
status: "pending",
|
||||
commandId,
|
||||
now,
|
||||
});
|
||||
upsertCreatedPlanTasks({
|
||||
planId: ids.planId,
|
||||
payload,
|
||||
@@ -878,6 +913,34 @@ function readExistingPlanSummary(planId: string): { title: string; objective: st
|
||||
}
|
||||
}
|
||||
|
||||
function upsertCreatedPlanSteps(input: {
|
||||
planId: string;
|
||||
payload: Record<string, unknown>;
|
||||
status: string;
|
||||
commandId: string;
|
||||
now: string;
|
||||
}): void {
|
||||
extractPlanStepInputs(input.payload, input.planId).forEach((step) => {
|
||||
upsertPlanStep({
|
||||
id: step.id,
|
||||
planId: input.planId,
|
||||
title: step.title,
|
||||
status: step.status || input.status,
|
||||
seq: step.seq,
|
||||
dependsOn: step.dependsOn,
|
||||
preferredSkillIds: step.preferredSkillIds,
|
||||
payload: {
|
||||
source: "qimingclaw-create-plan",
|
||||
commandId: input.commandId,
|
||||
dependsOn: step.dependsOn,
|
||||
preferredSkillIds: step.preferredSkillIds,
|
||||
original: step.original,
|
||||
},
|
||||
now: input.now,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function upsertCreatedPlanTasks(input: {
|
||||
planId: string;
|
||||
payload: Record<string, unknown>;
|
||||
@@ -885,7 +948,7 @@ function upsertCreatedPlanTasks(input: {
|
||||
commandId: string;
|
||||
now: string;
|
||||
}): void {
|
||||
extractPlanTaskInputs(input.payload).forEach((task, index) => {
|
||||
extractPlanTaskInputs(input.payload, input.planId).forEach((task, index) => {
|
||||
const title = task.title || `计划任务 ${index + 1}`;
|
||||
const taskId = task.id || `${input.planId}:task:${safeId(`${index + 1}-${title}`)}`;
|
||||
upsertTask({
|
||||
@@ -908,7 +971,60 @@ function upsertCreatedPlanTasks(input: {
|
||||
});
|
||||
}
|
||||
|
||||
function extractPlanTaskInputs(payload: Record<string, unknown>): Array<{
|
||||
function extractPlanStepInputs(
|
||||
payload: Record<string, unknown>,
|
||||
planId: string,
|
||||
): Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
status: string;
|
||||
seq: number;
|
||||
dependsOn: string[];
|
||||
preferredSkillIds: string[];
|
||||
original: unknown;
|
||||
}> {
|
||||
const steps = unknownArray(payload.steps)
|
||||
.map((step, index) => planStepInputFromRecord(objectRecord(step), index, planId))
|
||||
.filter((step): step is NonNullable<typeof step> => Boolean(step));
|
||||
if (steps.length > 0) return steps;
|
||||
if (unknownArray(payload.tasks).length === 0) return [];
|
||||
return [{
|
||||
id: `${planId}:step:default`,
|
||||
title: "默认执行步骤",
|
||||
status: "pending",
|
||||
seq: 1,
|
||||
dependsOn: [],
|
||||
preferredSkillIds: [],
|
||||
original: { source: "default_step", task_count: unknownArray(payload.tasks).length },
|
||||
}];
|
||||
}
|
||||
|
||||
function planStepInputFromRecord(
|
||||
record: Record<string, unknown> | null,
|
||||
index: number,
|
||||
planId: string,
|
||||
): ReturnType<typeof extractPlanStepInputs>[number] | null {
|
||||
if (!record) return null;
|
||||
const title = stringValue(record.title ?? record.name ?? record.summary)
|
||||
|| `计划步骤 ${index + 1}`;
|
||||
const stepId = stringValue(record.step_id ?? record.stepId ?? record.id)
|
||||
|| `${planId}:step:${index + 1}-${safeId(title)}`;
|
||||
return {
|
||||
id: stepId,
|
||||
title,
|
||||
status: stringValue(record.status) || "pending",
|
||||
seq: typeof record.seq === "number" ? record.seq : Number(record.seq) || index + 1,
|
||||
dependsOn: normalizeSkillIds(unknownArray(record.depends_on ?? record.dependsOn)),
|
||||
preferredSkillIds: normalizeSkillIds([
|
||||
...unknownArray(record.skills),
|
||||
...unknownArray(record.preferred_skills),
|
||||
...unknownArray(record.preferredSkills),
|
||||
]),
|
||||
original: record,
|
||||
};
|
||||
}
|
||||
|
||||
function extractPlanTaskInputs(payload: Record<string, unknown>, planId: string): Array<{
|
||||
id: string;
|
||||
stepId: string;
|
||||
title: string;
|
||||
@@ -918,9 +1034,11 @@ function extractPlanTaskInputs(payload: Record<string, unknown>): Array<{
|
||||
skills: string[];
|
||||
original: unknown;
|
||||
}> {
|
||||
const rawSteps = unknownArray(payload.steps);
|
||||
const defaultStepId = rawSteps.length > 0 ? "" : `${planId}:step:default`;
|
||||
const explicitTasks = unknownArray(payload.tasks)
|
||||
.map((task, index) => planTaskInputFromRecord(objectRecord(task), index, ""));
|
||||
const stepTasks = unknownArray(payload.steps).flatMap((step, stepIndex) => {
|
||||
.map((task, index) => planTaskInputFromRecord(objectRecord(task), index, defaultStepId));
|
||||
const stepTasks = rawSteps.flatMap((step, stepIndex) => {
|
||||
const stepRecord = objectRecord(step);
|
||||
if (!stepRecord) return [];
|
||||
const nestedTasks = unknownArray(stepRecord.tasks);
|
||||
@@ -1091,6 +1209,12 @@ function parseStoredPayload(value: unknown): unknown {
|
||||
}
|
||||
}
|
||||
|
||||
function parseStringArray(value: unknown): string[] {
|
||||
const parsed = parseStoredPayload(value);
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed.filter((item): item is string => typeof item === "string" && item.trim().length > 0);
|
||||
}
|
||||
|
||||
function toPlanRecord(row: Record<string, unknown>): DigitalEmployeePlanRecord {
|
||||
return {
|
||||
id: stringField(row, "id"),
|
||||
@@ -1125,6 +1249,25 @@ function toTaskRecord(row: Record<string, unknown>): DigitalEmployeeTaskRecord {
|
||||
};
|
||||
}
|
||||
|
||||
function toPlanStepRecord(row: Record<string, unknown>): DigitalEmployeePlanStepRecord {
|
||||
return {
|
||||
id: stringField(row, "id"),
|
||||
remoteId: optionalStringField(row, "remote_id"),
|
||||
planId: stringField(row, "plan_id"),
|
||||
title: stringField(row, "title"),
|
||||
status: stringField(row, "status"),
|
||||
seq: typeof row.seq === "number" ? row.seq : Number(row.seq) || 0,
|
||||
dependsOn: parseStringArray(row.depends_on),
|
||||
preferredSkillIds: parseStringArray(row.preferred_skill_ids),
|
||||
payload: parseStoredPayload(row.payload),
|
||||
syncStatus: stringField(row, "sync_status"),
|
||||
lastSyncedAt: optionalStringField(row, "last_synced_at"),
|
||||
syncError: optionalStringField(row, "sync_error"),
|
||||
createdAt: stringField(row, "created_at"),
|
||||
updatedAt: stringField(row, "updated_at"),
|
||||
};
|
||||
}
|
||||
|
||||
function toRunRecord(row: Record<string, unknown>): DigitalEmployeeRunRecord {
|
||||
return {
|
||||
id: stringField(row, "id"),
|
||||
@@ -1279,6 +1422,52 @@ function upsertPlan(input: {
|
||||
markOutbox("plan", input.id, "upsert", input, input.now);
|
||||
}
|
||||
|
||||
function upsertPlanStep(input: {
|
||||
id: string;
|
||||
planId: string;
|
||||
title: string;
|
||||
status: string;
|
||||
seq: number;
|
||||
dependsOn: string[];
|
||||
preferredSkillIds: string[];
|
||||
payload: unknown;
|
||||
now: string;
|
||||
}): void {
|
||||
const db = getDigitalEmployeeDb();
|
||||
if (!db) return;
|
||||
db.prepare(`
|
||||
INSERT INTO digital_plan_steps (
|
||||
id, plan_id, title, status, seq, depends_on, preferred_skill_ids,
|
||||
payload, sync_status, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
plan_id = excluded.plan_id,
|
||||
title = excluded.title,
|
||||
status = excluded.status,
|
||||
seq = excluded.seq,
|
||||
depends_on = excluded.depends_on,
|
||||
preferred_skill_ids = excluded.preferred_skill_ids,
|
||||
payload = excluded.payload,
|
||||
sync_status = CASE
|
||||
WHEN digital_plan_steps.sync_status = 'synced' THEN 'pending'
|
||||
ELSE digital_plan_steps.sync_status
|
||||
END,
|
||||
updated_at = excluded.updated_at
|
||||
`).run(
|
||||
input.id,
|
||||
input.planId,
|
||||
input.title,
|
||||
input.status,
|
||||
input.seq,
|
||||
stringify(input.dependsOn),
|
||||
stringify(input.preferredSkillIds),
|
||||
stringify(input.payload),
|
||||
input.now,
|
||||
input.now,
|
||||
);
|
||||
markOutbox("plan_step", input.id, "upsert", input, input.now);
|
||||
}
|
||||
|
||||
function upsertTask(input: {
|
||||
id: string;
|
||||
planId: string;
|
||||
|
||||
Reference in New Issue
Block a user