Files
goodbuddy/electron.vite.config.ts
T
mesalogo d80d42b032 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.
2026-10-06 15:40:18 +08:00

212 lines
6.0 KiB
TypeScript

import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import react from '@vitejs/plugin-react'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import type { Plugin } from 'vite'
import { packageObsidianMcpVault } from './src/main/obsidian/package-mcpvault'
type ProjectPackage = {
dependencies?: Record<string, string>
}
const projectPackage = JSON.parse(
readFileSync(resolve('package.json'), 'utf8')
) as ProjectPackage
function requireDependencyVersion(
dependencies: Record<string, string> | undefined,
name: string
): string {
const version = dependencies?.[name]
if (!version) {
throw new Error(`Missing ${name} dependency version`)
}
return version
}
const deepSeekHarnessLlmVersion = requireDependencyVersion(
projectPackage.dependencies,
'@deepseek-ai/dsh-llm'
)
export function serializeDeepSeekHarnessBundleManifest(
version: string
): string {
return `${JSON.stringify(
{
name: '@deepseek-ai/dsh-llm',
version,
private: true,
type: 'module'
},
null,
2
)}\n`
}
function deepSeekHarnessBundleManifestPlugin(): Plugin {
return {
name: 'deepseek-harness-bundle-manifest',
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'package.json',
source: serializeDeepSeekHarnessBundleManifest(
deepSeekHarnessLlmVersion
)
})
}
}
}
export function stableMainEntryFileName(chunk: {
readonly name: string
}): string {
if (chunk.name === 'remote-package-installer') {
return 'remote-package-installer.mjs'
}
return [
'deepseek-harness-host-bootstrap',
'embedding-inference-bootstrap'
].includes(chunk.name)
? `${chunk.name}.js`
: '[name].js'
}
// The renderer CSP has no font-src, so fonts fall back to default-src 'self'.
// Small fonts (e.g. KaTeX) must stay files rather than data: URLs, or Chromium
// refuses them. Other assets keep Vite's default inline threshold.
export function rendererAssetInlineLimit(
filePath: string
): false | undefined {
return /\.(?:woff2?|ttf|otf)(?:$|\?)/iu.test(filePath) ? false : undefined
}
export default defineConfig({
main: {
plugins: [
externalizeDepsPlugin({
exclude: [
'@agentclientprotocol/sdk',
'@deepseek-ai/cordis',
'@deepseek-ai/dsh-agent',
'@deepseek-ai/dsh-agent-loop',
'@deepseek-ai/dsh-bash-local',
'@deepseek-ai/dsh-credentials',
'@deepseek-ai/dsh-fs-local',
'@deepseek-ai/dsh-llm',
'@deepseek-ai/dsh-llm-pi-ai',
'@deepseek-ai/dsh-pwsh-local',
'@deepseek-ai/dsh-sandbox',
'@deepseek-ai/dsh-sandbox-policy',
'@deepseek-ai/dsh-session',
'@deepseek-ai/dsh-session-projection',
'@deepseek-ai/dsh-shell-env',
'@deepseek-ai/dsh-skill',
'@deepseek-ai/dsh-subprocess-local',
'@deepseek-ai/dsh-system-prompt',
'@deepseek-ai/dsh-token-meter',
'@deepseek-ai/dsh-tool-bash',
'@deepseek-ai/dsh-tool-fs',
'@deepseek-ai/dsh-tool-pwsh',
'@deepseek-ai/dsh-tool-skill',
'@deepseek-ai/dsh-tools',
'@deepseek-ai/dsh-user-approval',
'yaml',
'zod'
]
}),
deepSeekHarnessBundleManifestPlugin(),
{
name: 'obsidian-mcpvault-package',
async writeBundle(options) {
await packageObsidianMcpVault(
resolve('.'),
resolve(options.dir ?? 'out/main', 'obsidian-mcpvault')
)
}
}
],
build: {
rollupOptions: {
input: {
index: resolve('src/main/index.ts'),
'assistant-storage-worker': resolve('src/main/assistant-storage-worker.ts'),
'desktop-storage-entry': resolve('src/main/desktop-storage-entry.ts'),
'readonly-query-worker': resolve('src/main/readonly-query-worker.ts'),
'document-parse-worker': resolve('src/main/document-parse-worker.ts'),
'wechat-sidecar': resolve(
'src/main/channels/wechat-sidecar.ts'
),
'deepseek-harness-host-bootstrap': resolve(
'src/main/deepseek-harness-host-bootstrap.ts'
),
'embedding-inference-bootstrap': resolve(
'src/main/embedding-inference-bootstrap.ts'
),
'remote-package-installer': resolve(
'src/main/remote-agent/control-plane-package-installer-cli.ts'
)
},
external: [
'node-pty',
'koffi',
/^@koromix\/koffi-/u
],
output: {
entryFileNames(chunk) {
return stableMainEntryFileName(chunk)
},
chunkFileNames(chunk) {
const moduleIds = chunk.moduleIds.join('\n')
return moduleIds.includes('deepseek-harness') ||
moduleIds.includes('deepseek-harness-utility')
? 'chunks/deepseek-harness-[name]-[hash].js'
: 'chunks/[name]-[hash].js'
},
manualChunks(id) {
if (
id.includes('@deepseek-ai/dsh-llm') ||
id.includes('@deepseek-ai/dsh-credentials') ||
id.includes('@deepseek-ai/dsh-settings') ||
id.includes('@deepseek-ai/dsh-timeout') ||
id.includes('@deepseek-ai/dsh-token-meter') ||
id.includes('@deepseek-ai/dsh-llm-pi-ai') ||
id.includes('@mariozechner/pi-ai')
) {
return 'deepseek-harness-llm'
}
return undefined
}
}
}
}
},
preload: {
plugins: [externalizeDepsPlugin()],
build: {
rollupOptions: {
output: {
entryFileNames: '[name].cjs',
format: 'cjs'
}
}
}
},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src'),
'@shared': resolve('src/shared')
}
},
worker: {
format: 'es'
},
build: {
assetsInlineLimit: rendererAssetInlineLimit
},
plugins: [react()]
}
})