745 lines
47 KiB
JavaScript
745 lines
47 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 adapter = fs.readFileSync(adapterPath, "utf8");
|
|
const types = fs.readFileSync(typesPath, "utf8");
|
|
const page = fs.readFileSync(pagePath, "utf8");
|
|
const requiredSnippets = [
|
|
[adapter, "'audits'", "audit business view kind"],
|
|
[adapter, "buildDiagnosticSummary", "diagnostic summary builder"],
|
|
[adapter, "filterAuditRows", "audit filters"],
|
|
[adapter, "diagnosticNotifications", "diagnostic notifications"],
|
|
[adapter, "auditNotifications", "audit notifications"],
|
|
[types, "DigitalEmployeeDiagnosticSummary", "diagnostic summary type"],
|
|
[types, "diagnostic_summary?: DigitalEmployeeDiagnosticSummary", "workday diagnostic field"],
|
|
[page, "de-diagnostic-summary-strip", "dashboard diagnostic strip"],
|
|
];
|
|
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, "showBridgeDesktopNotification", "adapter desktop notification 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"],
|
|
[types, "NotificationPreferences", "notification preferences type"],
|
|
[types, "desktop_enabled", "notification desktop preference type field"],
|
|
[types, "NotificationUpdatesPullResult", "notification updates pull result type"],
|
|
[shell, "de-notification-preferences", "notification preferences UI"],
|
|
[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, "Notification", "Electron Notification usage"],
|
|
[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, "governanceProvideTaskInputBusinessView", "notification reply task input sync view"],
|
|
[syncService, "governance_provide_task_input", "task input sync event 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"],
|
|
[preload, "pullNotificationUpdates", "preload notification updates bridge"],
|
|
[preload, "submitNotificationAction", "preload notification action bridge"],
|
|
[preload, "showDesktopNotification", "preload desktop notification 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, "decision_history", "approval workflow decision history field"],
|
|
[syncService, "pullDigitalEmployeeApprovalUpdates", "remote approval updates pull service"],
|
|
[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"],
|
|
[api, "pullApprovalUpdates", "embedded approval updates API"],
|
|
[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"],
|
|
];
|
|
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"],
|
|
[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"],
|
|
[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"],
|
|
[commandDeck, "pull_route_decision_policy", "home route decision policy pull action"],
|
|
[commandDeck, "路由策略", "home route decision policy pull button"],
|
|
];
|
|
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();
|