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 分钟超时,用户零感知)
113 lines
5.3 KiB
JavaScript
113 lines
5.3 KiB
JavaScript
/* cdp-verify-gensb.mjs — 验证「生成分镜」弹窗 desc/prompt 无「改写」措辞残留(09-01 用户:没有AI改写脚本的说法了)
|
|
* 覆盖点:#/rewrites 页 → 查看(对比弹窗)→ 生成分镜 → confirmRun opts
|
|
* 方法:CDP 9223 → 复用现有 8900 标签页 → hook window.confirmRun 捕获 opts(不真实执行任务)
|
|
* 断言:title=生成分镜/重新生成分镜;desc/prompt 含「AI 脚本」;不含「改写」
|
|
* 运行:node cdp-verify-gensb.mjs
|
|
*/
|
|
const CDP = 'http://127.0.0.1:9223';
|
|
|
|
async function getWs(targetId) {
|
|
const tabs = await (await fetch(CDP + '/json')).json();
|
|
const t = tabs.find((x) => x.id === targetId);
|
|
if (!t) throw new Error('target not found: ' + targetId);
|
|
return t.webSocketDebuggerUrl;
|
|
}
|
|
function cdp(ws, method, params = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const id = ++cdp._id;
|
|
const w = new WebSocket(ws);
|
|
const timer = setTimeout(() => { try { w.close(); } catch (e) {} reject(new Error('timeout: ' + method)); }, 30000);
|
|
w.onopen = () => w.send(JSON.stringify({ id, method, params }));
|
|
w.onmessage = (ev) => {
|
|
const m = JSON.parse(ev.data);
|
|
if (m.id !== id) return;
|
|
clearTimeout(timer);
|
|
try { w.close(); } catch (e) {}
|
|
m.error ? reject(new Error(method + ': ' + JSON.stringify(m.error))) : resolve(m.result);
|
|
};
|
|
w.onerror = () => { clearTimeout(timer); reject(new Error('ws error: ' + method)); };
|
|
});
|
|
}
|
|
cdp._id = 0;
|
|
|
|
async function evalJs(ws, expr) {
|
|
const r = await cdp(ws, 'Runtime.evaluate', { expression: expr, awaitPromise: true, returnByValue: true });
|
|
if (r.exceptionDetails) throw new Error('eval err: ' + JSON.stringify(r.exceptionDetails).slice(0, 300));
|
|
return r.result && r.result.value;
|
|
}
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
async function waitFor(ws, expr, timeout = 40000, label = '') {
|
|
const t0 = Date.now();
|
|
while (Date.now() - t0 < timeout) {
|
|
try { if (await evalJs(ws, expr)) return true; } catch (e) {}
|
|
await sleep(800);
|
|
}
|
|
throw new Error('waitFor timeout: ' + label + ' -> ' + expr);
|
|
}
|
|
|
|
async function main() {
|
|
const tabs = await (await fetch(CDP + '/json')).json();
|
|
const wb = tabs.find((t) => t.url.includes('localhost:8900') || t.url.includes('127.0.0.1:8900'));
|
|
if (!wb) throw new Error('未找到 8900 工作台标签页,请先在内置浏览器打开工作台');
|
|
const ws = await getWs(wb.id);
|
|
console.log('▶ 复用标签页:', wb.url);
|
|
|
|
console.log('\n【#/rewrites → 对比弹窗 → 生成分镜】');
|
|
await evalJs(ws, `location.hash = '#/rewrites'`);
|
|
await cdp(ws, 'Page.reload', { ignoreCache: true });
|
|
await waitFor(ws, `!!document.querySelector('[data-detail]')`, 40000, 'rewrites 列表渲染(含查看按钮)');
|
|
|
|
// 打开对比弹窗(取第一条有 rewrite 的视频)
|
|
const hasView = await evalJs(ws, `document.querySelectorAll('[data-detail]').length`);
|
|
console.log('可测「查看」按钮数:', hasView);
|
|
if (!hasView) throw new Error('无可测视频(无 rewrite 数据)');
|
|
await evalJs(ws, `document.querySelector('[data-detail]').click(); true`);
|
|
await waitFor(ws, `!!document.querySelector('.rw-modal')`, 30000, '对比弹窗打开');
|
|
|
|
// 注入 hook(弹窗已打开,点击生成分镜前)
|
|
await evalJs(ws, `window.__cap = null; window.__origConfirmRun = window.confirmRun;
|
|
window.confirmRun = (o) => { window.__cap = o; }; true`);
|
|
|
|
// 优先点「生成分镜」(data-gen-sb);已有分镜则点「重新生成」(data-sgen)
|
|
const btnInfo = await evalJs(ws, `(() => {
|
|
const gen = document.querySelector('.rw-modal [data-gen-sb]');
|
|
const sgen = document.querySelector('.rw-modal [data-sgen]');
|
|
return { hasGen: !!gen, hasSgen: !!sgen };
|
|
})()`);
|
|
console.log('按钮: 生成分镜=', btnInfo.hasGen, '| 重新生成=', btnInfo.hasSgen);
|
|
const clicked = await evalJs(ws, `(() => {
|
|
const gen = document.querySelector('.rw-modal [data-gen-sb]');
|
|
const sgen = document.querySelector('.rw-modal [data-sgen]');
|
|
if (gen) { gen.click(); return 'gen'; }
|
|
if (sgen) { sgen.click(); return 'sgen'; }
|
|
return null;
|
|
})()`);
|
|
if (!clicked) throw new Error('未找到生成分镜按钮');
|
|
console.log('点击按钮:', clicked);
|
|
|
|
await waitFor(ws, `!!window.__cap`, 30000, 'confirmRun 捕获');
|
|
const cap = JSON.parse(await evalJs(ws, `JSON.stringify(window.__cap)`));
|
|
const p = cap.prompt || '';
|
|
const d = cap.desc || '';
|
|
console.log(' title:', cap.title, '| label:', cap.label, '| target:', cap.target);
|
|
console.log(' desc:', d);
|
|
console.log(' prompt:', p);
|
|
console.log(' desc 含「AI 脚本」:', d.includes('AI 脚本'));
|
|
console.log(' prompt 含「AI 脚本」:', p.includes('AI 脚本'));
|
|
console.log(' desc 含「改写」:', d.includes('改写'));
|
|
console.log(' prompt 含「改写」:', p.includes('改写'));
|
|
|
|
const ok = /生成分镜|重新生成分镜/.test(cap.title)
|
|
&& cap.label === '分镜指令'
|
|
&& d.includes('AI 脚本') && !d.includes('改写')
|
|
&& p.includes('AI 脚本') && !p.includes('改写');
|
|
console.log(' ✅ 生成分镜措辞断言:', ok ? 'PASS' : 'FAIL');
|
|
|
|
await evalJs(ws, `window.confirmRun = window.__origConfirmRun; true`);
|
|
console.log('\n==== 验证完成 ====');
|
|
}
|
|
|
|
main().catch((e) => { console.error('❌', e.message); process.exit(1); });
|