Files
goodbuddy/src/main/remote-agent/managed-remote-execution-services.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

219 lines
6.9 KiB
TypeScript

import type { RuntimeSessionBindingStore } from '../agent/runtime-session-binding-store'
import type { ResolvedModelProfile } from '../runtime-settings-store'
import type {
AgentRuntimeSelection,
RuntimeSelectionLayer
} from '../../shared/runtime-selection-contracts'
import type { SshHostStore } from '../ssh/ssh-host-store'
import {
resolveBundledRemoteRuntimeResourcePaths,
type BundledRemoteRuntimeResourcePaths
} from './bundled-remote-runtime-resources'
import { ManagedRemoteProjectRuntimeValidator } from './managed-remote-project-runtime-validator'
import {
createManagedRemoteAcpRuntime,
type ManagedRemoteAcpRuntimeOptions
} from './managed-remote-acp-runtime'
import { ManagedRemoteWorkspaceAccessFactory } from './managed-remote-workspace-access-factory'
import type { RemoteAgentServices } from './remote-agent-services'
import { RemoteProjectSaveService } from './remote-project-save-service'
import {
RemoteRuntimeInstallationManager,
type RemoteRuntimeActivator
} from './remote-runtime-installation-manager'
import {
createManagedModelBridge
} from './managed-model-bridge'
import { boundedDiagnostic } from './bounded-diagnostic'
export type ManagedRemoteExecutionServicesOptions = {
bindingStore: RuntimeSessionBindingStore
sshHostStore: SshHostStore
agentServices: Pick<
RemoteAgentServices,
| 'targetResolver'
| 'sshPool'
| 'installationManager'
| 'connectionManager'
| 'controllerState'
>
appPath: string
resourcesPath: string
packaged: boolean
resolveModelProfile(
selection: AgentRuntimeSelection
): Promise<ResolvedModelProfile | undefined>
resolveRuntimeSelection(
layer: RuntimeSelectionLayer | undefined
): Promise<AgentRuntimeSelection>
}
/**
* Main-only lifecycle owner for verified remote execution primitives.
* Construction performs no SSH or provider work.
*/
export class ManagedRemoteExecutionServices {
readonly runtimeResourcePaths: BundledRemoteRuntimeResourcePaths
readonly runtimeInstallationManager: RemoteRuntimeInstallationManager
readonly runtimeValidator: ManagedRemoteProjectRuntimeValidator
readonly workspaceAccessFactory: ManagedRemoteWorkspaceAccessFactory
readonly bindingStore: RuntimeSessionBindingStore
#disposePromise?: Promise<void>
readonly #readiness: Promise<void>
readonly #resolveRuntimeSelection: ManagedRemoteExecutionServicesOptions['resolveRuntimeSelection']
readonly #sshHosts: SshHostStore
readonly #agentServices:
ManagedRemoteExecutionServicesOptions['agentServices']
constructor(options: ManagedRemoteExecutionServicesOptions) {
this.#agentServices = options.agentServices
this.#resolveRuntimeSelection = options.resolveRuntimeSelection
this.#sshHosts = options.sshHostStore
this.runtimeResourcePaths =
resolveBundledRemoteRuntimeResourcePaths({
appPath: options.appPath,
resourcesPath: options.resourcesPath,
packaged: options.packaged
})
const activate: RemoteRuntimeActivator = async (
lease,
runtimeId,
bundleDigest,
architecture,
currentAgentInstallationId,
signal
) => {
const agentInstallationId =
currentAgentInstallationId ??
(
await options.agentServices.installationManager.activateInstalled(
lease.identity.hostId,
{ signal }
)
).installationId
signal.throwIfAborted()
if (runtimeId !== 'opencode' && runtimeId !== 'continue') {
throw new Error(
'Remote Runtime identity is unsupported'
)
}
const result = await lease.runAgentRuntimeAction(
agentInstallationId,
{
kind: 'runtime-activate',
runtimeId,
bundleDigest,
architecture
},
signal
)
if (result.exitCode !== 0) {
const detail = boundedDiagnostic(
`${result.stderr}\n${result.stdout}`
)
throw new Error(
detail.length > 0
? `Remote Runtime activation failed: ${detail}`
: `Remote Runtime activation failed (exit ${String(result.exitCode)})`
)
}
}
this.runtimeInstallationManager =
new RemoteRuntimeInstallationManager({
resolver: options.agentServices.targetResolver,
sshPool: options.agentServices.sshPool,
activate
})
this.runtimeValidator = new ManagedRemoteProjectRuntimeValidator({
installationManager: this.runtimeInstallationManager,
resolveModelProfile: options.resolveModelProfile
})
this.workspaceAccessFactory =
new ManagedRemoteWorkspaceAccessFactory({
connectionManager: options.agentServices.connectionManager,
installationManager:
options.agentServices.installationManager
})
this.bindingStore = options.bindingStore
this.#readiness = Promise.resolve()
}
createProjectSaveService(
options: Pick<
ConstructorParameters<typeof RemoteProjectSaveService>[0],
'database' | 'notify'
>
): RemoteProjectSaveService {
return new RemoteProjectSaveService({
database: options.database,
sshHosts: this.#sshHosts,
installationManager:
this.#agentServices.installationManager,
connectionManager: this.#agentServices.connectionManager,
resolveRuntimeSelection: this.#resolveRuntimeSelection,
runtimeValidator: this.runtimeValidator,
...(options.notify ? { notify: options.notify } : {})
})
}
async initialize(): Promise<void> {
await this.#readiness
}
invalidateHost(hostId: string): void {
this.runtimeInstallationManager.invalidateHost(hostId)
}
async createRuntime(
options: Pick<
ManagedRemoteAcpRuntimeOptions,
'executionSpace' | 'selection' | 'usage'
> & {
modelProfile: ResolvedModelProfile
}
) {
await this.#readiness
const modelBridge = createManagedModelBridge({
profile: options.modelProfile
})
return createManagedRemoteAcpRuntime({
...options,
modelBridge,
agentServices: {
connectionManager: this.#agentServices.connectionManager,
controllerState: this.#agentServices.controllerState,
installationManager:
this.#agentServices.installationManager
},
runtimeInstallationManager: this.runtimeInstallationManager,
bindingStore: this.bindingStore
})
}
dispose(): Promise<void> {
this.#disposePromise ??= this.#dispose()
return this.#disposePromise
}
async #dispose(): Promise<void> {
const errors: unknown[] = []
const settle = async (
operation: () => void | Promise<void>
): Promise<void> => {
try {
await operation()
} catch (error) {
errors.push(error)
}
}
await settle(() => this.runtimeInstallationManager.dispose())
if (errors.length > 0) {
throw new AggregateError(
errors,
'Managed remote execution services failed to dispose completely'
)
}
}
}