Skip to content

Commit 0a4d519

Browse files
committed
feat(eko): add task continuation and file attachment management
This commit introduces major enhancements to the Eko task system: Features: - Add task continuation support with full chain history preservation - Implement file attachment management system with auto-collection - Add FileAttachmentList component with filtering and collapsing - Support file preview in detail panel after clicking attachments UX Improvements: - Show spin loader during tool execution for better feedback - Display clickable file links inline after file_write completion - Pin attachment list above input box (collapsed by default) - Optimize user message layout (right-aligned, 80% max width) - Fix detail panel visibility issue in history mode Technical Changes: - Upgrade IndexedDB schema to version 4 with chain history field - Add chainPlanRequest, chainPlanResult, files fields to Task model - Enhance EkoService with context restoration and chain passing - Add IPC handlers: navigateDetailView, ekoRestoreTask - Improve MessageProcessor logic for better message handling - Update dependencies: @jarvis-agent/electron to 0.1.9 Version bumped from 0.0.7 to 0.0.9
1 parent 836395f commit 0a4d519

File tree

17 files changed

+869
-56
lines changed

17 files changed

+869
-56
lines changed

electron/main/ipc/eko-handlers.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,48 @@ export function registerEkoHandlers() {
9393
}
9494
});
9595

96+
// Get task context (workflow + contextParams) for restoration
97+
ipcMain.handle('eko:get-task-context', async (event, taskId: string) => {
98+
try {
99+
console.log('IPC eko:get-task-context received:', taskId);
100+
const context = windowContextManager.getContext(event.sender.id);
101+
if (!context || !context.ekoService) {
102+
throw new Error('EkoService not found for this window');
103+
}
104+
const taskContext = context.ekoService.getTaskContext(taskId);
105+
return taskContext;
106+
} catch (error: any) {
107+
console.error('IPC eko:get-task-context error:', error);
108+
throw error;
109+
}
110+
});
111+
112+
// Restore task from saved workflow and contextParams
113+
ipcMain.handle('eko:restore-task', async (
114+
event,
115+
workflow: any,
116+
contextParams?: Record<string, any>,
117+
chainPlanRequest?: any,
118+
chainPlanResult?: string
119+
) => {
120+
try {
121+
console.log('IPC eko:restore-task received:', workflow.taskId);
122+
const context = windowContextManager.getContext(event.sender.id);
123+
if (!context || !context.ekoService) {
124+
throw new Error('EkoService not found for this window');
125+
}
126+
const taskId = await context.ekoService.restoreTask(
127+
workflow,
128+
contextParams,
129+
chainPlanRequest,
130+
chainPlanResult
131+
);
132+
return { success: true, taskId };
133+
} catch (error: any) {
134+
console.error('IPC eko:restore-task error:', error);
135+
throw error;
136+
}
137+
});
138+
96139
console.log('[IPC] Eko service handlers registered');
97140
}

electron/main/ipc/view-handlers.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,24 @@ export function registerViewHandlers() {
5454
}
5555
});
5656

57+
// Navigate detail view to specified URL
58+
ipcMain.handle('navigate-detail-view', async (event, url: string) => {
59+
try {
60+
console.log('IPC navigate-detail-view received:', url);
61+
const context = windowContextManager.getContext(event.sender.id);
62+
if (!context || !context.detailView) {
63+
throw new Error('DetailView not found for this window');
64+
}
65+
66+
// Load URL in detail view
67+
await context.detailView.webContents.loadURL(url);
68+
69+
return { success: true, url };
70+
} catch (error: any) {
71+
console.error('IPC navigate-detail-view error:', error);
72+
throw error;
73+
}
74+
});
75+
5776
console.log('[IPC] View control handlers registered');
5877
}

electron/main/services/eko-service.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,92 @@ export class EkoService {
390390
return false;
391391
}
392392

