Files
qiming/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js
2026-06-12 10:52:35 +08:00

802 lines
51 KiB
JavaScript

#!/usr/bin/env node
const fs = require("fs");
const os = require("os");
const path = require("path");
const { spawnSync } = require("child_process");
const packageRoot = path.resolve(__dirname, "..");
const requiredDigitalTables = [
"digital_plans",
"digital_plan_steps",
"digital_schedules",
"digital_schedule_runs",
"digital_tasks",
"digital_runs",
"digital_events",
"digital_artifacts",
"digital_approvals",
"digital_memories",
"digital_sync_outbox",
"digital_sync_attempts",
];
const testFiles = [
"src/main/services/digitalEmployee/stateService.test.ts",
"src/main/services/digitalEmployee/artifactAccessService.test.ts",
"src/main/services/digitalEmployee/schedulerService.test.ts",
"src/main/services/digitalEmployee/desktopNotificationService.test.ts",
"src/main/ipc/digitalEmployeeHandlers.test.ts",
"src/main/ipc/eventForwarders.test.ts",
"src/main/services/engines/acp/acpEngine.test.ts",
"src/main/services/digitalEmployee/syncService.test.ts",
"src/main/services/digitalEmployee/skillCatalogService.test.ts",
"src/main/services/digitalEmployee/planTemplateService.test.ts",
];
const selfHealingDigitalSchemaSql = `
CREATE TABLE IF NOT EXISTS digital_plan_steps (
id TEXT PRIMARY KEY,
remote_id TEXT,
plan_id TEXT NOT NULL,
title TEXT NOT NULL,
status TEXT NOT NULL,
seq INTEGER NOT NULL,
depends_on TEXT NOT NULL DEFAULT '[]',
preferred_skill_ids TEXT NOT NULL DEFAULT '[]',
payload TEXT NOT NULL DEFAULT '{}',
sync_status TEXT NOT NULL DEFAULT 'pending',
last_synced_at TEXT,
sync_error TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(plan_id) REFERENCES digital_plans(id)
);
CREATE INDEX IF NOT EXISTS idx_digital_plan_steps_plan ON digital_plan_steps(plan_id, seq);
CREATE INDEX IF NOT EXISTS idx_digital_plan_steps_sync ON digital_plan_steps(sync_status);
CREATE TABLE IF NOT EXISTS digital_schedules (
id TEXT PRIMARY KEY,
remote_id TEXT,
plan_id TEXT,
name TEXT NOT NULL,
description TEXT,
cron_expr TEXT NOT NULL,
timezone TEXT NOT NULL DEFAULT 'system',
status TEXT NOT NULL DEFAULT 'enabled',
enabled INTEGER NOT NULL DEFAULT 1,
prompt TEXT,
command TEXT,
payload TEXT NOT NULL DEFAULT '{}',
next_run_at TEXT,
last_run_at TEXT,
catch_up_on_startup INTEGER NOT NULL DEFAULT 1,
max_catch_up_runs INTEGER NOT NULL DEFAULT 5,
max_run_history INTEGER NOT NULL DEFAULT 100,
failure_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT,
sync_status TEXT NOT NULL DEFAULT 'pending',
last_synced_at TEXT,
sync_error TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
deleted_at TEXT,
FOREIGN KEY(plan_id) REFERENCES digital_plans(id)
);
CREATE TABLE IF NOT EXISTS digital_schedule_runs (
id TEXT PRIMARY KEY,
remote_id TEXT,
schedule_id TEXT NOT NULL,
plan_id TEXT,
run_id TEXT,
due_at TEXT NOT NULL,
started_at TEXT,
finished_at TEXT,
status TEXT NOT NULL,
attempt_no INTEGER NOT NULL DEFAULT 1,
trigger_kind TEXT NOT NULL DEFAULT 'scheduled',
error TEXT,
payload TEXT NOT NULL DEFAULT '{}',
sync_status TEXT NOT NULL DEFAULT 'pending',
last_synced_at TEXT,
sync_error TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(schedule_id) REFERENCES digital_schedules(id),
FOREIGN KEY(plan_id) REFERENCES digital_plans(id),
FOREIGN KEY(run_id) REFERENCES digital_runs(id)
);
CREATE INDEX IF NOT EXISTS idx_digital_schedules_due ON digital_schedules(enabled, status, next_run_at);
CREATE INDEX IF NOT EXISTS idx_digital_schedules_plan ON digital_schedules(plan_id);
CREATE INDEX IF NOT EXISTS idx_digital_schedules_sync ON digital_schedules(sync_status);
CREATE INDEX IF NOT EXISTS idx_digital_schedule_runs_schedule ON digital_schedule_runs(schedule_id, due_at);
CREATE UNIQUE INDEX IF NOT EXISTS idx_digital_schedule_runs_attempt ON digital_schedule_runs(schedule_id, due_at, attempt_no);
CREATE INDEX IF NOT EXISTS idx_digital_schedule_runs_sync ON digital_schedule_runs(sync_status);
CREATE TABLE IF NOT EXISTS digital_memories (
id TEXT PRIMARY KEY,
remote_id TEXT,
key TEXT NOT NULL,
content TEXT NOT NULL,
category TEXT NOT NULL DEFAULT 'fact',
source TEXT NOT NULL DEFAULT 'qimingclaw',
source_path TEXT,
session_id TEXT,
score REAL,
status TEXT NOT NULL DEFAULT 'active',
payload TEXT NOT NULL DEFAULT '{}',
sync_status TEXT NOT NULL DEFAULT 'pending',
last_synced_at TEXT,
sync_error TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
deleted_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_digital_memories_key ON digital_memories(key, status, updated_at);
CREATE INDEX IF NOT EXISTS idx_digital_memories_category ON digital_memories(category, status, updated_at);
CREATE INDEX IF NOT EXISTS idx_digital_memories_sync ON digital_memories(sync_status);
`;
function runStep(title, command, args, options = {}) {
console.log(`\n[DigitalEmployeeCheck] ${title}`);
const result = spawnSync(command, args, {
cwd: packageRoot,
stdio: "inherit",
shell: process.platform === "win32",
...options,
});
if (result.status !== 0) {
process.exitCode = result.status || 1;
throw new Error(`${title} failed`);
}
}
function checkSgrobotDigitalAssets() {
console.log("\n[DigitalEmployeeCheck] Verify embedded digital assets");
const indexPath = path.join(packageRoot, "public", "sgrobot-digital", "index.html");
const html = fs.readFileSync(indexPath, "utf8");
const assetRefs = [...html.matchAll(/(?:src|href)="\.\/(assets\/[^"]+)"/g)]
.map((match) => match[1]);
if (assetRefs.length === 0) {
throw new Error("No embedded digital assets referenced by index.html");
}
for (const assetRef of assetRefs) {
const assetPath = path.join(packageRoot, "public", "sgrobot-digital", assetRef);
if (!fs.existsSync(assetPath)) {
throw new Error(`Missing embedded digital asset: ${assetRef}`);
}
}
console.log(`[DigitalEmployeeCheck] ${assetRefs.length} asset references OK`);
}
function checkDigitalDiagnosticAuditProjection() {
console.log("\n[DigitalEmployeeCheck] Verify diagnostic/audit projection hooks");
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
const typesPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "types", "api.ts");
const pagePath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "CommandDeck.tsx");
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
const adapter = fs.readFileSync(adapterPath, "utf8");
const types = fs.readFileSync(typesPath, "utf8");
const page = fs.readFileSync(pagePath, "utf8");
const syncService = fs.readFileSync(syncServicePath, "utf8");
const requiredSnippets = [
[syncService, "sandboxAuditBusinessView", "sandbox audit sync business view"],
[syncService, "tokenCostBusinessView", "token/cost sync business view"],
[syncService, "toolCallAuditBusinessView", "tool call sync business view"],
[syncService, "sandbox_audit", "sandbox audit business category"],
[syncService, "token_cost", "token cost business category"],
[syncService, "tool_call_audit", "tool call business category"],
[syncService, "estimated_cost_usd", "safe estimated cost field"],
[syncService, "total_tokens", "safe total token field"],
[syncService, "input_length", "sensitive input length redaction"],
[adapter, "'audits'", "audit business view kind"],
[adapter, "buildDiagnosticSummary", "diagnostic summary builder"],
[adapter, "buildCostSummary", "cost summary builder"],
[adapter, "costSampleFromPayload", "runtime token/cost aggregation"],
[adapter, "diagnosticAuditPayload", "diagnostic audit payload redaction"],
[adapter, "sandbox_audit", "sandbox audit row category"],
[adapter, "token_cost", "token cost row category"],
[adapter, "tool_call_audit", "tool call row category"],
[adapter, "estimated_cost_usd", "audit estimated cost field"],
[adapter, "total_tokens", "audit total token field"],
[adapter, "filterAuditRows", "audit filters"],
[adapter, "diagnosticNotifications", "diagnostic notifications"],
[adapter, "auditNotifications", "audit notifications"],
[types, "DigitalEmployeeDiagnosticSummary", "diagnostic summary type"],
[types, "diagnostic_summary?: DigitalEmployeeDiagnosticSummary", "workday diagnostic field"],
[types, "estimated_cost_usd", "diagnostic summary estimated cost type"],
[types, "total_tokens", "diagnostic summary token type"],
[page, "de-diagnostic-summary-strip", "dashboard diagnostic strip"],
[page, "Token", "dashboard Token metric"],
[page, "估算成本", "dashboard estimated cost metric"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing diagnostic/audit projection hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] diagnostic/audit projection hooks OK");
}
function checkDigitalNotificationPreferences() {
console.log("\n[DigitalEmployeeCheck] Verify notification preference hooks");
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
const apiPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "api.ts");
const typesPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "types", "api.ts");
const shellPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "DigitalEmployeeShell.tsx");
const stateServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "stateService.ts");
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
const desktopNotificationServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "desktopNotificationService.ts");
const ipcHandlersPath = path.join(packageRoot, "src", "main", "ipc", "digitalEmployeeHandlers.ts");
const preloadPath = path.join(packageRoot, "src", "preload", "webviewPerfBridge.ts");
const adapter = fs.readFileSync(adapterPath, "utf8");
const api = fs.readFileSync(apiPath, "utf8");
const types = fs.readFileSync(typesPath, "utf8");
const shell = fs.readFileSync(shellPath, "utf8");
const stateService = fs.readFileSync(stateServicePath, "utf8");
const syncService = fs.readFileSync(syncServicePath, "utf8");
const desktopNotificationService = fs.readFileSync(desktopNotificationServicePath, "utf8");
const ipcHandlers = fs.readFileSync(ipcHandlersPath, "utf8");
const preload = fs.readFileSync(preloadPath, "utf8");
const requiredSnippets = [
[adapter, "/api/notifications/preferences", "adapter notification preference endpoint"],
[adapter, "route-decisions|notifications|audits", "management notification business route"],
[adapter, "notificationRows", "management notification rows"],
[adapter, "filterNotificationRows", "management notification filters"],
[adapter, "muted_by_preferences", "management notification muted marker"],
[adapter, "recordNotificationTaskInput", "notification reply task input bridge"],
[adapter, "notification_reply", "notification reply task input source"],
[adapter, "task_input_recorded", "notification reply task input result"],
[adapter, "provide_task_input", "notification reply governance command"],
[adapter, "handleNotificationPreferencesPatch", "adapter notification preference patch handler"],
[adapter, "/api/notifications/updates/pull", "adapter notification updates pull endpoint"],
[adapter, "pullBridgeNotificationUpdates", "adapter notification updates bridge pull helper"],
[adapter, "submitNotificationAction", "adapter notification action bridge declaration"],
[adapter, "submitBridgeNotificationAction", "adapter notification action bridge submit helper"],
[adapter, "showDesktopNotification", "adapter desktop notification bridge declaration"],
[adapter, "/api/notifications/desktop", "adapter desktop notification endpoint"],
[adapter, "/api/notifications/desktop/status", "adapter desktop notification status endpoint"],
[adapter, "/api/notifications/desktop/settings/open", "adapter desktop notification settings endpoint"],
[adapter, "showBridgeDesktopNotification", "adapter desktop notification helper"],
[adapter, "readBridgeDesktopNotificationStatus", "adapter desktop notification status helper"],
[adapter, "openBridgeDesktopNotificationSettings", "adapter desktop notification settings helper"],
[adapter, "notificationMatchesPreferences", "adapter notification preference filter"],
[adapter, "desktop_enabled", "adapter notification desktop preference"],
[adapter, "update_notification_preferences", "adapter notification preference save action"],
[api, "getNotificationPreferences", "notification preferences API getter"],
[api, "patchNotificationPreferences", "notification preferences API patcher"],
[api, "pullNotificationUpdates", "embedded notification updates API"],
[api, "showDesktopNotification", "embedded desktop notification API"],
[api, "getDesktopNotificationStatus", "embedded desktop notification status API"],
[api, "openDesktopNotificationSettings", "embedded desktop notification settings API"],
[types, "NotificationPreferences", "notification preferences type"],
[types, "DesktopNotificationStatus", "desktop notification status type"],
[types, "DesktopNotificationSettingsOpenResult", "desktop notification settings result type"],
[types, "desktop_enabled", "notification desktop preference type field"],
[types, "NotificationUpdatesPullResult", "notification updates pull result type"],
[shell, "de-notification-preferences", "notification preferences UI"],
[shell, "桌面通知状态", "desktop notification status UI"],
[shell, "测试桌面通知", "desktop notification test UI"],
[shell, "打开系统设置", "desktop notification settings UI"],
[shell, "自动桌面通知发送失败", "automatic desktop notification failure feedback"],
[shell, "handleNotificationSync", "notification manual sync UI action"],
[shell, "showUnreadDesktopNotifications", "notification desktop trigger helper"],
[shell, "qimingclaw:digital-desktop-notified", "notification desktop dedupe key"],
[shell, "桌面通知", "notification desktop preference UI"],
[shell, "NOTIFICATION_KIND_OPTIONS", "notification kind mute UI options"],
[stateService, "update_notification_preferences", "notification preference runtime event"],
[stateService, "desktop_enabled", "notification desktop preference state field"],
[desktopNotificationService, "showDigitalEmployeeDesktopNotification", "desktop notification service"],
[desktopNotificationService, "getDigitalEmployeeDesktopNotificationStatus", "desktop notification status service"],
[desktopNotificationService, "openDigitalEmployeeDesktopNotificationSettings", "desktop notification settings service"],
[desktopNotificationService, "Notification", "Electron Notification usage"],
[desktopNotificationService, "shell.openExternal", "Electron notification settings opener"],
[desktopNotificationService, "notification_unavailable", "desktop notification unavailable fallback"],
[stateService, "notification_updates_pulled", "notification updates pulled runtime event"],
[stateService, "notification_action_submitted", "notification action submitted runtime event"],
[syncService, "pullDigitalEmployeeNotificationUpdates", "remote notification updates pull service"],
[syncService, "submitDigitalEmployeeNotificationAction", "remote notification action submit service"],
[syncService, "/api/digital-employee/notifications/updates", "remote notification updates default path"],
[syncService, "/api/digital-employee/notifications/actions", "remote notification actions default path"],
[syncService, "notificationUpdatesEndpoint", "notification updates endpoint config"],
[syncService, "notificationActionsEndpoint", "notification actions endpoint config"],
[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, "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"],
[ipcHandlers, "digitalEmployee:pullNotificationUpdates", "IPC notification updates pull handler"],
[ipcHandlers, "digitalEmployee:submitNotificationAction", "IPC notification action submit handler"],
[ipcHandlers, "digitalEmployee:showDesktopNotification", "IPC desktop notification handler"],
[ipcHandlers, "digitalEmployee:getDesktopNotificationStatus", "IPC desktop notification status handler"],
[ipcHandlers, "digitalEmployee:openDesktopNotificationSettings", "IPC desktop notification settings handler"],
[preload, "pullNotificationUpdates", "preload notification updates bridge"],
[preload, "submitNotificationAction", "preload notification action bridge"],
[preload, "showDesktopNotification", "preload desktop notification bridge"],
[preload, "getDesktopNotificationStatus", "preload desktop notification status bridge"],
[preload, "openDesktopNotificationSettings", "preload desktop notification settings bridge"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing notification preference hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] notification preference hooks OK");
}
function checkDigitalApprovalHistorySubscriptions() {
console.log("\n[DigitalEmployeeCheck] Verify approval history/subscription hooks");
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
const apiPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "api.ts");
const typesPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "types", "api.ts");
const stateServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "stateService.ts");
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
const adapter = fs.readFileSync(adapterPath, "utf8");
const api = fs.readFileSync(apiPath, "utf8");
const types = fs.readFileSync(typesPath, "utf8");
const stateService = fs.readFileSync(stateServicePath, "utf8");
const syncService = fs.readFileSync(syncServicePath, "utf8");
const requiredSnippets = [
[adapter, "/api/approval/subscriptions", "adapter approval subscription endpoint"],
[adapter, "/api/digital-employee/approval-history", "adapter approval history endpoint"],
[adapter, "approvalHistoryRows", "management approval history rows"],
[adapter, "filterApprovalHistoryRows", "management approval history filters"],
[adapter, "approvalSubscriptionPreferences", "adapter approval subscription preferences"],
[adapter, "update_approval_subscriptions", "adapter approval subscription save action"],
[adapter, "approval-history", "approval history business route"],
[api, "getApprovalSubscriptionPreferences", "approval subscription API getter"],
[api, "patchApprovalSubscriptionPreferences", "approval subscription API patcher"],
[api, "getApprovalHistory", "approval history API getter"],
[types, "ApprovalSubscriptionPreferences", "approval subscription preferences type"],
[stateService, "approvalSubscriptionPreferences", "approval subscription UI state"],
[stateService, "update_approval_subscriptions", "approval subscription runtime event"],
[syncService, "approvalDecisionBusinessView", "approval decision sync business view"],
[syncService, "approvalSubscriptionBusinessView", "approval subscription sync business view"],
[syncService, "digital_workday_update_approval_subscriptions", "approval subscription sync event kind"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing approval history/subscription hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] approval history/subscription hooks OK");
}
function checkDigitalApprovalWorkflowSync() {
console.log("\n[DigitalEmployeeCheck] Verify approval workflow/sync hooks");
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
const apiPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "api.ts");
const typesPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "types", "api.ts");
const commandDeckPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "CommandDeck.tsx");
const stateServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "stateService.ts");
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
const ipcHandlersPath = path.join(packageRoot, "src", "main", "ipc", "digitalEmployeeHandlers.ts");
const preloadPath = path.join(packageRoot, "src", "preload", "webviewPerfBridge.ts");
const adapter = fs.readFileSync(adapterPath, "utf8");
const api = fs.readFileSync(apiPath, "utf8");
const types = fs.readFileSync(typesPath, "utf8");
const commandDeck = fs.readFileSync(commandDeckPath, "utf8");
const stateService = fs.readFileSync(stateServicePath, "utf8");
const syncService = fs.readFileSync(syncServicePath, "utf8");
const ipcHandlers = fs.readFileSync(ipcHandlersPath, "utf8");
const preload = fs.readFileSync(preloadPath, "utf8");
const requiredSnippets = [
[stateService, "DigitalEmployeeApprovalWorkflow", "approval workflow type"],
[stateService, "normalizeDigitalEmployeeApprovalWorkflow", "approval workflow normalizer"],
[stateService, "applyApprovalWorkflowDecision", "approval workflow decision advancer"],
[stateService, "applyApprovalWorkflowAction", "approval workflow action updater"],
[stateService, "upsertDigitalEmployeeApprovalFromRemote", "remote approval update upsert helper"],
[stateService, "approval_conflict_detected", "approval conflict runtime event"],
[stateService, "terminal_status_mismatch", "approval terminal conflict reason"],
[stateService, "decision_history", "approval workflow decision history field"],
[syncService, "pullDigitalEmployeeApprovalUpdates", "remote approval updates pull service"],
[syncService, "conflicted", "approval pull conflict counter"],
[syncService, "submitDigitalEmployeeApprovalDecision", "approval decision submit service"],
[syncService, "/api/digital-employee/approvals/updates", "remote approval updates default path"],
[syncService, "/api/digital-employee/approvals/decisions", "remote approval decisions default path"],
[syncService, "approvalUpdatesEndpoint", "approval updates endpoint config"],
[syncService, "approvalDecisionsEndpoint", "approval decisions endpoint config"],
[syncService, "approvalSyncBusinessView", "approval sync business view"],
[syncService, "workflow_id", "approval workflow sync business field"],
[ipcHandlers, "digitalEmployee:pullApprovalUpdates", "IPC approval updates pull handler"],
[ipcHandlers, "digitalEmployee:submitApprovalDecision", "IPC approval decision submit handler"],
[preload, "pullApprovalUpdates", "preload approval updates bridge"],
[preload, "submitApprovalDecision", "preload approval decision bridge"],
[adapter, "pullApprovalUpdates", "adapter approval updates bridge declaration"],
[adapter, "submitApprovalDecision", "adapter approval decision bridge declaration"],
[adapter, "/api/approval/updates/pull", "adapter approval updates pull endpoint"],
[adapter, "approvalWorkflowSummary", "adapter approval workflow summary"],
[adapter, "approvalConflictSummary", "adapter approval conflict summary"],
[adapter, "approval_conflict_active", "approval conflict business field"],
[api, "pullApprovalUpdates", "embedded approval updates API"],
[types, "conflict?:", "approval decision conflict type field"],
[types, "current_step_label", "approval decision current step type field"],
[types, "approval_step_count", "approval decision step count type field"],
[commandDeck, "pull_approval_updates", "home approval updates pull action"],
[commandDeck, "审批更新", "home approval updates pull button"],
[commandDeck, "跨端冲突", "home approval conflict badge"],
[commandDeck, "冲突保护", "home approval conflict protection copy"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing approval workflow/sync hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] approval workflow/sync hooks OK");
}
function checkDigitalPlanStepGraphDispatchPolicy() {
console.log("\n[DigitalEmployeeCheck] Verify PlanStep graph/dispatch policy hooks");
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
const apiPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "api.ts");
const typesPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "types", "api.ts");
const commandDeckPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "CommandDeck.tsx");
const cssPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "index.css");
const stateServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "stateService.ts");
const schedulerServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "schedulerService.ts");
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
const ipcHandlersPath = path.join(packageRoot, "src", "main", "ipc", "digitalEmployeeHandlers.ts");
const preloadPath = path.join(packageRoot, "src", "preload", "webviewPerfBridge.ts");
const adapter = fs.readFileSync(adapterPath, "utf8");
const api = fs.readFileSync(apiPath, "utf8");
const types = fs.readFileSync(typesPath, "utf8");
const commandDeck = fs.readFileSync(commandDeckPath, "utf8");
const css = fs.readFileSync(cssPath, "utf8");
const stateService = fs.readFileSync(stateServicePath, "utf8");
const schedulerService = fs.readFileSync(schedulerServicePath, "utf8");
const syncService = fs.readFileSync(syncServicePath, "utf8");
const ipcHandlers = fs.readFileSync(ipcHandlersPath, "utf8");
const preload = fs.readFileSync(preloadPath, "utf8");
const requiredSnippets = [
[adapter, "/api/digital-employee/plan-steps", "adapter plan step endpoint"],
[adapter, "/api/digital-employee/plan-step-graph", "adapter plan step graph endpoint"],
[adapter, "dispatch-ready-steps", "adapter plan-scoped ready step dispatch endpoint"],
[adapter, "dispatchPlanReadySteps", "adapter plan-scoped ready step dispatcher"],
[adapter, "/api/plan-step/dispatch-policy", "adapter plan step dispatch policy endpoint"],
[adapter, "/api/plan-step/dispatch-policy/pull", "adapter plan step dispatch policy pull endpoint"],
[adapter, "pullPlanStepDispatchPolicy", "adapter plan step dispatch policy pull bridge"],
[adapter, "planStepRows", "management plan step rows"],
[adapter, "planStepGraphRows", "management plan step graph rows"],
[adapter, "filterPlanStepRows", "management plan step filters"],
[adapter, "activePlanStepDispatchLease", "adapter active plan step lease detector"],
[adapter, "lease_active", "adapter lease-active readiness skip reason"],
[adapter, "lease_state", "adapter plan step lease state projection"],
[adapter, "blocked_dependency_details", "plan step blocked dependency details"],
[adapter, "planStepDependencyDetails", "plan step blocked dependency detail mapper"],
[adapter, "update_plan_step_dispatch_policy", "adapter plan step dispatch policy save action"],
[api, "getPlanStepDispatchPolicy", "plan step dispatch policy API getter"],
[api, "patchPlanStepDispatchPolicy", "plan step dispatch policy API patcher"],
[api, "pullPlanStepDispatchPolicy", "plan step dispatch policy API puller"],
[api, "getPlanSteps", "plan steps API getter"],
[api, "getPlanStepGraph", "plan step graph API getter"],
[api, "dispatchPlanReadySteps", "plan-scoped ready step dispatch API"],
[api, "BusinessViewResponse<PlanStepBusinessRow>", "typed plan step business view API"],
[types, "trigger_source", "ready step dispatch trigger source type"],
[types, "orchestration_id", "ready step dispatch orchestration id type"],
[types, "PlanStepDispatchPolicy", "plan step dispatch policy type"],
[types, "PlanStepDispatchPolicyPullResult", "plan step dispatch policy pull result type"],
[types, "last_pull_error", "plan step dispatch policy pull error field"],
[types, "PlanStepLeaseSummary", "plan step lease summary type"],
[types, "remote_lease_id", "plan step remote lease type field"],
[types, "PlanStepDependencyDetail", "plan step blocked dependency detail type"],
[types, "lease_active", "plan step lease active type field"],
[types, "PlanStepBusinessRow", "plan step business row type"],
[types, "BusinessViewResponse", "business view response type"],
[commandDeck, "getPlanStepGraph", "home blocked plan step graph fetch"],
[commandDeck, "planStepOrchestrationSummary", "home plan step orchestration summary"],
[commandDeck, "leasedStepCount", "home plan step lease count"],
[commandDeck, "controlPlanReadySteps", "home plan-scoped ready step control"],
[commandDeck, "pull_plan_step_dispatch_policy", "home plan step dispatch policy pull action"],
[commandDeck, "controlBlockedPlanStep", "home blocked plan step control handler"],
[commandDeck, "de-blocked-step-control-strip", "home blocked plan step control strip"],
[commandDeck, "provide_task_input", "home blocked plan step input command"],
[css, "de-blocked-input-field", "home blocked plan step input styles"],
[stateService, "planStepDispatchPolicy", "plan step dispatch policy UI state"],
[stateService, "update_plan_step_dispatch_policy", "plan step dispatch policy runtime event"],
[stateService, "pull_plan_step_dispatch_policy", "plan step dispatch policy pull runtime event"],
[stateService, "last_pulled_at", "plan step dispatch policy last pulled field"],
[stateService, "last_pull_error", "plan step dispatch policy last pull error field"],
[stateService, "planIdFilter", "plan-scoped ready step candidate filter"],
[stateService, "dispatchLease", "plan step payload lease state"],
[stateService, "activeDigitalEmployeePlanStepLease", "plan step active lease helper"],
[stateService, "acquireDigitalEmployeePlanStepLease", "plan step lease acquire helper"],
[stateService, "releaseDigitalEmployeePlanStepLease", "plan step lease release helper"],
[stateService, "recordDigitalEmployeePlanStepLeaseSkipped", "plan step remote lease rejected helper"],
[stateService, "local_fallback", "plan step local fallback lease source"],
[stateService, "remote_release_error", "plan step remote release error state"],
[stateService, "plan_step_lease_acquired", "plan step lease acquired event"],
[stateService, "plan_step_lease_released", "plan step lease released event"],
[stateService, "plan_step_lease_skipped", "plan step lease skipped event"],
[schedulerService, "bypassAutoDispatchSwitch", "manual plan dispatch auto switch bypass"],
[schedulerService, "runDigitalEmployeePlanReadyStepsNow", "manual plan-scoped ready step dispatcher"],
[schedulerService, "acquireDigitalEmployeePlanStepLease", "scheduler acquire plan step lease"],
[schedulerService, "releaseDigitalEmployeePlanStepLease", "scheduler release plan step lease"],
[schedulerService, "acquireDigitalEmployeePlanStepRemoteLease", "scheduler remote plan step lease acquire"],
[schedulerService, "releaseDigitalEmployeePlanStepRemoteLease", "scheduler remote plan step lease release"],
[schedulerService, "remote_lease_rejected", "scheduler remote lease rejected skip reason"],
[schedulerService, "local_fallback", "scheduler remote lease fallback source"],
[schedulerService, "triggerSource", "ready step dispatch trigger source"],
[schedulerService, "readDigitalEmployeePlanStepDispatchPolicy", "scheduler plan step dispatch policy reader"],
[schedulerService, "pullDigitalEmployeePlanStepDispatchPolicy", "scheduler remote plan step dispatch policy pull"],
[schedulerService, "max_per_sweep", "scheduler max per sweep policy"],
[ipcHandlers, "digitalEmployee:dispatchPlanReadySteps", "IPC plan-scoped ready step dispatch handler"],
[ipcHandlers, "digitalEmployee:pullPlanStepDispatchPolicy", "IPC plan step dispatch policy pull handler"],
[preload, "dispatchPlanReadySteps", "preload plan-scoped ready step bridge"],
[preload, "pullPlanStepDispatchPolicy", "preload plan step dispatch policy pull bridge"],
[syncService, "planStepBusinessView", "plan step sync business view"],
[syncService, "pullDigitalEmployeePlanStepDispatchPolicy", "remote plan step dispatch policy pull service"],
[syncService, "planStepDispatchPolicyEndpoint", "remote plan step dispatch policy endpoint config"],
[syncService, "/api/digital-employee/policies/plan-step-dispatch", "remote plan step dispatch policy default path"],
[syncService, "acquireDigitalEmployeePlanStepRemoteLease", "remote plan step dispatch lease acquire service"],
[syncService, "releaseDigitalEmployeePlanStepRemoteLease", "remote plan step dispatch lease release service"],
[syncService, "planStepDispatchLeaseEndpoint", "remote plan step dispatch lease endpoint config"],
[syncService, "/api/digital-employee/leases/plan-step-dispatch", "remote plan step dispatch lease default path"],
[syncService, "lease_active", "plan step sync lease active field"],
[syncService, "remote_lease_error", "plan step sync remote lease error field"],
[syncService, "remote_release_error", "plan step sync remote release error field"],
[syncService, "planStepLeaseBusinessView", "plan step lease sync event view"],
[syncService, "plan_step_lease_acquired", "plan step lease acquired sync event kind"],
[syncService, "trigger_source", "plan step dispatch sync trigger source"],
[syncService, "orchestration_id", "plan step dispatch sync orchestration id"],
[syncService, "planStepDispatchPolicyBusinessView", "plan step dispatch policy sync view"],
[syncService, "digital_workday_update_plan_step_dispatch_policy", "plan step dispatch policy sync event kind"],
[syncService, "digital_workday_pull_plan_step_dispatch_policy", "plan step dispatch policy pull sync event kind"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing PlanStep graph/dispatch policy hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] PlanStep graph/dispatch policy hooks OK");
}
function checkDigitalSkillRemotePolicy() {
console.log("\n[DigitalEmployeeCheck] Verify skill remote policy hooks");
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
const apiPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "api.ts");
const skillLibraryPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "SkillLibrary.tsx");
const skillExecutionPolicyPath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "skillExecutionPolicy.ts");
const skillCatalogPath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "skillCatalogService.ts");
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
const ipcHandlersPath = path.join(packageRoot, "src", "main", "ipc", "digitalEmployeeHandlers.ts");
const preloadPath = path.join(packageRoot, "src", "preload", "webviewPerfBridge.ts");
const adapter = fs.readFileSync(adapterPath, "utf8");
const api = fs.readFileSync(apiPath, "utf8");
const skillLibrary = fs.readFileSync(skillLibraryPath, "utf8");
const skillExecutionPolicy = fs.readFileSync(skillExecutionPolicyPath, "utf8");
const skillCatalog = fs.readFileSync(skillCatalogPath, "utf8");
const syncService = fs.readFileSync(syncServicePath, "utf8");
const ipcHandlers = fs.readFileSync(ipcHandlersPath, "utf8");
const preload = fs.readFileSync(preloadPath, "utf8");
const requiredSnippets = [
[skillExecutionPolicy, "local_skills", "local skill policy map"],
[skillExecutionPolicy, "remote_skills", "remote skill policy map"],
[skillExecutionPolicy, "last_pull_error", "skill policy pull error metadata"],
[skillExecutionPolicy, "effectiveSkillPolicyFor", "effective skill policy merge helper"],
[skillExecutionPolicy, "matched_remote_policy", "skill call remote policy audit snapshot"],
[skillCatalog, "remote_policy", "skill catalog remote policy projection"],
[skillCatalog, "local_policy", "skill catalog local policy projection"],
[skillCatalog, "policy_source", "skill catalog policy source projection"],
[syncService, "pullDigitalEmployeeSkillPolicies", "remote skill policy pull service"],
[syncService, "skillPolicyEndpoint", "remote skill policy endpoint config"],
[syncService, "/api/digital-employee/policies/skills", "remote skill policy default path"],
[syncService, "readRemoteSkillPolicyPayload", "remote skill policy response parser"],
[syncService, "recordSkillPolicyPullFailure", "remote skill policy pull failure recorder"],
[ipcHandlers, "digitalEmployee:pullSkillPolicies", "IPC skill policy pull handler"],
[preload, "pullSkillPolicies", "preload skill policy pull bridge"],
[adapter, "pullSkillPolicies", "adapter skill policy pull bridge"],
[adapter, "/api/skill/policies/pull", "adapter skill policy pull endpoint"],
[api, "pullSkillPolicies", "embedded skill policy pull API"],
[skillLibrary, "拉取策略", "skill library pull policy action"],
[skillLibrary, "远端策略", "skill library remote policy marker"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing skill remote policy hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] skill remote policy hooks OK");
}
function checkDigitalRiskApprovalPolicy() {
console.log("\n[DigitalEmployeeCheck] Verify risk approval policy hooks");
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
const apiPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "api.ts");
const commandDeckPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "CommandDeck.tsx");
const opsSettingsPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "OpsSettings.tsx");
const stateServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "stateService.ts");
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
const ipcHandlersPath = path.join(packageRoot, "src", "main", "ipc", "digitalEmployeeHandlers.ts");
const preloadPath = path.join(packageRoot, "src", "preload", "webviewPerfBridge.ts");
const adapter = fs.readFileSync(adapterPath, "utf8");
const api = fs.readFileSync(apiPath, "utf8");
const commandDeck = fs.readFileSync(commandDeckPath, "utf8");
const opsSettings = fs.readFileSync(opsSettingsPath, "utf8");
const stateService = fs.readFileSync(stateServicePath, "utf8");
const syncService = fs.readFileSync(syncServicePath, "utf8");
const ipcHandlers = fs.readFileSync(ipcHandlersPath, "utf8");
const preload = fs.readFileSync(preloadPath, "utf8");
const requiredSnippets = [
[stateService, "riskApprovalPolicy", "risk approval policy UI state"],
[stateService, "normalizeRiskApprovalPolicy", "risk approval policy normalizer"],
[stateService, "evaluateDigitalEmployeeRiskPolicy", "risk approval policy evaluator"],
[stateService, "risk_approval_required", "risk approval-required event kind"],
[stateService, "risk_policy_rejected", "risk policy rejected event kind"],
[stateService, "update_risk_approval_policy", "risk approval policy update UI event"],
[stateService, "pull_risk_approval_policy", "risk approval policy pull UI event"],
[syncService, "pullDigitalEmployeeRiskApprovalPolicy", "remote risk approval policy pull service"],
[syncService, "riskApprovalPolicyEndpoint", "remote risk approval policy endpoint config"],
[syncService, "/api/digital-employee/policies/risk-approval", "remote risk approval policy default path"],
[syncService, "risk_approval_required", "risk approval-required sync event kind"],
[syncService, "risk_policy_rejected", "risk policy rejected sync event kind"],
[syncService, "riskPolicyBusinessView", "risk policy sync business view"],
[ipcHandlers, "digitalEmployee:pullRiskApprovalPolicy", "IPC risk approval policy pull handler"],
[ipcHandlers, "digitalEmployee:evaluateRiskPolicy", "IPC risk policy evaluation handler"],
[preload, "pullRiskApprovalPolicy", "preload risk approval policy pull bridge"],
[preload, "evaluateRiskPolicy", "preload risk policy evaluation bridge"],
[adapter, "pullRiskApprovalPolicy", "adapter risk approval policy pull bridge"],
[adapter, "evaluateRiskPolicy", "adapter risk policy evaluation bridge"],
[adapter, "/api/risk-approval/policy", "adapter risk approval policy local endpoint"],
[adapter, "handleRiskApprovalPolicyPatch", "adapter risk approval policy patch handler"],
[adapter, "/api/risk-approval/policy/pull", "adapter risk approval policy pull endpoint"],
[adapter, "riskPolicyBlockedResponse", "adapter risk policy blocked response"],
[adapter, "governanceCommandRiskLevel", "adapter governance risk level mapper"],
[adapter, "managed_service.restart", "managed service restart risk action"],
[adapter, "artifact.access", "artifact access risk action"],
[adapter, "plan_step.dispatch_ready_steps", "ready PlanStep dispatch risk action"],
[api, "getRiskApprovalPolicy", "embedded risk approval policy getter"],
[api, "patchRiskApprovalPolicy", "embedded risk approval policy patcher"],
[api, "pullRiskApprovalPolicy", "embedded risk approval policy pull API"],
[opsSettings, "riskPolicy", "settings risk policy state"],
[opsSettings, "updateRiskPolicy", "settings risk policy update handler"],
[opsSettings, "风险审批策略", "settings risk approval policy panel"],
[commandDeck, "pull_risk_approval_policy", "home risk approval policy pull action"],
[commandDeck, "风险策略", "home risk approval policy pull button"],
];
const missing = requiredSnippets
.filter(([content, snippet]) => !content.includes(snippet))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing risk approval policy hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] risk approval policy hooks OK");
}
function checkDigitalRouteDecisionPolicy() {
const adapterPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "qimingclawAdapter.ts");
const apiPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "lib", "api.ts");
const commandDeckPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "pages", "digital", "CommandDeck.tsx");
const typesPath = path.join(packageRoot, "embedded", "sgrobot-digital", "src", "types", "api.ts");
const stateServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "stateService.ts");
const syncServicePath = path.join(packageRoot, "src", "main", "services", "digitalEmployee", "syncService.ts");
const ipcHandlersPath = path.join(packageRoot, "src", "main", "ipc", "digitalEmployeeHandlers.ts");
const preloadPath = path.join(packageRoot, "src", "preload", "webviewPerfBridge.ts");
const adapter = fs.readFileSync(adapterPath, "utf8");
const api = fs.readFileSync(apiPath, "utf8");
const commandDeck = fs.readFileSync(commandDeckPath, "utf8");
const types = fs.readFileSync(typesPath, "utf8");
const stateService = fs.readFileSync(stateServicePath, "utf8");
const syncService = fs.readFileSync(syncServicePath, "utf8");
const ipcHandlers = fs.readFileSync(ipcHandlersPath, "utf8");
const preload = fs.readFileSync(preloadPath, "utf8");
const requiredSnippets = [
[stateService, "routeDecisionPolicy", "route decision policy UI state"],
[stateService, "normalizeRouteDecisionPolicy", "route decision policy normalizer"],
[stateService, "evaluateDigitalEmployeeRouteDecisionPolicy", "route decision policy evaluator"],
[stateService, "listDigitalEmployeeRouteInterventions", "route intervention list helper"],
[stateService, "recordDigitalEmployeeRouteInterventionResolution", "route intervention resolution recorder"],
[stateService, "route_decision_blocked", "route decision blocked event kind"],
[stateService, "route_decision_approval_required", "route decision approval-required event kind"],
[stateService, "route_decision_intervention_resolved", "route decision intervention resolved event kind"],
[syncService, "pullDigitalEmployeeRouteDecisionPolicy", "remote route decision policy pull service"],
[syncService, "routeDecisionPolicyEndpoint", "remote route decision policy endpoint config"],
[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"],
[ipcHandlers, "digitalEmployee:recordRouteInterventionResolution", "IPC route intervention resolution handler"],
[preload, "pullRouteDecisionPolicy", "preload route decision policy pull bridge"],
[preload, "evaluateRouteDecisionPolicy", "preload route decision policy evaluation bridge"],
[preload, "listRouteInterventions", "preload route intervention list bridge"],
[preload, "recordRouteInterventionResolution", "preload route intervention resolution bridge"],
[adapter, "pullRouteDecisionPolicy", "adapter route decision policy pull bridge"],
[adapter, "evaluateRouteDecisionPolicy", "adapter route decision policy evaluation bridge"],
[adapter, "/api/route-decision/policy/pull", "adapter route decision policy pull endpoint"],
[adapter, "/api/route-decisions/interventions", "adapter route intervention list endpoint"],
[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))
.map(([, , label]) => label);
if (missing.length > 0) {
throw new Error(`Missing route decision policy hooks: ${missing.join(", ")}`);
}
console.log("[DigitalEmployeeCheck] route decision policy hooks OK");
}
function checkLocalDatabaseSchema() {
console.log("\n[DigitalEmployeeCheck] Verify local SQLite digital schema");
const dbPath = path.join(os.homedir(), ".qimingclaw", "qimingclaw.db");
if (!fs.existsSync(dbPath)) {
console.log(`[DigitalEmployeeCheck] Skip DB schema check; database not found: ${dbPath}`);
return;
}
const healResult = spawnSync("sqlite3", [dbPath, selfHealingDigitalSchemaSql], {
encoding: "utf8",
shell: process.platform === "win32",
});
if (healResult.error) {
console.log(`[DigitalEmployeeCheck] Skip DB schema check; sqlite3 unavailable: ${healResult.error.message}`);
return;
}
if (healResult.status !== 0) {
throw new Error(healResult.stderr || "sqlite3 digital schema self-heal failed");
}
const result = spawnSync("sqlite3", [dbPath, ".tables"], {
encoding: "utf8",
shell: process.platform === "win32",
});
if (result.status !== 0) {
throw new Error(result.stderr || "sqlite3 .tables failed");
}
const tables = new Set(result.stdout.split(/\s+/).filter(Boolean));
const missing = requiredDigitalTables.filter((table) => !tables.has(table));
if (missing.length > 0) {
throw new Error(`Missing digital employee tables: ${missing.join(", ")}`);
}
console.log(`[DigitalEmployeeCheck] ${requiredDigitalTables.length} digital tables OK`);
}
function main() {
try {
runStep("Run digital employee unit tests", "npm", ["run", "test:run", "--", ...testFiles]);
runStep("Build Electron main process", "npm", ["run", "build:main:dev"]);
runStep("Build embedded digital employee", "npm", ["run", "build:sgrobot-digital"]);
checkSgrobotDigitalAssets();
checkDigitalDiagnosticAuditProjection();
checkDigitalNotificationPreferences();
checkDigitalApprovalHistorySubscriptions();
checkDigitalApprovalWorkflowSync();
checkDigitalPlanStepGraphDispatchPolicy();
checkDigitalSkillRemotePolicy();
checkDigitalRiskApprovalPolicy();
checkDigitalRouteDecisionPolicy();
checkLocalDatabaseSchema();
console.log("\n[DigitalEmployeeCheck] All checks passed");
} catch (error) {
console.error(`\n[DigitalEmployeeCheck] ${error instanceof Error ? error.message : String(error)}`);
process.exit(process.exitCode || 1);
}
}
main();