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 分钟超时,用户零感知)
85 lines
6.0 KiB
JavaScript
85 lines
6.0 KiB
JavaScript
// R30 冒烟:列表页 toolbar — 标题行只放 返回+标题,tab/筛选/搜索/按钮一律放标题下一行
|
|
// 覆盖账号列表 + 热点数据两个明确违规页面
|
|
const list = await fetch('http://localhost:9225/json').then((r) => r.json());
|
|
const page = 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');
|
|
const waitFor = async (expr, ms = 12000) => { const t0 = Date.now(); while (Date.now() - t0 < ms) { if (await ev(expr)) return true; await sleep(300); } return false; };
|
|
const fail = (m) => { console.log('FAIL@' + m); process.exit(1); };
|
|
|
|
await send('Page.navigate', { url: 'http://localhost:8900/?v=' + Date.now() });
|
|
if (!(await waitFor(`!!document.querySelector('#view')`))) fail('app boot');
|
|
await ev(`document.querySelectorAll('.modal-mask').forEach((m) => m.remove())`); // 清 root 设置弹窗
|
|
|
|
const out = { accounts: {}, hot: {} };
|
|
|
|
/* ============ 账号列表 ============ */
|
|
await ev(`location.hash = '#/accounts'`);
|
|
if (!(await waitFor(`document.querySelector('#view .acc-toolbar')`))) fail('accounts toolbar render');
|
|
out.accounts = await ev(`(() => {
|
|
const tb = document.querySelector('#view .page > .page-toolbar');
|
|
const acc = document.querySelector('#view .acc-toolbar');
|
|
const tbChildren = tb ? [...tb.children].map((c) => ({ tag: c.tagName, cls: c.className, txt: (c.textContent || '').trim().slice(0, 30) })) : null;
|
|
const tbW = tb ? Math.round(tb.getBoundingClientRect().width) : null;
|
|
const tbH = tb ? Math.round(tb.getBoundingClientRect().height) : null;
|
|
const accW = acc ? Math.round(acc.getBoundingClientRect().width) : null;
|
|
const accIds = acc ? [...acc.querySelectorAll('[id]')].map((e) => e.id) : [];
|
|
// 验证 toolbar 与 acc-toolbar 在不同行
|
|
const tbBottom = tb ? tb.getBoundingClientRect().bottom : null;
|
|
const accTop = acc ? acc.getBoundingClientRect().top : null;
|
|
const verticalGap = (tbBottom != null && accTop != null) ? Math.round(accTop - tbBottom) : null;
|
|
return { tbChildren, tbH, accH: acc ? Math.round(acc.getBoundingClientRect().height) : null, accIds, verticalGap, belowTb: verticalGap > 0, hasBackBtn: tb ? !!tb.querySelector('[data-back]') : null };
|
|
})()`);
|
|
|
|
/* ============ 热点数据 ============ */
|
|
await ev(`location.hash = '#/ranking'`);
|
|
if (!(await waitFor(`document.querySelector('#view .rank-toolbar')`))) fail('hot toolbar render');
|
|
out.hot = await ev(`(() => {
|
|
const tb = document.querySelector('#view .page > .page-toolbar');
|
|
const rt = document.querySelector('#view .rank-toolbar');
|
|
const tbChildren = tb ? [...tb.children].map((c) => ({ tag: c.tagName, cls: c.className, txt: (c.textContent || '').trim().slice(0, 30) })) : null;
|
|
const tabs = rt ? [...rt.querySelectorAll('.rank-tab')].map((t) => ({ txt: t.textContent.trim(), active: t.classList.contains('active') })) : [];
|
|
const right = rt ? [...rt.querySelectorAll('.toolbar-right [id], .toolbar-right [title]')].map((e) => ({ tag: e.tagName, id: e.id || null, txt: (e.textContent || '').trim().slice(0, 20) })) : [];
|
|
const tbBottom = tb ? tb.getBoundingClientRect().bottom : null;
|
|
const rtTop = rt ? rt.getBoundingClientRect().top : null;
|
|
const verticalGap = (tbBottom != null && rtTop != null) ? Math.round(rtTop - tbBottom) : null;
|
|
return { tbChildren, tabs, rightIds: right.map((r) => r.id).filter(Boolean), verticalGap, belowTb: verticalGap > 0 };
|
|
})()`);
|
|
|
|
/* ============ 回归:AI写脚本标题行仍合规(只有 返回+标题) ============ */
|
|
await ev(`location.hash = '#/rewrites'`);
|
|
if (!(await waitFor(`document.querySelector('#view .rw-tabs')`))) fail('rewrites toolbar render');
|
|
out.rewrites = await ev(`(() => {
|
|
const tb = document.querySelector('#view .page > .page-toolbar');
|
|
return { tbChildren: tb ? [...tb.children].map((c) => ({ tag: c.tagName, cls: c.className, txt: (c.textContent || '').trim().slice(0, 30) })) : null };
|
|
})()`);
|
|
|
|
console.log(JSON.stringify(out, null, 1));
|
|
|
|
// 断言
|
|
const A = out.accounts && !out.accounts.__err
|
|
&& out.accounts.tbChildren && out.accounts.tbChildren.length === 2
|
|
&& out.accounts.tbChildren[0].txt === '←' && out.accounts.tbChildren[1].cls.includes('page-title')
|
|
&& out.accounts.tbChildren.every((c) => !c.cls.includes('toolbar-right'))
|
|
&& out.accounts.accIds.includes('accType') && out.accounts.accIds.includes('accTrack') && out.accounts.accIds.includes('accSearch')
|
|
&& out.accounts.accIds.includes('accImport') && out.accounts.accIds.includes('accUpdate')
|
|
&& out.accounts.belowTb === true;
|
|
const H = out.hot && !out.hot.__err
|
|
&& out.hot.tbChildren && out.hot.tbChildren.length === 2
|
|
&& out.hot.tbChildren[0].txt === '←' && out.hot.tbChildren[1].cls.includes('page-title')
|
|
&& out.hot.tabs.length === 3
|
|
&& out.hot.rightIds.includes('rankCat') && out.hot.rightIds.includes('rankPrev') && out.hot.rightIds.includes('rankNext')
|
|
&& out.hot.belowTb === true;
|
|
const R = out.rewrites && out.rewrites.tbChildren && out.rewrites.tbChildren.length === 2
|
|
&& out.rewrites.tbChildren[0].txt === '←' && out.rewrites.tbChildren[1].cls.includes('page-title');
|
|
const ok = A && H && R;
|
|
console.log(ok ? '\n=== PASS ===' : '\n=== CHECK === (A=' + A + ' H=' + H + ' R=' + R + ')');
|
|
ws.close();
|
|
process.exit(ok ? 0 : 1); |