1- import { existsSync } from 'node:fs' ;
21import { join } from 'node:path' ;
32import { acpApiContract , type AcpApiContract } from '@emdash/core/acp' ;
43import {
@@ -7,31 +6,39 @@ import {
76 withValidation ,
87 type ContractClient ,
98} from '@emdash/wire/api' ;
10- import { type ManagedProcess } from '@emdash/wire/process' ;
11- import { childProcessHost } from '@emdash/wire/process/node' ;
12- import { forwardRuntimeLogs , spawnRuntime } from '@emdash/wire/util/process-runtime' ;
9+ import { lazyWorker , type WorkerHandle } from '@emdash/wire/worker' ;
1310import { app , ipcMain , MessageChannelMain } from 'electron' ;
11+ import { appScope } from '@main/app/app-scope' ;
1412import { setSessionId } from '@main/core/conversations/set-session-id' ;
1513import { log } from '@main/lib/logger' ;
14+ import { desktopWorkerPath } from '@main/worker-manifest' ;
1615
1716const ACP_WIRE_CHANNEL = 'acp-wire' ;
1817
19- type AcpRuntimeHandle = Awaited < ReturnType < typeof spawnAcpRuntime > > ;
2018export type AcpRuntimeClient = ContractClient < AcpApiContract > ;
19+ type AcpRuntimeHandle = WorkerHandle < AcpApiContract > & { readonly client : AcpRuntimeClient } ;
20+
21+ const acpRuntimeScope = appScope . child ( 'acp-runtime-host' ) ;
22+ const acpWorker = lazyWorker (
23+ ( ) => ( {
24+ name : 'acp' ,
25+ contract : acpApiContract ,
26+ entry : desktopWorkerPath ( 'acp' ) ,
27+ scope : acpRuntimeScope ,
28+ env : {
29+ ...process . env ,
30+ EMDASH_ACP_ATTACHMENTS_DIR : join ( app . getPath ( 'userData' ) , 'acp-attachments' ) ,
31+ } ,
32+ } ) ,
33+ {
34+ onSpawned : ( handle ) => installRendererWire ( withSessionIdPersistence ( handle . client ) ) ,
35+ }
36+ ) ;
2137
22- let handlePromise : Promise < AcpRuntimeHandle > | null = null ;
2338let rendererWireDispose : ( ( ) => void ) | null = null ;
2439
25- export function initializeAcpRuntimeProcess ( ) : Promise < AcpRuntimeHandle > {
26- if ( handlePromise ) return handlePromise ;
27- handlePromise = spawnAcpRuntime ( ) . then ( ( handle ) => {
28- installRendererWire ( handle . client ) ;
29- app . once ( 'before-quit' , ( ) => {
30- void disposeAcpRuntimeProcess ( ) ;
31- } ) ;
32- return handle ;
33- } ) ;
34- return handlePromise ;
40+ export async function initializeAcpRuntimeProcess ( ) : Promise < AcpRuntimeHandle > {
41+ return decorateAcpRuntimeHandle ( await acpWorker . get ( ) ) ;
3542}
3643
3744export async function getAcpRuntimeClient ( ) : Promise < AcpRuntimeClient > {
@@ -41,40 +48,13 @@ export async function getAcpRuntimeClient(): Promise<AcpRuntimeClient> {
4148export async function disposeAcpRuntimeProcess ( ) : Promise < void > {
4249 rendererWireDispose ?.( ) ;
4350 rendererWireDispose = null ;
44- const handle = await handlePromise ;
45- handlePromise = null ;
46- await handle ?. dispose ( ) ;
51+ await acpWorker . dispose ( ) ;
4752}
4853
49- async function spawnAcpRuntime ( ) {
50- const entry = resolveRuntimeEntry ( ) ;
51- log . info ( 'ACP runtime child process entry resolved' , { entry } ) ;
52- const handle = await spawnRuntime ( {
53- host : childProcessHost ( ) ,
54- contract : acpApiContract ,
55- spec : {
56- entry,
57- env : {
58- ...process . env ,
59- EMDASH_ACP_ATTACHMENTS_DIR : join ( app . getPath ( 'userData' ) , 'acp-attachments' ) ,
60- } ,
61- supervision : { restart : 'on-failure' , backoffMs : [ 250 , 1_000 , 2_500 ] , maxRestarts : 5 } ,
62- } ,
63- onProcess : attachAcpRuntimeLogging ,
64- } ) ;
65- handle . onRestarted ( ( ) => {
66- log . info ( 'ACP runtime child process restarted' ) ;
67- } ) ;
54+ function decorateAcpRuntimeHandle ( handle : WorkerHandle < AcpApiContract > ) : AcpRuntimeHandle {
6855 return { ...handle , client : withSessionIdPersistence ( handle . client ) } ;
6956}
7057
71- function attachAcpRuntimeLogging ( process : ManagedProcess ) : void {
72- forwardRuntimeLogs ( process , log , { source : 'acp-runtime' } ) ;
73- process . onExit ( ( exit ) => {
74- log . warn ( 'ACP runtime child process exited' , exit ) ;
75- } ) ;
76- }
77-
7858function withSessionIdPersistence ( client : AcpRuntimeClient ) : AcpRuntimeClient {
7959 return {
8060 ...client ,
@@ -128,14 +108,3 @@ function installRendererWire(client: AcpRuntimeClient): void {
128108function runtimeWireValidationPolicy ( ) {
129109 return import . meta. env . DEV ? 'full' : 'inputs' ;
130110}
131-
132- function resolveRuntimeEntry ( ) : string {
133- const candidates = [ join ( __dirname , 'acp-runtime.js' ) , join ( __dirname , 'acp-runtime.mjs' ) ] ;
134- const entry = candidates . find ( ( candidate ) => existsSync ( candidate ) ) ;
135- if ( ! entry ) {
136- throw new Error (
137- `ACP runtime child process entry is missing. Checked: ${ candidates . join ( ', ' ) } `
138- ) ;
139- }
140- return entry ;
141- }
0 commit comments