吸收数字员工治理审计补齐

This commit is contained in:
baiyanyun
2026-06-12 10:33:46 +08:00
parent e473b5c147
commit 3a159e2f5a
11 changed files with 230 additions and 57 deletions

View File

@@ -3387,7 +3387,12 @@ function routeDecisionFromRuntimePayload(payload: Record<string, unknown>, event
function routeDecisionAttentionLevel(decision: RouteDecisionRecord): 'info' | 'action' | 'warn' | 'error' {
if (decision.policy_result === 'approval_required') return 'action';
if (decision.policy_result && decision.policy_result !== 'accepted') return 'error';
if (decision.reason_codes.includes('candidate_skills_disabled') || decision.reason_codes.includes('route_action_missing_context')) return 'warn';
if (
decision.reason_codes.includes('candidate_skills_disabled')
|| decision.reason_codes.includes('route_action_missing_context')
|| decision.reason_codes.includes('route_action_policy_blocked')
|| decision.reason_codes.includes('route_action_not_mapped')
) return 'warn';
if (decision.created_command_id) return 'action';
return 'info';
}
@@ -3412,6 +3417,13 @@ function buildRouteDecisionSummary(snapshot: QimingclawSnapshot | null): Digital
return {
total: rows.length,
mapped_count: rows.filter((row) => stringValue(row.created_command_id)).length,
governance_mapped_count: rows.filter((row) => stringValue(row.created_command_id)).length,
governance_blocked_count: rows.filter((row) => {
const reasonCodes = arrayValue(row.reason_codes).map(stringValue);
return reasonCodes.includes('route_action_policy_blocked')
|| reasonCodes.includes('route_action_missing_context')
|| reasonCodes.includes('route_action_not_mapped');
}).length,
attention_count: rows.filter((row) => ['warn', 'error'].includes(stringValue(row.attention_level))).length,
blocked_count: rows.filter((row) => stringValue(row.policy_result) === 'blocked').length,
approval_required_count: rows.filter((row) => stringValue(row.policy_result) === 'approval_required').length,
@@ -6558,7 +6570,10 @@ function buildGovernanceCommandForRouteDecision(
if (decision.route_kind === 'PlanExecution' && decision.intent_kind === 'schedule') {
const scheduleId = stringValue(contextRefs.schedule_id) || stringValue(payload.schedule_id) || stringValue(payload.scheduleId);
if (!scheduleId) return { command: null, reasonCode: 'route_action_missing_context' };
return { command: baseCommand('run_schedule_now', { schedule_id: scheduleId }), reasonCode: 'route_action_mapped' };
const commandKind = 'run_schedule_now';
const policyReason = routeGovernanceActionPolicyReason(commandKind);
if (policyReason) return { command: null, reasonCode: policyReason };
return { command: baseCommand(commandKind, { schedule_id: scheduleId }), reasonCode: 'route_action_mapped' };
}
if (decision.route_kind === 'PlanExecution' && decision.intent_kind === 'execute') {
@@ -6566,8 +6581,11 @@ function buildGovernanceCommandForRouteDecision(
const taskId = stringValue(contextRefs.task_id) || stringValue(payload.task_id) || stringValue(payload.taskId) || taskIdForRouteStep(snapshot, stepId);
if (!taskId) return { command: null, reasonCode: 'route_action_missing_context' };
const task = runtimeTasks(snapshot).find((candidate) => candidate.id === taskId) ?? null;
const commandKind = 'start_run';
const policyReason = routeGovernanceActionPolicyReason(commandKind);
if (policyReason) return { command: null, reasonCode: policyReason };
return {
command: baseCommand('start_run', {
command: baseCommand(commandKind, {
plan_id: stringValue(contextRefs.plan_id) || task?.planId || stringValue(payload.plan_id) || stringValue(payload.planId),
task_id: taskId,
step_id: stepId || (task ? taskStepId(task) : ''),
@@ -6583,6 +6601,8 @@ function buildGovernanceCommandForRouteDecision(
const planId = stringValue(contextRefs.plan_id) || stringValue(payload.plan_id) || stringValue(payload.planId);
const commandKind = routeControlCommandKind(actionText, Boolean(taskId));
if (!commandKind || (!taskId && !planId)) return { command: null, reasonCode: 'route_action_missing_context' };
const policyReason = routeGovernanceActionPolicyReason(commandKind);
if (policyReason) return { command: null, reasonCode: policyReason };
return {
command: baseCommand(commandKind, {
plan_id: planId,
@@ -6777,6 +6797,32 @@ function compactRecord(record: Record<string, unknown>): Record<string, unknown>
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined && value !== null && value !== ''));
}
const ROUTE_LOW_RISK_GOVERNANCE_COMMANDS = new Set<GovernanceCommandRequest['command_kind']>([
'submit_plan',
'approve_plan',
'amend_plan',
'resume_plan',
'pause_plan',
'retry_task',
'skip_task',
'resume_task',
'provide_task_input',
]);
const ROUTE_RISK_PROTECTED_GOVERNANCE_COMMANDS = new Set<GovernanceCommandRequest['command_kind']>([
'activate_plan',
'run_schedule_now',
'start_run',
'cancel_run',
'cancel_task',
'delete_schedule',
]);
function routeGovernanceActionPolicyReason(commandKind: GovernanceCommandRequest['command_kind']): string | null {
if (ROUTE_LOW_RISK_GOVERNANCE_COMMANDS.has(commandKind)) return null;
if (ROUTE_RISK_PROTECTED_GOVERNANCE_COMMANDS.has(commandKind)) return null;
return 'route_action_policy_blocked';
}
function governanceCommandRiskLevel(commandKind: string): string {
if (['activate_plan', 'run_schedule_now', 'start_run', 'cancel_run', 'cancel_task'].includes(commandKind)) return 'high';
if (['delete_schedule', 'pause_schedule', 'skip_task', 'retry_task', 'provide_task_input'].includes(commandKind)) return 'medium';

View File

@@ -2455,6 +2455,8 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
<div className="de-route-summary-strip">
<span><em></em><strong>{routeSummary.total}</strong></span>
<span><em></em><strong>{routeSummary.mapped_count}</strong></span>
<span><em></em><strong>{routeSummary.governance_mapped_count ?? routeSummary.mapped_count}</strong></span>
{(routeSummary.governance_blocked_count ?? 0) > 0 && <span className="is-warning"><em></em><strong>{routeSummary.governance_blocked_count}</strong></span>}
<span className={routeSummary.attention_count > 0 ? 'is-warning' : ''}><em></em><strong>{routeSummary.attention_count}</strong></span>
{(routeSummary.blocked_count ?? 0) > 0 && <span className="is-warning"><em></em><strong>{routeSummary.blocked_count}</strong></span>}
{(routeSummary.approval_required_count ?? 0) > 0 && <span className="is-warning"><em></em><strong>{routeSummary.approval_required_count}</strong></span>}
@@ -2463,6 +2465,8 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
<small>
{latestRoute ? `${latestRoute.route_kind} / ${latestRoute.intent_kind} · ${latestRoute.status_label}` : '暂无路由决策'}
{routeSummary.policy_source ? ` · 策略${routeSummary.policy_source === 'remote' ? '远端' : '本地'}` : ''}
{` · 治理审计已映射 ${routeSummary.governance_mapped_count ?? routeSummary.mapped_count}`}
{(routeSummary.governance_blocked_count ?? 0) > 0 ? ` · 拦截/失败 ${routeSummary.governance_blocked_count}` : ''}
{routeSummary.latest_intervention_status ? ` · 干预${routeSummary.latest_intervention_status}` : ''}
{routeSummary.policy_last_pull_error ? ` · 拉取失败` : ''}
</small>

View File

@@ -1084,6 +1084,8 @@ export interface DigitalEmployeeDailyReport {
export interface DigitalEmployeeRouteSummary {
total: number;
mapped_count: number;
governance_mapped_count?: number;
governance_blocked_count?: number;
attention_count: number;
blocked_count?: number;
approval_required_count?: number;

View File

@@ -16,7 +16,7 @@
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
}
</script>
<script type="module" crossorigin src="./assets/index-luMzZfek.js"></script>
<script type="module" crossorigin src="./assets/index-DIYwF-B5.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CEcWFgGW.css">
</head>
<body>

View File

@@ -281,8 +281,8 @@ function checkDigitalNotificationPreferences() {
[syncService, "notificationBusinessView", "notification sync business view"],
[syncService, "digital_workday_notification_updates_pulled", "notification updates sync event kind"],
[syncService, "digital_workday_notification_action_submitted", "notification action sync event kind"],
[syncService, "governanceProvideTaskInputBusinessView", "notification reply task input sync view"],
[syncService, "governance_provide_task_input", "task input sync event kind"],
[syncService, "governanceCommandBusinessView", "notification reply governance sync view"],
[syncService, "provide_task_input", "task input governance command kind"],
[syncService, "input_length", "task input sync redacted length"],
[syncService, "isNotificationEvent", "notification event classifier"],
[syncService, "return \"notification\"", "notification business category"],
@@ -679,6 +679,9 @@ function checkDigitalRouteDecisionPolicy() {
[syncService, "/api/digital-employee/policies/route-decision", "remote route decision policy default path"],
[syncService, "routeDecisionPolicyBusinessView", "route decision policy sync business view"],
[syncService, "routeDecisionInterventionBusinessView", "route decision intervention sync business view"],
[syncService, "governanceCommandBusinessView", "governance command sync business view"],
[syncService, "kind.startsWith(\"governance_\")", "governance event business category"],
[syncService, "input_length", "governance task input redaction field"],
[ipcHandlers, "digitalEmployee:pullRouteDecisionPolicy", "IPC route decision policy pull handler"],
[ipcHandlers, "digitalEmployee:evaluateRouteDecisionPolicy", "IPC route decision policy evaluation handler"],
[ipcHandlers, "digitalEmployee:listRouteInterventions", "IPC route intervention list handler"],
@@ -694,12 +697,16 @@ function checkDigitalRouteDecisionPolicy() {
[adapter, "handleRouteInterventionDecision", "adapter route intervention decision handler"],
[adapter, "resolveApprovedRouteInterventions", "adapter approved route intervention auto resolver"],
[adapter, "auto_create_governance_commands", "adapter route command auto-create switch"],
[adapter, "routeGovernanceActionPolicyReason", "adapter route governance action guard"],
[adapter, "route_action_policy_blocked", "adapter route action policy blocked reason"],
[api, "pullRouteDecisionPolicy", "embedded route decision policy pull API"],
[api, "decideRouteIntervention", "embedded route intervention decision API"],
[types, "RouteDecisionPolicy", "embedded route decision policy type"],
[types, "RouteInterventionRecord", "embedded route intervention type"],
[types, "governance_blocked_count", "route governance blocked summary type"],
[commandDeck, "pull_route_decision_policy", "home route decision policy pull action"],
[commandDeck, "路由策略", "home route decision policy pull button"],
[commandDeck, "治理审计", "home governance audit summary"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))

View File

@@ -1798,7 +1798,14 @@ describe("digital employee state service", () => {
stepId: "step-governance",
accepted: true,
message: "跳过当前任务",
payload: { reason: "用户选择跳过" },
payload: {
reason: "用户选择跳过",
source: "qimingclaw-ingress-route",
route_decision_id: "route-decision-skip",
route_kind: "PlanControl",
intent_kind: "control",
risk_level: "medium",
},
result: { task_id: "task-governance", run_id: "run-governance", step_id: "step-governance" },
});
@@ -1834,6 +1841,12 @@ describe("digital employee state service", () => {
message: "跳过当前任务",
task_id: "task-governance",
});
expect(JSON.parse(mockState.db?.events.get("run-governance:skip_task:command-skip-task")?.payload ?? "{}")).toMatchObject({
routeDecisionId: "route-decision-skip",
routeKind: "PlanControl",
intentKind: "control",
riskLevel: "medium",
});
expect(mockState.db?.outbox.get("task:upsert:task-governance")).toMatchObject({ entity_type: "task" });
expect(mockState.db?.outbox.get("plan_step:upsert:step-governance")).toMatchObject({ entity_type: "plan_step" });
expect(mockState.db?.outbox.get("run:upsert:run-governance")).toMatchObject({ entity_type: "run" });

View File

@@ -4174,6 +4174,10 @@ export function recordDigitalEmployeeGovernanceCommand(
commandId,
correlationId: command.correlationId,
planId,
routeDecisionId: stringValue(payload.route_decision_id ?? payload.routeDecisionId) || null,
routeKind: stringValue(payload.route_kind ?? payload.routeKind) || null,
intentKind: stringValue(payload.intent_kind ?? payload.intentKind) || null,
riskLevel: stringValue(payload.risk_level ?? payload.riskLevel) || null,
scheduleId: command.scheduleId || stringValue(result.schedule_id) || null,
accepted: command.accepted !== false,
message: command.message,
@@ -4333,6 +4337,10 @@ export function applyDigitalEmployeeTaskGovernanceCommand(
taskId,
stepId: stepId || null,
runId,
routeDecisionId: stringValue(payload.route_decision_id ?? payload.routeDecisionId) || null,
routeKind: stringValue(payload.route_kind ?? payload.routeKind) || null,
intentKind: stringValue(payload.intent_kind ?? payload.intentKind) || null,
riskLevel: stringValue(payload.risk_level ?? payload.riskLevel) || null,
sourceRunId: stringValue(targetRun?.id) || null,
accepted: command.accepted !== false,
message: command.message,

View File

@@ -1348,6 +1348,50 @@ describe("digital employee sync service", () => {
source: "qimingclaw-task-agent",
},
});
insertEventEntity("event-governance-pause", "governance_pause_plan", {
commandKind: "pause_plan",
commandId: "command-pause-plan",
correlationId: "route-1",
planId: "plan-1",
accepted: true,
message: "暂停计划",
riskLevel: "normal",
payload: {
source: "qimingclaw-ingress-route",
route_decision_id: "route-1",
route_kind: "PlanControl",
intent_kind: "control",
plan_id: "plan-1",
},
result: {
plan_id: "plan-1",
status: "accepted",
source: "qimingclaw-adapter",
},
});
insertEventEntity("event-governance-skip", "governance_skip_task", {
command_kind: "skip_task",
command_id: "command-skip-task",
correlation_id: "route-2",
plan_id: "plan-1",
task_id: "task-1",
run_id: "run-1",
step_id: "step-1",
accepted: false,
error: "policy_blocked",
risk_level: "medium",
payload: {
source: "qimingclaw-ingress-route",
route_decision_id: "route-2",
route_kind: "PlanControl",
intent_kind: "control",
task_id: "task-1",
},
result: {
status: "rejected",
error: "policy_blocked",
},
});
insertEventEntity("event-notification-read", "digital_workday_notification_read", {
metadata: {
notification_id: "task:task-1",
@@ -1498,6 +1542,8 @@ describe("digital employee sync service", () => {
{ entity_type: "event", entity_id: "event-lease" },
{ entity_type: "event", entity_id: "event-plan-step-ready" },
{ entity_type: "event", entity_id: "event-governance-input" },
{ entity_type: "event", entity_id: "event-governance-pause" },
{ entity_type: "event", entity_id: "event-governance-skip" },
{ entity_type: "event", entity_id: "event-notification-read" },
{ entity_type: "event", entity_id: "event-notification-dismiss" },
{ entity_type: "event", entity_id: "event-notification-reply" },
@@ -1639,6 +1685,36 @@ describe("digital employee sync service", () => {
input_length: 10,
});
expect(businessView("event-governance-input")).not.toHaveProperty("input");
expect(businessView("event-governance-pause")).toMatchObject({
category: "governance",
command_kind: "pause_plan",
accepted: true,
command_id: "command-pause-plan",
correlation_id: "route-1",
plan_id: "plan-1",
route_decision_id: "route-1",
route_kind: "PlanControl",
intent_kind: "control",
source: "qimingclaw-ingress-route",
status: "accepted",
risk_level: "normal",
});
expect(businessView("event-governance-skip")).toMatchObject({
category: "governance",
command_kind: "skip_task",
accepted: false,
error: "policy_blocked",
command_id: "command-skip-task",
correlation_id: "route-2",
plan_id: "plan-1",
task_id: "task-1",
run_id: "run-1",
step_id: "step-1",
route_decision_id: "route-2",
route_kind: "PlanControl",
intent_kind: "control",
risk_level: "medium",
});
expect(businessView("event-notification-read")).toMatchObject({
category: "notification",
notification_id: "task:task-1",

View File

@@ -1736,7 +1736,7 @@ function eventSpecificBusinessView(
if (kind.startsWith("plan_step_dispatch_")) return planStepDispatchBusinessView(payload);
if (isPlanStepLeaseEvent(kind)) return planStepLeaseBusinessView(payload);
if (isPlanStepDispatchPolicyEvent(kind)) return planStepDispatchPolicyBusinessView(payload);
if (kind === "governance_provide_task_input") return governanceProvideTaskInputBusinessView(payload);
if (kind.startsWith("governance_")) return governanceCommandBusinessView(kind, payload);
if (isNotificationEvent(kind)) return notificationBusinessView(payload);
return {};
}
@@ -1989,18 +1989,35 @@ function planStepDispatchPolicyBusinessView(payload: Record<string, unknown>): R
};
}
function governanceProvideTaskInputBusinessView(payload: Record<string, unknown>): Record<string, unknown> {
function governanceCommandBusinessView(kind: string, payload: Record<string, unknown>): Record<string, unknown> {
const commandPayload = objectRecord(payload.payload) ?? {};
const result = objectRecord(payload.result) ?? {};
const commandKind = stringField(payload.commandKind ?? payload.command_kind) || kind.replace(/^governance_/, "");
const input = stringField(commandPayload.input ?? payload.input);
return {
command_kind: stringField(payload.commandKind ?? payload.command_kind) || "provide_task_input",
const view: Record<string, unknown> = {
command_kind: commandKind,
accepted: typeof payload.accepted === "boolean" ? payload.accepted : null,
error: stringField(payload.error ?? result.error) || null,
command_id: stringField(payload.commandId ?? payload.command_id) || null,
correlation_id: stringField(payload.correlationId ?? payload.correlation_id) || null,
plan_id: stringField(payload.planId ?? payload.plan_id ?? commandPayload.plan_id ?? result.plan_id) || null,
task_id: stringField(payload.taskId ?? payload.task_id ?? commandPayload.task_id ?? result.task_id) || null,
run_id: stringField(payload.runId ?? payload.run_id ?? commandPayload.run_id ?? result.run_id) || null,
step_id: stringField(payload.stepId ?? payload.step_id ?? commandPayload.step_id ?? result.step_id) || null,
schedule_id: stringField(payload.scheduleId ?? payload.schedule_id ?? commandPayload.schedule_id ?? result.schedule_id) || null,
route_decision_id: stringField(payload.routeDecisionId ?? payload.route_decision_id ?? commandPayload.route_decision_id ?? result.route_decision_id) || null,
route_kind: stringField(payload.routeKind ?? payload.route_kind ?? commandPayload.route_kind ?? result.route_kind) || null,
intent_kind: stringField(payload.intentKind ?? payload.intent_kind ?? commandPayload.intent_kind ?? result.intent_kind) || null,
source: stringField(commandPayload.source ?? result.source ?? payload.source) || null,
notification_id: stringField(commandPayload.notification_id ?? payload.notification_id) || null,
input_length: input ? input.length : null,
status: stringField(payload.status ?? result.status) || null,
next_action: stringField(payload.nextAction ?? payload.next_action ?? result.next_action) || null,
risk_level: stringField(payload.riskLevel ?? payload.risk_level ?? commandPayload.risk_level ?? result.risk_level) || null,
};
if (commandKind === "provide_task_input") {
view.notification_id = stringField(commandPayload.notification_id ?? payload.notification_id) || null;
view.input_length = input ? input.length : null;
}
return view;
}
function notificationBusinessView(payload: Record<string, unknown>): Record<string, unknown> {

View File

@@ -567,10 +567,10 @@ 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。
- 当前能力:`/api/plan/:id/submit``/api/debug/plan/:id/:action``/api/task/:id/:action` 会进入 qimingclaw 本地治理记录plan revision v1 可从 `governance_*` 事件投影只读时间线。
- 缺口:`escalate`、正式计划 revision 表、治理审计链和对真实 ACP 执行流的硬中断仍不完整。
- 建议:下一轮围绕治理审计和低风险 action policy把 route decision 到 governance command 的自动映射补齐
- 已吸收:`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` 消费治理命令审计字段
- 缺口:`escalate`、正式计划 revision 表、正式管理端治理工作台和对真实 ACP 执行流的硬中断仍不完整。
- 建议:下一轮围绕正式管理端治理工作台和 ACP 硬中断,把本地治理审计推进到完整跨端裁决与执行控制
5. **ManagedService 控制面**
- 已吸收Agent、MCP、Computer Server、Lanproxy、File Server、GUI Server 状态已进入 snapshot / projectionFile Server 与 GUI Server 已开放白名单式 restart bridge并会写入 `managed_service_restart` / `managed_service_restart_rejected` 事件,安全服务 restart bridge 已开始闭环。
@@ -599,7 +599,7 @@ GET /api/digital-employee/approvals
10. **入口路由 / 任务分流(路由业务视图与通知联动已开始)**
- 已吸收:管理端通过 `/computer/chat` 进入 qimingclaw 后会生成本地任务事实embedded `/api/ingress/route``/api/route-decisions``/api/digital-employee/route-decisions` 已由 qimingclaw adapter 接管。
- 当前能力入口请求会按上下文、ready PlanStep、调度、查询和控制动作生成 route decision并写入 `digital_events.kind = route_decision` 与现有 event outbox路由时间线优先从 qimingclaw 本地事件读取;低风险 route decision 已开始映射为明确的本地 governance command并把 `created_command_id` 回填到 route decision入口路由策略可从管理端远端下发支持按 intent 阻断、按 intent 要求审批、限制自动创建 governance command并在策略干预时先记录 route decision 再停止本地命令执行;首页已展示路由干预队列,审批通过/拒绝会写入本地 approval decision 并回写管理端,管理端审批更新拉取后会自动扫描已批准 route approval 并按幂等规则恢复执行scheduler check 已接入 ready PlanStep 安全派发,首页立即执行后会触发一次派发 sweep 并展示派发结果;路由决策本地业务视图、首页摘要、策略来源/拉取错误、阻断/待审/待干预/已恢复计数和通知联动已开始闭环。
- 当前能力入口请求会按上下文、ready PlanStep、调度、查询和控制动作生成 route decision并写入 `digital_events.kind = route_decision` 与现有 event outbox路由时间线优先从 qimingclaw 本地事件读取;低风险 route decision 已开始映射为明确的本地 governance command并把 `created_command_id` 回填到 route decision自动映射前会执行低风险/受保护高风险 action guard未允许的控制动作会记录 `route_action_policy_blocked``route_action_missing_context`,避免静默创建命令;入口路由策略可从管理端远端下发,支持按 intent 阻断、按 intent 要求审批、限制自动创建 governance command并在策略干预时先记录 route decision 再停止本地命令执行;首页已展示路由干预队列和治理审计摘要,审批通过/拒绝会写入本地 approval decision 并回写管理端,管理端审批更新拉取后会自动扫描已批准 route approval 并按幂等规则恢复执行scheduler check 已接入 ready PlanStep 安全派发,首页立即执行后会触发一次派发 sweep 并展示派发结果;路由决策本地业务视图、首页摘要、治理审计、策略来源/拉取错误、阻断/待审/待干预/已恢复计数和通知联动已开始闭环。
- 后续缺口:管理端正式路由 UI、跨设备路由干预冲突可视化和批量干预入口仍待吸收。
- 建议:下一轮围绕管理端正式路由 UI 和跨设备冲突可视化,把入口路由从客户端可运营干预推进到管理端运营台。
@@ -609,7 +609,7 @@ GET /api/digital-employee/approvals
- 建议:下一轮围绕 sandbox audit 与 token/cost 采集,把只读投影升级为可追溯的真实审计与成本统计。
12. **管理端业务查询模型**
- 已吸收:管理端已有通用 sync record 接收、查询、payload 摘要和深链embedded adapter 已拆出 `/api/digital-employee/plans``/tasks``/runs``/events``/artifacts``/approvals``/plans/:planId` 只读业务查询接口,从 qimingclaw runtime、artifact、approval 和 canonical event 投影生成统一分页视图;客户端 outbox 的 event `business_view` 已开始按 route decision、approval action、artifact lifecycle/access、skill call audit 和 ready PlanStep dispatch 细分常用字段,本地业务视图投影已开始闭环。
- 已吸收:管理端已有通用 sync record 接收、查询、payload 摘要和深链embedded adapter 已拆出 `/api/digital-employee/plans``/tasks``/runs``/events``/artifacts``/approvals``/plans/:planId` 只读业务查询接口,从 qimingclaw runtime、artifact、approval 和 canonical event 投影生成统一分页视图;客户端 outbox 的 event `business_view` 已开始按 route decision、governance command、approval action、artifact lifecycle/access、skill call audit 和 ready PlanStep dispatch 细分常用字段,本地业务视图投影已开始闭环。
- 缺口:尚未接入管理端真实业务表、复杂组合检索、跨设备维度过滤和业务视图 UI。
- 建议:先稳定本地投影 API 和管理端消费模型,再决定是否拆物理业务表。