393+
/**
394+
* Get task context (for restoring conversation)
395+
* Returns workflow, contextParams, and chain history needed to restore the task
396+
*/
397+
getTaskContext(taskId: string): {
398+
workflow: any;
399+
contextParams: Record<string, any>;
400+
chainPlanRequest?: any;
401+
chainPlanResult?: string;
402+
} | null {
403+
if (!this.eko) {
404+
Log.error('Eko service not initialized');
405+
return null;
406+
}
407+
408+
const context = this.eko.getTask(taskId);
409+
if (!context) {
410+
Log.error(`Task ${taskId} not found in Eko`);
411+
return null;
412+
}
413+
414+
// Extract workflow and convert variables Map to plain object
415+
const workflow = context.workflow;
416+
const contextParams: Record<string, any> = {};
417+
418+
// Convert Map to plain object for serialization
419+
context.variables.forEach((value, key) => {
420+
contextParams[key] = value;
421+
});
422+
423+
// Extract chain history (critical for replan to maintain conversation context)
424+
const chainPlanRequest = context.chain?.planRequest;
425+
const chainPlanResult = context.chain?.planResult;
426+
427+
Log.info('Extracted task context:', {
428+
taskId,
429+
hasWorkflow: !!workflow,
430+
contextParamsCount: Object.keys(contextParams).length,
431+
hasChainHistory: !!(chainPlanRequest && chainPlanResult)
432+
});
433+
434+
return {
435+
workflow,
436+
contextParams,
437+
chainPlanRequest,
438+
chainPlanResult
439+
};
440+
}
441+
442+
/**
443+
* Restore task from saved workflow and contextParams
444+
* Used to continue conversation from history
445+
*/
446+
async restoreTask(
447+
workflow: any,
448+
contextParams?: Record<string, any>,
449+
chainPlanRequest?: any,
450+
chainPlanResult?: string
451+
): Promise<string> {
452+
if (!this.eko) {
453+
throw new Error('Eko service not initialized');
454+
}
455+
456+
try {
457+
Log.info('Restoring task from workflow:', workflow.taskId);
458+
459+
// Use Eko's initContext to restore the task
460+
const context = await this.eko.initContext(workflow, contextParams);
461+
462+
// Restore chain history (critical for replan to maintain conversation context)
463+
if (chainPlanRequest && chainPlanResult) {
464+
context.chain.planRequest = chainPlanRequest;
465+
context.chain.planResult = chainPlanResult;
466+
Log.info('Chain history restored successfully');
467+
} else {
468+
Log.warn('No chain history to restore - replan will treat as new task');
469+
}
470+
471+
Log.info('Task restored successfully:', workflow.taskId);
472+
return workflow.taskId;
473+
} catch (error: any) {
474+
Log.error('Failed to restore task:', error);
475+
throw error;
476+
}
477+
}
478+
393479
/**
394480
* Abort all running tasks
395481
*/
@@ -422,7 +508,7 @@ export class EkoService {
422508
type: 'human_interaction',
423509
requestId,
424510
taskId: agentContext?.context?.taskId,
425-
agentName: agentContext?.agent?.name,
511+
agentName: agentContext?.agent?.Name,
426512
timestamp: new Date(),
427513
...payload
428514
};

electron/preload/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ const api = {
4343
// Human interaction APIs
4444
sendHumanResponse: (response: any) => ipcRenderer.invoke('eko:human-response', response),
4545

46+
// Task restoration APIs (for continuing conversation from history)
47+
ekoGetTaskContext: (taskId: string) => ipcRenderer.invoke('eko:get-task-context', taskId),
48+
ekoRestoreTask: (workflow: any, contextParams?: Record<string, any>, chainPlanRequest?: any, chainPlanResult?: string) =>
49+
ipcRenderer.invoke('eko:restore-task', workflow, contextParams, chainPlanRequest, chainPlanResult),
50+
4651
// Model configuration APIs
4752
getUserModelConfigs: () => ipcRenderer.invoke('config:get-user-configs'),
4853
saveUserModelConfigs: (configs: any) => ipcRenderer.invoke('config:save-user-configs', configs),
@@ -60,10 +65,11 @@ const api = {
6065

6166
// Detail view control APIs
6267
setDetailViewVisible: (visible: boolean) => ipcRenderer.invoke('set-detail-view-visible', visible),
68+
navigateDetailView: (url: string) => ipcRenderer.invoke('navigate-detail-view', url),
6369
// URL retrieval and monitoring APIs
6470
getCurrentUrl: () => ipcRenderer.invoke('get-current-url'),
6571
onUrlChange: (callback: (url: string) => void) => {
66-
ipcRenderer.on('url-changed', (event, url) => callback(url));
72+
ipcRenderer.on('url-changed', (_event, url) => callback(url));
6773
},
6874
// Screenshot related APIs
6975
getMainViewScreenshot: () => ipcRenderer.invoke('get-main-view-screenshot'),

electron/preload/view.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ const api = {
109109
captureWindowSync: (winNo: number, scale = 1) => ipcRenderer.sendSync('native:captureWindow:sync', winNo, scale),
110110
requestCapturePermission: () => ipcRenderer.invoke('native:requestCapturePermission'),
111111
onFileUpdated: (callback: (status: string, content: string) => void) => ipcRenderer.on('file-updated', (_, status, content) => callback(status, content)),
112+
113+
// Generic invoke method (for config and other features)
114+
invoke: (channel: string, ...args: any[]) => ipcRenderer.invoke(channel, ...args),
112115
};
113116

114117
// Use `contextBridge` APIs to expose Electron APIs to
@@ -128,7 +131,7 @@ if (process.contextIsolated) {
128131
window.api = api;
129132
}
130133

131-
ipcRenderer.on("call-view-func", async (event, { payload, replyChannel }) => {
134+
ipcRenderer.on("call-view-func", async (_event, { payload, replyChannel }) => {
132135
let func;
133136
try {
134137
// @ts-ignore (define in dts) Use eval to maintain execution context

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ai-browser",
3-
"version": "0.0.7",
3+
"version": "0.0.9",
44
"description": "DeepFundAI Browser - AI-Powered Intelligent Browser",
55
"author": "Shuai Liu <lsustc@mail.ustc.edu.cn>",
66
"private": true,
@@ -30,7 +30,7 @@
3030
"@ant-design/cssinjs": "^1.23.0",
3131
"@ant-design/icons": "5.x",
3232
"@jarvis-agent/core": "^0.1.4",
33-
"@jarvis-agent/electron": "^0.1.8",
33+
"@jarvis-agent/electron": "^0.1.9",
3434
"@jest/globals": "^30.1.2",
3535
"@react-spring/web": "^10.0.1",
3636
"antd": "^5.26.5",

pnpm-lock.yaml

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)