fix: resolve runtime, review, channel and chat state failures

Keep remote startup cancellable without blocking other sessions and surface local OpenCode submission failures promptly. Preserve supervisor project isolation, pause behavior and valid story destinations.

Restore generated-image channel delivery, Telegram reconnection on unchanged Save and DingTalk business-error retries. Preserve virtualized message disclosures using sparse state and release stale supervisor UI requests.

Add production-path regressions, Electron checks, isolated SQLite validation and audit evidence. Fix the SDK cleanup test fixture lifetime under garbage collection without weakening its assertions.

Validation: 6136 tests passed, 87 skipped; typecheck, lint and diff checks passed. Seven real text-model calls, current-source Linux x64 Host scenarios and large-history Electron comparisons completed.

Release note: Fix stuck runtime requests, inconsistent review results and channel delivery failures, and retain expanded details while browsing long conversations. Remote runtime fixes require an Agent build containing these changes.
This commit is contained in:
mesalogo
2026-10-05 10:25:43 +08:00
parent 126dbc57e2
commit bbf24c282a
49 changed files with 1710 additions and 106 deletions
+128
View File
@@ -0,0 +1,128 @@
import { app, BrowserWindow } from 'electron'
import assert from 'node:assert/strict'
import { join } from 'node:path'
import process from 'node:process'
import console from 'node:console'
app.setPath('userData', join(process.env.GB_EXPANSION_DIRECTORY, 'profile'))
app.commandLine.appendSwitch('force-device-scale-factor', '1')
app.whenReady().then(async () => {
const win = new BrowserWindow({ show: true, width: 1280, height: 800, useContentSize: true,
webPreferences: { sandbox: true, contextIsolation: true, nodeIntegration: false, backgroundThrottling: false } })
const errors = []
win.webContents.on('console-message', event => { if (event.level === 'error') errors.push(event.message) })
const evaluate = source => win.webContents.executeJavaScript(source)
const waitFor = source => evaluate(`new Promise((resolve, reject) => {
const deadline = performance.now() + 10000;
const check = () => { if (${source}) resolve(true); else if (performance.now() > deadline) reject(new Error(${JSON.stringify(source)})); else requestAnimationFrame(check); }; check(); })`)
const frames = () => evaluate('new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(() => resolve(true))))')
const click = async selector => {
await evaluate(`document.querySelector(${JSON.stringify(selector)}).scrollIntoView({block:'center'})`)
await frames()
const point = await evaluate(`(() => { const e = document.querySelector(${JSON.stringify(selector)}), r = e.getBoundingClientRect();
const x = r.x + r.width / 2, y = r.y + r.height / 2;
if (!e.contains(document.elementFromPoint(x,y))) throw new Error('Obscured ' + ${JSON.stringify(selector)});
return {x: Math.round(x), y: Math.round(y)}; })()`)
win.webContents.sendInputEvent({ type: 'mouseDown', ...point, button: 'left', clickCount: 1 })
win.webContents.sendInputEvent({ type: 'mouseUp', ...point, button: 'left', clickCount: 1 })
}
for (const count of [2000, 10000]) {
await win.loadURL(process.env.GB_EXPANSION_URL + '?count=' + count)
win.focus()
win.webContents.focus()
await waitFor('document.hasFocus() && document.querySelector("[data-message-id=m0] details")')
await evaluate('document.fonts.ready.then(() => true)')
const untouched = await evaluate(`(async () => {
const start = performance.now(), visited = new Set(); let maxRows = 0;
for (let index = 0; index < ${count}; index += Math.floor(${count} / 200)) {
window.expansionNavigate(index);
await new Promise((resolve, reject) => {
const deadline = performance.now() + 5000;
const check = () => {
const row = document.querySelector('[data-message-id=m' + index + ']');
if (row?.contains(document.activeElement) && document.activeElement.matches('article')) resolve();
else if (performance.now() > deadline) reject(new Error('Sweep navigation timeout'));
else requestAnimationFrame(check);
}; requestAnimationFrame(check);
});
const rows = [...document.querySelectorAll('.message-window-row')];
maxRows = Math.max(maxRows, rows.length); rows.forEach(row => visited.add(row.dataset.messageId));
}
return { visited: visited.size, entries: window.expansionEntryCount(), elapsedMs: performance.now() - start, maxRows };
})()`)
assert.ok(untouched.visited >= 1000, JSON.stringify(untouched))
assert.ok(untouched.maxRows <= 70, JSON.stringify(untouched))
if (process.env.GB_EXPANSION_DENSE_SOURCE) assert.ok(untouched.entries >= untouched.visited, JSON.stringify(untouched))
else if (!process.env.GB_HISTORY_SOURCE) assert.equal(untouched.entries, 0)
const times = []
let maxRows = 0
let maxNodes = 0
const visit = async index => {
const elapsed = await evaluate(`new Promise((resolve, reject) => {
const start = performance.now(); window.expansionNavigate(${index});
const check = () => {
const row = document.querySelector('[data-message-id=m${index}]');
if (row?.contains(document.activeElement) && document.activeElement.matches('article')) {
requestAnimationFrame(() => requestAnimationFrame(() => resolve(performance.now() - start)));
} else if (performance.now() - start > 10000) reject(new Error('Navigation timeout ${index}'));
else requestAnimationFrame(check);
}; requestAnimationFrame(check);
})`)
times.push(elapsed)
const size = await evaluate('({rows:document.querySelectorAll(".message-window-row").length,nodes:document.querySelectorAll("*").length})')
maxRows = Math.max(maxRows, size.rows)
maxNodes = Math.max(maxNodes, size.nodes)
assert.ok(size.rows <= 70, JSON.stringify(size))
}
for (let n = 0; n < 13; n++) {
const index = n * Math.floor(count / 15)
await visit(index)
const row = `[data-message-id=m${index}]`
await click(row + ' .tool-execution > summary')
await waitFor(`document.querySelector('${row} .tool-execution').open`)
await click(row + ' .tool-execution__input > summary')
await waitFor(`document.querySelector('${row} .tool-execution__input').open`)
await click(row + ' .message-reasoning > summary')
await waitFor(`document.querySelector('${row} .message-reasoning').open`)
}
assert.equal(await evaluate('!!document.querySelector("[data-message-id=m0]")'), false, 'original row must actually unmount')
await visit(0)
const restored = await evaluate('[...document.querySelectorAll("[data-message-id=m0] details")].map(e=>e.open)')
assert.deepEqual(restored, process.env.GB_HISTORY_SOURCE ? [false, false, false] : [true, true, true])
// Native keyboard toggling still closes a restored disclosure.
const summary = '[data-message-id=m0] .message-reasoning > summary'
await evaluate(`document.querySelector('${summary}').focus()`)
win.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Space' })
win.webContents.sendInputEvent({ type: 'keyUp', keyCode: 'Space' })
await waitFor(`document.querySelector('[data-message-id=m0] .message-reasoning').open === ${Boolean(process.env.GB_HISTORY_SOURCE)}`)
assert.equal(await evaluate('document.querySelector("textarea").value'), 'Preserved draft')
if (!process.env.GB_EXPANSION_DENSE_SOURCE && !process.env.GB_HISTORY_SOURCE) await waitFor('window.expansionEntryCount() === 38')
const changedEntries = await evaluate('window.expansionEntryCount()')
await evaluate('window.expansionDeleteMessages()')
await waitFor('document.querySelectorAll(".message-window-row").length === 0')
await frames()
if (!process.env.GB_EXPANSION_DENSE_SOURCE && !process.env.GB_HISTORY_SOURCE) await waitFor('window.expansionEntryCount() === 0')
const afterDeletion = await evaluate('window.expansionEntryCount()')
if (!process.env.GB_EXPANSION_DENSE_SOURCE && !process.env.GB_HISTORY_SOURCE) assert.equal(afterDeletion, 0)
times.sort((a,b)=>a-b)
console.log('MessageExpansion ' + JSON.stringify({ version: process.env.GB_HISTORY_SOURCE ? 'baseline' : process.env.GB_EXPANSION_DENSE_SOURCE ? 'dense' : 'sparse', count,
samples: times.length, navigationTwoFramesP95Ms: times[Math.ceil(times.length * .95)-1],
navigationTwoFramesMedianMs: times[Math.floor(times.length/2)], maxRows, maxNodes, restored, untouched, changedEntries, afterDeletion,
electron: process.versions.electron, platform: process.platform, arch: process.arch }))
}
await win.loadURL(process.env.GB_EXPANSION_URL + '?supervisor=1')
await waitFor('document.querySelector(".supervisor-workspace__detail .link-button")')
await click('.supervisor-workspace__detail .link-button')
await waitFor('typeof window.finishSource === "function"')
await click('#change-language')
await waitFor('document.documentElement.lang === "en-US" && document.querySelector(".supervisor-workspace__detail .link-button")')
await evaluate('window.finishSource()')
await frames()
const stuck = await evaluate('document.querySelector(".supervisor-workspace__detail .link-button").disabled')
assert.equal(stuck, Boolean(process.env.GB_HISTORY_SOURCE))
assert.equal(await evaluate('document.body.innerText.includes("Stale content")'), false)
console.log('MessageExpansion ' + JSON.stringify({ scenario: 'supervisor-invalidation', version: process.env.GB_HISTORY_SOURCE ? 'baseline' : 'fixed', stuck }))
assert.deepEqual(errors, [])
win.destroy()
app.quit()
}).catch(error => { console.error(error); app.exit(1) })
@@ -0,0 +1,71 @@
import { useState } from 'react'
import { createRoot } from 'react-dom/client'
import { ChatHistoryPane } from '../../src/renderer/src/ChatHistoryPane'
import type { Conversation } from '../../src/renderer/src/chat-conversation'
import { PageShell } from '../../src/renderer/src/WorkspacePrimitives'
import { UiLocaleProvider } from '../../src/renderer/src/i18n/UiLocaleProvider'
import { changeUiLocale } from '../../src/renderer/src/i18n'
import { SupervisorWorkspace } from '../../src/renderer/src/SupervisorWorkspace'
import '../../src/renderer/src/supervisor-workspace.css'
import '../../src/renderer/src/styles.css'
const count = Number(new URLSearchParams(location.search).get('count') ?? 2000)
const conversation: Conversation = { id: 'synthetic', title: 'Synthetic history', updatedAt: 1,
messages: Array.from({ length: count }, (_, index) => ({
id: `m${index}`, role: 'assistant', state: 'complete', createdAt: index + 1,
content: `Synthetic message ${index}. ${'A paragraph of rendered history. '.repeat(8)}`,
reasoning: `Reasoning ${index}. ${'A reasoning paragraph. '.repeat(8)}`,
tools: [{ callId: `tool${index}`, name: 'read', summary: `Read synthetic file ${index}`, state: 'completed',
input: '{"path":"synthetic.txt"}', output: 'Synthetic output\n'.repeat(8) }]
})) }
const noop = (): void => {}
const complete = async (): Promise<void> => {}
const artifacts = new Map()
// Fixture-only retention probe. Restore Map.set as soon as the expansion map
// first writes; no production diagnostic API or persistent instrumentation.
const expansionMaps = new Set<Map<unknown, unknown>>()
const originalSet = Map.prototype.set
Map.prototype.set = function (key, value) {
if (value && typeof value.open === 'boolean' && typeof value.automaticOpen === 'boolean') {
expansionMaps.add(this)
Map.prototype.set = originalSet
}
return originalSet.call(this, key, value)
}
Object.assign(window, { expansionEntryCount: () => [...expansionMaps].reduce((total, map) => total + map.size, 0) })
function Fixture() {
const [current, setCurrent] = useState(conversation)
const [navigation, setNavigation] = useState({ conversationId: conversation.id, messageId: 'm0', requestId: 1 })
Object.assign(window, {
expansionNavigate: (index: number) => setNavigation(current => ({ ...current, messageId: `m${index}`, requestId: current.requestId + 1 })),
expansionDeleteMessages: () => setCurrent({ ...conversation, messages: [] })
})
return <div className="app-shell"><div className="app-frame"><header className="topbar">Synthetic expansion validation</header>
<div className="app-content"><main className="workspace"><PageShell variant="reading"><div className="chat-scroll-region">
<ChatHistoryPane conversation={current} active artifactById={artifacts} locale="en-US"
conversationHtmlRenderingEnabled={false} noteMessageNavigation={navigation}
onOpenImageModelSettings={noop} onReselectImageSources={noop} onEditImage={noop}
onCopyMessage={async () => true} onDownloadImage={noop} onOpenCitationContext={complete}
onOpenCitationSource={complete} onOpenImage={noop} onRespondQuestion={complete} onRetry={noop}
onScrollSnapshotChange={noop} onSetInput={noop} quickActions={[]} visibleMessageCount={count}
onVisibleMessageCountChange={noop} />
</div><footer className="composer-wrap"><textarea aria-label="Composer" defaultValue="Preserved draft" /></footer></PageShell></main></div>
</div></div>
}
const supervisor = new URLSearchParams(location.search).has('supervisor')
if (supervisor) {
const at = '2026-10-01T00:00:00.000Z'
Object.assign(window, { goodbuddy: { supervision: {
overview: async () => [{ id: 'result', storyLineId: 'story', sourceId: null, summary: 'Synthetic review', changeDigest: '',
createdAt: at, scope: { kind: 'global' }, timeRange: { from: at, to: at }, openItems: [] }],
graph: async () => ({ storyLine: null, events: [{ id: 'event', title: 'Event', description: '', occurred_at: at }],
entities: [], relations: [], eventEntities: [], sources: [{ id: 'source', title: 'Synthetic source', occurred_at: at }],
eventSources: [{ event_id: 'event', source_id: 'source' }] }),
source: () => new Promise(resolve => Object.assign(window, { finishSource: () => resolve({ title: 'Synthetic source', content: 'Stale content', occurredAt: at }) }))
} } })
}
createRoot(document.getElementById('root')!).render(<UiLocaleProvider>{supervisor
? <><button id="change-language" onClick={() => void changeUiLocale('en-US')}>Change language</button><SupervisorWorkspace tab="graph" /></>
: <Fixture />}</UiLocaleProvider>)
+15 -1
View File
@@ -41,6 +41,7 @@ void app.whenReady().then(async () => {
const updates: object[] = []
let pollsAborted = 0
let pollingUnavailable = false
let pollingConflict = false
const originalNetFetch = net.fetch
// This is the only Telegram substitution: production driver construction and IPC stay intact.
net.fetch = async (input, init) => {
@@ -61,6 +62,9 @@ void app.whenReady().then(async () => {
assert.deepEqual(body.allowed_updates, ['message'])
try { await delay(50, undefined, { signal: init?.signal ?? undefined }) }
catch (error) { pollsAborted++; throw error }
if (pollingConflict) {
return new Response(JSON.stringify({ ok: false, error_code: 409 }), { status: 409 })
}
if (pollingUnavailable) {
return new Response(JSON.stringify({ ok: false, error_code: 503 }), { status: 503 })
}
@@ -174,6 +178,16 @@ void app.whenReady().then(async () => {
assert.equal(database.getProject(conversation.projectId!).channel, 'telegram')
await wait(() => run(`!document.querySelector('button.primary-button').disabled && document.querySelector('.channel-settings-card .capability-card__header > span')?.textContent === 'Connected'`), 'saved connection visible')
pollingConflict = true
await wait(() => run(`document.querySelector('.channel-settings-card [role="alert"]')?.textContent.includes('polling conflict')`), 'terminal polling conflict visible')
pollingConflict = false
const validationsBeforeRecovery = calls.filter(call => call.method === 'getMe').length
await click('button.primary-button')
await wait(() => calls.filter(call => call.method === 'getMe').length === validationsBeforeRecovery + 1, 'unchanged Save restarts conflicted poller')
await wait(() => run(`!document.querySelector('button.primary-button').disabled && document.querySelector('.channel-settings-card .capability-card__header > span')?.textContent === 'Connected'`), 'recovered connection visible')
await click('button.primary-button')
await wait(() => run(`!document.querySelector('button.primary-button').disabled`), 'healthy unchanged Save completed')
assert.equal(calls.filter(call => call.method === 'getMe').length, validationsBeforeRecovery + 1, 'Healthy Save must not restart polling')
await fill('input[aria-label="Telegram Bot Token"]', 'unsaved-token-placeholder')
await fill('textarea[aria-label="Telegram allowed sender IDs"]', '700001\n700003')
pollingUnavailable = true
@@ -223,7 +237,7 @@ void app.whenReady().then(async () => {
assert.deepEqual(blocked, [], 'No unexpected external requests')
console.log(JSON.stringify({ telegramSmoke: 'passed', uiConfigSaveTest: true, productionPreloadIpc: true,
productionExecutor: true, desktopConversationPersisted: true, reloadRestored: true,
disableCancelsModel: true, liveStatusPreservesDrafts: true, clearTokenDisablesAndPreservesHistory: true,
disableCancelsModel: true, liveStatusPreservesDrafts: true, unchangedSaveRecoversConflict: true, clearTokenDisablesAndPreservesHistory: true,
modelCalls: payloads.length, telegramReplies: replies.length,
pollsAborted, externalNetworkCalls: 0 }))
} finally {