吸收数字员工管理端治理命令预览
This commit is contained in:
@@ -1003,6 +1003,7 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
case "notifications" -> isNotificationRow(row);
|
||||
case "audits" -> isAuditRow(row);
|
||||
case "daily_reports" -> isDailyReportRow(row);
|
||||
case "governance_commands" -> isGovernanceCommandRow(row);
|
||||
case "route_governance" -> isRouteGovernanceRow(row);
|
||||
default -> true;
|
||||
};
|
||||
@@ -1072,6 +1073,12 @@ public class DigitalEmployeeSyncApplicationServiceImpl implements DigitalEmploye
|
||||
|| StringUtils.isNotBlank(row.getReportId());
|
||||
}
|
||||
|
||||
private boolean isGovernanceCommandRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||
return StringUtils.equals(row.getCategory(), "governance")
|
||||
|| startsWithAny(row.getKind(), "governance_")
|
||||
|| StringUtils.isNotBlank(firstString(row.getBusinessView().get("command_id"), row.getPayload().get("command_id")));
|
||||
}
|
||||
|
||||
private boolean isRouteGovernanceRow(DigitalEmployeeAdminWorkbenchRowDto row) {
|
||||
return StringUtils.equals(row.getCategory(), "route_governance")
|
||||
|| StringUtils.equals(row.getCategory(), "route_intervention")
|
||||
|
||||
@@ -468,6 +468,76 @@ class DigitalEmployeeSyncApplicationServiceImplTest {
|
||||
assertThat(result.getRecords().get(2).getRouteDecisionId()).isEqualTo("route-3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchGovernanceCommandsReturnsGovernanceRowsOnly() {
|
||||
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-governance-pause", "event", "event-governance-1", 101L, new Date(10_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "governance",
|
||||
"kind", "governance_pause_plan",
|
||||
"status", "recorded",
|
||||
"title", "暂停计划命令",
|
||||
"summary", "低风险治理命令已记录",
|
||||
"command_id", "command-1",
|
||||
"plan_id", "plan-1",
|
||||
"risk_level", "low"
|
||||
),
|
||||
Map.of("id", "event-governance-1", "kind", "governance_pause_plan")),
|
||||
record("outbox-governance-input", "event", "event-governance-2", 101L, new Date(11_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"kind", "governance_provide_task_input",
|
||||
"status", "accepted",
|
||||
"title", "补充任务输入",
|
||||
"command_id", "command-2",
|
||||
"task_id", "task-1",
|
||||
"input_length", 18
|
||||
),
|
||||
Map.of("id", "event-governance-2", "kind", "governance_provide_task_input")),
|
||||
record("outbox-governance-rejected", "event", "event-governance-3", 101L, new Date(12_000),
|
||||
Map.of(
|
||||
"entity_type", "event",
|
||||
"category", "governance",
|
||||
"kind", "governance_cancel_task",
|
||||
"status", "rejected",
|
||||
"title", "取消任务被拒绝",
|
||||
"command_id", "command-3",
|
||||
"task_id", "task-2",
|
||||
"error", "approval_required"
|
||||
),
|
||||
Map.of("id", "event-governance-3", "kind", "governance_cancel_task")),
|
||||
record("outbox-route", "event", "event-route-1", 101L, new Date(13_000),
|
||||
Map.of("entity_type", "event", "category", "route_governance", "title", "路由", "status", "blocked"),
|
||||
Map.of("id", "event-route-1", "status", "blocked")),
|
||||
record("outbox-task", "task", "task-1", 101L, new Date(14_000),
|
||||
Map.of("entity_type", "task", "title", "普通任务", "status", "running"),
|
||||
Map.of("id", "task-1", "status", "running")),
|
||||
record("outbox-approval", "approval", "approval-1", 101L, new Date(15_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(16_000),
|
||||
Map.of("entity_type", "notification", "title", "普通通知", "status", "unread"),
|
||||
Map.of("id", "notification-1", "status", "unread"))
|
||||
)));
|
||||
|
||||
DigitalEmployeeAdminWorkbenchPageDto result = service.queryAdminWorkbench(
|
||||
"governance_commands", null, null, null, null, null, null, null, null, null, null, null, 1L, 20L
|
||||
);
|
||||
|
||||
assertThat(result.getTotal()).isEqualTo(3);
|
||||
assertThat(result.getRecords()).extracting(DigitalEmployeeAdminWorkbenchRowDto::getEntityId)
|
||||
.containsExactly("event-governance-1", "event-governance-2", "event-governance-3");
|
||||
assertThat(result.getRecords().get(0).getView()).isEqualTo("governance_commands");
|
||||
assertThat(result.getRecords().get(0).getCategory()).isEqualTo("governance");
|
||||
assertThat(result.getRecords().get(0).getStatus()).isEqualTo("recorded");
|
||||
assertThat(result.getRecords().get(0).getSummary()).isEqualTo("低风险治理命令已记录");
|
||||
assertThat(result.getRecords().get(0).getBusinessView()).containsEntry("command_id", "command-1");
|
||||
assertThat(result.getRecords().get(1).getBusinessView()).containsEntry("input_length", 18);
|
||||
assertThat(result.getRecords().get(2).getBusinessView()).containsEntry("error", "approval_required");
|
||||
}
|
||||
|
||||
@Test
|
||||
void queryAdminWorkbenchNotificationsReturnsNotificationRowsOnly() {
|
||||
when(repository.queryBusinessRecords(eq(101L), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(null), eq(1L), eq(20L)))
|
||||
|
||||
@@ -44,6 +44,7 @@ const WORKBENCH_TABS: Array<{
|
||||
{ key: 'notifications', label: '通知运营' },
|
||||
{ key: 'audits', label: '审计诊断' },
|
||||
{ key: 'daily_reports', label: '日报运营' },
|
||||
{ key: 'governance_commands', label: '治理命令' },
|
||||
{ key: 'route_governance', label: '路由治理' },
|
||||
{ key: 'policy_conflicts', label: '策略冲突' },
|
||||
];
|
||||
@@ -168,6 +169,9 @@ function actionTarget(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalEmp
|
||||
if (view === 'audits') {
|
||||
return { entityType: 'audit', entityId: record.entity_id };
|
||||
}
|
||||
if (view === 'governance_commands') {
|
||||
return { entityType: 'event', entityId: record.entity_id };
|
||||
}
|
||||
if (view === 'policy_conflicts') {
|
||||
return {
|
||||
entityType: 'policy_snapshot',
|
||||
@@ -242,6 +246,12 @@ function allowedActions(record: DigitalEmployeeAdminWorkbenchRow, view: DigitalE
|
||||
{ action: 'dismiss', label: '忽略' },
|
||||
];
|
||||
}
|
||||
if (view === 'governance_commands') {
|
||||
return [
|
||||
{ action: 'mark_reviewed', label: '标记已看' },
|
||||
{ action: 'dismiss', label: '忽略' },
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -749,6 +759,17 @@ const DigitalEmployeeOps: React.FC = () => {
|
||||
</Typography.Text>
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
{activeView === 'governance_commands' && (
|
||||
<Typography.Paragraph>
|
||||
<Typography.Text strong>治理命令:</Typography.Text>
|
||||
{detailRecord.kind || detailRecord.category || detailRecord.entity_id || '--'}
|
||||
{detailRecord.status && ` · ${detailRecord.status}`}
|
||||
<Typography.Text type="secondary">
|
||||
{' '}
|
||||
· 只记录复核/忽略意图,不开放 ACP 硬中断或高风险执行。
|
||||
</Typography.Text>
|
||||
</Typography.Paragraph>
|
||||
)}
|
||||
{allowedActions(detailRecord, activeView).length > 0 && (
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<Typography.Title level={5}>低风险动作</Typography.Title>
|
||||
|
||||
@@ -565,6 +565,7 @@ export type DigitalEmployeeAdminWorkbenchView =
|
||||
| 'notifications'
|
||||
| 'audits'
|
||||
| 'daily_reports'
|
||||
| 'governance_commands'
|
||||
| 'route_governance'
|
||||
| 'policy_conflicts';
|
||||
|
||||
|
||||
@@ -1924,6 +1924,72 @@ function checkDigitalAdminDailyReportWorkbench() {
|
||||
console.log("[DigitalEmployeeCheck] admin daily report workbench hooks OK");
|
||||
}
|
||||
|
||||
function checkDigitalAdminGovernanceCommandWorkbench() {
|
||||
console.log("\n[DigitalEmployeeCheck] Verify admin governance command 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 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 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, "governance_commands", "backend governance command view"],
|
||||
[service, "isGovernanceCommandRow", "backend governance command matcher"],
|
||||
[service, "governance_", "backend governance command kind classifier"],
|
||||
[service, "command_id", "backend governance command id classifier"],
|
||||
[test, "queryAdminWorkbenchGovernanceCommandsReturnsGovernanceRowsOnly", "backend governance command test"],
|
||||
[qimingTypes, "governance_commands", "qiming governance command view type"],
|
||||
[qimingOps, "治理命令", "qiming governance command tab"],
|
||||
[qimingOps, "标记已看", "qiming governance reviewed action"],
|
||||
[qimingOps, "忽略", "qiming governance dismiss action"],
|
||||
[qimingOps, "ACP 硬中断", "qiming high-risk governance copy"],
|
||||
[docs, "正式管理端治理命令预览", "docs governance command absorption"],
|
||||
];
|
||||
const missing = requiredSnippets
|
||||
.filter(([content, snippet]) => !content.includes(snippet))
|
||||
.map(([, , label]) => label);
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Missing admin governance command workbench hooks: ${missing.join(", ")}`);
|
||||
}
|
||||
console.log("[DigitalEmployeeCheck] admin governance command workbench hooks OK");
|
||||
}
|
||||
|
||||
function checkLocalDatabaseSchema() {
|
||||
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
|
||||
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
|
||||
@@ -1993,6 +2059,7 @@ function main() {
|
||||
checkDigitalAdminArtifactLifecycleWorkbench();
|
||||
checkDigitalAdminAuditDiagnosticsWorkbench();
|
||||
checkDigitalAdminDailyReportWorkbench();
|
||||
checkDigitalAdminGovernanceCommandWorkbench();
|
||||
checkLocalDatabaseSchema();
|
||||
console.log("\n[DigitalEmployeeCheck] All checks passed");
|
||||
} catch (error) {
|
||||
|
||||
@@ -568,7 +568,7 @@ GET /api/digital-employee/approvals
|
||||
|
||||
4. **治理命令完整生命周期**
|
||||
- 已吸收:`create_plan`、`submit_plan`、`approve_plan`、`activate_plan` 等基础命令可记录本地运行事实;任务级 `retry_task`、`skip_task`、`pause_task`、`resume_task`、`provide_task_input`、`cancel_task`、`start_run`、`cancel_run` 已具备本地事实闭环;embedded plan / task action endpoint 已开始映射为正式 governance command;治理审计链和低风险 action policy 已开始吸收,route decision 自动映射 governance command 前会先经过低风险/受保护高风险 action guard,未命中的动作写入 `route_action_policy_blocked`,所有 `governance_*` event 的同步 `business_view` 会提炼 command、route、实体目标、风险等级和结果字段,`provide_task_input` 只同步 `input_length`。
|
||||
- 当前能力:`/api/plan/:id/submit`、`/api/debug/plan/:id/:action`、`/api/task/:id/:action` 会进入 qimingclaw 本地治理记录;plan revision v1 可从 `governance_*` 事件投影只读时间线;数字员工首页会展示“治理审计/治理拦截”摘要,管理端通用 sync record 可通过 `business_view.category = governance` 消费治理命令审计字段。
|
||||
- 当前能力:`/api/plan/:id/submit`、`/api/debug/plan/:id/:action`、`/api/task/:id/:action` 会进入 qimingclaw 本地治理记录;plan revision v1 可从 `governance_*` 事件投影只读时间线;数字员工首页会展示“治理审计/治理拦截”摘要,管理端通用 sync record 可通过 `business_view.category = governance` 消费治理命令审计字段。正式管理端治理命令预览已开始吸收,qiming-backend 的 `governance_commands` 运营台视图会聚合治理命令记录、拒绝和低风险输入审计,qiming“数字员工运营台”的“治理命令”tab 只开放标记已看和忽略意图,不开放 ACP 硬中断或高风险执行。
|
||||
- 缺口:`escalate`、正式计划 revision 表、正式管理端治理工作台和对真实 ACP 执行流的硬中断仍不完整。
|
||||
- 建议:下一轮围绕正式管理端治理工作台和 ACP 硬中断,把本地治理审计推进到完整跨端裁决与执行控制。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user