Skip to content
Merged
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
5 changes: 3 additions & 2 deletions src/main/chat/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Persona, ExecutionPreference, ExtensionChatTransportConfig } from '../../shared/types'
import type { Persona, ExecutionPreference, ExtensionChatTransportConfig, OmnigentSettings } from '../../shared/types'

export interface ChatMessage {
role: 'user' | 'assistant' | 'system'
Expand Down Expand Up @@ -74,6 +74,7 @@ export interface ChatRequest {
executionTarget?: 'local' | 'cloud'
cloudHostId?: string | null
executionPreference?: ExecutionPreference | null
omnigent?: OmnigentSettings | null
jobId?: string | null
jobSequence?: number
runMode?: 'foreground' | 'background'
Expand Down Expand Up @@ -103,4 +104,4 @@ export interface RuntimeChatSessionState {
cloudHostId: string | null
isStreaming: boolean
messages: ChatMessage[]
}
}
37 changes: 31 additions & 6 deletions src/main/ipc/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,11 @@ async function getExecutionRoutingState(): Promise<{
}

function supportsDaemonChatProvider(provider: string | null | undefined): boolean {
return provider === 'claude' || provider === 'codex' || provider === 'opencode' || provider === 'hermes'
return provider === 'claude' || provider === 'codex' || provider === 'opencode' || provider === 'hermes' || provider === 'omnigent'
}

function requiresDaemonChatProvider(provider: string | null | undefined): boolean {
return provider === 'omnigent'
}

function supportsProviderNativeBackground(provider: string | null | undefined): boolean {
Expand Down Expand Up @@ -630,10 +634,10 @@ async function selectChatExecutionHost(req: ChatRequest): Promise<ExecutionHostR
if (!supportsDaemonChatProvider(provider)) {
const providerLabel = provider || 'This provider'
if (req.executionTarget === 'cloud') {
throw new Error(`${providerLabel} does not support remote daemon execution yet. Daemon-backed chat currently supports Claude, Codex, OpenCode, and Hermes only.`)
throw new Error(`${providerLabel} does not support remote daemon execution yet. Daemon-backed chat currently supports Claude, Codex, OpenCode, Hermes, and Omnigent only.`)
}
if (executionPreference.mode === 'daemon-only' || executionPreference.mode === 'specific-host') {
throw new Error(`${providerLabel} does not support daemon-backed chat yet. Supported daemon providers: Claude, Codex, OpenCode, and Hermes.`)
throw new Error(`${providerLabel} does not support daemon-backed chat yet. Supported daemon providers: Claude, Codex, OpenCode, Hermes, and Omnigent.`)
}
return null
}
Expand All @@ -654,7 +658,21 @@ async function selectChatExecutionHost(req: ChatRequest): Promise<ExecutionHostR
preference: executionPreference,
localDaemonAvailable,
})
return resolution.host.type === 'runtime' ? null : resolution.host
if (resolution.host.type !== 'runtime') return resolution.host

if (requiresDaemonChatProvider(provider)) {
if (!localDaemonAvailable) {
throw new Error(`${provider || 'This provider'} requires daemon-backed chat, but the local daemon is unavailable.`)
}
const localDaemonHost = hosts.find(host => host.type === 'local-daemon' && host.enabled !== false)
?? getBuiltinExecutionHosts().find(host => host.type === 'local-daemon')
if (!localDaemonHost) {
throw new Error(`${provider || 'This provider'} requires daemon-backed chat, but no local daemon host is registered.`)
}
return localDaemonHost
}

return null
}

async function buildProjectContext(workspaceDir: string | undefined): Promise<{
Expand Down Expand Up @@ -768,14 +786,21 @@ async function sendChatToDaemon(req: ChatRequest, host: ExecutionHostRecord): Pr
effective: describeProjectContextEnvelope(projectContext),
})

const requestWithProviderSettings: ChatRequest = req.provider === 'omnigent'
? {
...req,
omnigent: req.omnigent ?? readSettingsSync().omnigent,
}
: req

