diff --git a/src/main/evalops/handlers.ts b/src/main/evalops/handlers.ts index a1f92e1..430aa0e 100644 --- a/src/main/evalops/handlers.ts +++ b/src/main/evalops/handlers.ts @@ -1,5 +1,6 @@ import { ipcMain } from 'electron' import { getEvalOpsAuthStatus, loginEvalOps, logoutEvalOps } from './auth' +import { registerKestrelAgentInBackground } from './registration' import { getEvalOpsServicesStatus, ingestEvalOpsSpans, @@ -26,7 +27,9 @@ import type { export function registerEvalOpsHandlers(): void { ipcMain.handle('evalops:authStatus', async () => getEvalOpsAuthStatus()) ipcMain.handle('evalops:login', async (_event, options?: EvalOpsLoginOptions) => { - return loginEvalOps(options) + const status = await loginEvalOps(options) + if (status.authenticated) registerKestrelAgentInBackground('login') + return status }) ipcMain.handle('evalops:logout', async () => logoutEvalOps()) ipcMain.handle('evalops:refreshAuth', async () => getEvalOpsAuthStatus()) diff --git a/src/main/evalops/registration.ts b/src/main/evalops/registration.ts new file mode 100644 index 0000000..5f006cc --- /dev/null +++ b/src/main/evalops/registration.ts @@ -0,0 +1,66 @@ +import { APP_ID, APP_NAME, APP_VERSION } from '../../shared/config' +import { getEvalOpsAuthStatus, getStoredEvalOpsSession } from './auth' +import { getEvalOpsConfig } from './config' +import { getEvalOpsConsumerClient } from './consumer' + +const KESTREL_CAPABILITIES = [ + 'context.capture', + 'llm.chat', + 'mcp.client', + 'meeting.recording', + 'memory.recall', + 'memory.store', + 'trace.ingest', + 'approval.request' +] + +const KESTREL_SURFACES = ['kestrel', 'desktop', 'chat', 'mcp', 'meetings'] + +export async function registerKestrelAgent(reason: 'startup' | 'login' = 'startup'): Promise { + const status = await getEvalOpsAuthStatus() + if (!status.authenticated && !status.tokenConfigured) return + + const config = getEvalOpsConfig() + const session = getStoredEvalOpsSession() + const client = await getEvalOpsConsumerClient() + + const response = await client.agentRegistry.register({ + id: config.agentId, + workspaceId: config.workspaceId, + name: `${APP_NAME} Desktop`, + description: 'Context-aware AI desktop assistant for macOS.', + agentType: 'desktop-assistant', + capabilities: KESTREL_CAPABILITIES, + surfaces: KESTREL_SURFACES, + status: 'active', + version: APP_VERSION, + ownerId: session?.organizationId, + labels: cleanLabels({ + app_id: APP_ID, + app_name: APP_NAME, + platform: process.platform, + runtime: 'electron', + registration_reason: reason, + organization_id: session?.organizationId, + workspace_id: config.workspaceId + }) + }) + + if (response.offline) { + console.warn('[evalops:registration] Agent registration used offline fallback:', response.reason) + } +} + +export function registerKestrelAgentInBackground(reason: 'startup' | 'login' = 'startup'): void { + registerKestrelAgent(reason).catch((err) => { + console.warn('[evalops:registration] Agent registration failed:', err) + }) +} + +function cleanLabels(labels: Record): Record { + const result: Record = {} + for (const [key, value] of Object.entries(labels)) { + if (value) result[key] = value + } + return result +} diff --git a/src/main/index.ts b/src/main/index.ts index a06ab63..8e4070e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -26,6 +26,7 @@ import { registerJournalHandlers } from './journal/handlers' import { MCPServerManager } from './mcp/manager' import { registerMCPHandlers } from './mcp/handlers' import { registerEvalOpsHandlers } from './evalops/handlers' +import { registerKestrelAgentInBackground } from './evalops/registration' import { registerUpdateHandlers } from './updates' import { registerKeyboardShortcutHandlers, unregisterKeyboardShortcuts } from './shortcuts' import { registerPlatformNotificationHandlers, unregisterPlatformNotificationHandlers } from './platform-notifications' @@ -106,6 +107,7 @@ if (!gotSingleInstanceLock) { registerMCPHandlers(mcpManager) registerPermissionHandlers() registerEvalOpsHandlers() + registerKestrelAgentInBackground('startup') registerUpdateHandlers() // Context IPC handlers