吸收数字员工管理端步骤调度预览
This commit is contained in:
@@ -33,6 +33,22 @@ public class DigitalEmployeeAdminWorkbenchRowDto {
|
|||||||
private String taskId;
|
private String taskId;
|
||||||
@JsonProperty("run_id")
|
@JsonProperty("run_id")
|
||||||
private String runId;
|
private String runId;
|
||||||
|
@JsonProperty("step_id")
|
||||||
|
private String stepId;
|
||||||
|
@JsonProperty("dependency_status")
|
||||||
|
private String dependencyStatus;
|
||||||
|
@JsonProperty("lease_state")
|
||||||
|
private String leaseState;
|
||||||
|
@JsonProperty("lease_source")
|
||||||
|
private String leaseSource;
|
||||||
|
@JsonProperty("dispatch_status")
|
||||||
|
private String dispatchStatus;
|
||||||
|
@JsonProperty("dispatch_policy_key")
|
||||||
|
private String dispatchPolicyKey;
|
||||||
|
@JsonProperty("orchestration_id")
|
||||||
|
private String orchestrationId;
|
||||||
|
@JsonProperty("trigger_source")
|
||||||
|
private String triggerSource;
|
||||||
@JsonProperty("approval_id")
|
@JsonProperty("approval_id")
|
||||||
private String approvalId;
|
private String approvalId;
|
||||||
@JsonProperty("artifact_id")
|
@JsonProperty("artifact_id")
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
"skill:mark_reviewed", "skill:dismiss",
|
"skill:mark_reviewed", "skill:dismiss",
|
||||||
"policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss",
|
"policy_snapshot:pull_policy", "policy_snapshot:mark_reviewed", "policy_snapshot:dismiss",
|
||||||
"scheduler:retry_scheduler_check", "scheduler:mark_reviewed", "scheduler:dismiss",
|
"scheduler:retry_scheduler_check", "scheduler:mark_reviewed", "scheduler:dismiss",
|
||||||
|
"plan_step:mark_reviewed", "plan_step:dismiss", "plan_step:retry_policy_check",
|
||||||
"plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation",
|
"plan:mark_reviewed", "plan:dismiss", "plan:request_revision_review", "plan:record_escalation",
|
||||||
"revision:mark_reviewed", "revision:dismiss", "revision:request_revision_review",
|
"revision:mark_reviewed", "revision:dismiss", "revision:request_revision_review",
|
||||||
"audit:mark_reviewed", "audit:dismiss",
|
"audit:mark_reviewed", "audit:dismiss",
|
||||||
@@ -995,6 +996,14 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
.planId(businessRow.getPlanId())
|
.planId(businessRow.getPlanId())
|
||||||
.taskId(businessRow.getTaskId())
|
.taskId(businessRow.getTaskId())
|
||||||
.runId(businessRow.getRunId())
|
.runId(businessRow.getRunId())
|
||||||
|
.stepId(firstString(businessView.get("step_id"), businessView.get("stepId"), payload.get("step_id"), payload.get("stepId"), "plan_step".equals(record.getEntityType()) ? record.getEntityId() : null))
|
||||||
|
.dependencyStatus(firstString(businessView.get("dependency_status"), businessView.get("dependencyStatus"), payload.get("dependency_status"), payload.get("dependencyStatus")))
|
||||||
|
.leaseState(firstString(businessView.get("lease_state"), businessView.get("leaseState"), payload.get("lease_state"), payload.get("leaseState")))
|
||||||
|
.leaseSource(firstString(businessView.get("lease_source"), businessView.get("leaseSource"), payload.get("lease_source"), payload.get("leaseSource")))
|
||||||
|
.dispatchStatus(firstString(businessView.get("dispatch_status"), businessView.get("dispatchStatus"), payload.get("dispatch_status"), payload.get("dispatchStatus")))
|
||||||
|
.dispatchPolicyKey(firstString(businessView.get("dispatch_policy_key"), businessView.get("dispatchPolicyKey"), businessView.get("policy_key"), payload.get("dispatch_policy_key"), payload.get("dispatchPolicyKey"), payload.get("policy_key")))
|
||||||
|
.orchestrationId(firstString(businessView.get("orchestration_id"), businessView.get("orchestrationId"), payload.get("orchestration_id"), payload.get("orchestrationId")))
|
||||||
|
.triggerSource(firstString(businessView.get("trigger_source"), businessView.get("triggerSource"), payload.get("trigger_source"), payload.get("triggerSource")))
|
||||||
.approvalId(firstString(businessView.get("approval_id"), payload.get("approval_id"), "approval".equals(record.getEntityType()) ? record.getEntityId() : null))
|
.approvalId(firstString(businessView.get("approval_id"), payload.get("approval_id"), "approval".equals(record.getEntityType()) ? record.getEntityId() : null))
|
||||||
.artifactId(firstString(businessView.get("artifact_id"), payload.get("artifact_id"), "artifact".equals(record.getEntityType()) ? record.getEntityId() : null))
|
.artifactId(firstString(businessView.get("artifact_id"), payload.get("artifact_id"), "artifact".equals(record.getEntityType()) ? record.getEntityId() : null))
|
||||||
.cleanupRequestId(firstString(businessView.get("cleanup_request_id"), businessView.get("cleanupRequestId"), payload.get("cleanup_request_id"), payload.get("cleanupRequestId")))
|
.cleanupRequestId(firstString(businessView.get("cleanup_request_id"), businessView.get("cleanupRequestId"), payload.get("cleanup_request_id"), payload.get("cleanupRequestId")))
|
||||||
@@ -1050,6 +1059,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
case "skill_audits" -> isSkillAuditRow(row);
|
case "skill_audits" -> isSkillAuditRow(row);
|
||||||
case "policy_center" -> isPolicyCenterRow(row);
|
case "policy_center" -> isPolicyCenterRow(row);
|
||||||
case "scheduler_operations" -> isSchedulerOperationsRow(row);
|
case "scheduler_operations" -> isSchedulerOperationsRow(row);
|
||||||
|
case "plan_step_dispatch" -> isPlanStepDispatchRow(row);
|
||||||
case "plan_governance" -> isPlanGovernanceRow(row);
|
case "plan_governance" -> isPlanGovernanceRow(row);
|
||||||
case "plan_revisions" -> isPlanRevisionRow(row);
|
case "plan_revisions" -> isPlanRevisionRow(row);
|
||||||
default -> true;
|
default -> true;
|
||||||
@@ -1183,6 +1193,36 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
|| StringUtils.isNotBlank(row.getJobId());
|
|| StringUtils.isNotBlank(row.getJobId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isPlanStepDispatchRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||||
|
String category = firstString(row.getCategory());
|
||||||
|
String kind = firstString(row.getKind());
|
||||||
|
String status = firstString(row.getStatus());
|
||||||
|
String source = firstString(
|
||||||
|
row.getLeaseSource(), row.getTriggerSource(),
|
||||||
|
row.getBusinessView().get("source"), row.getPayload().get("source"),
|
||||||
|
row.getBusinessView().get("lease_source"), row.getPayload().get("lease_source")
|
||||||
|
);
|
||||||
|
return StringUtils.equals(row.getEntityType(), "plan_step")
|
||||||
|
|| StringUtils.equals(category, "plan_step_dispatch")
|
||||||
|
|| StringUtils.equals(category, "plan_step")
|
||||||
|
|| startsWithAny(kind, "plan_step_dispatch", "plan_step_lease", "dispatch_ready_steps")
|
||||||
|
|| contains(kind, "plan_step_dispatch")
|
||||||
|
|| contains(kind, "plan_step_lease")
|
||||||
|
|| contains(kind, "dispatch_ready_steps")
|
||||||
|
|| contains(kind, "lease_active")
|
||||||
|
|| contains(kind, "remote_lease_rejected")
|
||||||
|
|| contains(kind, "local_fallback")
|
||||||
|
|| contains(status, "lease_active")
|
||||||
|
|| contains(status, "remote_lease_rejected")
|
||||||
|
|| contains(status, "local_fallback")
|
||||||
|
|| contains(source, "remote_lease_rejected")
|
||||||
|
|| contains(source, "local_fallback")
|
||||||
|
|| StringUtils.isNotBlank(row.getStepId())
|
||||||
|
|| StringUtils.isNotBlank(row.getLeaseState())
|
||||||
|
|| StringUtils.isNotBlank(row.getDispatchStatus())
|
||||||
|
|| StringUtils.isNotBlank(row.getDispatchPolicyKey());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isPlanGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
private boolean isPlanGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||||
String category = firstString(row.getCategory());
|
String category = firstString(row.getCategory());
|
||||||
String kind = firstString(row.getKind());
|
String kind = firstString(row.getKind());
|
||||||
@@ -1206,6 +1246,11 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
|||||||
private String adminCategory(DigitalEmployeeBusinessViewRowDto row, Map<String, Object> businessView, Map<String, Object> payload) {
|
private String adminCategory(DigitalEmployeeBusinessViewRowDto row, Map<String, Object> businessView, Map<String, Object> payload) {
|
||||||
if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict";
|
if (booleanValue(businessView.get("approval_conflict_active"))) return "approval_conflict";
|
||||||
String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind"));
|
String kind = firstString(row.getKind(), businessView.get("kind"), payload.get("kind"));
|
||||||
|
if (StringUtils.equals(row.getEntityType(), "plan_step")
|
||||||
|
|| startsWithAny(kind, "plan_step_dispatch", "plan_step_lease")
|
||||||
|
|| contains(kind, "dispatch_ready_steps")) {
|
||||||
|
return "plan_step_dispatch";
|
||||||
|
}
|
||||||
if (contains(kind, "route_decision_blocked") || contains(kind, "route_decision_approval_required")
|
if (contains(kind, "route_decision_blocked") || contains(kind, "route_decision_approval_required")
|
||||||
|| contains(kind, "route_action_policy_blocked") || contains(kind, "route_action_missing_context")
|
|| contains(kind, "route_action_policy_blocked") || contains(kind, "route_action_missing_context")
|
||||||
|| contains(kind, "route_action_not_mapped")) {
|
|| contains(kind, "route_action_not_mapped")) {
|
||||||
|
|||||||
@@ -567,6 +567,94 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
|||||||
assertThat(result.getRecords().get(1).getKind()).isEqualTo("scheduler_check_completed");
|
assertThat(result.getRecords().get(1).getKind()).isEqualTo("scheduler_check_completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queryAdminWorkbenchPlanStepDispatchReturnsStepLeaseAndDispatchRowsOnly() {
|
||||||
|
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||||
|
.thenReturn(page(List.of(
|
||||||
|
record("outbox-step", "plan_step", "step-1", 101L, new Date(10_000),
|
||||||
|
Map.of(
|
||||||
|
"entity_type", "plan_step",
|
||||||
|
"category", "plan_step",
|
||||||
|
"title", "步骤一",
|
||||||
|
"status", "ready",
|
||||||
|
"summary", "依赖已满足",
|
||||||
|
"plan_id", "plan-1",
|
||||||
|
"task_id", "task-1",
|
||||||
|
"step_id", "step-1",
|
||||||
|
"dependency_status", "ready"
|
||||||
|
),
|
||||||
|
Map.of("id", "step-1", "status", "ready")),
|
||||||
|
record("outbox-dispatch", "event", "event-step-1", 101L, new Date(11_000),
|
||||||
|
Map.ofEntries(
|
||||||
|
Map.entry("entity_type", "event"),
|
||||||
|
Map.entry("kind", "plan_step_dispatch_started"),
|
||||||
|
Map.entry("status", "running"),
|
||||||
|
Map.entry("title", "步骤派发开始"),
|
||||||
|
Map.entry("plan_id", "plan-1"),
|
||||||
|
Map.entry("task_id", "task-1"),
|
||||||
|
Map.entry("run_id", "run-1"),
|
||||||
|
Map.entry("step_id", "step-1"),
|
||||||
|
Map.entry("dispatch_status", "started"),
|
||||||
|
Map.entry("orchestration_id", "orch-1"),
|
||||||
|
Map.entry("trigger_source", "scheduler_sweep")
|
||||||
|
),
|
||||||
|
Map.of("id", "event-step-1", "kind", "plan_step_dispatch_started")),
|
||||||
|
record("outbox-lease", "event", "event-step-2", 101L, new Date(12_000),
|
||||||
|
Map.of(
|
||||||
|
"entity_type", "event",
|
||||||
|
"kind", "plan_step_lease_acquired",
|
||||||
|
"status", "lease_active",
|
||||||
|
"title", "步骤租约已获取",
|
||||||
|
"plan_id", "plan-1",
|
||||||
|
"step_id", "step-1",
|
||||||
|
"lease_state", "active",
|
||||||
|
"lease_source", "remote"
|
||||||
|
),
|
||||||
|
Map.of("id", "event-step-2", "kind", "plan_step_lease_acquired")),
|
||||||
|
record("outbox-policy", "event", "event-step-3", 101L, new Date(13_000),
|
||||||
|
Map.of(
|
||||||
|
"entity_type", "event",
|
||||||
|
"kind", "plan_step_dispatch_policy_pulled",
|
||||||
|
"status", "synced",
|
||||||
|
"title", "步骤派发策略已拉取",
|
||||||
|
"step_id", "step-1",
|
||||||
|
"dispatch_policy_key", "plan_step_dispatch"
|
||||||
|
),
|
||||||
|
Map.of("id", "event-step-3", "kind", "plan_step_dispatch_policy_pulled")),
|
||||||
|
record("outbox-approval", "approval", "approval-1", 101L, new Date(14_000),
|
||||||
|
Map.of("entity_type", "approval", "title", "普通审批", "status", "pending"),
|
||||||
|
Map.of("id", "approval-1", "status", "pending")),
|
||||||
|
record("outbox-notification", "notification", "notification-1", 101L, new Date(15_000),
|
||||||
|
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
|
||||||
|
Map.of("id", "notification-1", "status", "unread")),
|
||||||
|
record("outbox-artifact", "artifact", "artifact-1", 101L, new Date(16_000),
|
||||||
|
Map.of("entity_type", "artifact", "title", "普通产物", "status", "available"),
|
||||||
|
Map.of("id", "artifact-1", "status", "available")),
|
||||||
|
record("outbox-audit", "event", "event-audit-1", 101L, new Date(17_000),
|
||||||
|
Map.of("entity_type", "event", "category", "tool_call_audit", "title", "审计", "status", "warn"),
|
||||||
|
Map.of("id", "event-audit-1", "status", "warn"))
|
||||||
|
)));
|
||||||
|
|
||||||
|
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
|
||||||
|
"plan_step_dispatch", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(result.getTotal()).isEqualTo(4);
|
||||||
|
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
|
||||||
|
.containsExactly("step-1", "event-step-1", "event-step-2", "event-step-3");
|
||||||
|
assertThat(result.getRecords().get(0).getView()).isEqualTo("plan_step_dispatch");
|
||||||
|
assertThat(result.getRecords().get(0).getStepId()).isEqualTo("step-1");
|
||||||
|
assertThat(result.getRecords().get(0).getDependencyStatus()).isEqualTo("ready");
|
||||||
|
assertThat(result.getRecords().get(1).getDispatchStatus()).isEqualTo("started");
|
||||||
|
assertThat(result.getRecords().get(1).getOrchestrationId()).isEqualTo("orch-1");
|
||||||
|
assertThat(result.getRecords().get(1).getTriggerSource()).isEqualTo("scheduler_sweep");
|
||||||
|
assertThat(result.getRecords().get(2).getLeaseState()).isEqualTo("active");
|
||||||
|
assertThat(result.getRecords().get(2).getLeaseSource()).isEqualTo("remote");
|
||||||
|
assertThat(result.getRecords().get(3).getDispatchPolicyKey()).isEqualTo("plan_step_dispatch");
|
||||||
|
assertThat(result.getRecords().get(3).getTitle()).isEqualTo("步骤派发策略已拉取");
|
||||||
|
assertThat(result.getRecords().get(0).getSummary()).isEqualTo("依赖已满足");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void queryAdminWorkbenchPlanGovernanceReturnsGovernanceEscalationRowsOnly() {
|
void queryAdminWorkbenchPlanGovernanceReturnsGovernanceEscalationRowsOnly() {
|
||||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||||
@@ -1281,6 +1369,27 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
|||||||
assertThat(activatePlan.getReasonCodes()).contains("high_risk_governance_action_disabled");
|
assertThat(activatePlan.getReasonCodes()).contains("high_risk_governance_action_disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void createAdminActionRecordsPlanStepLowRiskAndRejectsRemoteDispatch() {
|
||||||
|
DigitalEmployeeAdminActionResultDto retryPolicyCheck = service.createAdminAction(
|
||||||
|
adminActionRequest("plan_step", "step-1", "retry_policy_check")
|
||||||
|
);
|
||||||
|
DigitalEmployeeAdminActionResultDto dispatchReadyStep = service.createAdminAction(
|
||||||
|
adminActionRequest("plan_step", "step-1", "dispatch_ready_step")
|
||||||
|
);
|
||||||
|
DigitalEmployeeAdminActionResultDto takeoverLease = service.createAdminAction(
|
||||||
|
adminActionRequest("plan_step", "step-1", "takeover_lease")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(retryPolicyCheck.getOk()).isTrue();
|
||||||
|
assertThat(retryPolicyCheck.getStatus()).isEqualTo("pending");
|
||||||
|
assertThat(retryPolicyCheck.getReasonCodes()).contains("action_recorded");
|
||||||
|
assertThat(dispatchReadyStep.getOk()).isFalse();
|
||||||
|
assertThat(dispatchReadyStep.getReasonCodes()).contains("unsupported_admin_action");
|
||||||
|
assertThat(takeoverLease.getOk()).isFalse();
|
||||||
|
assertThat(takeoverLease.getReasonCodes()).contains("unsupported_admin_action");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void queryPendingAdminActionsFiltersTenantClientDeviceAndMasksKey() {
|
void queryPendingAdminActionsFiltersTenantClientDeviceAndMasksKey() {
|
||||||
when(adminActionRepository.queryActions(eq(101L), eq("client-key-001"), eq("device-001"), eq("pending"), eq(1L), eq(20L)))
|
when(adminActionRepository.queryActions(eq(101L), eq("client-key-001"), eq("device-001"), eq("pending"), eq(1L), eq(20L)))
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ const WORKBENCH_TABS: Array<{
|
|||||||
{ key: 'skill_audits', label: '技能审计' },
|
{ key: 'skill_audits', label: '技能审计' },
|
||||||
{ key: 'policy_center', label: '策略中心' },
|
{ key: 'policy_center', label: '策略中心' },
|
||||||
{ key: 'scheduler_operations', label: '调度运营' },
|
{ key: 'scheduler_operations', label: '调度运营' },
|
||||||
|
{ key: 'plan_step_dispatch', label: '步骤调度' },
|
||||||
{ key: 'plan_governance', label: '计划治理' },
|
{ key: 'plan_governance', label: '计划治理' },
|
||||||
{ key: 'plan_revisions', label: '计划版本' },
|
{ key: 'plan_revisions', label: '计划版本' },
|
||||||
];
|
];
|
||||||
@@ -105,6 +106,10 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) {
|
|||||||
const title = compactText(record.title || record.entity_id, 72);
|
const title = compactText(record.title || record.entity_id, 72);
|
||||||
const summary = compactText(record.summary || record.kind || record.category);
|
const summary = compactText(record.summary || record.kind || record.category);
|
||||||
const meta = [
|
const meta = [
|
||||||
|
record.step_id && `Step ${record.step_id}`,
|
||||||
|
record.lease_state && `Lease ${record.lease_state}`,
|
||||||
|
record.dispatch_status && `Dispatch ${record.dispatch_status}`,
|
||||||
|
record.dispatch_policy_key && `Policy ${record.dispatch_policy_key}`,
|
||||||
record.plan_id && `Plan ${record.plan_id}`,
|
record.plan_id && `Plan ${record.plan_id}`,
|
||||||
record.task_id && `Task ${record.task_id}`,
|
record.task_id && `Task ${record.task_id}`,
|
||||||
record.run_id && `Run ${record.run_id}`,
|
record.run_id && `Run ${record.run_id}`,
|
||||||
@@ -142,7 +147,7 @@ function renderSummary(record: DigitalEmployeeAdminWorkbenchRow) {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
{meta.length > 0 && (
|
{meta.length > 0 && (
|
||||||
<Space size={[4, 4]} wrap style={{ marginTop: 4 }}>
|
<Space size={[4, 4]} wrap style={{ marginTop: 4 }}>
|
||||||
{meta.slice(0, 3).map((item) => (
|
{meta.slice(0, 4).map((item) => (
|
||||||
<Tag key={item} color="default" style={{ marginInlineEnd: 0 }}>
|
<Tag key={item} color="default" style={{ marginInlineEnd: 0 }}>
|
||||||
{item}
|
{item}
|
||||||
</Tag>
|
</Tag>
|
||||||
@@ -205,6 +210,9 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp
|
|||||||
if (view === 'scheduler_operations') {
|
if (view === 'scheduler_operations') {
|
||||||
return { entityType: 'scheduler', entityId: record.job_id || record.schedule_id || record.entity_id };
|
return { entityType: 'scheduler', entityId: record.job_id || record.schedule_id || record.entity_id };
|
||||||
}
|
}
|
||||||
|
if (view === 'plan_step_dispatch') {
|
||||||
|
return { entityType: 'plan_step', entityId: record.step_id || record.entity_id };
|
||||||
|
}
|
||||||
if (view === 'plan_governance') {
|
if (view === 'plan_governance') {
|
||||||
return { entityType: 'plan', entityId: record.plan_id || record.entity_id };
|
return { entityType: 'plan', entityId: record.plan_id || record.entity_id };
|
||||||
}
|
}
|
||||||
@@ -313,6 +321,13 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE
|
|||||||
{ action: 'dismiss', label: '忽略' },
|
{ action: 'dismiss', label: '忽略' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if (view === 'plan_step_dispatch') {
|
||||||
|
return [
|
||||||
|
{ action: 'mark_reviewed', label: '标记已看' },
|
||||||
|
{ action: 'dismiss', label: '忽略' },
|
||||||
|
{ action: 'retry_policy_check', label: '重试策略检查' },
|
||||||
|
];
|
||||||
|
}
|
||||||
if (view === 'plan_governance') {
|
if (view === 'plan_governance') {
|
||||||
return [
|
return [
|
||||||
{ action: 'mark_reviewed', label: '标记已看' },
|
{ action: 'mark_reviewed', label: '标记已看' },
|
||||||
@@ -782,6 +797,7 @@ const DigitalEmployeeOps: React.FC = () => {
|
|||||||
detailRecord.plan_id && `Plan ${detailRecord.plan_id}`,
|
detailRecord.plan_id && `Plan ${detailRecord.plan_id}`,
|
||||||
detailRecord.task_id && `Task ${detailRecord.task_id}`,
|
detailRecord.task_id && `Task ${detailRecord.task_id}`,
|
||||||
detailRecord.run_id && `Run ${detailRecord.run_id}`,
|
detailRecord.run_id && `Run ${detailRecord.run_id}`,
|
||||||
|
detailRecord.step_id && `Step ${detailRecord.step_id}`,
|
||||||
detailRecord.approval_id && `Approval ${detailRecord.approval_id}`,
|
detailRecord.approval_id && `Approval ${detailRecord.approval_id}`,
|
||||||
detailRecord.service_id && `Service ${detailRecord.service_id}`,
|
detailRecord.service_id && `Service ${detailRecord.service_id}`,
|
||||||
detailRecord.artifact_id && `Artifact ${detailRecord.artifact_id}`,
|
detailRecord.artifact_id && `Artifact ${detailRecord.artifact_id}`,
|
||||||
@@ -950,6 +966,20 @@ const DigitalEmployeeOps: React.FC = () => {
|
|||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
)}
|
)}
|
||||||
|
{activeView === 'plan_step_dispatch' && (
|
||||||
|
<Typography.Paragraph>
|
||||||
|
<Typography.Text strong>步骤调度:</Typography.Text>
|
||||||
|
{detailRecord.step_id || detailRecord.entity_id || '--'}
|
||||||
|
{detailRecord.dependency_status && ` · 依赖 ${detailRecord.dependency_status}`}
|
||||||
|
{detailRecord.lease_state && ` · 租约 ${detailRecord.lease_state}`}
|
||||||
|
{detailRecord.dispatch_status && ` · 派发 ${detailRecord.dispatch_status}`}
|
||||||
|
{detailRecord.dispatch_policy_key && ` · 策略 ${detailRecord.dispatch_policy_key}`}
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
{' '}
|
||||||
|
· 不远程派发、不抢占租约、不释放租约,只记录复核、忽略和策略复查意图。
|
||||||
|
</Typography.Text>
|
||||||
|
</Typography.Paragraph>
|
||||||
|
)}
|
||||||
{activeView === 'plan_governance' && (
|
{activeView === 'plan_governance' && (
|
||||||
<Typography.Paragraph>
|
<Typography.Paragraph>
|
||||||
<Typography.Text strong>计划治理:</Typography.Text>
|
<Typography.Text strong>计划治理:</Typography.Text>
|
||||||
|
|||||||
@@ -572,6 +572,7 @@ export type DigitalEmployeeAdminWorkbenchView =
|
|||||||
| 'skill_audits'
|
| 'skill_audits'
|
||||||
| 'policy_center'
|
| 'policy_center'
|
||||||
| 'scheduler_operations'
|
| 'scheduler_operations'
|
||||||
|
| 'plan_step_dispatch'
|
||||||
| 'plan_governance'
|
| 'plan_governance'
|
||||||
| 'plan_revisions';
|
| 'plan_revisions';
|
||||||
|
|
||||||
@@ -592,6 +593,14 @@ export interface DigitalEmployeeAdminWorkbenchRow {
|
|||||||
plan_id?: string;
|
plan_id?: string;
|
||||||
task_id?: string;
|
task_id?: string;
|
||||||
run_id?: string;
|
run_id?: string;
|
||||||
|
step_id?: string;
|
||||||
|
dependency_status?: string;
|
||||||
|
lease_state?: string;
|
||||||
|
lease_source?: string;
|
||||||
|
dispatch_status?: string;
|
||||||
|
dispatch_policy_key?: string;
|
||||||
|
orchestration_id?: string;
|
||||||
|
trigger_source?: string;
|
||||||
approval_id?: string;
|
approval_id?: string;
|
||||||
artifact_id?: string;
|
artifact_id?: string;
|
||||||
cleanup_request_id?: string;
|
cleanup_request_id?: string;
|
||||||
|
|||||||
@@ -1737,6 +1737,100 @@ function checkDigitalAdminPlanGovernanceWorkbenches() {
|
|||||||
console.log("[DigitalEmployeeCheck] admin plan governance and revision hooks OK");
|
console.log("[DigitalEmployeeCheck] admin plan governance and revision hooks OK");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkDigitalAdminPlanStepDispatchWorkbench() {
|
||||||
|
console.log("\n[DigitalEmployeeCheck] Verify admin plan step dispatch workbench hooks");
|
||||||
|
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
|
||||||
|
const qimingRoot = path.resolve(packageRoot, "..", "..", "..", "qiming");
|
||||||
|
const servicePath = path.join(
|
||||||
|
backendRoot,
|
||||||
|
"app-platform-modules",
|
||||||
|
"app-platform-agent",
|
||||||
|
"app-platform-agent-core-application",
|
||||||
|
"src",
|
||||||
|
"main",
|
||||||
|
"java",
|
||||||
|
"com",
|
||||||
|
"xspaceagi",
|
||||||
|
"agent",
|
||||||
|
"core",
|
||||||
|
"application",
|
||||||
|
"service",
|
||||||
|
"DigitalEmployeeSyncApplicationServiceImpl.java",
|
||||||
|
);
|
||||||
|
const dtoPath = path.join(
|
||||||
|
backendRoot,
|
||||||
|
"app-platform-modules",
|
||||||
|
"app-platform-agent",
|
||||||
|
"app-platform-agent-core-adapter",
|
||||||
|
"src",
|
||||||
|
"main",
|
||||||
|
"java",
|
||||||
|
"com",
|
||||||
|
"xspaceagi",
|
||||||
|
"agent",
|
||||||
|
"core",
|
||||||
|
"adapter",
|
||||||
|
"dto",
|
||||||
|
"digital",
|
||||||
|
"DigitalEmployeeAdminWorkbenchRowDto.java",
|
||||||
|
);
|
||||||
|
const testPath = path.join(
|
||||||
|
backendRoot,
|
||||||
|
"app-platform-modules",
|
||||||
|
"app-platform-agent",
|
||||||
|
"app-platform-agent-core-application",
|
||||||
|
"src",
|
||||||
|
"test",
|
||||||
|
"java",
|
||||||
|
"com",
|
||||||
|
"xspaceagi",
|
||||||
|
"agent",
|
||||||
|
"core",
|
||||||
|
"application",
|
||||||
|
"service",
|
||||||
|
"DigitalEmployeeSyncApplicationServiceImplTest.java",
|
||||||
|
);
|
||||||
|
const qimingOpsPath = path.join(qimingRoot, "src", "pages", "SystemManagement", "Content", "DigitalEmployeeOps", "index.tsx");
|
||||||
|
const qimingTypesPath = path.join(qimingRoot, "src", "types", "interfaces", "systemManage.ts");
|
||||||
|
const docsPath = path.resolve(packageRoot, "..", "..", "docs", "digital-employee-frontend-integration-plan.md");
|
||||||
|
const service = fs.readFileSync(servicePath, "utf8");
|
||||||
|
const dto = fs.readFileSync(dtoPath, "utf8");
|
||||||
|
const test = fs.readFileSync(testPath, "utf8");
|
||||||
|
const qimingOps = fs.readFileSync(qimingOpsPath, "utf8");
|
||||||
|
const qimingTypes = fs.readFileSync(qimingTypesPath, "utf8");
|
||||||
|
const docs = fs.readFileSync(docsPath, "utf8");
|
||||||
|
const requiredSnippets = [
|
||||||
|
[service, "plan_step_dispatch", "backend plan step dispatch view"],
|
||||||
|
[service, "isPlanStepDispatchRow", "backend plan step dispatch matcher"],
|
||||||
|
[service, "plan_step:retry_policy_check", "backend plan step retry policy action"],
|
||||||
|
[service, "remote_lease_rejected", "backend remote lease rejection matcher"],
|
||||||
|
[service, "local_fallback", "backend local fallback matcher"],
|
||||||
|
[dto, "step_id", "backend step id field"],
|
||||||
|
[dto, "lease_state", "backend lease state field"],
|
||||||
|
[dto, "dispatch_status", "backend dispatch status field"],
|
||||||
|
[dto, "dispatch_policy_key", "backend dispatch policy key field"],
|
||||||
|
[test, "queryAdminWorkbenchPlanStepDispatchReturnsStepLeaseAndDispatchRowsOnly", "backend plan step dispatch test"],
|
||||||
|
[qimingTypes, "plan_step_dispatch", "qiming plan step dispatch view type"],
|
||||||
|
[qimingTypes, "step_id", "qiming step id field"],
|
||||||
|
[qimingTypes, "lease_state", "qiming lease state field"],
|
||||||
|
[qimingTypes, "dispatch_status", "qiming dispatch status field"],
|
||||||
|
[qimingTypes, "dispatch_policy_key", "qiming dispatch policy field"],
|
||||||
|
[qimingOps, "步骤调度", "qiming plan step dispatch tab"],
|
||||||
|
[qimingOps, "重试策略检查", "qiming plan step retry policy action"],
|
||||||
|
[qimingOps, "不远程派发", "qiming remote dispatch guard copy"],
|
||||||
|
[qimingOps, "不抢占租约", "qiming lease takeover guard copy"],
|
||||||
|
[qimingOps, "不释放租约", "qiming lease release guard copy"],
|
||||||
|
[docs, "正式管理端步骤调度预览", "docs plan step dispatch absorption"],
|
||||||
|
];
|
||||||
|
const missing = requiredSnippets
|
||||||
|
.filter(([content, snippet]) => !content.includes(snippet))
|
||||||
|
.map(([, , label]) => label);
|
||||||
|
if (missing.length > 0) {
|
||||||
|
throw new Error(`Missing admin plan step dispatch hooks: ${missing.join(", ")}`);
|
||||||
|
}
|
||||||
|
console.log("[DigitalEmployeeCheck] admin plan step dispatch hooks OK");
|
||||||
|
}
|
||||||
|
|
||||||
function checkDigitalAdminCollaborationOperationsClosure() {
|
function checkDigitalAdminCollaborationOperationsClosure() {
|
||||||
console.log("\n[DigitalEmployeeCheck] Verify admin artifact, notification and daily report collaboration closure hooks");
|
console.log("\n[DigitalEmployeeCheck] Verify admin artifact, notification and daily report collaboration closure hooks");
|
||||||
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
|
const backendRoot = path.resolve(packageRoot, "..", "..", "..", "qiming-backend");
|
||||||
@@ -2437,6 +2531,7 @@ function main() {
|
|||||||
checkDigitalAdminMemorySkillWorkbench();
|
checkDigitalAdminMemorySkillWorkbench();
|
||||||
checkDigitalAdminPolicySchedulerWorkbenches();
|
checkDigitalAdminPolicySchedulerWorkbenches();
|
||||||
checkDigitalAdminPlanGovernanceWorkbenches();
|
checkDigitalAdminPlanGovernanceWorkbenches();
|
||||||
|
checkDigitalAdminPlanStepDispatchWorkbench();
|
||||||
checkDigitalAdminCollaborationOperationsClosure();
|
checkDigitalAdminCollaborationOperationsClosure();
|
||||||
checkDigitalAdminServiceOperationsWorkbench();
|
checkDigitalAdminServiceOperationsWorkbench();
|
||||||
checkDigitalAdminRouteGovernanceWorkbench();
|
checkDigitalAdminRouteGovernanceWorkbench();
|
||||||
|
|||||||
@@ -550,8 +550,8 @@ GET /api/digital-employee/approvals
|
|||||||
|
|
||||||
1. **PlanStep 与依赖图(依赖图调度已开始)**
|
1. **PlanStep 与依赖图(依赖图调度已开始)**
|
||||||
- 已吸收:Plan / Task / Run 记录、计划模板、`create_plan` 中的 steps/tasks 可物化为任务;PlanStep 已开始作为正式本地实体落入 `digital_plan_steps`。
|
- 已吸收:Plan / Task / Run 记录、计划模板、`create_plan` 中的 steps/tasks 可物化为任务;PlanStep 已开始作为正式本地实体落入 `digital_plan_steps`。
|
||||||
- 当前能力:PlanStep 会进入 `digital_sync_outbox`,entity type 为 `plan_step`;任务中心 flow 和 debug store 优先读取真实 PlanStep 生成步骤图;Task Agent 的 retry / skip / cancel / pause / resume / input / run 控制已可更新真实 Task、PlanStep、Run、Event 和 outbox;前置步骤跳过、取消或重试后会重新评估后继步骤,推进为 ready / blocked / pending 并写入依赖图事件;scheduler sweep 会识别可派发的 ready PlanStep,派发前先通过管理端 `POST /api/digital-employee/leases/plan-step-dispatch/acquire` 做跨设备强租约仲裁,管理端拒绝会以 `remote_lease_rejected` 跳过,管理端异常会降级为 5 分钟本地软租约 `payload.dispatchLease.source = local_fallback` 并记录 `remote_error`;调用本地 `/computer/chat` 后会写入 `plan_step_dispatch_*` 与 `governance_start_run` 事件,派发完成或失败会先尝试 `release` 管理端租约再释放本地 lease 并写入 `plan_step_lease_*` 事件;已有未过期租约的步骤仍会以 `lease_active` 跳过;auto sweep 会按节流从管理端 `GET /api/digital-employee/policies/plan-step-dispatch` 拉取远端派发策略,拉取失败时保留本地策略并记录 `last_pull_error`;embedded 已提供 `/api/digital-employee/plan-steps`、`/api/digital-employee/plan-step-graph`、`/api/digital-employee/plans/:planId/dispatch-ready-steps`、`/api/plan-step/dispatch-policy` 和 `/api/plan-step/dispatch-policy/pull`,ready PlanStep 安全派发、计划级跨任务推进、管理端依赖图视图、本地/远端派发策略、可观察租约状态和首页阻塞依赖处理入口已开始闭环。派发策略管理 UI 已开始吸收,embedded 新增“策略中心”集中展示和编辑 `enabled/max_per_sweep/auto_dispatch_ready_steps`。正式管理端策略中心预览已开始吸收,qiming-backend 新增 `policy_center` 运营台视图聚合派发、风险审批、入口路由和技能策略快照,qiming“数字员工运营台”的“策略中心”tab 可记录批量拉取策略、标记已看和忽略意图,不开放远程策略编辑。
|
- 当前能力:PlanStep 会进入 `digital_sync_outbox`,entity type 为 `plan_step`;任务中心 flow 和 debug store 优先读取真实 PlanStep 生成步骤图;Task Agent 的 retry / skip / cancel / pause / resume / input / run 控制已可更新真实 Task、PlanStep、Run、Event 和 outbox;前置步骤跳过、取消或重试后会重新评估后继步骤,推进为 ready / blocked / pending 并写入依赖图事件;scheduler sweep 会识别可派发的 ready PlanStep,派发前先通过管理端 `POST /api/digital-employee/leases/plan-step-dispatch/acquire` 做跨设备强租约仲裁,管理端拒绝会以 `remote_lease_rejected` 跳过,管理端异常会降级为 5 分钟本地软租约 `payload.dispatchLease.source = local_fallback` 并记录 `remote_error`;调用本地 `/computer/chat` 后会写入 `plan_step_dispatch_*` 与 `governance_start_run` 事件,派发完成或失败会先尝试 `release` 管理端租约再释放本地 lease 并写入 `plan_step_lease_*` 事件;已有未过期租约的步骤仍会以 `lease_active` 跳过;auto sweep 会按节流从管理端 `GET /api/digital-employee/policies/plan-step-dispatch` 拉取远端派发策略,拉取失败时保留本地策略并记录 `last_pull_error`;embedded 已提供 `/api/digital-employee/plan-steps`、`/api/digital-employee/plan-step-graph`、`/api/digital-employee/plans/:planId/dispatch-ready-steps`、`/api/plan-step/dispatch-policy` 和 `/api/plan-step/dispatch-policy/pull`,ready PlanStep 安全派发、计划级跨任务推进、管理端依赖图视图、本地/远端派发策略、可观察租约状态和首页阻塞依赖处理入口已开始闭环。派发策略管理 UI 已开始吸收,embedded 新增“策略中心”集中展示和编辑 `enabled/max_per_sweep/auto_dispatch_ready_steps`。正式管理端策略中心预览已开始吸收,qiming-backend 新增 `policy_center` 运营台视图聚合派发、风险审批、入口路由和技能策略快照,qiming“数字员工运营台”的“策略中心”tab 可记录批量拉取策略、标记已看和忽略意图,不开放远程策略编辑。正式管理端步骤调度预览已开始吸收,qiming“数字员工运营台”的“步骤调度”tab 会聚合 PlanStep fact、依赖状态、租约、派发事件和策略拉取审计,只记录标记已看、忽略和重试策略检查意图,不远程派发、不抢占租约、不释放租约。
|
||||||
- 后续缺口:跨设备审批策略 UI、跨设备策略冲突合并和远程策略编辑安全模型仍待吸收。
|
- 后续缺口:跨设备审批策略 UI、跨设备策略冲突合并、远程策略编辑安全模型、真实派发/租约裁决和跨设备策略合并仍待吸收。
|
||||||
- 建议:下一轮围绕管理端正式路由 UI 或跨端审批策略 UI,把本地可治理调度继续推进到跨端治理策略。
|
- 建议:下一轮围绕管理端正式路由 UI 或跨端审批策略 UI,把本地可治理调度继续推进到跨端治理策略。
|
||||||
|
|
||||||
2. **调度 / 定时 / 周期任务**
|
2. **调度 / 定时 / 周期任务**
|
||||||
|
|||||||
Reference in New Issue
Block a user