吸收数字员工入口路由动作映射

This commit is contained in:
baiyanyun
2026-06-09 19:05:15 +08:00
parent 23c3577ef4
commit 755a8f041d
5 changed files with 188 additions and 45 deletions

View File

@@ -3185,6 +3185,16 @@ async function handleIngressRoute(
entrypoint: body.entrypoint ?? null,
actor: body.actor ?? null,
};
const routeCommand = buildGovernanceCommandForRouteDecision(decision, body, snapshot);
const command = routeCommand.command
? await handleGovernanceCommand(routeCommand.command, snapshot)
: null;
if (command?.accepted && command.command_id) {
decision.created_command_id = command.command_id;
decision.reason_codes = uniqueStrings([...decision.reason_codes, 'route_action_mapped']);
} else if (routeCommand.reasonCode) {
decision.reason_codes = uniqueStrings([...decision.reason_codes, routeCommand.reasonCode]);
}
const bridge = window.QimingClawBridge?.digital;
const recorded = bridge?.recordRouteDecision
? await bridge.recordRouteDecision({
@@ -3213,6 +3223,7 @@ async function handleIngressRoute(
})
: null;
const routeDecision = recorded ?? decision;
const resultIds = routeCommandResultIds(command);
return {
accepted: true,
envelope: {
@@ -3224,14 +3235,142 @@ async function handleIngressRoute(
},
response_kind: 'route_decision',
route_decision: routeDecision,
command: null,
created_plan_id: null,
created_schedule_id: null,
created_task_ids: [],
created_run_ids: [],
command,
created_plan_id: resultIds.createdPlanId,
created_schedule_id: resultIds.createdScheduleId,
created_task_ids: resultIds.createdTaskIds,
created_run_ids: resultIds.createdRunIds,
projection_name: null,
projection_payload: null,
message: `已记录入口路由:${routeDecision.route_kind} / ${routeDecision.intent_kind}`,
message: command?.command_id
? `已记录入口路由并映射治理动作:${routeDecision.route_kind} / ${routeDecision.intent_kind}`
: `已记录入口路由:${routeDecision.route_kind} / ${routeDecision.intent_kind}`,
};
}
function buildGovernanceCommandForRouteDecision(
decision: RouteDecisionRecord,
body: IngressRouteRequest,
snapshot: QimingclawSnapshot | null,
): { command: GovernanceCommandRequest | null; reasonCode: string } {
if (decision.reason_codes.includes('candidate_skills_disabled')) {
return { command: null, reasonCode: 'route_action_not_mapped' };
}
const contextRefs = decision.context_refs ?? {};
const payload = asRecord(body.payload) ?? {};
const basePayload = compactRecord({
...payload,
source: 'qimingclaw-ingress-route',
route_decision_id: decision.id,
route_kind: decision.route_kind,
intent_kind: decision.intent_kind,
reason_codes: decision.reason_codes,
ingress_text: body.text ?? null,
});
const baseCommand = (commandKind: GovernanceCommandRequest['command_kind'], refs: Record<string, unknown>): GovernanceCommandRequest => ({
command_kind: commandKind,
context_refs: compactRecord({ ...contextRefs, ...refs }),
payload: compactRecord({ ...basePayload, ...refs }),
correlation_id: decision.correlation_id,
idempotency_key: `route-command:${decision.id}`,
});
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' };
}
if (decision.route_kind === 'PlanExecution' && decision.intent_kind === 'execute') {
const stepId = stringValue(contextRefs.step_id) || stringValue(payload.step_id) || stringValue(payload.stepId);
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;
return {
command: baseCommand('start_run', {
plan_id: stringValue(contextRefs.plan_id) || task?.planId || stringValue(payload.plan_id) || stringValue(payload.planId),
task_id: taskId,
step_id: stepId || (task ? taskStepId(task) : ''),
run_id: stringValue(contextRefs.run_id) || stringValue(payload.run_id) || stringValue(payload.runId),
}),
reasonCode: 'route_action_mapped',
};
}
if (decision.route_kind === 'PlanControl' && decision.intent_kind === 'control') {
const actionText = routeActionText(body);
const taskId = stringValue(contextRefs.task_id) || stringValue(payload.task_id) || stringValue(payload.taskId);
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' };
return {
command: baseCommand(commandKind, {
plan_id: planId,
task_id: taskId,
step_id: stringValue(contextRefs.step_id) || stringValue(payload.step_id) || stringValue(payload.stepId),
run_id: stringValue(contextRefs.run_id) || stringValue(payload.run_id) || stringValue(payload.runId),
}),
reasonCode: 'route_action_mapped',
};
}
return { command: null, reasonCode: 'route_action_not_mapped' };
}
function taskIdForRouteStep(snapshot: QimingclawSnapshot | null, stepId: string): string {
if (!stepId) return '';
const ready = runtimePlanSteps(snapshot).some((step) => step.id === stepId && step.status.toLowerCase() === 'ready');
if (!ready) return '';
return runtimeTasks(snapshot).find((task) => taskStepId(task) === stepId)?.id ?? '';
}
function routeActionText(body: IngressRouteRequest): string {
const payload = asRecord(body.payload) ?? {};
return [body.text, payload.action, payload.command, payload.intent, payload.requested_action]
.map(stringValue)
.filter(Boolean)
.join(' ')
.toLowerCase();
}
function routeControlCommandKind(actionText: string, hasTaskTarget: boolean): GovernanceCommandRequest['command_kind'] | null {
if (hasTaskTarget) {
if (matchesRouteText(actionText, ['retry', '重试'])) return 'retry_task';
if (matchesRouteText(actionText, ['skip', '跳过'])) return 'skip_task';
if (matchesRouteText(actionText, ['pause', '暂停'])) return 'pause_task';
if (matchesRouteText(actionText, ['resume', '恢复'])) return 'resume_task';
if (matchesRouteText(actionText, ['cancel', '取消'])) return 'cancel_task';
if (matchesRouteText(actionText, ['start', 'run', 'execute', 'begin', '启动', '开始', '执行'])) return 'start_run';
return null;
}
if (matchesRouteText(actionText, ['submit', '提交'])) return 'submit_plan';
if (matchesRouteText(actionText, ['approve', '批准', '同意'])) return 'approve_plan';
if (matchesRouteText(actionText, ['pause', '暂停'])) return 'pause_plan';
if (matchesRouteText(actionText, ['resume', '恢复'])) return 'resume_plan';
if (matchesRouteText(actionText, ['cancel', '取消'])) return 'cancel_plan';
if (matchesRouteText(actionText, ['amend', 'edit', 'revise', '修改', '调整'])) return 'amend_plan';
if (matchesRouteText(actionText, ['activate', 'start', 'run', 'execute', '启动', '开始', '执行'])) return 'activate_plan';
return null;
}
function routeCommandResultIds(command: GovernanceCommandResponse | null): {
createdPlanId: string | null;
createdScheduleId: string | null;
createdTaskIds: string[];
createdRunIds: string[];
} {
const result = asRecord(command?.result) ?? {};
const createdTaskIds = uniqueStrings([
result.task_id,
result.taskId,
...arrayValue(result.dispatched_tasks).map((task) => asRecord(task)?.task_id ?? asRecord(task)?.taskId),
]);
return {
createdPlanId: stringValue(result.plan_id) || stringValue(result.planId) || null,
createdScheduleId: stringValue(result.schedule_id) || stringValue(result.scheduleId) || null,
createdTaskIds,
createdRunIds: uniqueStrings([result.run_id, result.runId]),
};
}
@@ -3398,6 +3537,7 @@ async function handleGovernanceCommand(
const dispatch = await dispatchPlan(targetPlanId, snapshot, { recordCommand: false });
const response = governanceAccepted(commandId, correlationId, '已映射为 qimingclaw 本地任务激活。', {
plan_id: targetPlanId,
schedule_id: body.command_kind === 'run_schedule_now' ? scheduleId : null,
status: 'active',
dispatched_tasks: dispatch.dispatched_tasks,
source: 'qimingclaw-adapter',