吸收数字员工PlanStep本地软租约

This commit is contained in:
baiyanyun
2026-06-10 22:29:52 +08:00
parent 909f11dae5
commit 6f2ccdf783
13 changed files with 604 additions and 49 deletions

View File

@@ -2582,6 +2582,7 @@ function planStepRows(snapshot: QimingclawSnapshot | null): BusinessViewRow[] {
const payload = asRecord(step.payload) ?? {};
const dependencyGraph = asRecord(payload.dependencyGraph ?? payload.dependency_graph) ?? {};
const lastDispatch = asRecord(payload.lastDispatch ?? payload.last_dispatch) ?? {};
const lease = activePlanStepDispatchLease(payload);
const readiness = planStepDispatchReadiness(step, snapshot, payload);
const blockedDependencies = uniqueStrings(arrayValue(dependencyGraph.blockedDependencies ?? dependencyGraph.blocked_dependencies));
const waitingDependencies = uniqueStrings(arrayValue(dependencyGraph.waitingDependencies ?? dependencyGraph.waiting_dependencies));
@@ -2610,6 +2611,15 @@ function planStepRows(snapshot: QimingclawSnapshot | null): BusinessViewRow[] {
skip_reason: readiness.skipReason,
last_dispatch_status: stringValue(lastDispatch.status) || null,
last_dispatch_at: stringValue(lastDispatch.occurredAt ?? lastDispatch.occurred_at) || null,
lease,
lease_id: stringValue(lease?.lease_id ?? lease?.leaseId) || null,
lease_state: stringValue(lease?.state) || null,
lease_owner_device_id: stringValue(lease?.owner_device_id ?? lease?.ownerDeviceId) || null,
lease_acquired_at: stringValue(lease?.acquired_at ?? lease?.acquiredAt) || null,
lease_expires_at: stringValue(lease?.expires_at ?? lease?.expiresAt) || null,
lease_released_at: stringValue(lease?.released_at ?? lease?.releasedAt) || null,
lease_release_reason: stringValue(lease?.release_reason ?? lease?.releaseReason) || null,
lease_active: Boolean(lease),
sync_status: step.syncStatus,
last_synced_at: step.lastSyncedAt ?? null,
sync_error: step.syncError ?? null,
@@ -2641,6 +2651,15 @@ function planStepGraphRows(snapshot: QimingclawSnapshot | null): BusinessViewRow
latest_run_id: stringValue(row.latest_run_id) || null,
dispatchable: row.dispatchable === true,
skip_reason: stringValue(row.skip_reason) || null,
lease: asRecord(row.lease) ?? null,
lease_id: stringValue(row.lease_id) || null,
lease_state: stringValue(row.lease_state) || null,
lease_owner_device_id: stringValue(row.lease_owner_device_id) || null,
lease_acquired_at: stringValue(row.lease_acquired_at) || null,
lease_expires_at: stringValue(row.lease_expires_at) || null,
lease_released_at: stringValue(row.lease_released_at) || null,
lease_release_reason: stringValue(row.lease_release_reason) || null,
lease_active: row.lease_active === true,
sync_status: stringValue(row.sync_status) || null,
updated_at: stringValue(row.updated_at) || null,
entity_type: 'plan_step_graph_node',
@@ -2679,8 +2698,10 @@ function planStepDispatchReadiness(
?? null;
const taskId = task?.id ?? null;
const latestRunId = task ? latestRunForTask(runtimeRuns(snapshot), task.id)?.id ?? null : null;
const lease = activePlanStepDispatchLease(payload);
let skipReason: string | null = null;
if (step.status.toLowerCase() !== 'ready') skipReason = 'not_ready';
if (lease) skipReason = 'lease_active';
else if (step.status.toLowerCase() !== 'ready') skipReason = 'not_ready';
else if (!task) skipReason = 'missing_task';
else if (isTerminalRuntimeTaskStatus(task.status)) skipReason = 'task_terminal';
else if (runtimeRuns(snapshot).some((run) => run.taskId === task.id && isActiveRuntimeRunStatus(run.status))) skipReason = 'task_already_running';
@@ -2707,6 +2728,7 @@ function planStepDependencyDetails(dependencyIds: string[], snapshot: Qimingclaw
}
const payload = asRecord(dependency.payload) ?? {};
const graph = asRecord(payload.dependencyGraph ?? payload.dependency_graph) ?? {};
const lease = activePlanStepDispatchLease(payload);
const readiness = planStepDispatchReadiness(dependency, snapshot, payload);
return {
step_id: dependency.id,
@@ -2717,10 +2739,23 @@ function planStepDependencyDetails(dependencyIds: string[], snapshot: Qimingclaw
dependency_status: stringValue(graph.status) || dependency.status,
dispatchable: readiness.dispatchable,
skip_reason: readiness.skipReason,
lease_state: stringValue(lease?.state) || null,
lease_active: Boolean(lease),
};
});
}
function activePlanStepDispatchLease(payload: Record<string, unknown>): Record<string, unknown> | null {
const lease = asRecord(payload.dispatchLease ?? payload.dispatch_lease);
if (!lease) return null;
const state = stringValue(lease.state) || 'active';
const expiresAt = stringValue(lease.expires_at ?? lease.expiresAt);
const leaseId = stringValue(lease.lease_id ?? lease.leaseId);
const ownerDeviceId = stringValue(lease.owner_device_id ?? lease.ownerDeviceId);
if (state !== 'active' || !expiresAt || expiresAt <= new Date().toISOString() || !leaseId || !ownerDeviceId) return null;
return lease;
}
function isTerminalRuntimeTaskStatus(status: string): boolean {
return ['completed', 'succeeded', 'success', 'done', 'skipped', 'cancelled', 'canceled'].includes(status.toLowerCase());
}

View File

@@ -63,6 +63,7 @@ interface DigitalPlanItem {
readyStepCount: number;
runningStepCount: number;
blockedStepCount: number;
leasedStepCount: number;
schedulerJob?: CronJob | null;
source: 'plan_template';
}
@@ -71,6 +72,7 @@ interface PlanStepOrchestrationSummary {
ready: number;
running: number;
blocked: number;
leased: number;
}
interface BlockedPlanStepInputTarget {
@@ -417,6 +419,7 @@ function buildPlanTemplateItem(job: CronJob, duties: DigitalEmployeeDuty[], sele
readyStepCount: 0,
runningStepCount: 0,
blockedStepCount: 0,
leasedStepCount: 0,
schedulerJob: job,
source: 'plan_template',
};
@@ -441,9 +444,12 @@ function planStepOrchestrationSummary(rows: PlanStepBusinessRow[]): Map<string,
rows.forEach((step) => {
const planId = step.plan_id || '';
if (!planId) return;
const current = summary.get(planId) ?? { ready: 0, running: 0, blocked: 0 };
const current = summary.get(planId) ?? { ready: 0, running: 0, blocked: 0, leased: 0 };
const status = step.status.toLowerCase();
if (status === 'ready') current.ready += 1;
if (step.lease_active || step.skip_reason === 'lease_active') {
current.leased += 1;
current.running += 1;
} else if (status === 'ready' && step.dispatchable) current.ready += 1;
else if (['running', 'active', 'leased', 'queued'].includes(status)) current.running += 1;
else if (status === 'blocked') current.blocked += 1;
summary.set(planId, current);
@@ -1128,8 +1134,9 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
readyStepCount: orchestration.ready,
runningStepCount: orchestration.running,
blockedStepCount: orchestration.blocked,
resultText: orchestration.ready > 0 || orchestration.running > 0 || orchestration.blocked > 0
? `Ready ${orchestration.ready} · 运行 ${orchestration.running} · 阻塞 ${orchestration.blocked}`
leasedStepCount: orchestration.leased,
resultText: orchestration.ready > 0 || orchestration.running > 0 || orchestration.blocked > 0 || orchestration.leased > 0
? `Ready ${orchestration.ready} · 运行 ${orchestration.running} · 阻塞 ${orchestration.blocked}${orchestration.leased > 0 ? ` · 租约 ${orchestration.leased}` : ''}`
: item.resultText,
};
});

View File

@@ -211,6 +211,21 @@ export interface PlanStepDependencyDetail {
dependency_status: string | null;
dispatchable: boolean;
skip_reason: string | null;
lease_state?: string | null;
lease_active?: boolean;
}
export interface PlanStepLeaseSummary {
lease_id?: string | null;
state?: string | null;
owner_device_id?: string | null;
acquired_at?: string | null;
expires_at?: string | null;
released_at?: string | null;
release_reason?: string | null;
trigger_source?: string | null;
orchestration_id?: string | null;
candidate_count?: number | null;
}
export interface PlanStepBusinessRow {
@@ -230,6 +245,15 @@ export interface PlanStepBusinessRow {
blocked_dependency_details?: PlanStepDependencyDetail[];
dispatchable: boolean;
skip_reason: string | null;
lease?: PlanStepLeaseSummary | null;
lease_id?: string | null;
lease_state?: string | null;
lease_owner_device_id?: string | null;
lease_acquired_at?: string | null;
lease_expires_at?: string | null;
lease_released_at?: string | null;
lease_release_reason?: string | null;
lease_active?: boolean;
sync_status?: string | null;
updated_at?: string | null;
}