Fix EvalOps service registration contract#46
Conversation
PR SummaryMedium Risk Overview Adds per-service routing in the consumer SDK ( Updates agent registry calls to the canonical Reviewed by Cursor Bugbot for commit 945e92d. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: SDK interface missing
approvalsBaseUrlendpoint field- Added
approvalsBaseUrlto the exportedEvalOpsServiceEndpointsinterface so the SDK matches the rest of the EvalOps endpoint configuration surfaces.
- Added
Preview (945e92df88)
diff --git a/sdk/js/src/index.ts b/sdk/js/src/index.ts
--- a/sdk/js/src/index.ts
+++ b/sdk/js/src/index.ts
@@ -1,6 +1,5 @@
export const KESTREL_SDK_NAME = '@evalops/kestrel-sdk'
-export const DEFAULT_EVALOPS_LLM_GATEWAY_URL =
- 'http://llm-gateway-service.evalops.svc.cluster.local:8080/v1'
+export const DEFAULT_EVALOPS_LLM_GATEWAY_URL = 'https://llm-gateway.evalops.dev/v1'
export interface EvalOpsServiceEndpoints {
identityBaseUrl?: string
@@ -8,6 +7,7 @@
skillsBaseUrl?: string
memoryBaseUrl?: string
tracesBaseUrl?: string
+ approvalsBaseUrl?: string
llmGatewayBaseUrl?: string
}
diff --git a/src/main/evalops/config.ts b/src/main/evalops/config.ts
--- a/src/main/evalops/config.ts
+++ b/src/main/evalops/config.ts
@@ -4,6 +4,7 @@
EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL,
EVALOPS_DEFAULT_AGENT_ID,
EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL,
+ EVALOPS_DEFAULT_APPROVALS_BASE_URL,
EVALOPS_DEFAULT_MEMORY_BASE_URL,
EVALOPS_DEFAULT_PROVIDER_REF,
EVALOPS_DEFAULT_RESOURCE,
@@ -23,6 +24,7 @@
token?: string
llmGatewayBaseUrl: string
agentRegistryBaseUrl: string
+ approvalsBaseUrl: string
skillsBaseUrl: string
memoryBaseUrl: string
tracesBaseUrl: string
@@ -46,6 +48,7 @@
token?: unknown
llmGatewayBaseUrl?: unknown
agentRegistryBaseUrl?: unknown
+ approvalsBaseUrl?: unknown
skillsBaseUrl?: unknown
memoryBaseUrl?: unknown
tracesBaseUrl?: unknown
@@ -86,6 +89,11 @@
asString(stored.agentRegistryBaseUrl),
EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL
),
+ approvalsBaseUrl: cleanUrl(
+ process.env.EVALOPS_APPROVALS_BASE_URL,
+ asString(stored.approvalsBaseUrl),
+ EVALOPS_DEFAULT_APPROVALS_BASE_URL
+ ),
skillsBaseUrl: cleanUrl(
process.env.EVALOPS_SKILLS_BASE_URL,
asString(stored.skillsBaseUrl),
diff --git a/src/main/evalops/consumer-sdk/clients.ts b/src/main/evalops/consumer-sdk/clients.ts
--- a/src/main/evalops/consumer-sdk/clients.ts
+++ b/src/main/evalops/consumer-sdk/clients.ts
@@ -3,6 +3,7 @@
AgentRegistryListRequest,
AgentRegistryListResponse,
AgentRegistryRecord,
+ AgentRegistryRegisterRequest,
ApprovalGetRequest,
ApprovalGetResponse,
ApprovalListPendingRequest,
@@ -171,28 +172,55 @@
return this.transport.request({
service: 'agent-registry',
operation: 'list',
- path: '/agent-registry.v1.AgentRegistryService/ListAgents',
- body: request,
+ path: '/agents.v1.AgentService/List',
+ body: normalizeAgentListRequest(request),
signal: options?.signal,
- fallback: (reason) => ({ agents: [], ...offline(reason) })
+ fallback: (reason) => ({ agents: [], total: 0, ...offline(reason) })
})
}
register(
- agent: AgentRegistryRecord,
+ agent: AgentRegistryRegisterRequest,
options?: { signal?: AbortSignal }
): Promise<{ agent?: AgentRegistryRecord; offline?: boolean; reason?: string }> {
return this.transport.request({
service: 'agent-registry',
operation: 'register',
- path: '/agent-registry.v1.AgentRegistryService/RegisterAgent',
- body: agent,
+ path: '/agents.v1.AgentService/Register',
+ body: {
+ workspaceId: agent.workspaceId,
+ name: agent.name,
+ description: agent.description,
+ agentType: agent.agentType,
+ capabilities: agent.capabilities,
+ surfaces: agent.surfaces,
+ ownerId: agent.ownerId
+ },
signal: options?.signal,
fallback: (reason) => ({ ...offline(reason) })
})
}
}
+function normalizeAgentListRequest(request: AgentRegistryListRequest): Record<string, unknown> {
+ return {
+ workspaceId: request.workspaceId,
+ agentType: request.agentType,
+ capability: request.capability,
+ surface: request.surface,
+ status: normalizeAgentStatus(request.status),
+ limit: request.limit,
+ offset: request.offset
+ }
+}
+
+function normalizeAgentStatus(status: string | undefined): string | undefined {
+ if (!status) return undefined
+ const normalized = status.trim().toUpperCase().replace(/[-\s]+/gu, '_')
+ if (!normalized) return undefined
+ return normalized.startsWith('AGENT_STATUS_') ? normalized : `AGENT_STATUS_${normalized}`
+}
+
export class SkillsClient {
constructor(private readonly transport: EvalOpsTransport) {}
diff --git a/src/main/evalops/consumer-sdk/http.ts b/src/main/evalops/consumer-sdk/http.ts
--- a/src/main/evalops/consumer-sdk/http.ts
+++ b/src/main/evalops/consumer-sdk/http.ts
@@ -13,6 +13,7 @@
path: string
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
body?: unknown
+ query?: Record<string, string | number | boolean | null | undefined>
signal?: AbortSignal
fallback?: (reason: string) => TFallback
}
@@ -69,6 +70,7 @@
export class EvalOpsTransport {
readonly baseUrl: string
private readonly token?: string
+ private readonly serviceBaseUrls: Partial<Record<EvalOpsServiceName, string>>
private readonly headersOverride: Record<string, string>
private readonly featureFlags?: FeatureFlags
private readonly offlineFallback: boolean
@@ -80,6 +82,12 @@
config.baseUrl ?? getEnvValue('EVALOPS_BASE_URL') ?? DEFAULT_BASE_URL
)
this.token = config.token ?? getEnvValue('EVALOPS_TOKEN')
+ this.serviceBaseUrls = Object.fromEntries(
+ Object.entries(config.serviceBaseUrls ?? {})
+ .map(([service, baseUrl]) => [service, baseUrl?.trim()])
+ .filter((entry): entry is [EvalOpsServiceName, string] => Boolean(entry[1]))
+ .map(([service, baseUrl]) => [service, trimTrailingSlash(baseUrl)])
+ )
this.headersOverride = config.headers ?? {}
this.featureFlags = config.featureFlags
this.offlineFallback = config.offlineFallback ?? false
@@ -116,6 +124,16 @@
}
}
+ private requestUrl(options: EvalOpsTransportOptions<unknown>): string {
+ const baseUrl = this.serviceBaseUrls[options.service] ?? this.baseUrl
+ const url = new URL(`${baseUrl}${options.path}`)
+ for (const [key, value] of Object.entries(options.query ?? {})) {
+ if (value === undefined || value === null || value === '') continue
+ url.searchParams.set(key, String(value))
+ }
+ return url.toString()
+ }
+
async request<TResponse>(
options: EvalOpsTransportOptions<TResponse>
): Promise<TResponse> {
@@ -123,7 +141,7 @@
const method = options.method ?? 'POST'
try {
- const response = await this.fetchImpl(`${this.baseUrl}${options.path}`, {
+ const response = await this.fetchImpl(this.requestUrl(options), {
method,
headers: this.headers(),
signal: options.signal,
diff --git a/src/main/evalops/consumer-sdk/types.ts b/src/main/evalops/consumer-sdk/types.ts
--- a/src/main/evalops/consumer-sdk/types.ts
+++ b/src/main/evalops/consumer-sdk/types.ts
@@ -21,6 +21,7 @@
export interface EvalOpsClientConfig {
baseUrl?: string
+ serviceBaseUrls?: Partial<Record<EvalOpsServiceName, string>>
token?: string
headers?: Record<string, string>
featureFlags?: FeatureFlags
@@ -47,13 +48,19 @@
export interface AgentRegistryRecord extends JsonObject {
id?: string
+ workspace_id?: string
workspaceId?: string
+ organization_id?: string
+ organizationId?: string
name?: string
description?: string
+ agent_type?: string
agentType?: string
capabilities?: string[]
+ surface?: string
surfaces?: string[]
status?: string
+ metadata?: Record<string, string>
activeConfigVersion?: number
ownerId?: string
version?: string
@@ -70,6 +77,16 @@
offset?: number
}
+export interface AgentRegistryRegisterRequest extends JsonObject {
+ workspaceId?: string
+ name: string
+ description?: string
+ agentType: string
+ capabilities: string[]
+ surfaces: string[]
+ ownerId?: string
+}
+
export interface AgentRegistryListResponse extends JsonObject {
agents: AgentRegistryRecord[]
total?: number
diff --git a/src/main/evalops/consumer.ts b/src/main/evalops/consumer.ts
--- a/src/main/evalops/consumer.ts
+++ b/src/main/evalops/consumer.ts
@@ -8,6 +8,13 @@
const session = getStoredEvalOpsSession()
return new EvalOpsClient({
baseUrl: config.baseUrl,
+ serviceBaseUrls: {
+ 'agent-registry': config.agentRegistryBaseUrl,
+ approvals: config.approvalsBaseUrl,
+ memory: config.memoryBaseUrl,
+ skills: config.skillsBaseUrl,
+ traces: config.tracesBaseUrl
+ },
token,
headers: cleanHeaders({
'X-Organization-ID': session?.organizationId,
diff --git a/src/main/evalops/handlers.ts b/src/main/evalops/handlers.ts
--- a/src/main/evalops/handlers.ts
+++ b/src/main/evalops/handlers.ts
@@ -28,7 +28,7 @@
ipcMain.handle('evalops:authStatus', async () => getEvalOpsAuthStatus())
ipcMain.handle('evalops:login', async (_event, options?: EvalOpsLoginOptions) => {
const status = await loginEvalOps(options)
- if (status.authenticated) registerKestrelAgentInBackground('login')
+ if (status.authenticated) registerKestrelAgentInBackground()
return status
})
ipcMain.handle('evalops:logout', async () => logoutEvalOps())
diff --git a/src/main/evalops/registration.ts b/src/main/evalops/registration.ts
--- a/src/main/evalops/registration.ts
+++ b/src/main/evalops/registration.ts
@@ -1,22 +1,22 @@
-import { APP_ID, APP_NAME, APP_VERSION } from '../../shared/config'
+import { APP_NAME } 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'
+ 'x-kestrel:context.capture',
+ 'responses:create',
+ 'mcp',
+ 'x-kestrel:meeting.recording',
+ 'x-kestrel:memory.recall',
+ 'x-kestrel:memory.store',
+ 'x-kestrel:trace.ingest',
+ 'x-kestrel:approval.request'
]
const KESTREL_SURFACES = ['kestrel', 'desktop', 'chat', 'mcp', 'meetings']
-export async function registerKestrelAgent(reason: 'startup' | 'login' = 'startup'): Promise<void> {
+export async function registerKestrelAgent(): Promise<void> {
const status = await getEvalOpsAuthStatus()
if (!status.authenticated && !status.tokenConfigured) return
@@ -25,25 +25,13 @@
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',
+ agentType: 'desktop',
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
- })
+ ownerId: session?.organizationId
})
if (response.offline) {
@@ -51,16 +39,8 @@
}
}
-export function registerKestrelAgentInBackground(reason: 'startup' | 'login' = 'startup'): void {
- registerKestrelAgent(reason).catch((err) => {
+export function registerKestrelAgentInBackground(): void {
+ registerKestrelAgent().catch((err) => {
console.warn('[evalops:registration] Agent registration failed:', err)
})
}
-
-function cleanLabels(labels: Record<string, string | undefined>): Record<string, string> {
- const result: Record<string, string> = {}
- for (const [key, value] of Object.entries(labels)) {
- if (value) result[key] = value
- }
- return result
-}
diff --git a/src/main/evalops/services.ts b/src/main/evalops/services.ts
--- a/src/main/evalops/services.ts
+++ b/src/main/evalops/services.ts
@@ -136,11 +136,11 @@
baseUrl: string
run: () => Promise<unknown>
}> = [
- { service: 'agent-registry', baseUrl: config.baseUrl, run: () => listEvalOpsAgents({ limit: 1 }) },
- { service: 'skills', baseUrl: config.baseUrl, run: () => listEvalOpsSkills({ limit: 1 }) },
- { service: 'memory', baseUrl: config.baseUrl, run: () => recallEvalOpsMemory({ query: 'kestrel', topK: 1 }) },
- { service: 'approvals', baseUrl: config.baseUrl, run: () => listEvalOpsApprovals({ limit: 1 }) },
- { service: 'traces', baseUrl: config.baseUrl, run: () => listEvalOpsTraces({ limit: 1 }) }
+ { service: 'agent-registry', baseUrl: config.agentRegistryBaseUrl, run: () => listEvalOpsAgents({ limit: 1 }) },
+ { service: 'skills', baseUrl: config.skillsBaseUrl, run: () => listEvalOpsSkills({ limit: 1 }) },
+ { service: 'memory', baseUrl: config.memoryBaseUrl, run: () => recallEvalOpsMemory({ query: 'kestrel', topK: 1 }) },
+ { service: 'approvals', baseUrl: config.approvalsBaseUrl, run: () => listEvalOpsApprovals({ limit: 1 }) },
+ { service: 'traces', baseUrl: config.tracesBaseUrl, run: () => listEvalOpsTraces({ limit: 1 }) }
]
return Promise.all(checks.map(async (check) => {
diff --git a/src/main/index.ts b/src/main/index.ts
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -107,7 +107,7 @@
registerMCPHandlers(mcpManager)
registerPermissionHandlers()
registerEvalOpsHandlers()
- registerKestrelAgentInBackground('startup')
+ registerKestrelAgentInBackground()
registerUpdateHandlers()
// Context IPC handlers
diff --git a/src/renderer/main/index.html b/src/renderer/main/index.html
--- a/src/renderer/main/index.html
+++ b/src/renderer/main/index.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
- content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://*.evalops.svc.cluster.local:* https://api.openai.com"
+ content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://*.evalops.dev https://api.openai.com"
/>
<title>Kestrel</title>
</head>
diff --git a/src/renderer/main/src/components/settings/EvalOpsSettings.tsx b/src/renderer/main/src/components/settings/EvalOpsSettings.tsx
--- a/src/renderer/main/src/components/settings/EvalOpsSettings.tsx
+++ b/src/renderer/main/src/components/settings/EvalOpsSettings.tsx
@@ -2,12 +2,17 @@
import { AlertCircle, Check, LogIn, LogOut, RefreshCw, Save } from 'lucide-react'
import {
EVALOPS_DEFAULT_AGENT_ID,
+ EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL,
+ EVALOPS_DEFAULT_APPROVALS_BASE_URL,
EVALOPS_DEFAULT_BASE_URL,
EVALOPS_DEFAULT_IDENTITY_BASE_URL,
EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL,
+ EVALOPS_DEFAULT_MEMORY_BASE_URL,
EVALOPS_DEFAULT_PROVIDER_REF,
EVALOPS_DEFAULT_RESOURCE,
EVALOPS_DEFAULT_SCOPES,
+ EVALOPS_DEFAULT_SKILLS_BASE_URL,
+ EVALOPS_DEFAULT_TRACES_BASE_URL,
EVALOPS_DEFAULT_WORKSPACE_ID
} from '@shared/config'
import type { EvalOpsAuthStatus, EvalOpsServiceStatus } from '@shared/ipc'
@@ -20,6 +25,7 @@
resource?: string
scopes?: string[]
agentRegistryBaseUrl?: string
+ approvalsBaseUrl?: string
skillsBaseUrl?: string
memoryBaseUrl?: string
tracesBaseUrl?: string
@@ -38,6 +44,11 @@
const [identityBaseUrl, setIdentityBaseUrl] = useState(EVALOPS_DEFAULT_IDENTITY_BASE_URL)
const [baseUrl, setBaseUrl] = useState(EVALOPS_DEFAULT_BASE_URL)
const [llmGatewayBaseUrl, setLlmGatewayBaseUrl] = useState(EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL)
+ const [agentRegistryBaseUrl, setAgentRegistryBaseUrl] = useState(EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL)
+ const [approvalsBaseUrl, setApprovalsBaseUrl] = useState(EVALOPS_DEFAULT_APPROVALS_BASE_URL)
+ const [skillsBaseUrl, setSkillsBaseUrl] = useState(EVALOPS_DEFAULT_SKILLS_BASE_URL)
+ const [memoryBaseUrl, setMemoryBaseUrl] = useState(EVALOPS_DEFAULT_MEMORY_BASE_URL)
+ const [tracesBaseUrl, setTracesBaseUrl] = useState(EVALOPS_DEFAULT_TRACES_BASE_URL)
const [token, setToken] = useState('')
const [resource, setResource] = useState(EVALOPS_DEFAULT_RESOURCE)
const [scopes, setScopes] = useState(EVALOPS_DEFAULT_SCOPES.join(' '))
@@ -60,6 +71,11 @@
if (stored?.identityBaseUrl) setIdentityBaseUrl(stored.identityBaseUrl)
if (stored?.baseUrl) setBaseUrl(stored.baseUrl)
if (stored?.llmGatewayBaseUrl) setLlmGatewayBaseUrl(stored.llmGatewayBaseUrl)
+ if (stored?.agentRegistryBaseUrl) setAgentRegistryBaseUrl(stored.agentRegistryBaseUrl)
+ if (stored?.approvalsBaseUrl) setApprovalsBaseUrl(stored.approvalsBaseUrl)
+ if (stored?.skillsBaseUrl) setSkillsBaseUrl(stored.skillsBaseUrl)
+ if (stored?.memoryBaseUrl) setMemoryBaseUrl(stored.memoryBaseUrl)
+ if (stored?.tracesBaseUrl) setTracesBaseUrl(stored.tracesBaseUrl)
if (stored?.token) setToken(stored.token)
if (stored?.resource) setResource(stored.resource)
if (stored?.scopes?.length) setScopes(stored.scopes.join(' '))
@@ -84,6 +100,11 @@
identityBaseUrl: identityBaseUrl.trim(),
baseUrl: baseUrl.trim(),
llmGatewayBaseUrl: llmGatewayBaseUrl.trim(),
+ agentRegistryBaseUrl: agentRegistryBaseUrl.trim(),
+ approvalsBaseUrl: approvalsBaseUrl.trim(),
+ skillsBaseUrl: skillsBaseUrl.trim(),
+ memoryBaseUrl: memoryBaseUrl.trim(),
+ tracesBaseUrl: tracesBaseUrl.trim(),
token: token.trim(),
resource: resource.trim(),
scopes: parsedScopes,
@@ -104,7 +125,25 @@
} finally {
setBusy(false)
}
- }, [agentId, baseUrl, identityBaseUrl, llmGatewayBaseUrl, parsedScopes, provider, providerCredentialName, providerEnvironment, providerTeamId, resource, scopes, token, workspaceId])
+ }, [
+ agentId,
+ agentRegistryBaseUrl,
+ approvalsBaseUrl,
+ baseUrl,
+ identityBaseUrl,
+ llmGatewayBaseUrl,
+ memoryBaseUrl,
+ parsedScopes,
+ provider,
+ providerCredentialName,
+ providerEnvironment,
+ providerTeamId,
+ resource,
+ skillsBaseUrl,
+ token,
+ tracesBaseUrl,
+ workspaceId
+ ])
const signIn = useCallback(async () => {
setBusy(true)
@@ -230,6 +269,39 @@
placeholder={EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL}
/>
+ <div className="grid grid-cols-2 gap-4">
+ <TextSetting
+ label="Agent Registry URL"
+ value={agentRegistryBaseUrl}
+ onChange={setAgentRegistryBaseUrl}
+ placeholder={EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL}
+ />
+ <TextSetting
+ label="Approvals URL"
+ value={approvalsBaseUrl}
+ onChange={setApprovalsBaseUrl}
+ placeholder={EVALOPS_DEFAULT_APPROVALS_BASE_URL}
+ />
+ <TextSetting
+ label="Skills URL"
+ value={skillsBaseUrl}
+ onChange={setSkillsBaseUrl}
+ placeholder={EVALOPS_DEFAULT_SKILLS_BASE_URL}
+ />
+ <TextSetting
+ label="Memory URL"
+ value={memoryBaseUrl}
+ onChange={setMemoryBaseUrl}
+ placeholder={EVALOPS_DEFAULT_MEMORY_BASE_URL}
+ />
+ <TextSetting
+ label="Traces URL"
+ value={tracesBaseUrl}
+ onChange={setTracesBaseUrl}
+ placeholder={EVALOPS_DEFAULT_TRACES_BASE_URL}
+ />
+ </div>
+
<TextSetting
label="Bearer Token"
value={token}
@@ -306,7 +378,7 @@
<div>
<h4 className="text-sm font-medium mb-1">Platform Services</h4>
<p className="text-xs text-muted-foreground">
- Agent registry, skills, memory, approvals, and traces are checked through the unified EvalOps platform API.
+ Agent registry, approvals, skills, memory, and traces are checked through their configured EvalOps service URLs.
</p>
</div>
<button
diff --git a/src/renderer/overlay/index.html b/src/renderer/overlay/index.html
--- a/src/renderer/overlay/index.html
+++ b/src/renderer/overlay/index.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
http-equiv="Content-Security-Policy"
- content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://*.evalops.svc.cluster.local:* https://api.openai.com"
+ content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://*.evalops.dev https://api.openai.com"
/>
<title>Kestrel - Quick Access</title>
</head>
diff --git a/src/shared/config.ts b/src/shared/config.ts
--- a/src/shared/config.ts
+++ b/src/shared/config.ts
@@ -25,13 +25,14 @@
// ── EvalOps Platform Defaults ──
-export const EVALOPS_DEFAULT_IDENTITY_BASE_URL = 'http://identity-service.evalops.svc.cluster.local:8080'
+export const EVALOPS_DEFAULT_IDENTITY_BASE_URL = 'https://identity.evalops.dev'
export const EVALOPS_DEFAULT_BASE_URL = 'https://api.evalops.dev'
-export const EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL = 'http://llm-gateway-service.evalops.svc.cluster.local:8080/v1'
-export const EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL = 'http://agent-registry-service.evalops.svc.cluster.local:8080'
-export const EVALOPS_DEFAULT_SKILLS_BASE_URL = 'http://skills-service.evalops.svc.cluster.local:8080'
-export const EVALOPS_DEFAULT_MEMORY_BASE_URL = 'http://memory-service.evalops.svc.cluster.local:8080'
-export const EVALOPS_DEFAULT_TRACES_BASE_URL = 'http://traces-service.evalops.svc.cluster.local:8080'
+export const EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL = 'https://llm-gateway.evalops.dev/v1'
+export const EVALOPS_DEFAULT_AGENT_REGISTRY_BASE_URL = 'https://agent-registry.evalops.dev'
+export const EVALOPS_DEFAULT_APPROVALS_BASE_URL = 'https://approvals.evalops.dev'
+export const EVALOPS_DEFAULT_SKILLS_BASE_URL = 'https://skills.evalops.dev'
+export const EVALOPS_DEFAULT_MEMORY_BASE_URL = 'https://memory.evalops.dev'
+export const EVALOPS_DEFAULT_TRACES_BASE_URL = 'https://traces.evalops.dev'
export const EVALOPS_DEFAULT_RESOURCE = EVALOPS_DEFAULT_LLM_GATEWAY_BASE_URL
export const EVALOPS_DEFAULT_WORKSPACE_ID = 'default'
export const EVALOPS_DEFAULT_AGENT_ID = 'kestrel-desktop'You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 40dbd8e. Configure here.

Summary
Validation
Note: plain
npm cistill fails in postinstall because better-sqlite3 does not rebuild against Electron 41 headers in this local toolchain.npm testreaches the native ContextKit audio integration afternpm run contextkit:build, then fails an existing WAV duration assertion unrelated to EvalOps registration.