吸收数字员工技能调用审计
This commit is contained in:
@@ -12,6 +12,16 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
const mockDigitalEmployee = vi.hoisted(() => ({
|
||||
recordRuntimeEvent: vi.fn(),
|
||||
recordSkillCallAudit: vi.fn(),
|
||||
evaluateSkillCall: vi.fn(() => ({
|
||||
decision: "observed",
|
||||
reason_codes: ["skill_not_matched"],
|
||||
source: "qimingclaw-acp",
|
||||
server_id: null,
|
||||
tool_name: "Write",
|
||||
skill_id: null,
|
||||
policy_snapshot: {},
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("electron-log", () => ({
|
||||
@@ -29,6 +39,17 @@ vi.mock("../services/computerServer", () => ({
|
||||
|
||||
vi.mock("../services/digitalEmployee/stateService", () => ({
|
||||
recordDigitalEmployeeRuntimeEvent: mockDigitalEmployee.recordRuntimeEvent,
|
||||
recordDigitalEmployeeSkillCallAudit: mockDigitalEmployee.recordSkillCallAudit,
|
||||
}));
|
||||
|
||||
vi.mock("../services/digitalEmployee/skillExecutionPolicy", () => ({
|
||||
evaluateDigitalEmployeeSkillCall: mockDigitalEmployee.evaluateSkillCall,
|
||||
}));
|
||||
|
||||
vi.mock("../services/packages/mcp", () => ({
|
||||
mcpProxyManager: {
|
||||
getConfig: () => ({ mcpServers: {} }),
|
||||
},
|
||||
}));
|
||||
|
||||
// Use dynamic import to get the actual agentService (an EventEmitter) after mocking
|
||||
@@ -69,6 +90,8 @@ describe("eventForwarders", () => {
|
||||
unregisterEventForwarders();
|
||||
vi.clearAllMocks();
|
||||
mockDigitalEmployee.recordRuntimeEvent.mockClear();
|
||||
mockDigitalEmployee.recordSkillCallAudit.mockClear();
|
||||
mockDigitalEmployee.evaluateSkillCall.mockClear();
|
||||
});
|
||||
|
||||
it("registerEventForwarders registers listeners on agentService", () => {
|
||||
@@ -202,6 +225,13 @@ describe("eventForwarders", () => {
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(mockDigitalEmployee.recordSkillCallAudit).toHaveBeenCalledWith(expect.objectContaining({
|
||||
callId: "tool-1",
|
||||
decision: "observed",
|
||||
reasonCodes: ["skill_not_matched"],
|
||||
sessionId: "session-1",
|
||||
}));
|
||||
expect(JSON.stringify(mockDigitalEmployee.recordSkillCallAudit.mock.calls[0][0])).not.toContain("/tmp/report.md");
|
||||
});
|
||||
|
||||
it("projects permission and file events into approval and artifact hints", () => {
|
||||
|
||||
@@ -2,7 +2,12 @@ import { agentService } from "../services/engines/unifiedAgent";
|
||||
import type { UnifiedSessionMessage } from "../services/engines/unifiedAgent";
|
||||
import { pushSseEvent } from "../services/computerServer";
|
||||
import { firstTokenTrace } from "../services/engines/perf/firstTokenTrace";
|
||||
import { recordDigitalEmployeeRuntimeEvent } from "../services/digitalEmployee/stateService";
|
||||
import {
|
||||
recordDigitalEmployeeRuntimeEvent,
|
||||
recordDigitalEmployeeSkillCallAudit,
|
||||
} from "../services/digitalEmployee/stateService";
|
||||
import { evaluateDigitalEmployeeSkillCall } from "../services/digitalEmployee/skillExecutionPolicy";
|
||||
import { mcpProxyManager } from "../services/packages/mcp";
|
||||
import type { HandlerContext } from "@shared/types/ipc";
|
||||
import log from "electron-log";
|
||||
|
||||
@@ -25,6 +30,42 @@ function readEventField(data: unknown, keys: string[]): string | undefined {
|
||||
function recordForwardedEvent(eventType: string, data: unknown): void {
|
||||
const normalized = normalizeForwardedEvent(eventType, data);
|
||||
recordDigitalEmployeeRuntimeEvent(normalized);
|
||||
recordSkillCallAuditForForwardedEvent(eventType, data, normalized);
|
||||
}
|
||||
|
||||
function recordSkillCallAuditForForwardedEvent(
|
||||
eventType: string,
|
||||
data: unknown,
|
||||
normalized: Parameters<typeof recordDigitalEmployeeRuntimeEvent>[0],
|
||||
): void {
|
||||
const subtype = readEventField(data, ["subType", "sessionUpdate", "type", "messageType"]);
|
||||
if (eventType !== "computer:progress" || (subtype !== "tool_call" && subtype !== "tool_call_update")) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const toolName = readEventField(data, ["toolTitle", "title", "name", "kind"]);
|
||||
const policy = evaluateDigitalEmployeeSkillCall({
|
||||
source: "qimingclaw-acp",
|
||||
toolName,
|
||||
callId: readEventField(data, ["toolCallId", "id"]) || `${subtype}:${Date.now()}`,
|
||||
sessionId: readEventField(data, ["sessionId", "session_id", "id"]),
|
||||
mcpConfig: mcpProxyManager.getConfig(),
|
||||
});
|
||||
recordDigitalEmployeeSkillCallAudit({
|
||||
callId: readEventField(data, ["toolCallId", "id"]) || `${subtype}:${Date.now()}`,
|
||||
source: policy.source,
|
||||
serverId: policy.server_id,
|
||||
toolName: policy.tool_name || toolName,
|
||||
skillId: policy.skill_id,
|
||||
decision: policy.decision,
|
||||
reasonCodes: policy.reason_codes,
|
||||
policySnapshot: policy.policy_snapshot,
|
||||
sessionId: normalized.session_id,
|
||||
status: readEventField(data, ["status"]) || normalized.status,
|
||||
});
|
||||
} catch (error) {
|
||||
log.warn("[EventForwarders] Failed to record skill call audit:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeForwardedEvent(
|
||||
|
||||
Reference in New Issue
Block a user