fix: simplify continuing work from supervisor reviews

Let users continue discussions without opening sources first, edit a question, and send source-linked review context to the target conversation. Add explicit conversation navigation and remove redundant context disclosure and entity-writeback controls. Hide empty automatic supervision metrics until activity exists.

Release note: Continue work directly from supervision feedback or graph sources with relevant context, fewer controls, and clearer automatic supervision empty states.
This commit is contained in:
mesalogo
2026-09-28 01:51:23 +08:00
parent f35198df01
commit d47f967781
17 changed files with 494 additions and 104 deletions
+57 -12
View File
@@ -229,8 +229,8 @@ app
})()`)
assert(report.pageWidth <= width && report.scrollWidth <= report.clientWidth, 'Recap/automatic overflow')
assert.equal(report.refresh, 1, 'One refresh owner per panel')
assert.equal(report.reports, tab === 'plans')
assert.equal(report.suggestions, tab === 'plans')
assert.equal(report.reports, tab === 'plans' && scenario === 'populated')
assert.equal(report.suggestions, tab === 'plans' && scenario === 'populated')
assert.equal(report.recap, tab === 'overview' && scenario !== 'empty')
if (tab === 'overview') {
assert(!report.text.includes('暂无自动监督报告') && !report.text.includes('待确认记忆'))
@@ -240,13 +240,13 @@ app
assert(report.clientWidth - report.proseWidth <= 50, 'Prose fills the panel within card padding')
} else assert(report.text.includes('还没有成功回顾'))
} else {
assert.equal(report.text.includes('暂无自动监督报告'), scenario !== 'populated')
assert(report.text.includes('自动监督报告'))
assert(!report.text.includes('暂无自动监督报告'))
assert.equal(report.text.includes('自动监督报告'), scenario === 'populated')
}
reports.push({ scenario, tab, ...report })
const prefix = `recap-${scenario}-${tab}-${width}`
await writeFile(join(artifacts, `${prefix}.png`), (await win.webContents.capturePage()).toPNG())
if (tab === 'plans') {
if (tab === 'plans' && scenario === 'populated') {
for (const anchor of ['heartbeat-memory-title', 'heartbeat-reports-title', 'heartbeat-runs-title']) {
await js(`document.getElementById('${anchor}').scrollIntoView({block:'start'})`)
if (anchor === 'heartbeat-reports-title' && scenario === 'populated') await js(`document.querySelector('#heartbeat-panel-history button[aria-expanded=false]')?.click()`)
@@ -277,8 +277,9 @@ app
return
}
if (process.env.GOODBUDDY_SUPERVISOR_AUTOMATIC_OVERVIEW) {
for (const empty of [false, true]) {
await win.loadURL(process.env.GOODBUDDY_SUPERVISOR_URL + (empty ? '' : '?menu=1'))
for (const scenario of ['populated', 'empty', 'plan-only']) {
const empty = scenario !== 'populated'
await win.loadURL(process.env.GOODBUDDY_SUPERVISOR_URL + (scenario === 'populated' ? '?menu=1' : scenario === 'plan-only' ? '?plan-only=1' : ''))
await wait('!!document.querySelector("#supervisor-tab-plans")')
await js('document.fonts.ready')
await js('document.documentElement.dataset.theme = "light"')
@@ -295,22 +296,27 @@ app
overview: !!panel.querySelector('#heartbeat-panel-overview'),
metrics: !!panel.querySelector('.heartbeat-center__metrics'),
trend: !!panel.querySelector('#heartbeat-trend-title'),
audit: !!panel.querySelector('#heartbeat-runs-title'),
audit: !!panel.querySelector('#heartbeat-runs-title'),
reports: !!panel.querySelector('#heartbeat-reports-title'),
suggestions: !!panel.querySelector('#heartbeat-panel-suggestions'),
emptyReports: panel.textContent.includes('暂无自动监督报告'),
plans: !!panel.querySelector('#heartbeat-panel-plans'),
create: [...panel.querySelectorAll('button')].filter(b => b.textContent === '创建自动监督计划').length,
emptyStates: panel.querySelectorAll('#heartbeat-panel-overview .empty-state').length,
activity: panel.querySelectorAll('.supervisor-activity__item').length };
})()`)
assert(report.pageWidth <= width && report.scrollWidth <= report.panelWidth, 'Automatic panel overflow')
for (const key of ['overview', 'metrics', 'trend', 'audit', 'plans']) assert.equal(report[key], tab === 'plans', key)
for (const key of ['overview', 'plans']) assert.equal(report[key], tab === 'plans', key)
for (const key of ['metrics', 'trend', 'audit', 'reports', 'suggestions']) assert.equal(report[key], tab === 'plans' && !empty, key)
assert.equal(report.emptyReports, false)
assert.equal(report.create, tab === 'plans' ? 1 : 0)
assert.equal(report.emptyStates, 0)
if (tab === 'activity') assert.equal(report.activity, 3)
reports.push({ empty, tab, ...report })
const prefix = `automatic-${empty ? 'empty' : 'populated'}-${tab}-light-${width}`
reports.push({ scenario, empty, tab, ...report })
const prefix = `automatic-${scenario}-${tab}-light-${width}`
await writeFile(join(artifacts, `${prefix}.png`), (await win.webContents.capturePage()).toPNG())
if (tab === 'plans') {
for (const anchor of ['heartbeat-trend-title', 'heartbeat-runs-title']) {
for (const anchor of empty ? [] : ['heartbeat-trend-title', 'heartbeat-runs-title']) {
await js(`document.getElementById('${anchor}').scrollIntoView({block:'start'})`)
await settle()
await writeFile(join(artifacts, `${prefix}-${anchor}.png`), (await win.webContents.capturePage()).toPNG())
@@ -335,6 +341,45 @@ app
}
assert.deepEqual(errors, [])
await writeFile(join(artifacts, 'automatic-overview-measurements.json'), JSON.stringify(reports, null, 2))
await win.loadURL(process.env.GOODBUDDY_SUPERVISOR_URL + '?preview=1')
await wait('document.querySelector(".supervisor-workspace")?.getAttribute("aria-busy") === "false"')
await js('document.querySelector("#supervisor-tab-graph").click()')
await wait('!!document.querySelector(".supervisor-workspace__map")')
await js(`document.querySelector('.supervisor-workspace__map [aria-label="观察工作过程"]').dispatchEvent(new MouseEvent('click', {bubbles:true}))`)
await settle()
await js('document.querySelector(".supervisor-workspace__detail > .link-button").click()')
await wait('!!document.querySelector(".supervisor-discussion > button")')
await js('document.querySelector(".supervisor-discussion > button").click()')
await wait('!!document.querySelector(".supervisor-discussion textarea")')
await js('document.fonts.ready')
const previews = []
for (const theme of ['light', 'dark']) {
await js(`document.documentElement.dataset.theme = '${theme}'`)
for (const width of [1440, 390]) {
win.setContentSize(width, 1100)
await js(`document.querySelector('.supervisor-discussion').scrollIntoView({block:'center'})`)
await settle()
const report = await js(`(() => {
const preview = document.querySelector('.supervisor-discussion');
const box = e => { const r = e.getBoundingClientRect(); return {left:r.left, right:r.right, top:r.top, bottom:r.bottom}; };
return { width:innerWidth, pageWidth:document.documentElement.scrollWidth,
clientWidth:preview.clientWidth, scrollWidth:preview.scrollWidth,
question:preview.querySelector('textarea').value, text:preview.textContent,
controls:[...preview.querySelectorAll('textarea, button')].map(box) };
})()`)
assert(report.pageWidth <= width && report.scrollWidth <= report.clientWidth, 'Discussion preview overflow')
assert(report.question.trim() && report.text.includes('模拟会议记录'), 'Discussion question and destination visible')
assert(report.controls.every(box => box.left >= 0 && box.right <= width && box.top >= 0 && box.bottom <= 1100), 'Preview controls fit viewport')
assert(await js('!document.querySelector(".supervisor-discussion [role=alert]")'), 'Preview loads without error')
assert(await js('!document.querySelector(".supervisor-discussion details")'), 'No redundant context disclosure')
previews.push({ theme, ...report })
await writeFile(join(artifacts, `discussion-preview-${theme}-${width}.png`), (await win.webContents.capturePage()).toPNG())
}
}
await js('document.querySelector(".supervisor-discussion .secondary-button").click()')
await wait('!document.querySelector(".supervisor-discussion textarea")')
assert.deepEqual(errors, [])
await writeFile(join(artifacts, 'discussion-preview-measurements.json'), JSON.stringify(previews, null, 2))
console.log(JSON.stringify({ artifacts, cases: reports.length, errors }))
win.destroy()
app.quit()
@@ -146,6 +146,8 @@ Object.defineProperty(window, 'goodbuddy', {
? {}
: {
supervision: {
continueContext: async () => ({ prompt: '模拟讨论上下文:本周已核对交付清单,负责人已确认。\n\n原始依据:模拟会议记录。外部评审时间仍待确认,下一步需要核对验收条件。' }),
continue: async () => { throw new Error('Preview fixture must not send a message') },
execution: async () => ({ active: params.has('activity'), ...(params.has('activity') ? { runId: 'activity-1' } : {}) }),
batches: async ({ runId, offset, limit }: { runId: string; offset: number; limit: number }) => Array.from({ length: runId === 'activity-0' ? 6 : 12 }, (_, index) => ({
id: `leaf-${index}`, projectId: index < 3 ? 'Atlas' : 'Beacon', conversationId: `conversation-${Math.floor(index / 2)}`,
@@ -204,6 +206,7 @@ Object.defineProperty(window, 'goodbuddy', {
entityAction: async () => {},
relationAction: async () => {},
source: async () => ({
...(params.has('preview') ? { sourceType: 'conversation', sourceId: 'simulated-conversation-uuid' } : {}),
title: '模拟会议记录',
content: '视觉 fixture,不是真实用户数据。',
occurredAt: '2026-09-21T08:00:00.000Z'
@@ -252,7 +255,7 @@ createRoot(document.getElementById('root')!).render(
<HeartbeatCenter
applicationSettings={applicationSettingsSchema.parse({ checkUpdatesOnStartup: true, updateSource: 'github', modelDownloadSource: 'modelscope', localToolEnvironment: defaultLocalToolEnvironmentSettings, conversationHtmlRenderingEnabled: true, remoteProjectsEnabled: false })}
onUpdateApplicationSettings={async () => true}
configs={menu ? [{ id: 'plan', name: '模拟每日回顾', scope: { kind: 'global' }, timezone: 'Asia/Shanghai', recurrence: { type: 'daily', localTime: '09:00' }, enabled: true, lookbackHours: 48, retentionDays: 90, nextRunAt: '2026-09-24T01:00:00.000Z', createdAt, updatedAt: createdAt }] : []}
configs={menu || params.has('plan-only') ? [{ id: 'plan', name: '模拟每日回顾', scope: { kind: 'global' }, timezone: 'Asia/Shanghai', recurrence: { type: 'daily', localTime: '09:00' }, enabled: true, lookbackHours: 48, retentionDays: 90, nextRunAt: '2026-09-24T01:00:00.000Z', createdAt, updatedAt: createdAt }] : []}
runs={menu ? [{ id: 'run', configId: 'plan', trigger: 'scheduled', scheduledFor: createdAt, status: 'completed', attemptCount: 1, createdAt, updatedAt: createdAt }] : []}
entries={menu ? [{ id: 'report', configId: 'plan', runId: 'run', scheduledFor: createdAt, summary: '模拟自动监督报告:交付计划已更新,待核对负责人和验收日期。', highlights: ['保留原始依据,核对交付时间。'], proposedMemoryIds: ['memory'], followUpTaskIds: ['task'], createdAt }] : []}
memories={menu ? [{ id: 'memory', scope: 'global', type: 'preference', content: '模拟建议:周会总结保留负责人和下一次检查日期。', confidence: 0.9, salience: 0.8, status: 'proposed', createdAt, updatedAt: createdAt }] : []}
@@ -267,7 +270,8 @@ createRoot(document.getElementById('root')!).render(
onSetMemoryStatus={noop}
onSetTaskStatus={noop}
onUseFollowUpTask={() => {}}
onRetryLoad={noop}
onRetryLoad={noop}
onOpenConversation={params.has('preview') ? () => { throw new Error('Preview fixture must not navigate') } : undefined}
/>
</PageShell>}
</div>