1. 目录 mcn-work-shop -> mcn-workshop(空间分组名取会话 cwd 的目录名,只改字符串无效,必须真改名) 2. 全仓替换 mcn-work-shop -> mcn-workshop:37 文件 121 处;历史日志按沿革句规矩保留当时目录名 3. 任务进度可见:/api/run/status 产出 已运行时长/工具调用/最近动作/停滞判定(读会话日志尾部),前端新增右下角常驻面板,四处任务入口接入 4. 会话归属目录配置化:新增 config.sessionCwd(留空=工作台自身目录),现指向 D://AI技能//mcn-workshop,使任务显示为命名分组而非「未分组任务」 5. 修前端轮询静默缺陷:连续 3 次查询失败即提示服务断开(原逻辑静默空转到 15 分钟超时,用户零感知)
63 lines
4.6 KiB
JavaScript
63 lines
4.6 KiB
JavaScript
// R68 验证:账号信息 / 视频信息属性内容宽度 180px
|
|
const CDP = 9223;
|
|
const list = await fetch(`http://localhost:${CDP}/json`).then((r) => r.json());
|
|
const page = list.find((t) => t.type === 'page' && (t.url || '').includes('8900')) || list.find((t) => t.type === 'page');
|
|
if (!page) { console.log('ERR: no page'); process.exit(1); }
|
|
const ws = new WebSocket(page.webSocketDebuggerUrl);
|
|
let id = 0; const pending = new Map();
|
|
const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); });
|
|
ws.onmessage = (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.error ? p.rej(new Error(m.error.message)) : p.res(m.result); } };
|
|
await new Promise((r) => (ws.onopen = r));
|
|
const ev = async (expr) => { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) return { __err: (r.exceptionDetails.exception?.description || r.exceptionDetails.text).slice(0, 200) }; return r.result.value; };
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
await send('Page.enable'); await send('Runtime.enable'); await send('Network.enable');
|
|
await send('Network.setCacheDisabled', { cacheDisabled: true });
|
|
await send('Emulation.setDeviceMetricsOverride', { width: 900, height: 1100, deviceScaleFactor: 1, mobile: false });
|
|
const waitFor = async (expr, ms = 15000) => { const t0 = Date.now(); while (Date.now() - t0 < ms) { if (await ev(expr)) return true; await sleep(300); } return false; };
|
|
const fs = await import('node:fs');
|
|
|
|
const out = {};
|
|
// 1) 视频详情
|
|
await send('Page.navigate', { url: `http://127.0.0.1:8900/?t=${Date.now()}#/video/93` });
|
|
if (!(await waitFor(`location.hash === '#/video/93' && !!document.querySelector('#view .video-grid') && [...document.styleSheets].some((s) => (s.href || '').includes('style.css?v=20260904i'))`, 15000))) { console.log('FAIL video route'); process.exit(1); }
|
|
await sleep(500);
|
|
const vw = await ev(`(() => {
|
|
const items = [...document.querySelectorAll('.video-info .video-grid .info-item:not(.info-wide)')];
|
|
if (!items.length) return { err: 'no items' };
|
|
const widths = items.slice(0, 4).map((el) => Math.round(el.getBoundingClientRect().width));
|
|
const body = document.querySelector('.detail-body') || document.querySelector('main');
|
|
const over = items.some((el) => el.getBoundingClientRect().right > (body?.getBoundingClientRect().right || 9999));
|
|
return { widths, over, n: items.length };
|
|
})()`);
|
|
out.video = vw;
|
|
|
|
// 2) 账号详情(取第一个账号)
|
|
const accId = await ev(`fetch('/api/dsh/accounts?page=1&pageSize=1').then(r=>r.json()).then(d => d.items && d.items[0] ? d.items[0].id : null).catch(()=>null)`);
|
|
if (accId) {
|
|
await send('Page.navigate', { url: `http://127.0.0.1:8900/?t=${Date.now()}#/account/${accId}` });
|
|
if (!(await waitFor(`location.hash === '#/account/${accId}' && !!document.querySelector('.account-info .account-grid')`, 15000))) { console.log('FAIL account route'); process.exit(1); }
|
|
await sleep(500);
|
|
const aw = await ev(`(() => {
|
|
const items = [...document.querySelectorAll('.account-info .account-grid .info-item')];
|
|
if (!items.length) return { err: 'no items' };
|
|
const widths = items.slice(0, 4).map((el) => Math.round(el.getBoundingClientRect().width));
|
|
const body = document.querySelector('.detail-body') || document.querySelector('main');
|
|
const over = items.some((el) => el.getBoundingClientRect().right > (body?.getBoundingClientRect().right || 9999));
|
|
return { widths, over, n: items.length, acc: '#' + location.hash };
|
|
})()`);
|
|
out.account = aw;
|
|
// 截图账号信息卡
|
|
const rect = await ev(`(() => { const el = document.querySelector('.account-info'); if (!el) return null; const r = el.getBoundingClientRect(); return { x: r.x, y: r.y, w: r.width, h: r.height }; })()`);
|
|
if (rect) {
|
|
const shot = await send('Page.captureScreenshot', { format: 'png', clip: { x: Math.max(0, rect.x - 6), y: Math.max(0, rect.y - 6), width: Math.min(rect.w + 12, 1200), height: Math.min(rect.h + 12, 900), scale: 1 } });
|
|
fs.writeFileSync('tmp/r68_account_info.png', Buffer.from(shot.data, 'base64'));
|
|
}
|
|
} else { out.account = { err: 'no account in db' }; }
|
|
|
|
console.log(JSON.stringify(out, null, 1));
|
|
const ok = out.video && !out.video.err && out.video.widths.length && out.video.widths.every((w) => w === 180) && !out.video.over
|
|
&& out.account && !out.account.err && out.account.widths.length && out.account.widths.every((w) => w === 180) && !out.account.over;
|
|
console.log(ok ? '\n=== PASS ===' : '\n=== CHECK ===');
|
|
ws.close();
|
|
process.exit(ok ? 0 : 1);
|