@@ -20,8 +20,18 @@ import {
2020 listScheduledTasks ,
2121 cancelScheduledTask ,
2222 disposeScheduler ,
23+ mapResultToStatus ,
24+ createTaskId ,
25+ } from '@accomplish_ai/agent-core' ;
26+ import type {
27+ TaskManagerAPI ,
28+ StorageAPI ,
29+ TaskCallbacks ,
30+ TaskMessage ,
31+ TaskResult ,
32+ TaskStatus ,
33+ PermissionResponse ,
2334} from '@accomplish_ai/agent-core' ;
24- import type { TaskManagerAPI , StorageAPI } from '@accomplish_ai/agent-core' ;
2535import { app } from 'electron' ;
2636
2737let server : DaemonServer | null = null ;
@@ -161,6 +171,48 @@ function getDaemonEntryPath(): string {
161171 return path . join ( app . getAppPath ( ) , 'out' , 'main' , 'daemon' , 'entry.js' ) ;
162172}
163173
174+ /**
175+ * Build task lifecycle callbacks that forward events back through the in-process DaemonServer.
176+ */
177+ function buildInProcessCallbacks (
178+ taskId : string ,
179+ srv : DaemonServer ,
180+ storage : StorageAPI ,
181+ ) : TaskCallbacks {
182+ return {
183+ onBatchedMessages : ( messages : TaskMessage [ ] ) => {
184+ for ( const msg of messages ) {
185+ storage . addTaskMessage ( taskId , msg ) ;
186+ }
187+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
188+ srv . notify ( 'task.message' as any , { taskId, messages } ) ;
189+ } ,
190+ onProgress : ( progress ) => {
191+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192+ srv . notify ( 'task.progress' as any , { taskId, ...progress } ) ;
193+ } ,
194+ onPermissionRequest : ( request ) => {
195+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
196+ srv . notify ( 'permission.request' as any , request ) ;
197+ } ,
198+ onComplete : ( result : TaskResult ) => {
199+ const taskStatus = mapResultToStatus ( result ) ;
200+ storage . updateTaskStatus ( taskId , taskStatus , new Date ( ) . toISOString ( ) ) ;
201+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
202+ srv . notify ( 'task.complete' as any , { taskId, result } ) ;
203+ } ,
204+ onError : ( error : Error ) => {
205+ storage . updateTaskStatus ( taskId , 'failed' , new Date ( ) . toISOString ( ) ) ;
206+ console . error ( `[DaemonBootstrap] Task ${ taskId } error:` , error . message ) ;
207+ } ,
208+ onStatusChange : ( status : TaskStatus ) => {
209+ storage . updateTaskStatus ( taskId , status , new Date ( ) . toISOString ( ) ) ;
210+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
211+ srv . notify ( 'task.statusChange' as any , { taskId, status } ) ;
212+ } ,
213+ } ;
214+ }
215+
164216/**
165217 * Register in-process daemon handlers (same as daemon/entry.ts).
166218 */
@@ -273,6 +325,57 @@ function registerInProcessHandlers(
273325 }
274326 } ) ;
275327
328+ // ── Task execution ────────────────────────────────────────────────
329+
330+ srv . registerMethod ( 'task.start' , async ( params ) => {
331+ const { taskId : providedTaskId , config } = params ;
332+ const taskId = providedTaskId ?? createTaskId ( ) ;
333+
334+ const callbacks = buildInProcessCallbacks ( taskId , srv , storage ) ;
335+ const task = await taskManager . startTask ( taskId , config , callbacks ) ;
336+ storage . saveTask ( task ) ;
337+ return task ;
338+ } ) ;
339+
340+ srv . registerMethod ( 'session.resume' , async ( params ) => {
341+ const { sessionId, prompt, existingTaskId } = params ;
342+ const taskId = existingTaskId ?? createTaskId ( ) ;
343+
344+ const callbacks = buildInProcessCallbacks ( taskId , srv , storage ) ;
345+ const task = await taskManager . startTask ( taskId , { prompt, sessionId } , callbacks ) ;
346+
347+ if ( existingTaskId ) {
348+ storage . updateTaskStatus ( existingTaskId , task . status , new Date ( ) . toISOString ( ) ) ;
349+ } else {
350+ storage . saveTask ( task ) ;
351+ }
352+
353+ return task ;
354+ } ) ;
355+
356+ srv . registerMethod ( 'permission.respond' , async ( params ) => {
357+ const { response } = params ;
358+ const { taskId, decision, requestId, selectedOptions } = response as PermissionResponse ;
359+
360+ if ( ! taskManager . hasActiveTask ( taskId ) ) {
361+ console . warn ( `[DaemonBootstrap] Permission response for inactive task ${ taskId } ` ) ;
362+ return ;
363+ }
364+
365+ if ( requestId ) {
366+ // requestId-based responses are handled externally (permission-api); log and skip
367+ console . warn ( `[DaemonBootstrap] Unhandled requestId-based permission.respond: ${ requestId } ` ) ;
368+ return ;
369+ }
370+
371+ if ( decision === 'allow' ) {
372+ const message = selectedOptions ?. join ( ', ' ) || 'yes' ;
373+ await taskManager . sendResponse ( taskId , message ) ;
374+ } else {
375+ await taskManager . sendResponse ( taskId , 'no' ) ;
376+ }
377+ } ) ;
378+
276379 // ── Scheduling ───────────────────────────────────────────────────
277380
278381 srv . registerMethod ( 'task.schedule' , ( params ) => {
@@ -338,4 +441,4 @@ export function shutdownDaemon(): void {
338441 disposeScheduler ( ) ;
339442 mode = null ;
340443 console . log ( '[DaemonBootstrap] Daemon shut down' ) ;
341- }
444+ }
0 commit comments