feat(storage): move desktop SQLite ownership into a storage utility process

Main no longer opens desktop business SQLite. A single utility-process
storage owner holds the Assistant, Knowledge, attachment, document-result,
remote-binding and native model-ledger databases and runs whole domain
transactions; Main and Runtime callers use explicit async domain operations.

- Remove the Main readWithFallback path and the supervision worker's
  separate business write connection; manifests are scanned read-only and
  committed by the sole writer.
- Remote events are ACKed only after commit; process loss reports
  unconfirmed writes instead of replaying them.
- Use native utilityProcess messaging with one in-flight bound (32) and
  ordered waiting instead of capacity rejection and retries.
- Start the storage process before the window so startup overlaps.
- Keep short tool output in memory and store full output once in owned
  storage; paging, branching, restart and deletion reuse that copy.
- Put required runtime launch files under goodbuddy-prefixed owned temp
  directories and reclaim confirmed stale ones; stop writing duplicate
  parsed.md.
- Add lint guards for SQLite ownership and architecture, concurrency
  acceptance fixtures, and maintenance/architecture documentation.

Validation: typecheck and lint pass. Alternating baseline (edf019aa) vs
candidate runs show no regression on single-session, C01, C02, C04 and a
5-minute C06; Main p99 drops from ~21 ms to ~16.7 ms under concurrency.
Fresh portable copy (1.75M events) upgrade content unchanged; real-model
output paging passes with 4 external requests. Full npm test runs had
1-9 different timing-sensitive failures per run that pass when rerun.
Not covered: C03 review publication, C07 source body, app-level C05.
This commit is contained in:
mesalogo
2026-10-06 15:40:18 +08:00
parent bc677a3718
commit d80d42b032
229 changed files with 13632 additions and 3800 deletions
+31 -12
View File
@@ -1,8 +1,7 @@
import { runtimePrivacyEnvironment } from '../shared/runtime-privacy-environment'
import { randomBytes, randomUUID } from 'node:crypto'
import { spawn as nodeSpawn } from 'node:child_process'
import { mkdtemp, writeFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import fs, { mkdtemp, rm } from 'node:fs/promises'
import { pathToFileURL } from 'node:url'
import { openCodeSubagentPluginSource } from './opencode-subagent-plugin'
import {
@@ -13,7 +12,7 @@ import {
type ServerResponse
} from 'node:http'
import type { Socket } from 'node:net'
import { isAbsolute, resolve, join } from 'node:path'
import { dirname, isAbsolute, resolve, join } from 'node:path'
import type {
ModelBridgeModelProtocol
} from '../shared/model-bridge-contracts'
@@ -93,6 +92,7 @@ export type OpenCodeModelBridgeProviderConfig = {
}
export type ModelBridgeHelperChild = {
kill?(signal?: NodeJS.Signals): unknown
once(event: 'error', listener: (error: Error) => void): unknown
once(
event: 'close',
@@ -467,12 +467,23 @@ export async function runOpenCodeModelBridgeHelper(options: {
)
const exchange = createUnixModelBridgeExchange({ socketPath })
const proxy = new ModelBridgeLoopbackProxy({ exchange, sharedSessions: options.sharedSessions })
const origin = await proxy.listen()
let pluginDirectory: string | undefined
let child: ModelBridgeHelperChild | undefined
let stopping = false
const stop = (): void => {
if (stopping) return
stopping = true
child?.kill?.('SIGTERM')
}
process.on('SIGTERM', stop)
process.on('SIGINT', stop)
try {
pluginDirectory = await mkdtemp(join(tmpdir(), 'goodbuddy-opencode-plugin-'))
const origin = await proxy.listen()
const launchRoot = join(dirname(socketPath), 'goodbuddy-runtime-launch')
await fs.mkdir(launchRoot, { recursive: true, mode: 0o700 })
pluginDirectory = await mkdtemp(join(launchRoot, 'goodbuddy-opencode-plugin-'))
const pluginPath = join(pluginDirectory, 'subagent.mjs')
await writeFile(pluginPath, openCodeSubagentPluginSource(options.sharedSessions ? origin : undefined), 'utf8')
await fs.writeFile(pluginPath, openCodeSubagentPluginSource(options.sharedSessions ? origin : undefined), 'utf8')
const config = createOpenCodeModelBridgeProviderConfig({
protocol: options.protocol,
model: options.model,
@@ -486,12 +497,13 @@ export async function runOpenCodeModelBridgeHelper(options: {
plugin: [pathToFileURL(pluginPath).href]
})
)
if (stopping) return 128
if (options.sharedSessions) {
process.stdout.write(JSON.stringify({
jsonrpc: '2.0', method: 'goodbuddy/modelBridgeReady', params: { origin }
}) + '\n')
}
const child = (options.spawn ?? defaultHelperSpawn)(
child = (options.spawn ?? defaultHelperSpawn)(
opencodeEntrypoint,
['acp'],
{
@@ -500,9 +512,10 @@ export async function runOpenCodeModelBridgeHelper(options: {
env: environment
}
)
const runningChild = child
return await new Promise<number>((resolveExit, reject) => {
child.once('error', reject)
child.once('close', (code, signal) => {
runningChild.once('error', reject)
runningChild.once('close', (code, signal) => {
if (signal !== null) {
resolveExit(128)
return
@@ -511,9 +524,15 @@ export async function runOpenCodeModelBridgeHelper(options: {
})
})
} finally {
await proxy.close()
if (pluginDirectory) {
await rm(pluginDirectory, { recursive: true, force: true })
try {
await proxy.close()
} finally {
try {
if (pluginDirectory) await rm(pluginDirectory, { recursive: true, force: true })
} finally {
process.off('SIGTERM', stop)
process.off('SIGINT', stop)
}
}
}
}