const job = await hostRequest<{
id: string
status: string
}>(host, '/chat/job/start', {
body: {
request: {
...req,
messages: getPreparedMessages(req),
...requestWithProviderSettings,
messages: getPreparedMessages(requestWithProviderSettings),
projectContext,
},
},
Expand Down
11 changes: 10 additions & 1 deletion src/renderer/src/config/providers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** Chat provider/model/mode configuration — extracted from ChatTile for reuse. */

export type BuiltinProvider = 'claude' | 'codex' | 'opencode' | 'openclaw' | 'hermes' | 'csagent'
export type BuiltinProvider = 'claude' | 'codex' | 'opencode' | 'openclaw' | 'hermes' | 'omnigent' | 'csagent'

export interface ModelOption {
id: string
Expand Down Expand Up @@ -69,6 +69,9 @@ export const DEFAULT_MODELS: Record<BuiltinProvider, ModelOption[]> = {
{ id: 'openrouter/deepseek/deepseek-v4-pro', label: 'DeepSeek V4 Pro', description: 'openrouter' },
{ id: 'openrouter/qwen/qwen3.6-plus', label: 'Qwen 3.6 Plus', description: 'openrouter' },
],
omnigent: [
{ id: 'omnigent:default', label: 'Default agent', description: 'Configured/default Omnigent agent' },
],
csagent: [
{ id: 'anthropic/claude-sonnet-4-6', label: 'Sonnet 4.6' },
],
Expand Down Expand Up @@ -106,6 +109,9 @@ export const PROVIDER_MODES: Record<BuiltinProvider, ModeOption[]> = {
{ id: 'web', label: 'Web', description: 'Web + browser tools', color: '#3fb950' },
{ id: 'query', label: 'Query', description: 'No tools, query only', color: '#58a6ff' },
],
omnigent: [
{ id: 'default', label: 'Default', description: 'Use Omnigent agent defaults', color: '#3fb950' },
],
csagent: [
{ id: 'default', label: 'Default', description: 'Ask before risky actions', color: '#3fb950' },
{ id: 'bypass', label: 'Bypass', description: 'Full auto, no approval', color: '#e54d2e' },
Expand Down Expand Up @@ -148,6 +154,7 @@ export const PROVIDER_LABELS: Record<BuiltinProvider, string> = {
opencode: 'OpenCode',
openclaw: 'OpenClaw',
hermes: 'Hermes',
omnigent: 'Omnigent',
csagent: 'Pi',
}

Expand All @@ -157,6 +164,7 @@ export function isBuiltinProvider(providerId: string): providerId is BuiltinProv
|| providerId === 'opencode'
|| providerId === 'openclaw'
|| providerId === 'hermes'
|| providerId === 'omnigent'
|| providerId === 'csagent'
}

Expand Down Expand Up @@ -194,6 +202,7 @@ export function getApproxSystemOverheadTokens(providerId: string, modelId: strin
if (normalizedProvider === 'opencode') return 12_000
if (normalizedProvider === 'openclaw') return 12_000
if (normalizedProvider === 'hermes') return 8_000
if (normalizedProvider === 'omnigent') return 8_000
return 6_000
}

Expand Down
12 changes: 11 additions & 1 deletion src/renderer/src/hooks/useChatTileProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const PROVIDER_ICON: Record<BuiltinProvider, React.ReactNode> = {
opencode: React.createElement(Bot, { size: TOOLBAR_PILL_ICON_SIZE }),
openclaw: React.createElement(OpenClawIcon, { size: TOOLBAR_PILL_ICON_SIZE }),
hermes: React.createElement(HermesIcon, { size: TOOLBAR_PILL_ICON_SIZE }),
omnigent: React.createElement(Bot, { size: TOOLBAR_PILL_ICON_SIZE }),
csagent: React.createElement(PiIcon, { size: TOOLBAR_PILL_ICON_SIZE }),
}

Expand Down Expand Up @@ -169,6 +170,14 @@ export function useChatTileProviders({
models: DEFAULT_MODELS.hermes,
kind: 'builtin',
},
omnigent: {
id: 'omnigent',
label: PROVIDER_LABELS.omnigent,
noun: 'agent',
icon: PROVIDER_ICON.omnigent,
models: DEFAULT_MODELS.omnigent,
kind: 'builtin',
},
csagent: {
id: 'csagent',
label: PROVIDER_LABELS.csagent,
Expand Down Expand Up @@ -213,6 +222,7 @@ export function useChatTileProviders({
builtinProviderEntries.opencode,
builtinProviderEntries.openclaw,
builtinProviderEntries.hermes,
builtinProviderEntries.omnigent,
builtinProviderEntries.csagent,
...extensionProviderEntries,
], [builtinProviderEntries, extensionProviderEntries])
Expand Down Expand Up @@ -283,4 +293,4 @@ export function useChatTileProviders({
thinkingOptions,
handleProviderChange,
}
}
}
21 changes: 21 additions & 0 deletions src/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,14 @@ export interface CodexExecutionSettings {
executionProvider: 'cli' | 'sdk'
}

export interface OmnigentSettings {
enabled: boolean
baseUrl: string
apiKey: string
agentId: string
autoStart: boolean
}

export interface AppSettings {
// The three font tokens
fonts: FontSettings
Expand Down Expand Up @@ -647,6 +655,8 @@ export interface AppSettings {
execution: ExecutionPreference
// Codex execution backend for daemon-backed jobs.
codex: CodexExecutionSettings
// Omnigent daemon provider configuration.
omnigent: OmnigentSettings
// Last selected chat execution / permission mode by provider.
chatProviderModes: Partial<Record<string, string>>
// Daemon-owned background memory consolidation.
Expand Down Expand Up @@ -785,6 +795,13 @@ export const DEFAULT_SETTINGS: AppSettings = {
codex: {
executionProvider: 'cli',
},
omnigent: {
enabled: true,
baseUrl: 'http://127.0.0.1:6767',
apiKey: '',
agentId: '',
autoStart: true,
},
chatProviderModes: {},
autoDream: {
enabled: true,
Expand Down Expand Up @@ -988,6 +1005,10 @@ export function withDefaultSettings(input: Partial<AppSettings> | null | undefin
...DEFAULT_SETTINGS.codex,
...(settings.codex ?? {}),
},
omnigent: {
...DEFAULT_SETTINGS.omnigent,
...(settings.omnigent ?? {}),
},
chatProviderModes,
autoDream: {
...DEFAULT_SETTINGS.autoDream,
Expand Down
60 changes: 60 additions & 0 deletions test/omnigent-desktop-wiring.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, test } from 'node:test'
import { readFileSync } from 'node:fs'
import { expect } from './node-expect.ts'
import {
DEFAULT_MODELS,
PROVIDER_LABELS,
PROVIDER_MODES,
isBuiltinProvider,
} from '../src/renderer/src/config/providers.ts'
import {
DEFAULT_SETTINGS,
withDefaultSettings,
type AppSettings,
} from '../src/shared/types.ts'

describe('Omnigent desktop provider wiring', () => {
test('exposes Omnigent as a built-in provider with a default agent option', () => {
expect(isBuiltinProvider('omnigent')).toBe(true)
expect(PROVIDER_LABELS.omnigent).toBe('Omnigent')
expect(DEFAULT_MODELS.omnigent[0]).toEqual({
id: 'omnigent:default',
label: 'Default agent',
description: 'Configured/default Omnigent agent',
})
expect(PROVIDER_MODES.omnigent[0].id).toBe('default')
})

test('normalizes AppSettings.omnigent with daemon-compatible defaults', () => {
expect(DEFAULT_SETTINGS.omnigent).toEqual({
enabled: true,
baseUrl: 'http://127.0.0.1:6767',
apiKey: '',
agentId: '',
autoStart: true,
})

const settings = withDefaultSettings({
omnigent: {
enabled: false,
apiKey: 'token',
},
} as unknown as Partial<AppSettings>)

expect(settings.omnigent).toEqual({
enabled: false,
baseUrl: 'http://127.0.0.1:6767',
apiKey: 'token',
agentId: '',
autoStart: true,
})
})

test('main process forwards Omnigent settings in daemon job-start payloads', () => {
const source = readFileSync(`${process.cwd()}/src/main/ipc/chat.ts`, 'utf8')

expect(source).toContain("provider === 'omnigent'")
expect(source).toContain('readSettingsSync().omnigent')
expect(source).toContain('requestWithProviderSettings')
})
})
Loading