feat: complete WorkBuddy parity and Windows release workflow

This commit is contained in:
xiaohan.yuan committed 2026-10-18 12:45:21 +08:00
1 parent 143516b3a8
commit d5512df549
434 files changed
+54977 -1854

No files matched your search

+121 -24
View File
@@ -15,9 +15,11 @@
// - 回收样式表:`contexts-BkjwYSZT.css`(`._branchLabel_*`)、`src-CyXHA0IC.css`(`.cr-context-usage*`)。
// - 文案:已回收 i18n 表 `chatInput.contextUsage.*`(title 上下文用量 / used 已使用 /
// tooltip "{percentage}% · {used} / {total} 上下文已使用" / needMessage / requesting / unavailable)。
// - REA 保留的 `ContextUsageDisplay` 源码进一步确认:百分比固定一位小数,token 达 1K 后
// 固定一位小数并显示为 K(例如 `20.0% · 12.8K / 64.0K`)。
//
// YCode 侧数据真实性:分支名由主进程真实执行 git;用量只用上游真实 usage(经 OMP 归一化)+
// 模型自己的真实上下文窗口,拿不到就显示原版的「无法获取」文案,不填 0、不估算。
// 该回合冻结的真实上下文窗口。历史样本没有分母时保持未知,不套当前模型配置。
const fs = require('node:fs/promises')
const path = require('node:path')
const assert = require('node:assert/strict')
@@ -29,6 +31,9 @@ const QA_PROMPT_TOKENS = 12800
const QA_COMPLETION_TOKENS = 40
const QA_CONTEXT_WINDOW = 64000
const QA_EXPECTED_PERCENT = 20
const QA_EXPECTED_PERCENT_TEXT = `${QA_EXPECTED_PERCENT.toFixed(1)}%`
const QA_EXPECTED_USED_TEXT = '12.8K'
const QA_EXPECTED_TOTAL_TEXT = '64.0K'
const RING_CIRCUMFERENCE = 2 * Math.PI * 7
if (!process.versions.electron) {
@@ -53,8 +58,19 @@ if (!process.versions.electron) {
const now = Date.now()
const sessions = [{ id: 'context-info-session', title: 'Context Info', cwd: repo, modelId: 'local-usage', createdAt: now, updatedAt: now + 1, messages: [
{ id: 'context-user', role: 'user', content: '上下文信息验收', createdAt: now, status: 'done', requestId: 'context-run' },
{ id: 'context-assistant', role: 'assistant', content: '收到。', createdAt: now + 1, status: 'done', requestId: 'context-run', tools: [], parts: [] }
{ id: 'context-assistant', role: 'assistant', content: '收到。', createdAt: now + 1, status: 'done', requestId: 'context-run', tools: [], parts: [],
contextUsage: { usage: { inputTokens: 8000, outputTokens: 40, totalTokens: 8040 } } }
] }]
const nativePath=path.join(data,'omp-sessions','session-native-context-session.jsonl')
await fs.mkdir(path.dirname(nativePath),{recursive:true})
await fs.writeFile(nativePath,[
{id:'native-user',type:'message',message:{role:'user',content:[{type:'text',text:'old'}]}},
{id:'native-reply',parentId:'native-user',type:'message',message:{role:'assistant',model:'old-model',usage:{input:4009,cacheRead:1152,output:125,totalTokens:5286},stopReason:'stop',timestamp:now-1000}}
].map(value=>JSON.stringify(value)).join('\n'))
sessions.push({id:'native-context-session',title:'Native Usage Recovery',cwd:repo,modelId:'local-usage',createdAt:now-1000,updatedAt:now-1000,messages:[
{id:'legacy-user',role:'user',content:'old',createdAt:now-1000,status:'done',requestId:'legacy-native',nativeEntryId:'native-user',nativeSessionPath:nativePath},
{id:'legacy-reply',role:'assistant',content:'old reply',createdAt:now-900,status:'done',requestId:'legacy-native',modelId:'local-usage'}
]})
await fs.writeFile(path.join(data, 'sessions.json'), JSON.stringify({ sessions }))
const env = { ...process.env, YCODE_QA_DIRECTORY: directory, YCODE_USER_DATA_DIR: data, YCODE_OMP_HOME: home,
YCODE_QA_REAL_OMP: process.env.YCODE_OMP_PATH || path.join(process.env.LOCALAPPDATA || '', 'Ygent', 'omp.exe'),
@@ -133,7 +149,7 @@ if (!process.versions.electron) {
const track=ring.querySelector('circle:not(.cr-context-usage__ring-progress)');const progress=ring.querySelector('.cr-context-usage__ring-progress');
const wrapper=right.querySelector('.cr-context-usage-tooltip-trigger');const model=right.querySelector('.cr-model-selector');
return {ring:{box:box(ring),radius:style.borderRadius,width:style.width,height:style.height,transition:style.transitionProperty+' '+style.transitionDuration,
display:style.display,role:ring.getAttribute('role'),background:style.backgroundColor},
display:style.display,role:ring.getAttribute('role'),background:style.backgroundColor,ariaLabel:ring.getAttribute('aria-label')},
wrapper:wrapper?box(wrapper):null,
svg:svg?{box:box(svg),viewBox:svg.getAttribute('viewBox'),className:svg.getAttribute('class')}:null,
track:track?{r:track.getAttribute('r'),strokeWidth:track.getAttribute('stroke-width'),fill:track.getAttribute('fill'),stroke:getComputedStyle(track).stroke}:null,
@@ -161,8 +177,15 @@ if (!process.versions.electron) {
bar:bar?{box:box(bar),height:getComputedStyle(bar).height,radius:getComputedStyle(bar).borderRadius,fillWidth:popover.querySelector('.cr-context-usage-popover__bar-fill')?.style.width}:null,
rows,bodyText:popover.textContent,tokenTertiary:probe('color','--cr-text-tertiary')}})()`
const requestingTipSource = `(()=>{const tip=document.querySelector('.cr-context-usage-requesting-tip');if(!tip)return null;
const style=getComputedStyle(tip);const text=tip.querySelector('.cr-context-usage-requesting-tip__text');
return {role:tip.getAttribute('role'),text:text?.textContent,background:style.backgroundColor,border:style.border,
borderRadius:style.borderRadius,padding:style.padding,font:style.fontSize+'/'+style.lineHeight,
popoverPresent:!!document.querySelector('.cr-context-usage-popover')}})()`
;(async () => {
// 受控上游:最后一次 chunk 带真实 usage(provider 语义,含总 prompt tokens)。
let omitUsage=false
server = createServer(async (req, res) => {
let raw = ''
for await (const part of req) raw += part
@@ -172,7 +195,7 @@ if (!process.versions.electron) {
const chunk = payload => `data: ${JSON.stringify({ id: 'local-usage', object: 'chat.completion.chunk', created: 1, model: body.model, ...payload })}\n\n`
res.write(chunk({ choices: [{ index: 0, delta: { role: 'assistant', content: 'USAGE_REPLY' }, finish_reason: null }] }))
res.write(chunk({ choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
usage: { prompt_tokens: QA_PROMPT_TOKENS, completion_tokens: QA_COMPLETION_TOKENS, total_tokens: QA_PROMPT_TOKENS + QA_COMPLETION_TOKENS } }))
...(omitUsage?{}:{usage: { prompt_tokens: QA_PROMPT_TOKENS, completion_tokens: QA_COMPLETION_TOKENS, total_tokens: QA_PROMPT_TOKENS + QA_COMPLETION_TOKENS }}) }))
res.write('data: [DONE]\n\n')
res.end()
})
@@ -250,20 +273,20 @@ if (!process.versions.electron) {
check('ring track matches the recovered circle', [ring.track.r, ring.track.strokeWidth, ring.track.fill], ['7', '2', 'none'])
check('ring progress circle matches the recovered geometry', [ring.progress.r, ring.progress.strokeWidth, ring.progress.strokeLinecap, ring.progress.transform], ['7', '2', 'round', 'rotate(-90 8 8)'])
check('ring progress keeps the recovered dashoffset transition', ring.progress.transition, 'stroke-dashoffset 0.3s')
check('no usage yet: ring stays empty', Math.round(ring.progress.dashOffset), Math.round(RING_CIRCUMFERENCE))
check('legacy usage without a frozen denominator keeps the ring empty',
Math.round(ring.progress.dashOffset), Math.round(RING_CIRCUMFERENCE))
await step('usage-popover-without-usage')
await step('usage-requesting-tip-without-denominator')
await read(`(()=>{document.querySelector('.cr-context-usage').click();return true})()`)
await wait('!!document.querySelector(".cr-context-usage-popover")', 'usage popover')
const emptyPopover = await read(popoverSource)
snapshots.emptyPopover = emptyPopover
check('popover keeps the recovered 280-wide card', [Math.round(emptyPopover.box[2]), emptyPopover.padding, emptyPopover.borderRadius, emptyPopover.font], [280, '16px', '16px', '12px/18px'])
check('popover keeps the recovered header', [emptyPopover.title.text, emptyPopover.title.font, Math.round(emptyPopover.close[2]), Math.round(emptyPopover.close[3])], ['上下文用量', '12px/400/20px', 16, 16])
check('no usage yet: popover says so instead of inventing numbers', emptyPopover.bodyText.includes('暂时无法获取用量明细,请稍后重试'), true)
check('no usage yet: popover shows no percentage', emptyPopover.percent, null)
await read(`(()=>{document.querySelector('.cr-context-usage-popover__close').click();return true})()`)
await wait('!document.querySelector(".cr-context-usage-popover")', 'popover closed')
check('close button dismisses the popover', await read('!document.querySelector(".cr-context-usage-popover")'), true)
await wait('!!document.querySelector(".cr-context-usage-popover")', 'known usage with unknown denominator')
const legacyPopover = await read(popoverSource)
snapshots.legacyPopover = legacyPopover
check('known token count is visible even without its historical window',legacyPopover.usedValue,'8.0K/ 未知')
check('legacy unknown denominator is not replaced by the current model configuration',legacyPopover.percent.text,'—')
check('legacy classifications remain unknown',legacyPopover.rows.map(row=>row.value),['—','—','—','—','—'])
await read(`(()=>{document.querySelector('.cr-context-usage').click();return true})()`)
await wait('!document.querySelector(".cr-context-usage-popover")', 'legacy popover closed')
check('clicking the ring dismisses the usage panel', await read('!document.querySelector(".cr-context-usage-popover")'), true)
await step('real-turn-usage')
await read(`(()=>{const input=[...document.querySelectorAll('.wb-composer__input')].find(node=>node.offsetParent!==null);
@@ -280,6 +303,8 @@ if (!process.versions.electron) {
check('ring dashoffset follows the real provider usage', Math.abs(usedRing.progress.dashOffset - expectedOffset) < 0.6, true)
check('ring stroke uses the recovered progress colour', usedRing.progress.stroke, 'rgb(156, 163, 175)')
check('ring track uses the recovered track colour', usedRing.track.stroke, 'rgba(128, 128, 128, 0.2)')
check('ring accessible label uses the same recovered compact usage sentence', usedRing.ring.ariaLabel,
`${QA_EXPECTED_PERCENT_TEXT} · ${QA_EXPECTED_USED_TEXT} / ${QA_EXPECTED_TOTAL_TEXT} 上下文已使用`)
await step('usage-tooltip')
await hover('.cr-context-usage')
@@ -289,7 +314,8 @@ if (!process.versions.electron) {
background:style.backgroundColor,color:style.color,borderRadius:style.borderRadius,padding:style.padding,font:style.fontSize+'/'+style.lineHeight,
arrow:!!node.querySelector('.wb-tooltip__arrow'),tokenBg:probe('backgroundColor','--wb-tooltip-bg'),tokenFg:probe('color','--wb-tooltip-text-color')}})()`)
snapshots.tooltip = tooltip
check('hover tooltip uses the recovered usage sentence', tooltip?.text, `${QA_EXPECTED_PERCENT}% · ${QA_PROMPT_TOKENS} / ${QA_CONTEXT_WINDOW} 上下文已使用`)
check('hover tooltip uses the recovered one-decimal and compact-token formatting', tooltip?.text,
`${QA_EXPECTED_PERCENT_TEXT} · ${QA_EXPECTED_USED_TEXT} / ${QA_EXPECTED_TOTAL_TEXT} 上下文已使用`)
check('tooltip keeps the recovered base surface', [tooltip.background, tooltip.color, tooltip.borderRadius, tooltip.padding, tooltip.font, tooltip.arrow],
[tooltip.tokenBg || 'rgba(0, 0, 0, 0.75)', tooltip.tokenFg || 'rgb(255, 255, 255)', '8px', '4px 8px', '12px/20px', true])
@@ -299,16 +325,38 @@ if (!process.versions.electron) {
await pause(200)
const usedPopover = await read(popoverSource)
snapshots.usedPopover = usedPopover
check('popover shows the real percentage', usedPopover.percent.text, `${QA_EXPECTED_PERCENT}%`)
check('popover keeps the recovered one-decimal percentage', usedPopover.percent.text, QA_EXPECTED_PERCENT_TEXT)
check('popover percent keeps 20/700/28', usedPopover.percent.font, '20px/700/28px')
check('popover shows the real used/total pair', usedPopover.usedValue, `${QA_PROMPT_TOKENS} / ${QA_CONTEXT_WINDOW}`)
check('popover keeps the recovered compact used/total pair', usedPopover.usedValue,
`${QA_EXPECTED_USED_TEXT}/ ${QA_EXPECTED_TOTAL_TEXT}`)
check('popover used label keeps the recovered copy', [usedPopover.usedLabel.text, usedPopover.usedLabel.color], ['已使用', usedPopover.tokenTertiary])
check('popover bar keeps the recovered 8px track', [Math.round(usedPopover.bar.box[3]), usedPopover.bar.height, usedPopover.bar.radius], [8, '8px', '3px'])
check('popover bar fill follows the same percentage', usedPopover.bar.fillWidth, `${QA_EXPECTED_PERCENT}%`)
const prepared = await read(`window.ycode.session.messages('context-info-session').then(messages=>{const m=messages.filter(item=>item.role==='assistant').at(-1);return {breakdown:m?.contextBreakdown,requestId:m?.requestId}})`)
snapshots.preparedInput=prepared
check('real OMP provider hook records this request, not an unrelated historical sample',
[prepared.breakdown?.scope,prepared.breakdown?.requestId,prepared.breakdown?.method],['provider-request',prepared.requestId,'utf8-estimate'])
check('classification contains actual system, tool definitions, dialogue and skill catalogue',
['system','tools','conversation','skills'].every(key=>prepared.breakdown?.counts[key]>0),true)
check('category sum is independent from controlled upstream count',prepared.breakdown?.inputTokens!==QA_PROMPT_TOKENS,true)
check('classification bar uses its own independent local estimate',Math.abs(parseFloat(usedPopover.bar.fillWidth)-Math.min(100,prepared.breakdown.inputTokens/QA_CONTEXT_WINDOW*100))<0.01,true)
check('popover legend keeps the five recovered categories', usedPopover.rows.map(row => row.name),
['系统提示词', '工具及子智能体', '对话消息', '连接器及MCP', '技能'])
check('legend swatches keep the recovered 8px dots', usedPopover.rows.map(row => row.swatch), [[8, 8], [8, 8], [8, 8], [8, 8], [8, 8]])
check('legend values stay unknown instead of fabricated', usedPopover.rows.map(row => row.value), ['—', '—', '—', '—', '—'])
check('legend values are explicitly labeled local estimates',usedPopover.rows.every(row=>row.value.startsWith('≈')),true)
await fs.writeFile(path.join(directory,'context-breakdown-light.png'),(await main.webContents.capturePage()).toPNG())
await step('frozen-window-survives-model-config-change')
await read(`window.ycode.models.save(${JSON.stringify({ id: 'local-usage', previousId: 'local-usage', name: 'Local Usage', providerId: 'custom',
model: 'model-usage', baseUrl, apiKey: 'LOCAL_TEST_KEY', supportsToolCall: true,
maxInputTokens: QA_CONTEXT_WINDOW * 2, maxOutputTokens: 4096 })})`)
await pause(250)
const frozenRing = await read(ringSource)
snapshots.frozenWindow = frozenRing
check('historical usage keeps its own frozen denominator after model config changes',
frozenRing.ring.ariaLabel,
`${QA_EXPECTED_PERCENT_TEXT} · ${QA_EXPECTED_USED_TEXT} / ${QA_EXPECTED_TOTAL_TEXT} 上下文已使用`)
check('historical usage does not silently inherit the newly configured model window',
Math.abs(frozenRing.progress.dashOffset - expectedOffset) < 0.6, true)
await step('dark-theme-via-native-settings')
// 真实路径:设置窗口切成深色,聊天区 `data-cr-theme` 随之变化,输入区 token 跟着切。
@@ -327,8 +375,8 @@ if (!process.versions.electron) {
snapshots.settingsWindow = await read(`({url:location.href,search:location.search,bodyClass:document.body.className,rootHtml:(document.querySelector('#root')?.innerHTML||'').slice(0,240)})`, settings)
await wait('!!document.querySelector(".settings-window-root")', 'settings root', settings)
await read(`(()=>{const tab=document.querySelector('[data-settings-tab="appearance"]');if(tab)tab.click();return true})()`, settings)
await wait(`!!document.querySelector('.segmented')`, 'appearance panel', settings)
await read(`(()=>{const node=[...document.querySelectorAll('.segmented__item')].find(item=>item.textContent.trim()==='深色');if(!node)throw new Error('missing dark option');node.click();return true})()`, settings)
await wait(`!!document.querySelector('.appearance-card')`, 'appearance panel', settings)
await read(`(()=>{const node=document.querySelector('.appearance-card[aria-label="深色"]');if(!node)throw new Error('missing dark option');node.click();return true})()`, settings)
await wait(`document.documentElement.dataset.theme==='dark'`, 'main window dark theme')
await wait(`document.querySelector('.chat-pane')?.dataset.crTheme==='dark'`, 'composer dark scope')
await pause(250)
@@ -339,16 +387,65 @@ if (!process.versions.electron) {
tokenElevated:probe('backgroundColor','--cr-bg-elevated'),tokenActive:probe('backgroundColor','--cr-bg-active'),
tokenTextPrimary:probe('color','--wb-color-text-primary'),scope:document.querySelector('.chat-pane')?.dataset.crTheme ?? null}})()`)
snapshots.dark = { lightTokens, darkPopover }
await fs.writeFile(path.join(directory,'context-breakdown-dark.png'),(await main.webContents.capturePage()).toPNG())
check('native settings switches the composer to the dark scope', [lightTokens.scope, darkPopover.scope], ['light', 'dark'])
check('dark popover uses the recovered dark elevated surface', darkPopover.popoverBackground, 'rgb(36, 38, 41)')
check('dark popover bar uses the recovered dark active fill', darkPopover.bar, 'rgba(255, 255, 255, 0.1)')
check('dark pill switches to the dark primary text colour', [darkPopover.pillColor, lightTokens.pillColor, darkPopover.pillColor === darkPopover.tokenTextPrimary], ['rgb(255, 255, 255)', 'rgb(0, 0, 0)', true])
await read(`(()=>{const node=[...document.querySelectorAll('.segmented__item')].find(item=>item.textContent.trim()==='浅色');if(!node)throw new Error('missing light option');node.click();return true})()`, settings)
await read(`(()=>{const node=document.querySelector('.appearance-card[aria-label="浅色"]');if(!node)throw new Error('missing light option');node.click();return true})()`, settings)
await wait(`document.documentElement.dataset.theme==='light'`, 'main window light theme')
await wait(`document.querySelector('.chat-pane')?.dataset.crTheme==='light'`, 'composer light scope')
await read(`document.querySelector('.settings-window-close').click()`, settings)
for (let index = 0; index < 200 && !settings.isDestroyed(); index++) await pause(20)
check('light theme restored through the same settings window', await read(`[document.documentElement.dataset.theme,document.querySelector('.chat-pane').dataset.crTheme]`), ['light', 'light'])
await step('persisted-usage-after-renderer-reload')
await wait(`window.ycode.chat.activeRun('context-info-session').then(run=>!run)`, 'real turn persistence barrier')
const persistedUsage = await read(`window.ycode.session.messages('context-info-session').then(messages=>messages.filter(message=>message.role==='assistant').at(-1)?.contextUsage)`)
snapshots.persistedUsage = persistedUsage
check('actual turn stores its upstream input sample and original model window',
[persistedUsage?.usage?.inputTokens, persistedUsage?.contextWindow], [QA_PROMPT_TOKENS, QA_CONTEXT_WINDOW])
const reloaded = new Promise(resolve => main.webContents.once('did-finish-load', resolve))
main.webContents.reload()
await reloaded
await wait(`document.querySelector('.cr-context-usage')?.getAttribute('aria-label')===${JSON.stringify(`${QA_EXPECTED_PERCENT_TEXT} · ${QA_EXPECTED_USED_TEXT} / ${QA_EXPECTED_TOTAL_TEXT} 上下文已使用`)}`,
'restored usage with frozen denominator')
snapshots.reloadedUsage = await read(ringSource)
check('renderer reload recovers usage without adopting the newly doubled model window',
snapshots.reloadedUsage.ring.ariaLabel,
`${QA_EXPECTED_PERCENT_TEXT} · ${QA_EXPECTED_USED_TEXT} / ${QA_EXPECTED_TOTAL_TEXT} 上下文已使用`)
await fs.writeFile(path.join(directory, 'context-usage-restored.png'), (await main.webContents.capturePage()).toPNG())
await step('new-request-without-provider-usage')
omitUsage=true
await read(`(()=>{const input=[...document.querySelectorAll('.wb-composer__input')].find(node=>node.offsetParent!==null);
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value').set.call(input,'NO_USAGE_NEW_REQUEST');input.dispatchEvent(new Event('input',{bubbles:true}));return true})()`)
await read(`document.querySelector('.cr-input-toolbar__send').click()`)
await wait(`window.ycode.session.messages('context-info-session').then(messages=>messages.filter(m=>m.role==='user').at(-1)?.content==='NO_USAGE_NEW_REQUEST')`,'new round created')
await wait(`window.ycode.chat.activeRun('context-info-session').then(run=>!run)`,'no-usage round complete')
const noUsage=await read(`window.ycode.session.messages('context-info-session').then(messages=>messages.filter(m=>m.role==='assistant').at(-1))`)
snapshots.noUsage={contextUsage:noUsage.contextUsage,contextBreakdown:noUsage.contextBreakdown,requestId:noUsage.requestId}
check('a new no-usage request cannot inherit the previous provider total',noUsage.contextUsage??null,null)
check('the new request still retains its independently prepared classification',noUsage.contextBreakdown?.requestId,noUsage.requestId)
await wait(`document.querySelector('.cr-context-usage')?.getAttribute('aria-label')?.startsWith('≈')`,'local estimate clearly identified')
const noUsageReload=new Promise(resolve=>main.webContents.once('did-finish-load',resolve));main.webContents.reload();await noUsageReload
await wait(`document.querySelector('.cr-context-usage')?.getAttribute('aria-label')?.startsWith('≈')`,'estimate restored without reviving old provider sample')
check('reopening a no-usage round does not pick an older assistant sample',
await read(`window.ycode.session.contextUsage('context-info-session').then(info=>[!!info.sample,info.breakdown?.requestId])`),[false,noUsage.requestId])
await step('native-history-usage-recovery')
const nativeUpdatedBefore=await read(`window.ycode.session.list().then(sessions=>sessions.find(session=>session.id==='native-context-session').updatedAt)`)
await wait(`!!document.querySelector('[data-session-id="native-context-session"]')`,'legacy sidebar item')
await read(`document.querySelector('[data-session-id="native-context-session"]').click()`)
await wait(`document.querySelector('.cr-context-usage')?.getAttribute('aria-label')?.includes('5.2K / 未知')`,'native sample recovered without a fabricated denominator')
await read(`document.querySelector('.cr-context-usage').click()`)
await wait(`document.querySelector('.yc-context-source')?.textContent.includes('原生记录恢复')`,'source of recovered sample visible')
const recovered=await read(`window.ycode.session.messages('native-context-session').then(messages=>messages.at(-1).contextUsage)`)
snapshots.nativeRecovered=recovered
check('real IPC saves the recovered native sample with its provenance',
[recovered.usage.inputTokens,recovered.contextWindow??null,recovered.source,recovered.nativeEntryId],[5161,null,'native-history','native-reply'])
check('restoring usage metadata does not reorder the sidebar task',await read(`window.ycode.session.list().then(sessions=>sessions.find(session=>session.id==='native-context-session').updatedAt)`),nativeUpdatedBefore)
await fs.writeFile(path.join(directory,'context-native-recovered.png'),(await main.webContents.capturePage()).toPNG())
check('querying old records does not start a native agent',await fs.readdir(path.join(process.env.YCODE_USER_DATA_DIR,'context-usage')).then(files=>files.some(name=>name.includes('native-context-session'))),false)
check('no renderer error logs', rendererErrors, [])
server.close()