吸收数字员工治理审计补齐
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 / projection;File 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 和管理端消费模型,再决定是否拆物理业务表。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user