Files
ycode/docs/协作/evidence/B01-review-02/mode-host-probe.cjs
T

85 lines
8.0 KiB
JavaScript

const fs = require('node:fs/promises')
const path = require('node:path')
const { pathToFileURL } = require('node:url')
if (!process.versions.electron) {
;(async () => {
const dir = await fs.mkdtemp(path.resolve('.tmp/alignment-window-'))
const data = path.join(dir, 'data'), home = path.join(dir, 'home'), cwd = path.join(dir, 'workspace')
for (const folder of [data, home, cwd]) await fs.mkdir(folder)
const now = Date.now()
const model = { id:'audit-model', name:'Audit Model', providerId:'custom', model:'fixture', baseUrl:'http://127.0.0.1:1', authMode:'none', protocol:'anthropic', supportsToolCall:true }
await fs.writeFile(path.join(data,'models.json'), JSON.stringify({version:1, models:[model], selectedModelId:model.id}))
await fs.writeFile(path.join(data,'workspaces.json'),JSON.stringify({selectedWorkspaceId:'audit-space',workspaces:[{id:'audit-space',name:'Audit Workspace',root:cwd,createdAt:now}]}))
const question = { id:'q1', question:'Which target should be inspected?', answers:['Local fixture'] }
const messages = [
{id:'u1',role:'user',content:'Inspect the local fixture.',createdAt:now-10000,status:'done'},
{id:'a1',role:'assistant',content:'The local fixture is ready.',createdAt:now-9000,status:'done',turnStartedAt:now-10000,turnFinishedAt:now,turnDurationMs:10000,
tools:[{id:'q-tool',name:'AskUserQuestion',args:{questions:[question]},argsRaw:'{}',status:'ok',output:'ANSWER_RAW_SENTINEL',metadata:{questionAnswer:{questions:[question]}}}],
parts:[{type:'reasoning',id:'r1',text:'I need the target.',finished:true},{type:'tool',toolId:'q-tool'},{type:'reasoning',id:'r2',text:'The target is selected.',finished:true},{type:'text',text:'The local fixture is ready.'}]}
]
const session = (id,agentMode,ms) => ({id,title:`Audit ${agentMode}`,cwd,workspaceId:'audit-space',modelId:model.id,agentMode,permissionMode:'full-access',createdAt:ms,updatedAt:ms,messages})
await fs.writeFile(path.join(data,'sessions.json'),JSON.stringify({sessions:[session('audit-plan','plan',now+1),session('audit-ask','ask',now)]}))
const env={...process.env,YCODE_USER_DATA_DIR:data,YCODE_QA_DIRECTORY:dir,YCODE_QA_OFFSCREEN:'1',YCODE_DISABLE_GPU:'1',YCODE_DISABLE_SYSTEM_INTEGRATION:'1',AUDIT_WINDOW_DIR:dir}
delete env.ELECTRON_RUN_AS_NODE; delete env.ELECTRON_RENDERER_URL
const child=require('node:child_process').spawn(require('electron'),['--no-sandbox','--disable-gpu-sandbox',__filename],{env,windowsHide:true,stdio:['ignore','pipe','pipe']})
let log='';child.stdout.on('data',x=>{log+=x});child.stderr.on('data',x=>{log+=x})
const timeout=setTimeout(()=>child.kill(),90000)
child.on('exit',async code=>{clearTimeout(timeout);await fs.writeFile(path.join(dir,'process.log'),log);console.log(JSON.stringify({directory:dir,exitCode:code}));if(code)console.error(log.slice(-7000));process.exitCode=code??1})
})().catch(e=>{console.error(e);process.exitCode=1})
} else {
const {app,BrowserWindow,ipcMain}=require('electron')
const dir=process.env.AUDIT_WINDOW_DIR
app.disableHardwareAcceleration();app.setPath('home',path.join(dir,'home'))
const originalHandle=ipcMain.handle.bind(ipcMain)
ipcMain.handle=(channel,listener)=>originalHandle(channel,async(...args)=>{if(channel==='session:messages')await new Promise(r=>setTimeout(r,1200));return listener(...args)})
let win
const pause=ms=>new Promise(r=>setTimeout(r,ms))
const read=code=>win.webContents.executeJavaScript(code)
async function wait(code){for(let i=0;i<250;i++){const x=await read(code);if(x)return x;await pause(40)}throw new Error('Timeout '+code)}
async function shot(name){await read('document.fonts.ready');await pause(80);await fs.writeFile(path.join(dir,name+'.png'),(await win.webContents.capturePage()).toPNG())}
const styleProbe=`(selector)=>{const e=document.querySelector(selector);if(!e)return null;const c=getComputedStyle(e),r=e.getBoundingClientRect();return {selector,text:e.textContent,width:r.width,height:r.height,fontSize:c.fontSize,fontWeight:c.fontWeight,lineHeight:c.lineHeight,padding:c.padding,borderRadius:c.borderRadius,background:c.backgroundColor,color:c.color,display:c.display,opacity:c.opacity}}`
const styles=selectors=>read(`(()=>{const p=${styleProbe};return ${JSON.stringify(selectors)}.map(p)})()`)
;(async()=>{
await import(pathToFileURL(path.resolve('out/main/index.mjs')).href);await app.whenReady();win=BrowserWindow.getAllWindows()[0];win.setSize(1336,902);win.showInactive()
const errors=[];win.webContents.on('console-message',(_e,level,message)=>{if(level>=3)errors.push(message)})
await wait('!!document.querySelector(".yc-session-skeleton")')
const skeleton=await styles(['.yc-session-skeleton','.yc-session-skeleton__avatar','.yc-session-skeleton__name','.yc-session-skeleton__user','.yc-session-skeleton__line','.yc-session-skeleton__line--short'])
await shot('loading')
await wait('!!document.querySelector(".yc-mode-chip") && !!document.querySelector(".msg--assistant")')
await pause(350)
const mode=await styles(['.yc-mode-chip','.yc-mode-chip__mode-icon','.yc-mode-chip__clear'])
await shot('plan')
await read('document.querySelector(".yc-mode-chip").focus()')
const focusedMode=await styles(['.yc-mode-chip','.yc-mode-chip__mode-icon','.yc-mode-chip__clear'])
await shot('plan-focus')
const checks=[];
for(const agentMode of ['plan','ask']){
const sessionId='audit-'+agentMode;
if(agentMode==='ask'){
await read('document.querySelector("[data-session-id=\\"audit-ask\\"]").click()');
await wait('!!document.querySelector(".yc-mode-chip") && !document.querySelector(".yc-session-skeleton") && document.querySelector(".yc-mode-chip").textContent.includes("仅问答")');
}
await read('document.querySelector(".wb-composer textarea").focus()');
await win.webContents.insertText('KEEP_'+agentMode+'_DRAFT');
await wait('document.querySelector(".wb-composer textarea").value.includes("KEEP_'+agentMode+'_DRAFT")');
const before=await read('({draft:document.querySelector(".wb-composer textarea").value,permissionDisabled:document.querySelector(".yc-source-permission-trigger > button").disabled})');
const metadataBefore=await read('window.ycode.session.list().then(list=>list.find(item=>item.id==='+JSON.stringify(sessionId)+'))');
await read('document.querySelector(".yc-mode-chip__clear").focus()');
win.focus();win.webContents.focus();await pause(100);
win.webContents.sendInputEvent({type:'keyDown',keyCode:'Enter'});
win.webContents.sendInputEvent({type:'char',keyCode:'\r'});
win.webContents.sendInputEvent({type:'keyUp',keyCode:'Enter'});
await wait('!document.querySelector(".yc-mode-chip")');
const after=await read('({draft:document.querySelector(".wb-composer textarea").value,permissionDisabled:document.querySelector(".yc-source-permission-trigger > button").disabled})');
const metadataAfter=await read('window.ycode.session.list().then(list=>list.find(item=>item.id==='+JSON.stringify(sessionId)+'))');
if(before.draft!==after.draft||!before.permissionDisabled||after.permissionDisabled||metadataBefore.permissionMode!=='full-access'||metadataAfter.permissionMode!=='full-access'||metadataAfter.agentMode!=='craft')throw Error('Mode host state mismatch '+agentMode);
checks.push({agentMode,before,after,savedPermissionBefore:metadataBefore.permissionMode,savedPermissionAfter:metadataAfter.permissionMode,savedModeAfter:metadataAfter.agentMode});
await shot(agentMode+'-craft-draft-kept');
}
await fs.writeFile(path.join(dir,'report.json'),JSON.stringify({codeSha:'1ed71b63b5af9a98b942ff47c66df5799d4d2c8a',scope:'Actual isolated app; native keyboard exits plan/ask with draft and saved permission retained; no model requests',checks,errors},null,2));
if(errors.length)throw Error('Renderer errors');app.exit(0);
})().catch(async e=>{console.error(e);if(win)await shot('failure').catch(()=>{});await fs.writeFile(path.join(dir,'failure.txt'),String(e));app.exit(1)})
}