diff --git a/qimingclaw/crates/agent-electron-client/package.json b/qimingclaw/crates/agent-electron-client/package.json index c2ffb283..9b04f60f 100644 --- a/qimingclaw/crates/agent-electron-client/package.json +++ b/qimingclaw/crates/agent-electron-client/package.json @@ -15,6 +15,7 @@ "build:renderer": "vite build", "sync:sgrobot-digital": "node scripts/sync-sgrobot-digital.js", "build:sgrobot-digital": "node scripts/build-sgrobot-digital.js", + "check:digital-employee": "node scripts/check-digital-employee.js", "build": "dotenv -e .env.production -- cross-env NODE_ENV=production npm run build:main && npm run build:renderer", "test": "vitest", "test:run": "vitest run", diff --git a/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js new file mode 100644 index 00000000..32b6400b --- /dev/null +++ b/qimingclaw/crates/agent-electron-client/scripts/check-digital-employee.js @@ -0,0 +1,102 @@ +#!/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_tasks", + "digital_runs", + "digital_events", + "digital_artifacts", + "digital_approvals", + "digital_sync_outbox", + "digital_sync_attempts", +]; + +const testFiles = [ + "src/main/services/digitalEmployee/stateService.test.ts", + "src/main/ipc/eventForwarders.test.ts", + "src/main/services/digitalEmployee/syncService.test.ts", + "src/main/services/digitalEmployee/skillCatalogService.test.ts", + "src/main/services/digitalEmployee/planTemplateService.test.ts", +]; + +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 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 result = spawnSync("sqlite3", [dbPath, ".tables"], { + encoding: "utf8", + shell: process.platform === "win32", + }); + if (result.error) { + console.log(`[DigitalEmployeeCheck] Skip DB schema check; sqlite3 unavailable: ${result.error.message}`); + return; + } + 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(); + 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(); diff --git a/qimingclaw/docs/digital-employee-frontend-integration-plan.md b/qimingclaw/docs/digital-employee-frontend-integration-plan.md index 796d1629..8c4ce145 100644 --- a/qimingclaw/docs/digital-employee-frontend-integration-plan.md +++ b/qimingclaw/docs/digital-employee-frontend-integration-plan.md @@ -1122,6 +1122,7 @@ User - qimingclaw 内有数字员工前端源码目录。 - `public/sgrobot-digital` 可由脚本生成。 +- `npm run check:digital-employee` 可重复验证数字员工关键单测、main build、embedded build、静态资源引用和本机 SQLite digital schema。 - 不启动 sgRobot 后端也能打开“概览”。 - “概览”直接显示数字员工页面。 - 不出现 sgRobot 配对页。 @@ -1129,6 +1130,15 @@ User - sgRobot 仓库不参与 qimingclaw 提交。 - `npm run build:renderer` 通过。 +### 本地验证入口 + +```bash +cd crates/agent-electron-client +npm run check:digital-employee +``` + +该命令不启动 sgRobot daemon。本机已有 `~/.qimingclaw/qimingclaw.db` 时会检查 `digital_*` 表;数据库尚未生成或 `sqlite3` 不可用时只跳过 schema 检查,代码回归仍继续执行。 + ## 最终验收标准 整个融合完成后,应满足: