09-01 调度机制修复落地+并发方案A+全链路复测通过

- 调度根因修复:工作台/api/run 显式写 next_run_at=now+5s(17列),scheduledAt=now+5s 带秒
- 新增 自动化任务调度机制.md(根因/硬规则/状态机/并发上限≈3/排查命令)
- 并发方案A(浏览器锁):/api/run 对浏览器类任务自动注入锁约束;浏览器搜索抖音账号操作规范.md 新增5.6并发互斥
- 端口 8899→8900 可传参(8899被小米MiPCAudio.exe系统服务占用且杀后复活)
- cdp-test-junxi.mjs 支持 --port 参数
- SKILL.md 工作台段落同步修正(旧认知 scheduled_at=now 已废弃)
- 俊希全流程 TC-01~11 复测全通过(验收表更新):并行解析6条+AI写脚本会话链路完整跑通
This commit is contained in:
maogeigei committed 2026-09-01 00:36:02 +08:00
1 parent ccdd79ea40
commit 24886338d1
12 files changed
+265 -49

No files matched your search

@@ -1,5 +1,5 @@
// MCN工作台 - 本地 Web 服务(零依赖 Node 实现)
// 启动: node server.js → http://localhost:8899
// 启动: node server.js [端口] → http://localhost:8899(默认;可传参指定端口,如 node server.js 8900)
// 功能: 技能导航 + 产出内容浏览(默认读桌面 MCNSkill项目)+ dsh 数据功能页(账号/视频/脚本/复盘/周榜,只读)
'use strict';
@@ -10,7 +10,9 @@ const os = require('os');
const { URL } = require('url');
const dsh = require('./dsh-data');
const PORT_BASE = 8899;
// 09-01 端口改为可传参(默认8899):本机 8899 可能被小米 PC 管家 MiPCAudio.exe 系统服务占用(0.0.0.0 监听且自动复活),
// 此时 127.0.0.1:8899 绑定不生效 → 用 `node server.js 8900` 显式换端口
const PORT_BASE = parseInt(process.argv[2], 10) || 8899;
const ROOT = __dirname;
const PUBLIC_DIR = path.join(ROOT, 'public');
const CONFIG_FILE = path.join(ROOT, 'config.json');
@@ -361,19 +363,35 @@ const server = http.createServer((req, res) => {
try {
const { prompt, name } = JSON.parse(body || '{}');
if (!prompt || typeof prompt !== 'string' || !prompt.trim()) return sendErr(400, 'prompt 不能为空');
// 09-01 并发方案A:浏览器类任务(账号信息/视频列表/采集/导入)自动注入浏览器锁约束——
// 多个 AI 会话任务并行共享同一 9223 Chrome 实例,必须互斥使用,否则标签页互相导航抢占
const BROWSER_HINT = /账号信息|视频列表|保存并分析|采集|导入账号|网页采集|浏览器/;
const BROWSER_LOCK = 'D:\\\\AgentSkill\\\\mcn-workshop\\\\.browser-lock';
let finalPrompt = prompt.trim();
if (BROWSER_HINT.test(finalPrompt)) {
finalPrompt += `
【浏览器锁约束(必须遵守)】本任务需要操作浏览器(browser-harness / 9223 Chrome)。多个 AI 会话任务可能并行,浏览器是共享单实例,必须互斥使用:
1. 执行任何浏览器操作前,先检查锁文件:${BROWSER_LOCK} 是否存在(bash: ls)
2. 锁存在 → 等待 10 秒后重试,最多重试 18 次(约 3 分钟);若锁文件修改时间已超过 10 分钟视为死锁,可删除后抢占
3. 拿到锁(bash: echo <任务名+时间戳> > ${BROWSER_LOCK})→ 才可操作浏览器
4. 浏览器操作全部完成后(无论成功失败)必须删除锁文件(bash: rm -f ${BROWSER_LOCK})
5. 若确认本任务实际不需要浏览器(如数据已齐),忽略本条约束,直接跳过`;
}
const { DatabaseSync } = require('node:sqlite');
const db = new DatabaseSync(process.env.WORKBUDDY_DB || path.join(os.homedir(), '.workbuddy', 'workbuddy.db'));
const now = Date.now();
const id = 'automation-' + now;
const cwd = 'D:\\AgentSkill\\mcn-workshop'; // 工作台触发的会话归入 mcn-workshop 空间分组
const d = new Date();
const d = new Date(Date.now() + 5 * 1000); // 未来 5 秒(08-31 根因:客户端只对未来 scheduledAt 补算 next_run_at;写 now=过去时间→不补算→调度器扫不到→卡死)
const pad = (n) => String(n).padStart(2, '0');
const scheduledAt = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
const scheduledAt = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
const nextRunAt = Date.now() + 5 * 1000; // 08-31 实测:客户端调度器按 next_run_at 扫描;缺此列=null=永不拾取(工作台任务卡死根因)
const uidRow = db.prepare("SELECT user_id FROM sessions WHERE user_id IS NOT NULL AND user_id <> '' ORDER BY created_at DESC LIMIT 1").get();
const ownerUserId = uidRow ? uidRow.user_id : '';
db.prepare(`INSERT INTO automations (id,name,prompt,status,schedule_type,scheduled_at,rrule,cwds,created_at,updated_at,skills_json,connector_ids_json,model_id,permission_mode,owner_user_id,owner_status)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)
.run(id, name || '工作台任务', prompt.trim(), 'ACTIVE', 'once', scheduledAt, '', JSON.stringify([cwd]), now, now, '[]', '[]', 'deepseek-v4-flash', 'fullAccess', ownerUserId, 'confirmed');
db.prepare(`INSERT INTO automations (id,name,prompt,status,schedule_type,scheduled_at,next_run_at,rrule,cwds,created_at,updated_at,skills_json,connector_ids_json,model_id,permission_mode,owner_user_id,owner_status)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`)
.run(id, name || '工作台任务', finalPrompt, 'ACTIVE', 'once', scheduledAt, nextRunAt, '', JSON.stringify([cwd]), now, now, '[]', '[]', 'deepseek-v4-flash', 'fullAccess', ownerUserId, 'confirmed');
db.close();
log('已提交 AI 任务: ' + id + (name ? ' (' + name + ')' : ''));
return sendJSON(200, { ok: true, id, message: '任务已提交,请在左侧会话栏查看执行' });