Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions apps/electron/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ ipcMain.handle('sessions:getMessages', async (_event, sessionId: unknown) =>
toIpcResult(() => agentKernelHost.getMessages(sessionId))
);

ipcMain.handle('sessions:listBranches', async (_event, sessionId: unknown) =>
toIpcResult(() => agentKernelHost.listBranches(sessionId))
);

ipcMain.handle('sessions:setActiveBranch', async (_event, input: unknown) =>
toIpcResult(() => agentKernelHost.setActiveBranch(input))
);

ipcMain.handle('sessions:listRuns', async (_event, sessionId: unknown) =>
toIpcResult(() => agentKernelHost.listRuns(sessionId))
);
Expand All @@ -124,6 +132,22 @@ ipcMain.handle('runs:start', async (_event, input: unknown) =>
toIpcResult(() => agentKernelHost.startRun(input))
);

ipcMain.handle('runs:retry', async (_event, input: unknown) =>
toIpcResult(() => agentKernelHost.retryRun(input))
);

ipcMain.handle('messages:edit', async (_event, input: unknown) =>
toIpcResult(() => agentKernelHost.editMessage(input))
);

ipcMain.handle('messages:regenerate', async (_event, input: unknown) =>
toIpcResult(() => agentKernelHost.regenerateMessage(input))
);

ipcMain.handle('branches:fork', async (_event, input: unknown) =>
toIpcResult(() => agentKernelHost.forkBranch(input))
);

ipcMain.handle('runs:cancel', async (_event, input: unknown) =>
toIpcResult(() => agentKernelHost.cancelRun(input))
);
Expand Down
111 changes: 83 additions & 28 deletions apps/electron/src/main/kernelHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
ApiResult,
CapabilityRegistry,
Comparison,
ConversationBranch,
CredentialInfo,
CustomProviderConfig,
FinancialProviderStatus,
Expand Down Expand Up @@ -490,40 +491,25 @@ export class AgentKernelHost {
return this.kernel.sessions.listMessages(requireString(sessionId, 'sessionId'));
}

async listBranches(sessionId: unknown): Promise<ConversationBranch[]> {
return this.kernel.sessions.listBranches(requireString(sessionId, 'sessionId'));
}

async setActiveBranch(input: unknown): Promise<ConversationBranch> {
const request = requireObject(input);
return this.kernel.runs.setActiveBranch(
requireString(request.sessionId, 'sessionId'),
requireString(request.branchId, 'branchId')
);
}

async listRuns(sessionId: unknown): Promise<Run[]> {
return this.kernel.sessions.listRuns(requireString(sessionId, 'sessionId'));
}

async startRun(input: unknown): Promise<Run> {
const request = requireObject(input) as Partial<StartRunRequest>;
let workspaceContext: WorkspaceContext | undefined;
if (request.workspaceContext && typeof request.workspaceContext === 'object') {
const context = request.workspaceContext as Record<string, unknown>;
workspaceContext = {};
if (typeof context.activeSymbol === 'string') {
workspaceContext.activeSymbol = context.activeSymbol.toUpperCase();
}
if (
context.activeView === 'overview' ||
context.activeView === 'chart' ||
context.activeView === 'financials' ||
context.activeView === 'news' ||
context.activeView === 'portfolio'
) {
workspaceContext.activeView = context.activeView;
}
if (typeof context.selectedPosition === 'string') {
workspaceContext.selectedPosition = context.selectedPosition;
}
if (
Array.isArray(context.comparisonSymbols) &&
context.comparisonSymbols.every((entry) => typeof entry === 'string')
) {
workspaceContext.comparisonSymbols = context.comparisonSymbols.map((entry) =>
entry.toUpperCase()
);
}
}
const workspaceContext = readWorkspaceContext(request.workspaceContext);
// V8: new agent responses follow the *effective* app locale unless the
// user explicitly requests another language in the prompt (spec §41–42).
// Resolved after validation and with a safe fallback so a prefs failure
Expand All @@ -536,6 +522,47 @@ export class AgentKernelHost {
);
}

async retryRun(input: unknown): Promise<Run> {
const request = requireObject(input);
return this.kernel.runs.retryRun(
requireString(request.sessionId, 'sessionId'),
requireString(request.runId, 'runId'),
readWorkspaceContext(request.workspaceContext),
await this.effectiveRunLocale()
);
}

