吸收数字员工技能调用审计
This commit is contained in:
@@ -186,6 +186,32 @@ export interface DigitalEmployeeEventRecord {
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export type DigitalEmployeeSkillCallDecision = "allowed" | "rejected" | "observed";
|
||||
|
||||
export interface DigitalEmployeeSkillCallAuditInput {
|
||||
callId?: string | null;
|
||||
source?: string | null;
|
||||
serverId?: string | null;
|
||||
toolName?: string | null;
|
||||
skillId?: string | null;
|
||||
decision: DigitalEmployeeSkillCallDecision;
|
||||
reasonCodes?: string[] | null;
|
||||
policySnapshot?: Record<string, unknown> | null;
|
||||
sessionId?: string | null;
|
||||
taskId?: string | null;
|
||||
runId?: string | null;
|
||||
status?: string | null;
|
||||
occurredAt?: string | null;
|
||||
message?: string | null;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeSkillCallAuditRecord {
|
||||
eventId: string;
|
||||
kind: "skill_call_allowed" | "skill_call_rejected" | "skill_call_observed";
|
||||
decision: DigitalEmployeeSkillCallDecision;
|
||||
payload: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeRouteDecisionRecord {
|
||||
id: string;
|
||||
route_kind: string;
|
||||
@@ -943,6 +969,80 @@ export function listDigitalEmployeeRouteDecisions(options: { limit?: number } =
|
||||
.filter((decision): decision is DigitalEmployeeRouteDecisionRecord => Boolean(decision));
|
||||
}
|
||||
|
||||
export function recordDigitalEmployeeSkillCallAudit(
|
||||
input: DigitalEmployeeSkillCallAuditInput,
|
||||
): DigitalEmployeeSkillCallAuditRecord | null {
|
||||
const db = getDigitalEmployeeDb();
|
||||
if (!db) return null;
|
||||
const now = input.occurredAt || new Date().toISOString();
|
||||
const decision: DigitalEmployeeSkillCallDecision = ["allowed", "rejected", "observed"].includes(input.decision)
|
||||
? input.decision
|
||||
: "observed";
|
||||
const kind = `skill_call_${decision}` as DigitalEmployeeSkillCallAuditRecord["kind"];
|
||||
const callId = stringValue(input.callId)
|
||||
|| stringValue(input.runId)
|
||||
|| stringValue(input.sessionId)
|
||||
|| `${Date.now()}`;
|
||||
const eventId = `skill-call:${decision}:${safeId(callId)}:${safeId(now)}`;
|
||||
const source = stringValue(input.source) || "qimingclaw-acp";
|
||||
const toolName = stringValue(input.toolName) || null;
|
||||
const serverId = stringValue(input.serverId) || null;
|
||||
const skillId = stringValue(input.skillId) || null;
|
||||
const payload: Record<string, unknown> = {
|
||||
call_id: callId,
|
||||
source,
|
||||
server_id: serverId,
|
||||
tool_name: toolName,
|
||||
skill_id: skillId,
|
||||
decision,
|
||||
reason_codes: normalizeSkillIds(input.reasonCodes ?? []),
|
||||
policy_snapshot: sanitizeSkillPolicySnapshot(objectRecord(input.policySnapshot) ?? {}),
|
||||
session_id: stringValue(input.sessionId) || null,
|
||||
task_id: stringValue(input.taskId) || null,
|
||||
run_id: stringValue(input.runId) || null,
|
||||
status: stringValue(input.status) || decision,
|
||||
};
|
||||
insertEvent(
|
||||
{
|
||||
event_id: eventId,
|
||||
kind,
|
||||
occurred_at: now,
|
||||
message: input.message || skillCallAuditMessage(decision, toolName, skillId),
|
||||
plan_id: null,
|
||||
task_id: stringValue(input.taskId) || null,
|
||||
},
|
||||
stringValue(input.runId) || null,
|
||||
payload,
|
||||
);
|
||||
return { eventId, kind, decision, payload };
|
||||
}
|
||||
|
||||
function sanitizeSkillPolicySnapshot(snapshot: Record<string, unknown>): Record<string, unknown> {
|
||||
const allowedKeys = new Set([
|
||||
"skill_enabled",
|
||||
"matched_skill_id",
|
||||
"server_enabled",
|
||||
"global_allow_tools",
|
||||
"global_deny_tools",
|
||||
"server_allow_tools",
|
||||
"server_deny_tools",
|
||||
]);
|
||||
return Object.fromEntries(
|
||||
Object.entries(snapshot).filter(([key]) => allowedKeys.has(key)),
|
||||
);
|
||||
}
|
||||
|
||||
function skillCallAuditMessage(
|
||||
decision: DigitalEmployeeSkillCallDecision,
|
||||
toolName: string | null,
|
||||
skillId: string | null,
|
||||
): string {
|
||||
const target = toolName || skillId || "unknown";
|
||||
if (decision === "rejected") return `技能调用已拒绝:${target}`;
|
||||
if (decision === "allowed") return `技能调用已允许:${target}`;
|
||||
return `技能调用已观测:${target}`;
|
||||
}
|
||||
|
||||
function routeDecisionFromEventPayload(payload: unknown): DigitalEmployeeRouteDecisionRecord | null {
|
||||
const record = objectRecord(payload);
|
||||
const decision = objectRecord(record?.routeDecision ?? record?.route_decision ?? payload);
|
||||
|
||||
Reference in New Issue
Block a user