forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodingAgent.ts
More file actions
143 lines (129 loc) · 5.09 KB
/
Copy pathcodingAgent.ts
File metadata and controls
143 lines (129 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// IPC surface for delegated coding-agent tasks. Follows the house pattern:
// invoke-style handlers plus a broadcast channel for streaming task events
// (both the main window and the overlay may render the same task's progress).
import { ipcMain, BrowserWindow, shell } from 'electron'
import {
ADAPTER_PROFILES,
adapterActivationError,
adapterIsActivated,
type AdapterCommandOverrides
} from '../codingAgent/adapterRegistry'
import { PRODUCTION_ADAPTER_IDS } from '../codingAgent/interface'
import { cancelTask, runCodingAgentTask, testAgentConnection } from '../codingAgent/taskRunner'
import {
claudeAuthStatus,
removeClaudeCredentials,
startClaudeOAuthFlow,
validateClaudeOAuthUrl,
type ClaudeOAuthFlowHandle
} from '../codingAgent/claudeOAuth'
import { messageFrom } from '../codingAgent/failures'
import { detectAgents } from '../codingAgent/agentDetect'
import { codexApiKeyStatus, saveCodexApiKey } from '../codingAgent/codexAuth'
import type { ProductionAdapterId } from '../codingAgent/interface'
import type {
AgentDetectionMap,
CodexKeyResult,
CodexKeyStatus,
CodingAgentAuthStatus,
CodingAgentEvent,
CodingAgentInfo,
CodingAgentResult,
CodingAgentRunArgs,
CodingAgentStartAuthResult
} from '../../shared/types'
function broadcast(event: CodingAgentEvent): void {
for (const win of BrowserWindow.getAllWindows()) {
if (!win.isDestroyed()) {
win.webContents.send('codingAgent:event', event)
}
}
}
export function registerCodingAgentHandlers(): void {
ipcMain.handle(
'codingAgent:list',
(_e, commandOverrides?: AdapterCommandOverrides): CodingAgentInfo[] => {
const overrides = commandOverrides ?? {}
return PRODUCTION_ADAPTER_IDS.map((id) => {
const connected = adapterIsActivated(id, overrides)
return {
id,
displayName: ADAPTER_PROFILES[id].displayName,
connected,
installHint: connected ? undefined : adapterActivationError(id)
}
})
}
)
ipcMain.handle(
'codingAgent:run',
(_e, args: CodingAgentRunArgs): Promise<CodingAgentResult> =>
runCodingAgentTask(args, broadcast, (message) => console.log(`[codingAgent] ${message}`))
)
ipcMain.handle('codingAgent:cancel', (_e, taskId: string): boolean => cancelTask(taskId))
ipcMain.handle(
'codingAgent:test',
(_e, agentId: ProductionAdapterId, commandOverrides?: AdapterCommandOverrides) =>
testAgentConnection(agentId, commandOverrides ?? {}, (message) =>
console.log(`[codingAgent] ${message}`)
)
)
ipcMain.handle('codingAgent:authStatus', (): CodingAgentAuthStatus => claudeAuthStatus())
ipcMain.handle(
'codingAgent:startAuth',
(): Promise<CodingAgentStartAuthResult> => startClaudeAuth()
)
ipcMain.handle('codingAgent:signOut', (): CodingAgentAuthStatus => {
removeClaudeCredentials()
return claudeAuthStatus()
})
// PATH auto-detection for the external agent CLIs (Codex / Hermes / OpenClaw).
ipcMain.handle('codingAgent:detect', (): Promise<AgentDetectionMap> => detectAgents())
// Codex OpenAI API-key lane (encrypted store, boolean-only status to renderer).
ipcMain.handle('codingAgent:codexKeyStatus', (): CodexKeyStatus => codexApiKeyStatus())
ipcMain.handle(
'codingAgent:setCodexKey',
(_e, key: string): Promise<CodexKeyResult> =>
saveCodexApiKey(typeof key === 'string' ? key : '')
)
}
// One in-flight Claude sign-in at a time. A duplicate request (e.g. the user
// double-clicks "Sign in") joins the running flow instead of opening a second
// browser tab or spinning up a second callback server — mirrors macOS's
// idempotent startAuthFlow / one-launch latch.
let activeAuth: Promise<CodingAgentStartAuthResult> | null = null
async function startClaudeAuth(): Promise<CodingAgentStartAuthResult> {
if (activeAuth) return activeAuth
activeAuth = runClaudeAuthOnce().finally(() => {
activeAuth = null
})
return activeAuth
}
async function runClaudeAuthOnce(): Promise<CodingAgentStartAuthResult> {
const log = (message: string): void => console.log(`[codingAgent] ${message}`)
let flow: ClaudeOAuthFlowHandle | null = null
try {
flow = await startClaudeOAuthFlow(log)
// Validate before opening: never hand the browser a URL that isn't the
// exact claude.ai PKCE loopback authorize request we built.
const validated = validateClaudeOAuthUrl(flow.authUrl)
if (!validated) {
flow.cancel()
// Fail-closed (macOS parity): don't hand the browser a URL that isn't the
// exact claude.ai PKCE loopback request; surface the same generic copy.
return {
ok: false,
error: 'Unable to start Claude sign-in. Try again.',
status: claudeAuthStatus()
}
}
// Don't spawn a real browser under E2E (keeps the harness hermetic and lets
// it screenshot the upsell sheet without a claude.ai tab opening).
if (!process.env.OMI_E2E) void shell.openExternal(validated.toString())
await flow.complete
return { ok: true, status: claudeAuthStatus() }
} catch (error) {
flow?.cancel()
return { ok: false, error: messageFrom(error), status: claudeAuthStatus() }
}
}