feat: let agents read supervisor story graphs

Expose saved decisions, timelines and source evidence through read-only Story Graph tools across Model, Harness and local/remote OpenCode and Continue. Supervisor enablement and runtime assignments govern discovery and calls; reads do not invoke models or modify stored evidence. Remote use requires matching Desktop and Agent changes.

Make Continue discussion show the complete editable message with a separated destination and shared action layout. Sending uses the visible draft without reattaching removed context.

Validation: existing focused MCP, runtime, controlled Linux Host, discussion component and Electron layout checks plus typechecks; staged diff check passed. Cross-scope fact reuse, historical reconstruction and full package acceptance remain deferred.

Release note: Agents can consult saved supervisor decisions and their sources when continuing work. Continue discussion now lets you review and edit the complete message before sending.
This commit is contained in:
mesalogo
2026-09-28 12:38:00 +08:00
parent 83a3ea261e
commit 66f77b83c2
61 changed files with 1527 additions and 150 deletions
+36 -4
View File
@@ -61,6 +61,29 @@ app
await settle()
}
const reports = []
const checkDiscussion = async () => {
const report = await js(`(() => {
const editor = document.querySelector('.supervision-discussion-editor');
const box = e => { const r = e.getBoundingClientRect(); return {left:r.left, right:r.right, top:r.top, bottom:r.bottom}; };
const target = editor.querySelector('.supervision-discussion-editor__target');
const field = editor.querySelector('.field'), label = field.querySelector('span'), input = field.querySelector('textarea');
const footer = editor.querySelector('footer');
return { target:box(target), field:box(field), label:box(label), input:box(input), footer:box(footer),
buttons:[...footer.querySelectorAll('button')].map(box), align:getComputedStyle(footer).justifyContent,
clientWidth:editor.clientWidth, scrollWidth:editor.scrollWidth, value:input.value,
border:getComputedStyle(target).borderBottomWidth, disclosures:editor.querySelectorAll('details').length };
})()`)
assert(report.value.includes('本周已核对交付清单') && report.value.includes('原始依据:模拟会议记录'), 'Actual reference content is editable')
assert.equal(report.field.top - report.target.bottom, 16, 'Target to message spacing')
assert.equal(report.input.top - report.label.bottom, 8, 'Label to input spacing')
assert.equal(report.footer.top - report.field.bottom, 16, 'Message to footer spacing')
assert.equal(report.border, '1px', 'Destination is separated')
assert.equal(report.align, 'flex-end', 'Shared right aligned footer')
assert(Math.abs(report.buttons[1].right - report.footer.right) < 1, 'Send at right edge')
assert(report.buttons[0].right < report.buttons[1].left && report.buttons[0].top === report.buttons[1].top, 'Cancel then send')
assert(report.scrollWidth <= report.clientWidth && report.disclosures === 0, 'No overflow or redundant disclosure')
return report
}
if (process.env.GOODBUDDY_SUPERVISOR_CONTENT_LAYOUT) {
win.show()
const controlsOnly = process.env.GOODBUDDY_SUPERVISOR_CONTENT_LAYOUT === 'controls'
@@ -276,8 +299,8 @@ app
app.quit()
return
}
if (process.env.GOODBUDDY_SUPERVISOR_AUTOMATIC_OVERVIEW) {
for (const scenario of ['populated', 'empty', 'plan-only']) {
if (process.env.GOODBUDDY_SUPERVISOR_AUTOMATIC_OVERVIEW || process.env.GOODBUDDY_SUPERVISOR_DISCUSSION) {
for (const scenario of process.env.GOODBUDDY_SUPERVISOR_DISCUSSION ? [] : ['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")')
@@ -372,7 +395,7 @@ app
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 })
previews.push({ theme, ...report, editor: await checkDiscussion() })
await writeFile(join(artifacts, `discussion-preview-${theme}-${width}.png`), (await win.webContents.capturePage()).toPNG())
}
}
@@ -391,6 +414,9 @@ app
await js('document.fonts.ready')
win.show()
win.focus()
await wait('[...document.querySelectorAll(".supervision-card button")].some(b => b.textContent === "继续讨论" && !b.disabled)')
await js('[...document.querySelectorAll(".supervision-card button")].find(b => b.textContent === "继续讨论").click()')
await wait('!!document.querySelector(".supervision-discussion-editor textarea")')
for (const theme of ['light', 'dark']) {
await js(`document.documentElement.dataset.theme = '${theme}'`)
for (const width of [480, 300, 200]) {
@@ -419,7 +445,13 @@ app
assert(report.buttons.every(b => b.width === 34 && b.height === 34), 'Compact shared buttons')
assert.equal(report.focus, 'solid')
assert(!report.text.includes('simulated-conversation-uuid'), 'Raw UUID visible')
reports.push({ theme, ...report })
reports.push({ theme, ...report, editor: await checkDiscussion() })
await js('document.querySelector(".supervision-discussion-editor footer").scrollIntoView({block:"end"})')
await settle()
assert(await js(`([...document.querySelectorAll('.supervision-discussion-editor footer button')].every(e => {
const r = e.getBoundingClientRect();
return r.top >= 0 && r.bottom <= innerHeight && e.contains(document.elementFromPoint(r.x+r.width/2, r.y+r.height/2));
}))`), 'Sidebar footer reachable and hittable after scrolling')
await writeFile(join(artifacts, `sidebar-${theme}-${width}.png`), (await win.webContents.capturePage()).toPNG())
}
}
@@ -244,10 +244,12 @@ createRoot(document.getElementById('root')!).render(
</a>
))}
</div>
{params.has('sidebar') ? <div className="assistant-sidebar__section task-center">
{params.has('sidebar') ? <div className="assistant-sidebar__section task-center" style={{ overflowY: 'auto', minHeight: 0 }}>
<SupervisionCard
target={{ type: 'conversation', conversationId: 'simulated-conversation-uuid' }}
conversationTitle={'模拟会话:核对项目交付清单与阶段回顾'.repeat(3)}
activeConversationId="simulated-conversation-uuid"
onContinueSupervision={async () => { throw new Error('Preview fixture must not send a message') }}
pinned
onTogglePinned={() => {}}
/>