// Production Electron integration with isolated data and a localhost-only model. const assert = require('node:assert/strict') const fs = require('node:fs/promises') const path = require('node:path') if (!process.versions.electron) { ;(async () => { await fs.mkdir(path.resolve('.tmp'), { recursive: true }) const directory = await fs.mkdtemp(path.resolve('.tmp/three-modules-')) const env = { ...process.env, YCODE_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: path.join(directory, 'data'), YCODE_OMP_HOME: path.join(directory, 'home'), YCODE_DISABLE_GPU: '1', YCODE_QA_OFFSCREEN: '1', YCODE_DISABLE_SYSTEM_INTEGRATION: '1' } await Promise.all(['data', 'home', 'workspace'].map(name => fs.mkdir(path.join(directory, name)))) 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', bytes => { log += bytes; process.stdout.write(bytes) }) child.stderr.on('data', bytes => { log += bytes }) const timer = setTimeout(() => child.kill(), 120_000) child.once('exit', async code => { clearTimeout(timer) await fs.writeFile(path.join(directory, 'process.log'), log) console.log(JSON.stringify({ directory, exitCode: code })) if (code) console.error(log.slice(-12000)) process.exitCode = code ?? 1 }) })().catch(error => { console.error(error); process.exitCode = 1 }) } else { const { app, BrowserWindow } = require('electron') const { pathToFileURL } = require('node:url') const { createServer } = require('node:http') const directory = process.env.YCODE_QA_DIRECTORY const relative = path.relative(path.resolve('.tmp'), directory || '') assert.ok(relative.startsWith('three-modules-') && !relative.includes(path.sep) && !path.isAbsolute(relative)) app.setPath('home', path.join(directory, 'home')) const checks = [] const requests = [] const errors = [] let win, server, releaseManual const manualGate = new Promise(resolve => { releaseManual = resolve }) const read = source => win.webContents.executeJavaScript(source) const pause = ms => new Promise(resolve => setTimeout(resolve, ms)) const wait = async (operation, label) => { for (let attempt = 0; attempt < 650; attempt++) { const result = await operation() if (result) return result await pause(40) } throw Error(`Timeout: ${label}`) } const waitUi = source => wait(() => read(source), source) const check = label => { checks.push(label); console.log(`PASS ${label}`) } const invoke = (namespace, method, ...args) => read(`window.ycode[${JSON.stringify(namespace)}][${JSON.stringify(method)}](...${JSON.stringify(args)})`) const fill = (selector, value, tag = 'HTMLInputElement', event = 'input') => read(`(() => { const input = document.querySelector(${JSON.stringify(selector)}); Object.getOwnPropertyDescriptor(${tag}.prototype, 'value').set.call(input, ${JSON.stringify(value)}); input.dispatchEvent(new Event(${JSON.stringify(event)}, {bubbles:true})); })()`) const sse = (body, delta, finishReason = null) => `data: ${JSON.stringify({ id: 'three-modules-response', object: 'chat.completion.chunk', created: 1, model: body.model, choices: [{ index: 0, delta, finish_reason: finishReason }] })}\n\n` const navigate = async label => { await read(`Array.from(document.querySelectorAll('.conversation-list-tab-button')).find(el => el.textContent.trim() === ${JSON.stringify(label)}).click()`) } ;(async () => { server = createServer(async (req, res) => { let raw = '' for await (const chunk of req) raw += chunk const body = JSON.parse(raw || '{}') const prompt = [...(body.messages || [])].reverse().find(message => message.role === 'user') const text = JSON.stringify(prompt?.content ?? '') requests.push({ model: body.model, text, tools: body.tools?.map(tool => tool.function?.name) }) if (text.includes('FORCED_FAILURE')) { res.writeHead(400, { 'content-type': 'application/json' }) res.end(JSON.stringify({ error: { message: 'CONTROLLED_UPSTREAM_FAILURE' } })) return } res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' }) if (text.includes('PROJECT_FULL_ACCESS_COMMAND') && !body.messages.some(message => message.role === 'tool')) { const tool = body.tools?.find(item => item.function?.name === 'execute_command') assert.ok(tool, 'actual model tool schema must expose command execution') const args = { command: `node -e "require('fs').writeFileSync('project-full-access.txt','FULL_ACCESS_OK')"` } res.write(sse(body, { role: 'assistant', tool_calls: [{ index: 0, id: 'three-modules-command', type: 'function', function: { name: tool.function.name, arguments: JSON.stringify(args) } }] })) res.write(sse(body, {}, 'tool_calls')) } else if (text.includes('SCHEDULE_WRITE_ARTIFACT') && !body.messages.some(message => message.role === 'tool')) { const tool = body.tools?.find(item => /^(write|write_file|write_to_file)$/i.test(item.function?.name)) assert.ok(tool, 'actual model tool schema must expose file writing') const args = { path: 'scheduled-report.md', file_path: 'scheduled-report.md', content: '# Scheduled artifact\n\nGenerated through the real tool loop.\n' } res.write(sse(body, { role: 'assistant', tool_calls: [{ index: 0, id: 'three-modules-write', type: 'function', function: { name: tool.function.name, arguments: JSON.stringify(args) } }] })) res.write(sse(body, {}, 'tool_calls')) } else { res.write(sse(body, { role: 'assistant', content: text.includes('MANUAL_HELD') ? 'MANUAL_PARTIAL' : 'SCHEDULE_SUCCEEDED' })) if (text.includes('MANUAL_HELD')) await manualGate if (res.destroyed) return res.write(sse(body, { content: '\nFINAL_PERSISTED_RESULT' })) res.write(sse(body, {}, 'stop')) } res.end('data: [DONE]\n\n') }) await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) await import(pathToFileURL(path.resolve('out/main/index.mjs')).href) await app.whenReady() await wait(async () => { win = BrowserWindow.getAllWindows().find(item => item.getTitle() === 'YCode') return win && !win.webContents.isLoading() && await read('!!window.ycode?.automation && !!document.querySelector(".chat-pane")') }, 'production renderer') win.setSize(1336, 902) win.webContents.on('console-message', (...args) => { const details = args[0] const level = typeof details === 'object' ? details.level : args[1] const message = typeof details === 'object' ? details.message : args[2] if (level === 'error' || Number(level) >= 3) errors.push(message) }) const registry = await invoke('workspace', 'attach', path.join(directory, 'workspace')) const workspaceId = registry.registry?.selectedWorkspaceId ?? registry.selectedWorkspaceId assert.ok(workspaceId, JSON.stringify(registry)) await invoke('models', 'save', { id: 'three-modules-model', name: 'Three modules QA', providerId: 'custom', model: 'three-modules-upstream', baseUrl: `http://127.0.0.1:${server.address().port}/v1`, authMode: 'none', supportsToolCall: true, maxToolRounds: 5, supportsReasoning: true, defaultEffort: 'medium', supportedEfforts: ['low', 'medium', 'high', 'xhigh', 'max'], canDisableThinking: true }) await invoke('models', 'select', 'three-modules-model') const project = await invoke('projects', 'create', { name: '三模块验收', instructions: 'QA_PROJECT_INSTRUCTION', workspaceId, experts: [], skills: [], connectors: [] }) const definition = await invoke('automation', 'create', { name: '自动产物验收', prompt: 'SCHEDULE_WRITE_ARTIFACT', schedule: { type: 'once', scheduledAt: new Date(Date.now() + 2500).toISOString(), timeZone: 'Asia/Shanghai' }, execution: { projectId: project.projectId, workspaceId, modelId: 'three-modules-model', permissionMode: 'full-access' } }) const completed = await wait(async () => (await invoke('automation', 'listRuns', { automationId: definition.id })) .find(run => ['succeeded', 'failed', 'cancelled'].includes(run.status)), 'scheduled execution terminal') assert.equal(completed.status, 'succeeded', JSON.stringify(completed)) assert.ok(completed.sessionId && completed.runId) const messages = await invoke('session', 'messages', completed.sessionId) assert.equal(messages.at(-1).status, 'done') assert.ok(messages.at(-1).content.includes('FINAL_PERSISTED_RESULT')) const session = (await invoke('session', 'list')).find(item => item.id === completed.sessionId) assert.equal(session.projectId, project.projectId) assert.equal(session.workspaceId, workspaceId) assert.equal(session.modelId, 'three-modules-model') await waitUi(`!!document.querySelector('[data-session-id="${completed.sessionId}"]')`) assert.equal(await fs.readFile(path.join(directory, 'workspace', 'scheduled-report.md'), 'utf8'), '# Scheduled artifact\n\nGenerated through the real tool loop.\n') check('scheduled task executes a real model/tool turn with frozen project, workspace and model; terminal follows persistence') const manualDefinition = await invoke('automation', 'create', { name: '运行中验收', prompt: 'MANUAL_HELD', schedule: { type: 'once', scheduledAt: new Date(Date.now() + 3600_000).toISOString(), timeZone: 'Asia/Shanghai' }, execution: { workspaceId, modelId: 'three-modules-model' } }) const beforeManual = Date.now() const acknowledged = await invoke('automation', 'runNow', manualDefinition.id) assert.equal(acknowledged.status, 'running') assert.ok(Date.now() - beforeManual < 5000, 'runNow must acknowledge before the held response completes') const running = await wait(async () => (await invoke('automation', 'listRuns', { automationId: manualDefinition.id })).find(run => run.sessionId), 'real session linked while running') assert.equal(running.status, 'running') assert.equal(await invoke('automation', 'runNow', manualDefinition.id).then(() => false, () => true), true) check('manual execution acknowledges immediately, publishes real session while streaming and rejects overlapping execution') releaseManual() await wait(async () => (await invoke('automation', 'listRuns', { automationId: manualDefinition.id })).some(run => run.status === 'succeeded'), 'manual terminal') await invoke('automation', 'setEnabled', manualDefinition.id, false) assert.equal(await invoke('automation', 'runNow', manualDefinition.id).then(() => false, () => true), true) const failing = await invoke('automation', 'create', { name: '失败验收', prompt: 'FORCED_FAILURE', schedule: { type: 'once', scheduledAt: new Date(Date.now() + 3600_000).toISOString(), timeZone: 'UTC' }, execution: { workspaceId, modelId: 'three-modules-model' } }) await invoke('automation', 'runNow', failing.id) const failure = await wait(async () => (await invoke('automation', 'listRuns', { automationId: failing.id })).find(run => run.status === 'failed'), 'actual upstream failure') assert.ok(failure.error) assert.equal((await invoke('session', 'messages', failure.sessionId)).at(-1).status, 'error') check('upstream error is persisted as a failed automation and assistant error; paused task cannot run') const snapshot = await invoke('library', 'getSnapshot') const artifact = snapshot.artifacts.find(entry => entry.sessionId === completed.sessionId && entry.name === 'scheduled-report.md') assert.ok(artifact, JSON.stringify(snapshot)) assert.equal(artifact.availability, 'available') assert.equal(artifact.projectId, project.projectId) assert.equal(snapshot.artifacts.length, 1, 'read-only source and unrelated workspace files must not be indexed') await navigate('资料库') await waitUi('!!document.querySelector(".library-page")') await read(`Array.from(document.querySelectorAll('.library-nav-item')).find(el => el.textContent.includes('本地产物')).click()`) await waitUi('!!document.querySelector(".library-file-row")') await read('document.querySelector(".library-file-row button[aria-label^=更多操作]").click()') await waitUi('!!document.querySelector(".library-file-menu .library-favorite")') await read('document.querySelector(".library-favorite").click()') await wait(async () => (await invoke('library', 'getSnapshot')).artifacts.find(entry => entry.id === artifact.id)?.favorite, 'favorite saved through visible more menu') await fs.writeFile(path.join(directory, 'library-light.png'), (await win.webContents.capturePage()).toPNG()) await read('document.querySelector(".library-file-button").click()') await waitUi('!!document.querySelector(".chat-pane") && document.body.innerText.includes("Scheduled artifact")') const recent = await invoke('library', 'getSnapshot') assert.ok(recent.artifacts[0].recentAt) assert.equal(recent.artifacts[0].favorite, true) check('library lists only the actual generated artifact and opens it through its source session; favorite/recent persist') const loaded = new Promise(resolve => win.webContents.once('did-finish-load', resolve)) win.webContents.reload() await loaded await waitUi('!!window.ycode?.automation && !!document.querySelector(".conversation-list-tab-button")') assert.equal((await invoke('library', 'getSnapshot')).artifacts[0].favorite, true) assert.ok((await invoke('automation', 'list')).some(item => item.id === definition.id)) await navigate('定时任务') await waitUi('!!document.querySelector(".automation-page")') await waitUi('document.body.innerText.includes("自动产物验收")') await fs.writeFile(path.join(directory, 'automations-light.png'), (await win.webContents.capturePage()).toPNG()) await read('localStorage.setItem("ycode:theme", "dark"); window.dispatchEvent(new StorageEvent("storage", {key:"ycode:theme",newValue:"dark"}))') await waitUi('document.documentElement.dataset.theme === "dark"') await pause(350) await fs.writeFile(path.join(directory, 'automations-dark.png'), (await win.webContents.capturePage()).toPNG()) check('definitions, run records and library preferences survive renderer reload; real pages render in light/dark') await read('document.querySelector(".automation-create-main").click()') await waitUi('!!document.querySelector(".automation-editor-modal")') await read('document.querySelector("button[aria-label=重复频率]").click()') await waitUi('document.querySelectorAll(".atm-freq-menu-popover [role=option]").length === 7') await read('Array.from(document.querySelectorAll(".atm-freq-menu-popover [role=option]")).find(el => el.textContent.trim() === "双周").click()') await waitUi('!document.querySelector(".atm-freq-menu-popover")') await fill('.automation-name-input', '表单验收') await fill('.automation-prompt-input', 'FORM_DAILY_TASK', 'HTMLTextAreaElement') await fs.writeFile(path.join(directory, 'automation-editor-dark.png'), (await win.webContents.capturePage()).toPNG()) await read('document.querySelector(".automation-editor-footer .automation-button--primary").click()') await waitUi(`!document.querySelector('.automation-editor-modal') && !!document.querySelector('button[aria-label="编辑 表单验收"]')`) const formCreated = (await invoke('automation', 'list')).find(item => item.name === '表单验收') assert.equal(formCreated.schedule.frequency, 'biweekly') assert.ok(formCreated.schedule.rrule.includes('INTERVAL=2')) assert.equal(formCreated.execution.modelId, 'three-modules-model') await read(`document.querySelector('button[aria-label="编辑 表单验收"]').click()`) await waitUi('!!document.querySelector(".automation-prompt-input")') await fill('.automation-prompt-input', 'FORM_EDITED_TASK', 'HTMLTextAreaElement') await read('document.querySelector(".automation-editor-footer .automation-button--primary").click()') await waitUi('!document.querySelector(".automation-editor-modal")') const formEdited = (await invoke('automation', 'list')).find(item => item.id === formCreated.id) assert.equal(formEdited.prompt, 'FORM_EDITED_TASK') assert.equal(formEdited.schedule.anchorAt, formCreated.schedule.anchorAt) await read(`document.querySelector('button[aria-label="暂停 表单验收"]').click()`) await waitUi(`!!document.querySelector('button[aria-label="启用 表单验收"]')`) assert.equal((await invoke('automation', 'list')).find(item => item.id === formCreated.id).status, 'PAUSED') await read(`document.querySelector('button[aria-label="更多操作 表单验收"]').click()`) await waitUi('!!document.querySelector(".automation-task-menu")') await read('Array.from(document.querySelectorAll(".automation-task-menu button")).find(el=>el.textContent.trim()==="删除").click()') await waitUi('!!document.querySelector(".automation-confirm-dialog")') await read('document.querySelector(".automation-confirm-actions .automation-button--danger").click()') await waitUi(`!document.querySelector('.automation-confirm-dialog') && !document.querySelector('button[aria-label="编辑 表单验收"]')`) assert.ok(!(await invoke('automation', 'list')).some(item => item.id === formCreated.id)) check('visible editor creates/edits a biweekly rule without moving its anchor, then pauses/deletes through real IPC') await navigate('资料库') await waitUi('!!document.querySelector(".library-page")') await read(`Array.from(document.querySelectorAll('.library-nav-item')).find(el => el.textContent.includes('本地产物')).click()`) await waitUi('!!document.querySelector(".library-file-row")') await pause(350) await fs.writeFile(path.join(directory, 'library-dark.png'), (await win.webContents.capturePage()).toPNG()) await read('document.querySelector(".conversation-list-tab-button-more").click()') await waitUi('!!document.querySelector(".yc-project-more button")') await read('document.querySelector(".yc-project-more button").click()') await waitUi('!!document.querySelector(".project-grid__card[role=button]")') await read('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))') await read(`document.querySelector('.project-grid__card[role=button]') .dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))`) await waitUi('!!document.querySelector("textarea[aria-label=项目任务输入]")') await read('document.querySelector(".project-detail-view__project-composer .cr-model-selector__trigger").click()') await waitUi('!!document.querySelector(".cr-model-selector__menu")') await waitUi('!!document.querySelector(".cr-model-selector__menu .cr-model-selector__item[aria-selected=true]")') await pause(220) const modelPoint = await read(`(() => { const item = document.querySelector('.cr-model-selector__menu .cr-model-selector__item[aria-selected="true"]') if (!item) return null const rect = item.getBoundingClientRect() return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 } })()`) assert.ok(modelPoint) win.webContents.sendInputEvent({ type: 'mouseMove', x: Math.round(modelPoint.x), y: Math.round(modelPoint.y) }) await waitUi('!!document.querySelector(".cr-model-selector__sub-menu-action")') await pause(220) const effortPoint = await read(`(() => { const item = document.querySelector('.cr-model-selector__sub-menu-action') const rect = item.getBoundingClientRect() return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 } })()`) win.webContents.sendInputEvent({ type: 'mouseMove', x: Math.round(effortPoint.x), y: Math.round(effortPoint.y) }) await waitUi('!!document.querySelector(".cr-model-selector__sub-panel")') await read(`Array.from(document.querySelectorAll('.cr-model-selector__sub-panel-item')) .find(el => el.textContent.trim() === '极致').click()`) await waitUi(`Array.from(document.querySelectorAll('.cr-model-selector__sub-panel-item')) .some(el => el.textContent.trim() === '极致' && el.classList.contains('cr-model-selector__sub-panel-item--active'))`) win.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' }) win.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'Escape' }) await waitUi('!document.querySelector(".cr-model-selector__menu")') const projectSessionIdsBefore = new Set((await invoke('session', 'list')).map(item => item.id)) await fill('textarea[aria-label=项目任务输入]', 'PROJECT_FULL_ACCESS_COMMAND', 'HTMLTextAreaElement') await read('document.querySelector(".project-detail-view__project-composer .cr-send-button").click()') await waitUi('!document.querySelector(".project-detail-view") && document.body.innerText.includes("PROJECT_FULL_ACCESS_COMMAND") && document.body.innerText.includes("FINAL_PERSISTED_RESULT")') const projectTask = (await invoke('session', 'list')).find(item => item.projectId === project.projectId && !projectSessionIdsBefore.has(item.id)) assert.ok(projectTask) assert.equal((await invoke('session', 'messages', projectTask.id)).find(message => message.role === 'user').content, 'PROJECT_FULL_ACCESS_COMMAND') assert.equal(projectTask.projectId, project.projectId) assert.equal(projectTask.thinkingLevel, 'max') assert.equal(await fs.readFile(path.join(directory, 'workspace', 'project-full-access.txt'), 'utf8'), 'FULL_ACCESS_OK') check('project composer forwards max thinking and full-access, switches immediately, and executes a real privileged tool turn') // A separate, unused directory avoids Windows locking an OMP process cwd. const failingWorkspace = path.resolve(directory, 'first-send-failure-workspace') assert.ok(failingWorkspace.startsWith(path.resolve(directory) + path.sep)) await fs.mkdir(failingWorkspace) const failingRegistry = await invoke('workspace', 'attach', failingWorkspace) await invoke('projects', 'create', { name: '失败恢复项目', instructions: '', workspaceId: failingRegistry.selectedWorkspaceId, experts: [], skills: [], connectors: [] }) await read('document.querySelector(".conversation-list-tab-button-more").click()') await waitUi('!!document.querySelector(".yc-project-more button")') await read('document.querySelector(".yc-project-more button").click()') await waitUi(`Array.from(document.querySelectorAll('.project-grid__card[role=button]')).some(el => el.textContent.includes('失败恢复项目'))`) await read(`Array.from(document.querySelectorAll('.project-grid__card[role=button]')).find(el => el.textContent.includes('失败恢复项目')).click()`) await waitUi('!!document.querySelector("textarea[aria-label=项目任务输入]")') const beforeFailedSend = (await invoke('session', 'list')).length await fs.rmdir(failingWorkspace) try { await fill('textarea[aria-label=项目任务输入]', 'RESTORE_PROJECT_DRAFT', 'HTMLTextAreaElement') await read('document.querySelector(".project-detail-view__project-composer .cr-send-button").click()') await waitUi('!document.querySelector(".project-detail-view") && document.querySelector(".wb-composer__input")?.value === "RESTORE_PROJECT_DRAFT"') assert.equal((await invoke('session', 'list')).length, beforeFailedSend) } finally { await fs.mkdir(failingWorkspace) } check('project first-send failure restores the composer draft and does not leave an empty session') assert.deepEqual(errors, []) assert.ok(requests.every(request => request.model === 'three-modules-upstream')) await fs.writeFile(path.join(directory, 'report.json'), JSON.stringify({ result: 'PASS', checks, completed, failure, artifact, requests, rendererErrors: errors }, null, 2)) console.log(`THREE_MODULES_PASS ${directory}`) server.closeAllConnections() server.close() app.exit(0) })().catch(async error => { console.error(error) releaseManual?.() if (win && !win.isDestroyed()) await fs.writeFile(path.join(directory, 'failure.png'), (await win.webContents.capturePage()).toPNG()).catch(() => {}) await fs.writeFile(path.join(directory, 'failure.json'), JSON.stringify({ error: String(error.stack || error), checks, requests, rendererErrors: errors }, null, 2)) server?.closeAllConnections() server?.close() app.exit(1) }) }