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
@@ -43,6 +43,8 @@ import { defaultRuntimeSettings } from '../../shared/contracts'
import type { ResolvedRuntimeSettings } from '../runtime-settings-store'
import { AssistantDatabase } from '../assistant/assistant-database'
import { ImageGenerationService } from './image-generation-service'
import { KnowledgeMcpGateway } from './knowledge-mcp-gateway'
import type { KnowledgeService } from '../knowledge/knowledge-service'
vi.mock('electron', () => ({ nativeImage: {} }))
@@ -101,6 +103,41 @@ it('generates and edits durable images through the controlled Harness Main proxy
}, 60_000)
const CREDENTIAL_REF = 'GOODBUDDY_HARNESS_MODEL_API_KEY'
it('reads Story Graph through the controlled Harness ACP Main proxy in Ask and Execute, then removes it when disabled', async () => {
const root = await realpath(await mkdtemp(join(tmpdir(), 'goodbuddy-dsh-graph-')))
const db = new AssistantDatabase(':memory:'); db.initialize(root)
let enabled = true
const projectId = db.listProjects()[0]!.id
const gateway = new KnowledgeMcpGateway({} as KnowledgeService, { storyGraphService: {
available: async () => enabled, read: db.readStoryGraph.bind(db)
} })
const signal = new AbortController().signal
const token = gateway.grant('harness-graph', [], signal, 'none', undefined, undefined, undefined, undefined, undefined,
{ projectId, runtimeTarget: 'deepseek-harness' })!
const inProcess = createInProcessLaunch(root, { stream(options) {
const prompt = latestUserText(options)
const tool = options.tools?.find(tool => tool.name === 'story_graph_search')
if (!enabled) { expect(tool).toBeUndefined(); return textResponse('DISABLED') }
expect(tool).toBeDefined()
if (!toolResultText(options, prompt)) return toolCall(prompt, 'story_graph_search', { query: 'Decision' })
expect(toolResultText(options, prompt)).toContain(projectId)
return textResponse('GRAPH_READ')
} })
const provider = new ModelToolProvider(root, [], undefined, gateway)
const runtime = new DeepSeekHarnessRuntime({ defaultWorkspace: root, baseUrl: 'https://chat.test/v1', model: 'fixture',
launch: inProcess.launch, credentialRefs: { [CREDENTIAL_REF]: 'fixture' }, toolProvider: provider,
initializationTimeoutMs: 20_000, promptTimeoutMs: 20_000, shutdownTimeoutMs: 5_000 })
try {
for (const mode of ['ask', 'execute', 'disabled'] as const) {
enabled = mode !== 'disabled'
const events = await collect(runtime.run({ requestId: crypto.randomUUID(), conversationId: 'graph', prompt: mode,
workMode: mode === 'execute' ? 'execute' : 'ask', knowledgeCapabilityToken: token }, signal))
expect(events.at(-1)?.type).toBe('done')
expect(JSON.stringify(events)).toContain(enabled ? 'GRAPH_READ' : 'DISABLED')
}
} finally { await runtime.dispose(); await provider.dispose(); await gateway.dispose(); db.close(); await rm(root, { recursive: true, force: true }) }
}, 60_000)
const SKILL_CALL_ID = 'e2e-skill-call'
const MCP_CALL_ID = 'e2e-mcp-call'
const ASK_MCP_CALL_ID = 'e2e-ask-mcp-call'