|
|
|
|
@@ -38,6 +38,8 @@ import type {
|
|
|
|
|
PlanStepDispatchPolicy,
|
|
|
|
|
PlanStepDispatchPolicyPullResult,
|
|
|
|
|
PlanStepSummary,
|
|
|
|
|
RouteDecisionPolicy,
|
|
|
|
|
RouteDecisionPolicyPullResult,
|
|
|
|
|
RouteDecisionRecord,
|
|
|
|
|
RouteDecisionTimelineResponse,
|
|
|
|
|
SkillSpec,
|
|
|
|
|
@@ -59,6 +61,8 @@ declare global {
|
|
|
|
|
pullApprovalUpdates?: () => Promise<{ ok?: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }>;
|
|
|
|
|
submitApprovalDecision?: (input: Record<string, unknown>) => Promise<{ ok?: boolean; error?: string | null }>;
|
|
|
|
|
evaluateRiskPolicy?: (input: QimingclawRiskPolicyInput) => Promise<QimingclawRiskPolicyResult>;
|
|
|
|
|
pullRouteDecisionPolicy?: () => Promise<RouteDecisionPolicyPullResult>;
|
|
|
|
|
evaluateRouteDecisionPolicy?: (input: QimingclawRouteDecisionPolicyInput) => Promise<QimingclawRouteDecisionPolicyResult>;
|
|
|
|
|
getPlanTemplates?: () => Promise<QimingclawPlanTemplateList>;
|
|
|
|
|
listMemories?: (options?: { query?: string; category?: string; limit?: number }) => Promise<QimingclawMemoryRecord[]>;
|
|
|
|
|
addMemory?: (input: QimingclawMemoryUpsertInput) => Promise<QimingclawMemoryRecord | null>;
|
|
|
|
|
@@ -100,6 +104,7 @@ interface AdapterState {
|
|
|
|
|
notificationPreferences?: NotificationPreferences;
|
|
|
|
|
approvalSubscriptionPreferences?: ApprovalSubscriptionPreferences;
|
|
|
|
|
planStepDispatchPolicy?: PlanStepDispatchPolicy;
|
|
|
|
|
routeDecisionPolicy?: RouteDecisionPolicy;
|
|
|
|
|
consoleRole?: string;
|
|
|
|
|
rolePreferences?: Record<string, unknown>;
|
|
|
|
|
profile: {
|
|
|
|
|
@@ -360,6 +365,10 @@ interface QimingclawRouteDecisionInput {
|
|
|
|
|
createsTaskRun?: boolean;
|
|
|
|
|
approvalMode?: string | null;
|
|
|
|
|
policyResult?: string | null;
|
|
|
|
|
policySource?: string | null;
|
|
|
|
|
blockedReason?: string | null;
|
|
|
|
|
approvalId?: string | null;
|
|
|
|
|
matchedIntent?: string | null;
|
|
|
|
|
entrypoint?: string | null;
|
|
|
|
|
actor?: string | null;
|
|
|
|
|
message?: string | null;
|
|
|
|
|
@@ -367,6 +376,38 @@ interface QimingclawRouteDecisionInput {
|
|
|
|
|
recordedAt?: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface QimingclawRouteDecisionPolicyInput {
|
|
|
|
|
routeKind?: string | null;
|
|
|
|
|
intentKind?: string | null;
|
|
|
|
|
reasonCodes?: string[] | null;
|
|
|
|
|
candidateSkills?: string[] | null;
|
|
|
|
|
contextRefs?: Record<string, unknown> | null;
|
|
|
|
|
correlationId?: string | null;
|
|
|
|
|
idempotencyKey?: string | null;
|
|
|
|
|
entrypoint?: string | null;
|
|
|
|
|
actor?: string | null;
|
|
|
|
|
source?: string | null;
|
|
|
|
|
payload?: Record<string, unknown> | null;
|
|
|
|
|
occurredAt?: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface QimingclawRouteDecisionPolicyResult {
|
|
|
|
|
ok?: boolean;
|
|
|
|
|
decision?: 'allowed' | 'approval_required' | 'blocked' | string;
|
|
|
|
|
status?: 'allowed' | 'approval_required' | 'blocked' | string;
|
|
|
|
|
policy_result?: 'accepted' | 'approval_required' | 'blocked' | string;
|
|
|
|
|
route_kind?: string;
|
|
|
|
|
intent_kind?: string;
|
|
|
|
|
reason_codes?: string[];
|
|
|
|
|
matched_intent?: string | null;
|
|
|
|
|
blocked_reason?: string | null;
|
|
|
|
|
approval_id?: string | null;
|
|
|
|
|
event_id?: string | null;
|
|
|
|
|
policy_source?: string | null;
|
|
|
|
|
policy?: RouteDecisionPolicy;
|
|
|
|
|
error?: string | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface QimingclawRiskPolicyInput {
|
|
|
|
|
actionKind?: string | null;
|
|
|
|
|
actionLabel?: string | null;
|
|
|
|
|
@@ -1022,6 +1063,9 @@ export async function handleQimingclawDigitalApi<T>(
|
|
|
|
|
if (method === 'POST' && pathname === '/api/risk-approval/policy/pull') {
|
|
|
|
|
return await pullBridgeRiskApprovalPolicy() as T;
|
|
|
|
|
}
|
|
|
|
|
if (method === 'POST' && pathname === '/api/route-decision/policy/pull') {
|
|
|
|
|
return await pullBridgeRouteDecisionPolicy() as T;
|
|
|
|
|
}
|
|
|
|
|
if (method === 'POST' && pathname === '/api/approval/updates/pull') {
|
|
|
|
|
return await pullBridgeApprovalUpdates() as T;
|
|
|
|
|
}
|
|
|
|
|
@@ -1045,6 +1089,7 @@ function defaultState(): AdapterState {
|
|
|
|
|
notificationPreferences: defaultNotificationPreferences(),
|
|
|
|
|
approvalSubscriptionPreferences: defaultApprovalSubscriptionPreferences(),
|
|
|
|
|
planStepDispatchPolicy: defaultPlanStepDispatchPolicy(),
|
|
|
|
|
routeDecisionPolicy: defaultRouteDecisionPolicy(),
|
|
|
|
|
consoleRole: 'sales',
|
|
|
|
|
rolePreferences: {},
|
|
|
|
|
profile: {
|
|
|
|
|
@@ -1072,6 +1117,7 @@ function normalizeAdapterState(value: Partial<AdapterState>): AdapterState {
|
|
|
|
|
notificationPreferences: notificationPreferences(value),
|
|
|
|
|
approvalSubscriptionPreferences: approvalSubscriptionPreferences(value),
|
|
|
|
|
planStepDispatchPolicy: planStepDispatchPolicy(value),
|
|
|
|
|
routeDecisionPolicy: routeDecisionPolicy(value),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1177,6 +1223,27 @@ async function pullBridgeRiskApprovalPolicy(): Promise<{ ok: boolean; skipped?:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function pullBridgeRouteDecisionPolicy(): Promise<RouteDecisionPolicyPullResult> {
|
|
|
|
|
const fallbackPolicy = routeDecisionPolicy((await readBridgeUiState()) ?? readState());
|
|
|
|
|
const bridge = window.QimingClawBridge?.digital;
|
|
|
|
|
if (!bridge?.pullRouteDecisionPolicy) {
|
|
|
|
|
return { ok: false, policy: fallbackPolicy, error: 'qimingclaw_bridge_unavailable' };
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const result = await bridge.pullRouteDecisionPolicy();
|
|
|
|
|
return {
|
|
|
|
|
ok: result?.ok !== false,
|
|
|
|
|
skipped: result?.skipped,
|
|
|
|
|
endpoint: result?.endpoint ?? null,
|
|
|
|
|
pulled_at: result?.pulled_at ?? null,
|
|
|
|
|
policy: routeDecisionPolicy({ routeDecisionPolicy: result?.policy ?? fallbackPolicy }),
|
|
|
|
|
error: result?.error ?? null,
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return { ok: false, policy: fallbackPolicy, error: error instanceof Error ? error.message : String(error) };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function pullBridgeApprovalUpdates(): Promise<{ ok: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }> {
|
|
|
|
|
const bridge = window.QimingClawBridge?.digital;
|
|
|
|
|
if (!bridge?.pullApprovalUpdates) {
|
|
|
|
|
@@ -1223,6 +1290,39 @@ async function evaluateBridgeRiskPolicy(input: QimingclawRiskPolicyInput): Promi
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function evaluateBridgeRouteDecisionPolicy(input: QimingclawRouteDecisionPolicyInput): Promise<QimingclawRouteDecisionPolicyResult> {
|
|
|
|
|
const bridge = window.QimingClawBridge?.digital;
|
|
|
|
|
if (!bridge?.evaluateRouteDecisionPolicy) {
|
|
|
|
|
return {
|
|
|
|
|
ok: true,
|
|
|
|
|
decision: 'allowed',
|
|
|
|
|
status: 'allowed',
|
|
|
|
|
policy_result: 'accepted',
|
|
|
|
|
route_kind: input.routeKind || 'ChatOnly',
|
|
|
|
|
intent_kind: normalizeRouteIntentKind(input.intentKind),
|
|
|
|
|
reason_codes: ['qimingclaw_bridge_unavailable'],
|
|
|
|
|
policy_source: 'local',
|
|
|
|
|
policy: defaultRouteDecisionPolicy(),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
return await bridge.evaluateRouteDecisionPolicy(input);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return {
|
|
|
|
|
ok: true,
|
|
|
|
|
decision: 'allowed',
|
|
|
|
|
status: 'allowed',
|
|
|
|
|
policy_result: 'accepted',
|
|
|
|
|
route_kind: input.routeKind || 'ChatOnly',
|
|
|
|
|
intent_kind: normalizeRouteIntentKind(input.intentKind),
|
|
|
|
|
reason_codes: ['route_policy_evaluation_failed'],
|
|
|
|
|
policy_source: 'local',
|
|
|
|
|
policy: defaultRouteDecisionPolicy(),
|
|
|
|
|
error: error instanceof Error ? error.message : String(error),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function riskPolicyBlockedResponse(result: QimingclawRiskPolicyResult): Record<string, unknown> | null {
|
|
|
|
|
if (result.decision === 'allowed') return null;
|
|
|
|
|
return {
|
|
|
|
|
@@ -3097,6 +3197,10 @@ function routeDecisionFromRuntimePayload(payload: Record<string, unknown>, event
|
|
|
|
|
creates_task_run: Boolean(decision.creates_task_run ?? decision.createsTaskRun),
|
|
|
|
|
approval_mode: stringValue(decision.approval_mode ?? decision.approvalMode) || null,
|
|
|
|
|
policy_result: stringValue(decision.policy_result ?? decision.policyResult) || 'accepted',
|
|
|
|
|
policy_source: stringValue(decision.policy_source ?? decision.policySource) || null,
|
|
|
|
|
blocked_reason: stringValue(decision.blocked_reason ?? decision.blockedReason) || null,
|
|
|
|
|
approval_id: stringValue(decision.approval_id ?? decision.approvalId) || null,
|
|
|
|
|
matched_intent: stringValue(decision.matched_intent ?? decision.matchedIntent) || null,
|
|
|
|
|
recorded_at: stringValue(decision.recorded_at ?? decision.recordedAt) || event.occurredAt,
|
|
|
|
|
entrypoint: stringValue(decision.entrypoint) || null,
|
|
|
|
|
actor: stringValue(decision.actor) || null,
|
|
|
|
|
@@ -3104,6 +3208,7 @@ 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.created_command_id) return 'action';
|
|
|
|
|
@@ -3113,6 +3218,7 @@ function routeDecisionAttentionLevel(decision: RouteDecisionRecord): 'info' | 'a
|
|
|
|
|
function routeDecisionStatusLabel(decision: RouteDecisionRecord): string {
|
|
|
|
|
const attentionLevel = routeDecisionAttentionLevel(decision);
|
|
|
|
|
if (attentionLevel === 'error') return '策略拒绝';
|
|
|
|
|
if (decision.policy_result === 'approval_required') return '待审批';
|
|
|
|
|
if (attentionLevel === 'warn') return '需要关注';
|
|
|
|
|
if (decision.created_command_id) return '已映射命令';
|
|
|
|
|
return '已记录';
|
|
|
|
|
@@ -3121,10 +3227,16 @@ function routeDecisionStatusLabel(decision: RouteDecisionRecord): string {
|
|
|
|
|
function buildRouteDecisionSummary(snapshot: QimingclawSnapshot | null): DigitalEmployeeRouteSummary {
|
|
|
|
|
const rows = routeDecisionRows(snapshot);
|
|
|
|
|
const latest = rows[0] ?? null;
|
|
|
|
|
const policy = routeDecisionPolicy(snapshot?.uiState ?? readState());
|
|
|
|
|
return {
|
|
|
|
|
total: rows.length,
|
|
|
|
|
mapped_count: rows.filter((row) => stringValue(row.created_command_id)).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,
|
|
|
|
|
policy_source: policy.source ?? null,
|
|
|
|
|
policy_last_pulled_at: policy.last_pulled_at ?? null,
|
|
|
|
|
policy_last_pull_error: policy.last_pull_error ?? null,
|
|
|
|
|
latest: latest
|
|
|
|
|
? {
|
|
|
|
|
id: stringValue(latest.route_decision_id),
|
|
|
|
|
@@ -3133,6 +3245,9 @@ function buildRouteDecisionSummary(snapshot: QimingclawSnapshot | null): Digital
|
|
|
|
|
status_label: stringValue(latest.status_label),
|
|
|
|
|
attention_level: stringValue(latest.attention_level),
|
|
|
|
|
created_command_id: stringValue(latest.created_command_id) || null,
|
|
|
|
|
policy_source: stringValue(latest.policy_source) || null,
|
|
|
|
|
blocked_reason: stringValue(latest.blocked_reason) || null,
|
|
|
|
|
approval_id: stringValue(latest.approval_id) || null,
|
|
|
|
|
recorded_at: stringValue(latest.recorded_at),
|
|
|
|
|
}
|
|
|
|
|
: null,
|
|
|
|
|
@@ -4152,6 +4267,21 @@ function defaultPlanStepDispatchPolicy(): PlanStepDispatchPolicy {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function defaultRouteDecisionPolicy(): RouteDecisionPolicy {
|
|
|
|
|
return {
|
|
|
|
|
enabled: true,
|
|
|
|
|
auto_create_governance_commands: true,
|
|
|
|
|
approval_required_intents: [],
|
|
|
|
|
blocked_intents: [],
|
|
|
|
|
preferred_route_kinds: [],
|
|
|
|
|
updated_at: null,
|
|
|
|
|
source: 'local',
|
|
|
|
|
remote_updated_at: null,
|
|
|
|
|
last_pulled_at: null,
|
|
|
|
|
last_pull_error: null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function approvalSubscriptionPreferences(state: Partial<AdapterState> | null | undefined): ApprovalSubscriptionPreferences {
|
|
|
|
|
const stored = state?.approvalSubscriptionPreferences;
|
|
|
|
|
const fallback = defaultApprovalSubscriptionPreferences();
|
|
|
|
|
@@ -4188,6 +4318,28 @@ function planStepDispatchPolicy(state: Partial<AdapterState> | null | undefined)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function routeDecisionPolicy(state: Partial<AdapterState> | null | undefined): RouteDecisionPolicy {
|
|
|
|
|
const stored = state?.routeDecisionPolicy;
|
|
|
|
|
const fallback = defaultRouteDecisionPolicy();
|
|
|
|
|
if (!stored || typeof stored !== 'object') return fallback;
|
|
|
|
|
return {
|
|
|
|
|
enabled: stored.enabled !== false,
|
|
|
|
|
auto_create_governance_commands: stored.auto_create_governance_commands !== false,
|
|
|
|
|
approval_required_intents: uniqueStrings(arrayValue(stored.approval_required_intents).map(normalizeRouteIntentKind)),
|
|
|
|
|
blocked_intents: uniqueStrings(arrayValue(stored.blocked_intents).map(normalizeRouteIntentKind)),
|
|
|
|
|
preferred_route_kinds: uniqueStrings(arrayValue(stored.preferred_route_kinds)),
|
|
|
|
|
updated_at: typeof stored.updated_at === 'string' ? stored.updated_at : fallback.updated_at,
|
|
|
|
|
source: stored.source === 'remote' ? 'remote' : 'local',
|
|
|
|
|
remote_updated_at: typeof stored.remote_updated_at === 'string' ? stored.remote_updated_at : null,
|
|
|
|
|
last_pulled_at: typeof stored.last_pulled_at === 'string' ? stored.last_pulled_at : null,
|
|
|
|
|
last_pull_error: typeof stored.last_pull_error === 'string' ? stored.last_pull_error : null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeRouteIntentKind(value: unknown): string {
|
|
|
|
|
return stringValue(value).trim().toLowerCase().replace(/[^a-z0-9_.:-]+/g, '_') || 'chat';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function notificationPreferences(state: Partial<AdapterState> | null | undefined): NotificationPreferences {
|
|
|
|
|
const stored = state?.notificationPreferences;
|
|
|
|
|
const fallback = defaultNotificationPreferences();
|
|
|
|
|
@@ -5720,11 +5872,50 @@ async function handleIngressRoute(
|
|
|
|
|
creates_task_run: classification.createsTaskRun,
|
|
|
|
|
approval_mode: null,
|
|
|
|
|
policy_result: 'accepted',
|
|
|
|
|
policy_source: null,
|
|
|
|
|
blocked_reason: null,
|
|
|
|
|
approval_id: null,
|
|
|
|
|
matched_intent: null,
|
|
|
|
|
recorded_at: recordedAt,
|
|
|
|
|
entrypoint: body.entrypoint ?? null,
|
|
|
|
|
actor: body.actor ?? null,
|
|
|
|
|
};
|
|
|
|
|
const routeCommand = buildGovernanceCommandForRouteDecision(decision, body, snapshot);
|
|
|
|
|
const routePolicy = await evaluateBridgeRouteDecisionPolicy({
|
|
|
|
|
routeKind: decision.route_kind,
|
|
|
|
|
intentKind: decision.intent_kind,
|
|
|
|
|
reasonCodes: decision.reason_codes,
|
|
|
|
|
candidateSkills: decision.candidate_skills,
|
|
|
|
|
contextRefs: decision.context_refs,
|
|
|
|
|
correlationId,
|
|
|
|
|
idempotencyKey: body.idempotency_key,
|
|
|
|
|
entrypoint: body.entrypoint ?? null,
|
|
|
|
|
actor: body.actor ?? null,
|
|
|
|
|
source: 'qimingclaw-ingress-route',
|
|
|
|
|
payload: {
|
|
|
|
|
text: body.text ?? null,
|
|
|
|
|
payload: body.payload ?? null,
|
|
|
|
|
attachments: body.attachments ?? [],
|
|
|
|
|
},
|
|
|
|
|
occurredAt: recordedAt,
|
|
|
|
|
});
|
|
|
|
|
const routePolicyDecision = stringValue(routePolicy.decision) || 'allowed';
|
|
|
|
|
decision.policy_result = routePolicy.policy_result || (routePolicyDecision === 'allowed' ? 'accepted' : routePolicyDecision) || 'accepted';
|
|
|
|
|
decision.policy_source = routePolicy.policy_source ?? routePolicy.policy?.source ?? null;
|
|
|
|
|
decision.blocked_reason = routePolicy.blocked_reason ?? null;
|
|
|
|
|
decision.approval_id = routePolicy.approval_id ?? null;
|
|
|
|
|
decision.matched_intent = routePolicy.matched_intent ?? null;
|
|
|
|
|
decision.reason_codes = uniqueStrings([...decision.reason_codes, ...(routePolicy.reason_codes ?? [])]);
|
|
|
|
|
const preferredKinds = routePolicy.policy?.preferred_route_kinds ?? [];
|
|
|
|
|
if (preferredKinds.length > 0 && !preferredKinds.includes(decision.route_kind)) {
|
|
|
|
|
decision.reason_codes = uniqueStrings([...decision.reason_codes, 'route_kind_not_preferred']);
|
|
|
|
|
}
|
|
|
|
|
const shouldExecuteCommand = routePolicyDecision === 'allowed' && routePolicy.policy?.auto_create_governance_commands !== false;
|
|
|
|
|
if (routePolicyDecision === 'allowed' && routePolicy.policy?.auto_create_governance_commands === false) {
|
|
|
|
|
decision.reason_codes = uniqueStrings([...decision.reason_codes, 'route_command_auto_create_disabled']);
|
|
|
|
|
}
|
|
|
|
|
const routeCommand = shouldExecuteCommand
|
|
|
|
|
? buildGovernanceCommandForRouteDecision(decision, body, snapshot)
|
|
|
|
|
: { command: null, reasonCode: routePolicyDecision === 'allowed' ? 'route_command_auto_create_disabled' : `route_policy_${decision.policy_result}` };
|
|
|
|
|
const command = routeCommand.command
|
|
|
|
|
? await handleGovernanceCommand(routeCommand.command, snapshot)
|
|
|
|
|
: null;
|
|
|
|
|
@@ -5750,6 +5941,10 @@ async function handleIngressRoute(
|
|
|
|
|
createsTaskRun: decision.creates_task_run,
|
|
|
|
|
approvalMode: decision.approval_mode,
|
|
|
|
|
policyResult: decision.policy_result,
|
|
|
|
|
policySource: decision.policy_source,
|
|
|
|
|
blockedReason: decision.blocked_reason,
|
|
|
|
|
approvalId: decision.approval_id,
|
|
|
|
|
matchedIntent: decision.matched_intent,
|
|
|
|
|
entrypoint: decision.entrypoint,
|
|
|
|
|
actor: decision.actor,
|
|
|
|
|
message: `入口路由决策:${decision.route_kind} / ${decision.intent_kind}`,
|
|
|
|
|
@@ -5763,6 +5958,31 @@ async function handleIngressRoute(
|
|
|
|
|
: null;
|
|
|
|
|
const routeDecision = recorded ?? decision;
|
|
|
|
|
const resultIds = routeCommandResultIds(command);
|
|
|
|
|
if (routePolicyDecision !== 'allowed') {
|
|
|
|
|
return {
|
|
|
|
|
accepted: false,
|
|
|
|
|
envelope: {
|
|
|
|
|
entrypoint: body.entrypoint ?? 'chat',
|
|
|
|
|
text: body.text ?? null,
|
|
|
|
|
payload: body.payload ?? null,
|
|
|
|
|
attachments: body.attachments ?? [],
|
|
|
|
|
context_refs: contextRefs,
|
|
|
|
|
},
|
|
|
|
|
response_kind: 'route_decision',
|
|
|
|
|
route_decision: routeDecision,
|
|
|
|
|
command: null,
|
|
|
|
|
created_plan_id: null,
|
|
|
|
|
created_schedule_id: null,
|
|
|
|
|
created_task_ids: [],
|
|
|
|
|
created_run_ids: [],
|
|
|
|
|
projection_name: null,
|
|
|
|
|
projection_payload: null,
|
|
|
|
|
error: 'policy_blocked',
|
|
|
|
|
message: routePolicyDecision === 'approval_required'
|
|
|
|
|
? `入口路由需要审批:${routeDecision.route_kind} / ${routeDecision.intent_kind}`
|
|
|
|
|
: `入口路由已被策略阻断:${routeDecision.route_kind} / ${routeDecision.intent_kind}`,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
accepted: true,
|
|
|
|
|
envelope: {
|
|
|
|
|
|