// Real Electron preload/IPC, real OMP process, controlled local OpenAI-compatible upstream. // This verifies YCode functionality; original WorkBuddy parity is assessed separately via REA. const fs = require('node:fs/promises') const path = require('node:path') const assert = require('node:assert/strict') if (!process.versions.electron) { const { spawn } = require('node:child_process') ;(async () => { const directory = await fs.mkdtemp(path.resolve('.tmp/local-workflows-')) const home = path.join(directory, 'home') await fs.mkdir(home, { recursive: true }) const env = { ...process.env, YCODE_USER_DATA_DIR: path.join(directory, 'data'), YCODE_QA_DIRECTORY: directory, YCODE_QA_REAL_OMP: process.env.YCODE_OMP_PATH || path.join(process.env.LOCALAPPDATA || '', 'Ygent', 'omp.exe'), YCODE_OMP_HOME: home, YCODE_DISABLE_GPU: '1', YCODE_QA_OFFSCREEN: '1' } delete env.ELECTRON_RUN_AS_NODE const child = spawn(require('electron'), ['--no-sandbox', '--disable-gpu-sandbox', __filename], { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }) let output = '' child.stdout.on('data', chunk => { output += chunk; if (chunk.toString().includes('LOCAL_WORKFLOWS_PASS')) process.stdout.write(chunk) }) child.stderr.on('data', chunk => { output += chunk }) const timeout = setTimeout(() => child.kill(), 120000) child.once('exit', async code => { clearTimeout(timeout) await fs.writeFile(path.join(directory, 'process.log'), output) console.log(JSON.stringify({ directory, exitCode: code })) if (code) console.error(output.slice(-6000)) process.exitCode = code ?? 1 }) })().catch(error => { console.error(error); process.exitCode = 1 }) } else { const { app, BrowserWindow } = require('electron') const { createServer } = require('node:http') const { pathToFileURL } = require('node:url') const directory = process.env.YCODE_QA_DIRECTORY app.disableHardwareAcceleration() process.env.YCODE_OMP_PATH = process.env.YCODE_QA_REAL_OMP app.setPath('home', path.join(directory, 'home')) const requests = [] let main, settings, server const pause = ms => new Promise(resolve => setTimeout(resolve, ms)) const read = (expression, win = main) => Promise.race([win.webContents.executeJavaScript(expression), new Promise((_, reject) => setTimeout(() => reject(new Error('Renderer call timeout: ' + expression.slice(0,180))), 15000))]) const phase = label => fs.writeFile(path.join(directory, 'phase.txt'), label) async function wait(expression, label, win = main) { for (let i = 0; i < 500; i++) { const result = await read(expression, win); if (result) return result; await pause(40) } throw new Error('Timed out: ' + label) } async function fill(selector, value, win = main) { await read(`(()=>{const input=document.querySelector(${JSON.stringify(selector)});if(!input)throw new Error('Missing input');const proto=input.tagName==='TEXTAREA'?HTMLTextAreaElement.prototype:HTMLInputElement.prototype;Object.getOwnPropertyDescriptor(proto,'value').set.call(input,${JSON.stringify(value)});input.dispatchEvent(new Event('input',{bubbles:true}))})()`, win) } async function shot(name, win = main) { await phase('shot-fonts-' + name) await read('document.fonts.ready.then(()=>true)', win) await phase('shot-capture-' + name) await pause(100) const image = await Promise.race([win.webContents.capturePage(undefined, { stayHidden: true, stayAwake: true }), pause(10000).then(() => { throw new Error('capturePage timed out: ' + name) })]) await fs.writeFile(path.join(directory, name + '.png'), image.toPNG()) } ;(async () => { // Refuse to let a functional test write OMP profiles in the user's normal home. assert.equal(path.resolve(process.env.YCODE_OMP_HOME), path.resolve(directory, 'home')) server = createServer(async (req, res) => { let raw = ''; for await (const part of req) raw += part const body = JSON.parse(raw) requests.push({ model: body.model, stream: body.stream, authenticated: req.headers.authorization === 'Bearer LOCAL_TEST_KEY', messages: body.messages.map(message => ({ role: message.role, content: message.content })) }) if (req.headers.authorization !== 'Bearer LOCAL_TEST_KEY') { res.writeHead(401); res.end('{}'); return } const content = `LOCAL_REPLY_${body.model}` if (!body.stream) { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify({ choices: [{ message: { role: 'assistant', content } }] })); return } res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' }) const chunk = delta => `data: ${JSON.stringify({ id: 'local-test', object: 'chat.completion.chunk', created: 1, model: body.model, choices: [{ index: 0, delta, finish_reason: null }] })}\n\n` res.write(chunk({ role: 'assistant', content })) res.write(`data: ${JSON.stringify({ id: 'local-test', object: 'chat.completion.chunk', created: 1, model: body.model, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 } })}\n\ndata: [DONE]\n\n`) res.end() }) await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) const baseUrl = `http://127.0.0.1:${server.address().port}/v1` await fs.mkdir(process.env.YCODE_USER_DATA_DIR, { recursive: true }) app.setPath('userData', process.env.YCODE_USER_DATA_DIR) app.setPath('sessionData', process.env.YCODE_USER_DATA_DIR) await import(pathToFileURL(path.resolve('out/main/index.mjs')).href) await app.whenReady() assert.equal(path.resolve(app.getPath('userData')), path.resolve(process.env.YCODE_USER_DATA_DIR)) for (let i = 0; i < 100; i++) { main = BrowserWindow.getAllWindows().find(win => win.getTitle() === 'YCode'); if (main && !main.webContents.isLoading()) break; await pause(50) } assert.ok(main) await wait('!!window.ycode && !!document.querySelector(".chat-pane")', 'main renderer') await phase('main-ready') assert.equal(path.resolve((await read('window.ycode.app.info()')).configDir), path.resolve(process.env.YCODE_USER_DATA_DIR)) await read(`window.ycode.models.save(${JSON.stringify({ id: 'local-one', name: 'Local One', providerId: 'custom', model: 'model-one', baseUrl, apiKey: 'LOCAL_TEST_KEY', supportsToolCall: true, maxInputTokens: 64000, maxOutputTokens: 4096 })})`) await read('window.ycode.models.select("local-one")') await read('window.ycode.workspace.generate("空间甲")') const spaces = await read('window.ycode.workspace.generate("空间乙")') assert.equal(spaces.workspaces.length, 2) for (const space of spaces.workspaces) assert.ok((await fs.stat(space.root)).isDirectory()) const sessionA = await read(`window.ycode.session.create('A',${JSON.stringify({ workspaceId: spaces.workspaces[0].id, modelId: 'local-one' })})`) await fs.writeFile(path.join(spaces.workspaces[0].root, 'sentinel.txt'), 'SPACE_A') await fs.writeFile(path.join(spaces.workspaces[1].root, 'sentinel.txt'), 'SPACE_B') const sessionB = await read(`window.ycode.session.create('B',${JSON.stringify({ workspaceId: spaces.workspaces[1].id, modelId: 'local-one' })})`) assert.equal(await read(`window.ycode.resources.readText('sentinel.txt',${JSON.stringify(sessionA.id)})`), 'SPACE_A') assert.equal(await read(`window.ycode.resources.readText('sentinel.txt',${JSON.stringify(sessionB.id)})`), 'SPACE_B') await read(`window.ycode.chat.send({sessionId:${JSON.stringify(sessionA.id)},text:'Please reply with one short sentence.'})`) const doneA = await wait(`window.ycode.session.messages(${JSON.stringify(sessionA.id)}).then(messages=>messages.at(-1)?.status!=='streaming'&&messages.at(-1))`, 'real OMP turn A') assert.equal(doneA.status, 'done', JSON.stringify(doneA)) assert.equal(doneA.content, 'LOCAL_REPLY_model-one') await phase('first-real-omp-turn-passed') await read(`window.ycode.models.save(${JSON.stringify({ id: 'local-one', previousId: 'local-one', name: 'Local Edited', providerId: 'custom', model: 'model-two', baseUrl, supportsToolCall: true, maxInputTokens: 64000, maxOutputTokens: 4096 })})`) await read(`window.ycode.chat.send({sessionId:${JSON.stringify(sessionA.id)},text:'Reply again after changing the model.'})`) const doneB = await wait(`window.ycode.session.messages(${JSON.stringify(sessionA.id)}).then(messages=>messages.length===4&&messages.at(-1)?.status!=='streaming'&&messages.at(-1))`, 'real OMP changed model') assert.equal(doneB.status, 'done', JSON.stringify(doneB)) assert.equal(doneB.content, 'LOCAL_REPLY_model-two') await phase('changed-model-real-omp-turn-passed') assert.ok(requests.some(req => req.model === 'model-one' && req.authenticated)) assert.ok(requests.some(req => req.model === 'model-two' && req.authenticated)) await phase('opening-settings') await read('void window.ycode.app.openSettings("model")') for (let i = 0; i < 100; i++) { settings = BrowserWindow.getAllWindows().find(win => win !== main); if (settings && !settings.webContents.isLoading()) break; await pause(40) } await wait('!!document.querySelector(".models-settings-panel__add-button")', 'settings model list', settings) await phase('settings-loaded') await phase('settings-get-size') assert.deepEqual(await read('[innerWidth,innerHeight]', settings), [900, 680]) await phase('settings-size-passed') await shot('models-list', settings) await read('document.querySelector(".models-settings-panel__add-button").click()', settings) await wait('!!document.querySelector(".models-settings-panel__editor")', 'model editor', settings) await shot('model-add', settings) await read('document.querySelector(".models-settings-panel__editor-cancel").click()', settings) settings.close() const file = JSON.parse(await fs.readFile(path.join(directory, 'data', 'models.json'), 'utf8')) assert.equal(file.models.find(model => model.id === 'local-one').model, 'model-two') const yamlFiles = await fs.readdir(path.join(directory, 'home', '.omp', 'profiles')) for (const profile of yamlFiles) assert.ok(!(await fs.readFile(path.join(directory, 'home', '.omp', 'profiles', profile, 'agent', 'models.yml'), 'utf8')).includes('LOCAL_TEST_KEY')) const report = { scope: 'Real YCode Electron + preload + IPC + real OMP; controlled local upstream, not WorkBuddy parity or live supplier validation', sessions: [sessionA, sessionB], requests, result: 'PASS' } await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify(report, null, 2)) console.log('LOCAL_WORKFLOWS_PASS ' + directory) server.close(); app.quit() })().catch(async error => { console.error(error) if (main && !main.isDestroyed()) await shot('failure').catch(() => undefined) await fs.writeFile(path.join(directory, 'failure.json'), JSON.stringify({ error: String(error), requests }, null, 2)) server?.close(); app.exit(1) }) }