吸收数字员工管理端步骤调度预览

This commit is contained in:
baiyanyun
2026-06-13 15:38:30 +08:00
parent c9d8aa3b93
commit 5ab6bc0fd0
7 changed files with 307 additions and 3 deletions

View File

@@ -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")

View File

@@ -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")) {

View File

@@ -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)))

View File

@@ -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>

View File

@@ -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;

View File

@@ -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();

View File

@@ -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. **调度 / 定时 / 周期任务**