async editMessage(input: unknown): Promise<Run> {
const request = requireObject(input);
return this.kernel.runs.editMessage(
requireString(request.sessionId, 'sessionId'),
requireString(request.messageId, 'messageId'),
requireString(request.content, 'content'),
readWorkspaceContext(request.workspaceContext),
await this.effectiveRunLocale()
);
}

async regenerateMessage(input: unknown): Promise<Run> {
const request = requireObject(input);
return this.kernel.runs.regenerateMessage(
requireString(request.sessionId, 'sessionId'),
requireString(request.messageId, 'messageId'),
readWorkspaceContext(request.workspaceContext),
await this.effectiveRunLocale()
);
}

async forkBranch(input: unknown): Promise<ConversationBranch> {
const request = requireObject(input);
const name = request.name === undefined ? undefined : requireString(request.name, 'name');
return this.kernel.runs.forkBranch(
requireString(request.sessionId, 'sessionId'),
requireString(request.messageId, 'messageId'),
name
);
}

private async effectiveRunLocale(): Promise<SupportedLocale> {
try {
return (await this.getAppPreferences()).effectiveLocale;
Expand Down Expand Up @@ -2610,6 +2637,34 @@ function requireObject(value: unknown): Record<string, unknown> {
return value as Record<string, unknown>;
}

function readWorkspaceContext(value: unknown): WorkspaceContext | undefined {
if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;
const context = value as Record<string, unknown>;
const result: WorkspaceContext = {};
if (typeof context.activeSymbol === 'string') {
result.activeSymbol = context.activeSymbol.toUpperCase();
}
if (
context.activeView === 'overview' ||
context.activeView === 'chart' ||
context.activeView === 'financials' ||
context.activeView === 'news' ||
context.activeView === 'portfolio'
) {
result.activeView = context.activeView;
}
if (typeof context.selectedPosition === 'string') {
result.selectedPosition = context.selectedPosition;
}
if (
Array.isArray(context.comparisonSymbols) &&
context.comparisonSymbols.every((entry) => typeof entry === 'string')
) {
result.comparisonSymbols = context.comparisonSymbols.map((entry) => String(entry).toUpperCase());
}
return result;
}

function createCodeError(code: string, message: string, action?: string) {
const error = new Error(message) as Error & { code: string; action?: string };
error.code = code;
Expand Down
6 changes: 6 additions & 0 deletions apps/electron/src/preload/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,14 @@ var electronAPI = {
createSession: (title) => import_electron.ipcRenderer.invoke("sessions:create", title),
deleteSession: (sessionId) => import_electron.ipcRenderer.invoke("sessions:delete", sessionId),
getMessages: (sessionId) => import_electron.ipcRenderer.invoke("sessions:getMessages", sessionId),
listBranches: (sessionId) => import_electron.ipcRenderer.invoke("sessions:listBranches", sessionId),
setActiveBranch: (input) => import_electron.ipcRenderer.invoke("sessions:setActiveBranch", input),
listRuns: (sessionId) => import_electron.ipcRenderer.invoke("sessions:listRuns", sessionId),
startRun: (input) => import_electron.ipcRenderer.invoke("runs:start", input),
retryRun: (input) => import_electron.ipcRenderer.invoke("runs:retry", input),
editMessage: (input) => import_electron.ipcRenderer.invoke("messages:edit", input),
regenerateMessage: (input) => import_electron.ipcRenderer.invoke("messages:regenerate", input),
forkBranch: (input) => import_electron.ipcRenderer.invoke("branches:fork", input),
cancelRun: (input) => import_electron.ipcRenderer.invoke("runs:cancel", input),
onAgentEvent: (callback) => {
const listener = (_event, agentEvent) => callback(agentEvent);
Expand Down
26 changes: 26 additions & 0 deletions apps/electron/src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,23 @@ export interface ElectronAPI {
createSession: (title?: string) => Promise<unknown>;
deleteSession: (sessionId: string) => Promise<unknown>;
getMessages: (sessionId: string) => Promise<unknown>;
listBranches: (sessionId: string) => Promise<unknown>;
setActiveBranch: (input: { sessionId: string; branchId: string }) => Promise<unknown>;
listRuns: (sessionId: string) => Promise<unknown>;
startRun: (input: {
sessionId: string;
content: string;
workspaceContext?: unknown;
}) => Promise<unknown>;
retryRun: (input: { sessionId: string; runId: string; workspaceContext?: unknown }) => Promise<unknown>;
editMessage: (input: {
sessionId: string;
messageId: string;
content: string;
workspaceContext?: unknown;
}) => Promise<unknown>;
regenerateMessage: (input: { sessionId: string; messageId: string; workspaceContext?: unknown }) => Promise<unknown>;
forkBranch: (input: { sessionId: string; messageId: string; name?: string }) => Promise<unknown>;
cancelRun: (input: { sessionId: string; runId: string }) => Promise<unknown>;
onAgentEvent: (callback: (event: unknown) => void) => () => void;
};
Expand Down Expand Up @@ -197,12 +208,27 @@ const electronAPI: ElectronAPI = {
createSession: (title?: string) => ipcRenderer.invoke('sessions:create', title),
deleteSession: (sessionId: string) => ipcRenderer.invoke('sessions:delete', sessionId),
getMessages: (sessionId: string) => ipcRenderer.invoke('sessions:getMessages', sessionId),
listBranches: (sessionId: string) => ipcRenderer.invoke('sessions:listBranches', sessionId),
setActiveBranch: (input: { sessionId: string; branchId: string }) =>
ipcRenderer.invoke('sessions:setActiveBranch', input),
listRuns: (sessionId: string) => ipcRenderer.invoke('sessions:listRuns', sessionId),
startRun: (input: {
sessionId: string;
content: string;
workspaceContext?: unknown;
}) => ipcRenderer.invoke('runs:start', input),
retryRun: (input: { sessionId: string; runId: string; workspaceContext?: unknown }) =>
ipcRenderer.invoke('runs:retry', input),
editMessage: (input: {
sessionId: string;
messageId: string;
content: string;
workspaceContext?: unknown;
}) => ipcRenderer.invoke('messages:edit', input),
regenerateMessage: (input: { sessionId: string; messageId: string; workspaceContext?: unknown }) =>
ipcRenderer.invoke('messages:regenerate', input),
forkBranch: (input: { sessionId: string; messageId: string; name?: string }) =>
ipcRenderer.invoke('branches:fork', input),
cancelRun: (input: { sessionId: string; runId: string }) => ipcRenderer.invoke('runs:cancel', input),
onAgentEvent: (callback: (event: unknown) => void) => {
const listener = (_event: Electron.IpcRendererEvent, agentEvent: unknown) => callback(agentEvent);
Expand Down
15 changes: 15 additions & 0 deletions apps/electron/src/renderer/finagentClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,24 @@ function createElectronClient(): FinagentClient {
createSession: (title?: string) => ipcResult(window.electronAPI.kernel.createSession(title)),
deleteSession: (sessionId: string) => ipcResult(window.electronAPI.kernel.deleteSession(sessionId)),
getMessages: (sessionId: string) => ipcResult(window.electronAPI.kernel.getMessages(sessionId)),
listBranches: (sessionId: string) => ipcResult(window.electronAPI.kernel.listBranches(sessionId)),
setActiveBranch: (sessionId: string, branchId: string) =>
ipcResult(window.electronAPI.kernel.setActiveBranch({ sessionId, branchId })),
listRuns: (sessionId: string) => ipcResult(window.electronAPI.kernel.listRuns(sessionId)),
startRun: (sessionId: string, content: string, workspaceContext?: WorkspaceContext) =>
ipcResult(window.electronAPI.kernel.startRun({ sessionId, content, workspaceContext })),
retryRun: (sessionId: string, runId: string, workspaceContext?: WorkspaceContext) =>
ipcResult(window.electronAPI.kernel.retryRun({ sessionId, runId, workspaceContext })),
editMessage: (
sessionId: string,
messageId: string,
content: string,
workspaceContext?: WorkspaceContext
) => ipcResult(window.electronAPI.kernel.editMessage({ sessionId, messageId, content, workspaceContext })),
regenerateMessage: (sessionId: string, messageId: string, workspaceContext?: WorkspaceContext) =>
ipcResult(window.electronAPI.kernel.regenerateMessage({ sessionId, messageId, workspaceContext })),
forkBranch: (sessionId: string, messageId: string, name?: string) =>
ipcResult(window.electronAPI.kernel.forkBranch({ sessionId, messageId, name })),
cancelRun: (sessionId: string, runId: string) =>
ipcResult(window.electronAPI.kernel.cancelRun({ sessionId, runId })),
onAgentEvent: (callback: (event: AgentEvent) => void) =>
Expand Down
Loading