agentgui 1.0.700 → 1.0.702
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ws-handlers-util.js +67 -42
- package/package.json +1 -1
- package/server.js +144 -0
- package/static/js/agent-auth.js +106 -0
package/lib/ws-handlers-util.js
CHANGED
|
@@ -9,6 +9,7 @@ export function register(router, deps) {
|
|
|
9
9
|
const { queries, wsOptimizer, modelDownloadState, ensureModelsDownloaded,
|
|
10
10
|
broadcastSync, getSpeech, getProviderConfigs, saveProviderConfig,
|
|
11
11
|
startGeminiOAuth, exchangeGeminiOAuthCode, geminiOAuthState,
|
|
12
|
+
startCodexDeviceAuth, codexDeviceAuthState,
|
|
12
13
|
STARTUP_CWD, activeScripts, voiceCacheManager, toolManager, discoveredAgents } = deps;
|
|
13
14
|
|
|
14
15
|
router.handle('home', () => ({ home: os.homedir(), cwd: STARTUP_CWD }));
|
|
@@ -157,6 +158,30 @@ export function register(router, deps) {
|
|
|
157
158
|
} catch (e) { err(400, e.message); }
|
|
158
159
|
});
|
|
159
160
|
|
|
161
|
+
router.handle('codex.start', async () => {
|
|
162
|
+
try {
|
|
163
|
+
const result = startCodexDeviceAuth();
|
|
164
|
+
const st = typeof codexDeviceAuthState === 'function' ? codexDeviceAuthState() : codexDeviceAuthState;
|
|
165
|
+
if (result.alreadyAuthenticated) return { status: 'success', authenticated: true };
|
|
166
|
+
const waitForCode = () => new Promise((resolve) => {
|
|
167
|
+
let tries = 12;
|
|
168
|
+
const poll = () => {
|
|
169
|
+
const state = typeof codexDeviceAuthState === 'function' ? codexDeviceAuthState() : codexDeviceAuthState;
|
|
170
|
+
if (state.authUrl || tries-- <= 0) resolve(state);
|
|
171
|
+
else setTimeout(poll, 400);
|
|
172
|
+
};
|
|
173
|
+
poll();
|
|
174
|
+
});
|
|
175
|
+
const state = await waitForCode();
|
|
176
|
+
return { status: state.status, authUrl: state.authUrl, userCode: state.userCode };
|
|
177
|
+
} catch (e) { err(500, e.message); }
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
router.handle('codex.status', () => {
|
|
181
|
+
const st = typeof codexDeviceAuthState === 'function' ? codexDeviceAuthState() : codexDeviceAuthState;
|
|
182
|
+
return st;
|
|
183
|
+
});
|
|
184
|
+
|
|
160
185
|
router.handle('gemini.complete', async (p) => {
|
|
161
186
|
const pastedUrl = (p.url || '').trim();
|
|
162
187
|
if (!pastedUrl) err(400, 'No URL provided');
|
|
@@ -268,47 +293,47 @@ export function register(router, deps) {
|
|
|
268
293
|
}
|
|
269
294
|
});
|
|
270
295
|
|
|
271
|
-
router.handle('agent.subagents', async (p) => {
|
|
272
|
-
const { id } = p;
|
|
273
|
-
if (!id) err(400, 'Missing agent id');
|
|
274
|
-
|
|
275
|
-
// Claude Code: run 'claude agents list' and parse output
|
|
276
|
-
if (id === 'claude-code' || id === 'cli-claude') {
|
|
277
|
-
const spawnEnv = { ...process.env };
|
|
278
|
-
delete spawnEnv.CLAUDECODE;
|
|
279
|
-
const result = spawnSync('claude', ['agents', 'list'], {
|
|
280
|
-
encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
281
|
-
env: spawnEnv
|
|
282
|
-
});
|
|
283
|
-
if (result.status !== 0 || !result.stdout) return { subAgents: [] };
|
|
284
|
-
const output = result.stdout.trim();
|
|
285
|
-
// Output format: ' agentId · model' lines under section headers
|
|
286
|
-
const agents = [];
|
|
287
|
-
for (const line of output.split('\n').filter(l => l.trim())) {
|
|
288
|
-
const match = line.match(/^ (\S+)\s+·/);
|
|
289
|
-
if (match) {
|
|
290
|
-
const id = match[1];
|
|
291
|
-
agents.push({ id, name: id });
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
console.log('[agent.subagents] claude agents list found:', agents.map(a => a.id).join(', '));
|
|
295
|
-
return { subAgents: agents };
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ACP agents: hardcoded map filtered by installed tools
|
|
299
|
-
const subAgentMap = {
|
|
300
|
-
'opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }],
|
|
301
|
-
'cli-opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }],
|
|
302
|
-
'gemini': [{ id: 'gm-gc', name: 'GM Gemini' }],
|
|
303
|
-
'cli-gemini': [{ id: 'gm-gc', name: 'GM Gemini' }],
|
|
304
|
-
'kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }],
|
|
305
|
-
'cli-kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }],
|
|
306
|
-
'codex': [],
|
|
307
|
-
'cli-codex': []
|
|
308
|
-
};
|
|
309
|
-
const subAgents = subAgentMap[id] || [];
|
|
310
|
-
const tools = await toolManager.getAllToolsAsync();
|
|
311
|
-
const installed = new Set(tools.filter(t => t.category === 'plugin' && t.installed).map(t => t.id));
|
|
312
|
-
return { subAgents: subAgents.filter(sa => installed.has(sa.id)) };
|
|
296
|
+
router.handle('agent.subagents', async (p) => {
|
|
297
|
+
const { id } = p;
|
|
298
|
+
if (!id) err(400, 'Missing agent id');
|
|
299
|
+
|
|
300
|
+
// Claude Code: run 'claude agents list' and parse output
|
|
301
|
+
if (id === 'claude-code' || id === 'cli-claude') {
|
|
302
|
+
const spawnEnv = { ...process.env };
|
|
303
|
+
delete spawnEnv.CLAUDECODE;
|
|
304
|
+
const result = spawnSync('claude', ['agents', 'list'], {
|
|
305
|
+
encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
306
|
+
env: spawnEnv
|
|
307
|
+
});
|
|
308
|
+
if (result.status !== 0 || !result.stdout) return { subAgents: [] };
|
|
309
|
+
const output = result.stdout.trim();
|
|
310
|
+
// Output format: ' agentId · model' lines under section headers
|
|
311
|
+
const agents = [];
|
|
312
|
+
for (const line of output.split('\n').filter(l => l.trim())) {
|
|
313
|
+
const match = line.match(/^ (\S+)\s+·/);
|
|
314
|
+
if (match) {
|
|
315
|
+
const id = match[1];
|
|
316
|
+
agents.push({ id, name: id });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
console.log('[agent.subagents] claude agents list found:', agents.map(a => a.id).join(', '));
|
|
320
|
+
return { subAgents: agents };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ACP agents: hardcoded map filtered by installed tools
|
|
324
|
+
const subAgentMap = {
|
|
325
|
+
'opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }],
|
|
326
|
+
'cli-opencode': [{ id: 'gm-oc', name: 'GM OpenCode' }],
|
|
327
|
+
'gemini': [{ id: 'gm-gc', name: 'GM Gemini' }],
|
|
328
|
+
'cli-gemini': [{ id: 'gm-gc', name: 'GM Gemini' }],
|
|
329
|
+
'kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }],
|
|
330
|
+
'cli-kilo': [{ id: 'gm-kilo', name: 'GM Kilo' }],
|
|
331
|
+
'codex': [],
|
|
332
|
+
'cli-codex': []
|
|
333
|
+
};
|
|
334
|
+
const subAgents = subAgentMap[id] || [];
|
|
335
|
+
const tools = await toolManager.getAllToolsAsync();
|
|
336
|
+
const installed = new Set(tools.filter(t => t.category === 'plugin' && t.installed).map(t => t.id));
|
|
337
|
+
return { subAgents: subAgents.filter(sa => installed.has(sa.id)) };
|
|
313
338
|
});
|
|
314
339
|
}
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -696,6 +696,96 @@ const GEMINI_ACCOUNTS_FILE = path.join(GEMINI_DIR, 'google_accounts.json');
|
|
|
696
696
|
let geminiOAuthState = { status: 'idle', error: null, email: null };
|
|
697
697
|
let geminiOAuthPending = null;
|
|
698
698
|
|
|
699
|
+
const CODEX_AUTH_FILE = path.join(os.homedir(), '.codex', 'auth.json');
|
|
700
|
+
let codexDeviceAuthState = { status: 'idle', error: null, userCode: null, authUrl: null };
|
|
701
|
+
let codexDeviceAuthProcess = null;
|
|
702
|
+
|
|
703
|
+
function getCodexAuthStatus() {
|
|
704
|
+
try {
|
|
705
|
+
if (fs.existsSync(CODEX_AUTH_FILE)) {
|
|
706
|
+
const creds = JSON.parse(fs.readFileSync(CODEX_AUTH_FILE, 'utf-8'));
|
|
707
|
+
if (creds.auth_mode === 'oauth' || creds.access_token || creds.refresh_token) {
|
|
708
|
+
return { authenticated: true, detail: creds.email || 'oauth' };
|
|
709
|
+
}
|
|
710
|
+
if (creds.auth_mode === 'apikey' && creds.OPENAI_API_KEY) {
|
|
711
|
+
return { authenticated: true, detail: 'api-key' };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} catch (_) {}
|
|
715
|
+
return { authenticated: false, detail: 'no credentials' };
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function startCodexDeviceAuth() {
|
|
719
|
+
if (codexDeviceAuthProcess) {
|
|
720
|
+
return { alreadyRunning: true };
|
|
721
|
+
}
|
|
722
|
+
const codexStatus = getCodexAuthStatus();
|
|
723
|
+
if (codexStatus.authenticated) {
|
|
724
|
+
codexDeviceAuthState = { status: 'success', error: null, userCode: null, authUrl: null };
|
|
725
|
+
return { alreadyAuthenticated: true };
|
|
726
|
+
}
|
|
727
|
+
codexDeviceAuthState = { status: 'pending', error: null, userCode: null, authUrl: null };
|
|
728
|
+
const child = spawn('npx', ['@openai/codex', 'login', '--device-auth'], {
|
|
729
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
730
|
+
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
|
|
731
|
+
shell: os.platform() === 'win32',
|
|
732
|
+
});
|
|
733
|
+
codexDeviceAuthProcess = child;
|
|
734
|
+
|
|
735
|
+
const onData = (chunk) => {
|
|
736
|
+
const text = chunk.toString().replace(/\x1b\[[0-9;]*m/g, '');
|
|
737
|
+
const urlMatch = text.match(/https:\/\/auth\.openai\.com\/codex\/device[^\s]*/);
|
|
738
|
+
const codeMatch = text.match(/\b([0-9A-Z]{4}-[0-9A-Z]{5})\b/);
|
|
739
|
+
if (urlMatch && !codexDeviceAuthState.authUrl) codexDeviceAuthState.authUrl = urlMatch[0];
|
|
740
|
+
if (codeMatch && !codexDeviceAuthState.userCode) codexDeviceAuthState.userCode = codeMatch[1];
|
|
741
|
+
};
|
|
742
|
+
child.stdout.on('data', onData);
|
|
743
|
+
child.stderr.on('data', onData);
|
|
744
|
+
|
|
745
|
+
const authFileWatcher = fs.watchFile ? null : null;
|
|
746
|
+
const pollInterval = setInterval(() => {
|
|
747
|
+
const st = getCodexAuthStatus();
|
|
748
|
+
if (st.authenticated) {
|
|
749
|
+
clearInterval(pollInterval);
|
|
750
|
+
clearTimeout(timeoutId);
|
|
751
|
+
codexDeviceAuthState = { status: 'success', error: null, userCode: codexDeviceAuthState.userCode, authUrl: codexDeviceAuthState.authUrl };
|
|
752
|
+
if (codexDeviceAuthProcess) { try { codexDeviceAuthProcess.kill('SIGTERM'); } catch (_) {} codexDeviceAuthProcess = null; }
|
|
753
|
+
}
|
|
754
|
+
}, 1500);
|
|
755
|
+
|
|
756
|
+
const timeoutId = setTimeout(() => {
|
|
757
|
+
clearInterval(pollInterval);
|
|
758
|
+
if (codexDeviceAuthState.status === 'pending') {
|
|
759
|
+
codexDeviceAuthState = { status: 'error', error: 'Authentication timed out', userCode: null, authUrl: null };
|
|
760
|
+
}
|
|
761
|
+
if (codexDeviceAuthProcess) { try { codexDeviceAuthProcess.kill('SIGTERM'); } catch (_) {} codexDeviceAuthProcess = null; }
|
|
762
|
+
}, 5 * 60 * 1000);
|
|
763
|
+
|
|
764
|
+
child.on('error', (e) => {
|
|
765
|
+
clearInterval(pollInterval);
|
|
766
|
+
clearTimeout(timeoutId);
|
|
767
|
+
codexDeviceAuthState = { status: 'error', error: e.message, userCode: null, authUrl: null };
|
|
768
|
+
codexDeviceAuthProcess = null;
|
|
769
|
+
});
|
|
770
|
+
child.on('close', (code) => {
|
|
771
|
+
clearInterval(pollInterval);
|
|
772
|
+
clearTimeout(timeoutId);
|
|
773
|
+
codexDeviceAuthProcess = null;
|
|
774
|
+
if (codexDeviceAuthState.status === 'pending') {
|
|
775
|
+
if (code === 0) {
|
|
776
|
+
const st = getCodexAuthStatus();
|
|
777
|
+
codexDeviceAuthState = st.authenticated
|
|
778
|
+
? { status: 'success', error: null, userCode: codexDeviceAuthState.userCode, authUrl: codexDeviceAuthState.authUrl }
|
|
779
|
+
: { status: 'error', error: 'Process exited without saving credentials', userCode: null, authUrl: null };
|
|
780
|
+
} else {
|
|
781
|
+
codexDeviceAuthState = { status: 'error', error: 'Authentication cancelled', userCode: null, authUrl: null };
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
return { started: true };
|
|
787
|
+
}
|
|
788
|
+
|
|
699
789
|
function buildBaseUrl(req) {
|
|
700
790
|
const override = process.env.AGENTGUI_BASE_URL;
|
|
701
791
|
if (override) return override.replace(/\/+$/, '');
|
|
@@ -2177,6 +2267,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
2177
2267
|
} else {
|
|
2178
2268
|
status.detail = 'no credentials';
|
|
2179
2269
|
}
|
|
2270
|
+
} else if (agent.id === 'codex') {
|
|
2271
|
+
const codexSt = getCodexAuthStatus();
|
|
2272
|
+
status.authenticated = codexSt.authenticated;
|
|
2273
|
+
status.detail = codexSt.detail;
|
|
2180
2274
|
} else {
|
|
2181
2275
|
status.detail = 'unknown';
|
|
2182
2276
|
}
|
|
@@ -2659,6 +2753,55 @@ const server = http.createServer(async (req, res) => {
|
|
|
2659
2753
|
}
|
|
2660
2754
|
}
|
|
2661
2755
|
|
|
2756
|
+
if (agentId === 'codex') {
|
|
2757
|
+
const conversationId = '__agent_auth__';
|
|
2758
|
+
const result = startCodexDeviceAuth();
|
|
2759
|
+
if (result.alreadyRunning) {
|
|
2760
|
+
sendJSON(req, res, 200, { ok: true, agentId, authUrl: codexDeviceAuthState.authUrl, userCode: codexDeviceAuthState.userCode, status: 'pending' });
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
if (result.alreadyAuthenticated) {
|
|
2764
|
+
sendJSON(req, res, 200, { ok: true, agentId, status: 'success' });
|
|
2765
|
+
return;
|
|
2766
|
+
}
|
|
2767
|
+
broadcastSync({ type: 'script_started', conversationId, script: 'auth-codex', agentId: 'codex', timestamp: Date.now() });
|
|
2768
|
+
broadcastSync({ type: 'script_output', conversationId, data: '\x1b[36mStarting Codex device authorization...\x1b[0m\r\n', stream: 'stdout', timestamp: Date.now() });
|
|
2769
|
+
|
|
2770
|
+
const waitForCode = (tries) => {
|
|
2771
|
+
if (tries <= 0) return;
|
|
2772
|
+
if (codexDeviceAuthState.authUrl && codexDeviceAuthState.userCode) {
|
|
2773
|
+
const msg = `\r\nVisit: ${codexDeviceAuthState.authUrl}\r\nEnter code: ${codexDeviceAuthState.userCode}\r\n`;
|
|
2774
|
+
broadcastSync({ type: 'script_output', conversationId, data: msg, stream: 'stdout', timestamp: Date.now() });
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
setTimeout(() => waitForCode(tries - 1), 500);
|
|
2778
|
+
};
|
|
2779
|
+
waitForCode(10);
|
|
2780
|
+
|
|
2781
|
+
const pollId = setInterval(() => {
|
|
2782
|
+
if (codexDeviceAuthState.status === 'success') {
|
|
2783
|
+
clearInterval(pollId);
|
|
2784
|
+
broadcastSync({ type: 'script_output', conversationId, data: '\r\n\x1b[32mCodex authentication successful\x1b[0m\r\n', stream: 'stdout', timestamp: Date.now() });
|
|
2785
|
+
broadcastSync({ type: 'script_stopped', conversationId, code: 0, timestamp: Date.now() });
|
|
2786
|
+
} else if (codexDeviceAuthState.status === 'error') {
|
|
2787
|
+
clearInterval(pollId);
|
|
2788
|
+
broadcastSync({ type: 'script_output', conversationId, data: `\r\n\x1b[31mAuthentication failed: ${codexDeviceAuthState.error}\x1b[0m\r\n`, stream: 'stderr', timestamp: Date.now() });
|
|
2789
|
+
broadcastSync({ type: 'script_stopped', conversationId, code: 1, error: codexDeviceAuthState.error, timestamp: Date.now() });
|
|
2790
|
+
}
|
|
2791
|
+
}, 1500);
|
|
2792
|
+
setTimeout(() => clearInterval(pollId), 5 * 60 * 1000 + 5000);
|
|
2793
|
+
|
|
2794
|
+
const waitForInfo = (tries) => {
|
|
2795
|
+
if (codexDeviceAuthState.authUrl || tries <= 0) {
|
|
2796
|
+
sendJSON(req, res, 200, { ok: true, agentId, authUrl: codexDeviceAuthState.authUrl, userCode: codexDeviceAuthState.userCode, status: codexDeviceAuthState.status });
|
|
2797
|
+
} else {
|
|
2798
|
+
setTimeout(() => waitForInfo(tries - 1), 400);
|
|
2799
|
+
}
|
|
2800
|
+
};
|
|
2801
|
+
waitForInfo(12);
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2662
2805
|
const authCommands = {
|
|
2663
2806
|
'claude-code': { cmd: 'claude', args: ['setup-token'] },
|
|
2664
2807
|
'opencode': { cmd: 'opencode', args: ['auth', 'login'] },
|
|
@@ -4190,6 +4333,7 @@ registerUtilHandlers(wsRouter, {
|
|
|
4190
4333
|
broadcastSync, getSpeech, getProviderConfigs, saveProviderConfig,
|
|
4191
4334
|
startGeminiOAuth, exchangeGeminiOAuthCode,
|
|
4192
4335
|
geminiOAuthState: () => geminiOAuthState,
|
|
4336
|
+
startCodexDeviceAuth, codexDeviceAuthState: () => codexDeviceAuthState,
|
|
4193
4337
|
STARTUP_CWD, activeScripts, voiceCacheManager, toolManager, discoveredAgents
|
|
4194
4338
|
});
|
|
4195
4339
|
|
package/static/js/agent-auth.js
CHANGED
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
function closeDropdown() { dropdown.classList.remove('open'); editingProvider = null; }
|
|
129
129
|
|
|
130
130
|
var oauthPollInterval = null, oauthPollTimeout = null, oauthFallbackTimer = null;
|
|
131
|
+
var codexPollInterval = null, codexPollTimeout = null;
|
|
131
132
|
|
|
132
133
|
function cleanupOAuthPolling() {
|
|
133
134
|
if (oauthPollInterval) { clearInterval(oauthPollInterval); oauthPollInterval = null; }
|
|
@@ -135,6 +136,96 @@
|
|
|
135
136
|
if (oauthFallbackTimer) { clearTimeout(oauthFallbackTimer); oauthFallbackTimer = null; }
|
|
136
137
|
}
|
|
137
138
|
|
|
139
|
+
function cleanupCodexPolling() {
|
|
140
|
+
if (codexPollInterval) { clearInterval(codexPollInterval); codexPollInterval = null; }
|
|
141
|
+
if (codexPollTimeout) { clearTimeout(codexPollTimeout); codexPollTimeout = null; }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function removeCodexModal() {
|
|
145
|
+
var el = document.getElementById('codexDeviceModal');
|
|
146
|
+
if (el) el.remove();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function showCodexDeviceModal(authUrl, userCode) {
|
|
150
|
+
removeCodexModal();
|
|
151
|
+
var overlay = document.createElement('div');
|
|
152
|
+
overlay.id = 'codexDeviceModal';
|
|
153
|
+
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
|
154
|
+
var displayUrl = authUrl || 'https://auth.openai.com/codex/device';
|
|
155
|
+
var displayCode = userCode || '';
|
|
156
|
+
overlay.innerHTML = '<div style="background:var(--color-bg-secondary,#1f2937);border-radius:1rem;padding:2rem;max-width:30rem;width:calc(100% - 2rem);box-shadow:0 25px 50px rgba(0,0,0,0.5);color:var(--color-text-primary,white);font-family:system-ui,sans-serif;" onclick="event.stopPropagation()">' +
|
|
157
|
+
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1.25rem;">' +
|
|
158
|
+
'<h2 style="font-size:1.125rem;font-weight:700;margin:0;">Codex Sign-In</h2>' +
|
|
159
|
+
'<button id="codexModalClose" style="background:none;border:none;color:var(--color-text-secondary,#9ca3af);font-size:1.5rem;cursor:pointer;padding:0;line-height:1;">\u00d7</button></div>' +
|
|
160
|
+
'<div id="codexModalContent" style="text-align:center;">' +
|
|
161
|
+
'<div style="font-size:2rem;margin-bottom:1rem;animation:pulse 2s infinite;">⏳</div>' +
|
|
162
|
+
'<p style="font-size:0.85rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 1rem;">Follow these steps to sign in with your OpenAI account:</p>' +
|
|
163
|
+
'<div style="margin-bottom:1rem;">' +
|
|
164
|
+
'<p style="font-size:0.8rem;color:var(--color-text-secondary,#9ca3af);margin:0 0 0.5rem;text-align:left;">1. Open this link in your browser:</p>' +
|
|
165
|
+
'<div style="display:flex;gap:0.5rem;align-items:center;">' +
|
|
166
|
+
'<a href="' + esc(displayUrl) + '" target="_blank" style="flex:1;padding:0.5rem 0.75rem;background:var(--color-primary,#3b82f6);color:white;text-decoration:none;border-radius:0.375rem;font-size:0.8rem;font-weight:600;text-align:center;">Open Sign-In Page</a>' +
|
|
167
|
+
'</div></div>' +
|
|
168
|
+
'<div style="margin-bottom:1.25rem;">' +
|
|
169
|
+
'<p style="font-size:0.8rem;color:var(--color-text-secondary,#9ca3af);margin:0 0 0.5rem;text-align:left;">2. Enter this one-time code:</p>' +
|
|
170
|
+
'<div style="display:flex;gap:0.5rem;align-items:center;">' +
|
|
171
|
+
'<div id="codexUserCode" style="flex:1;padding:0.625rem;background:var(--color-bg-primary,#111827);border:2px solid var(--color-primary,#3b82f6);border-radius:0.5rem;font-family:monospace;font-size:1.25rem;font-weight:700;letter-spacing:0.15em;text-align:center;">' + esc(displayCode) + '</div>' +
|
|
172
|
+
'<button id="codexCopyBtn" style="padding:0.5rem 0.75rem;background:var(--color-bg-primary,#374151);border:1px solid var(--color-border,#4b5563);border-radius:0.375rem;color:var(--color-text-primary,white);font-size:0.75rem;cursor:pointer;flex-shrink:0;">Copy</button>' +
|
|
173
|
+
'</div></div>' +
|
|
174
|
+
'<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0;">This dialog will close automatically when sign-in completes.</p>' +
|
|
175
|
+
'</div>' +
|
|
176
|
+
'<div id="codexModalSuccess" style="display:none;text-align:center;padding:1rem 0;">' +
|
|
177
|
+
'<div style="font-size:3rem;color:#10b981;margin-bottom:0.75rem;">✓</div>' +
|
|
178
|
+
'<p style="font-weight:600;margin:0 0 0.25rem;">Authentication Successful</p>' +
|
|
179
|
+
'<p style="font-size:0.8rem;color:var(--color-text-secondary,#9ca3af);margin:0;">Codex CLI is now authenticated.</p>' +
|
|
180
|
+
'</div>' +
|
|
181
|
+
'<div style="margin-top:1.25rem;">' +
|
|
182
|
+
'<button id="codexModalCancel" style="width:100%;padding:0.625rem;border-radius:0.5rem;border:1px solid var(--color-border,#4b5563);background:transparent;color:var(--color-text-primary,white);font-size:0.8rem;cursor:pointer;font-weight:600;">Cancel</button></div>' +
|
|
183
|
+
'<style>@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}</style></div>';
|
|
184
|
+
document.body.appendChild(overlay);
|
|
185
|
+
|
|
186
|
+
var dismiss = function() { cleanupCodexPolling(); authRunning = false; removeCodexModal(); };
|
|
187
|
+
document.getElementById('codexModalClose').addEventListener('click', dismiss);
|
|
188
|
+
document.getElementById('codexModalCancel').addEventListener('click', dismiss);
|
|
189
|
+
|
|
190
|
+
var copyBtn = document.getElementById('codexCopyBtn');
|
|
191
|
+
if (copyBtn && displayCode) {
|
|
192
|
+
copyBtn.addEventListener('click', function(e) {
|
|
193
|
+
e.stopPropagation();
|
|
194
|
+
navigator.clipboard.writeText(displayCode).then(function() {
|
|
195
|
+
copyBtn.textContent = 'Copied!';
|
|
196
|
+
setTimeout(function() { copyBtn.textContent = 'Copy'; }, 2000);
|
|
197
|
+
}).catch(function() {});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
cleanupCodexPolling();
|
|
202
|
+
codexPollInterval = setInterval(function() {
|
|
203
|
+
window.wsClient.rpc('codex.status')
|
|
204
|
+
.then(function(st) {
|
|
205
|
+
if (st.status === 'success') {
|
|
206
|
+
cleanupCodexPolling();
|
|
207
|
+
authRunning = false;
|
|
208
|
+
var content = document.getElementById('codexModalContent');
|
|
209
|
+
var success = document.getElementById('codexModalSuccess');
|
|
210
|
+
var cancel = document.getElementById('codexModalCancel');
|
|
211
|
+
if (content) content.style.display = 'none';
|
|
212
|
+
if (success) success.style.display = 'block';
|
|
213
|
+
if (cancel) cancel.textContent = 'Close';
|
|
214
|
+
setTimeout(function() { removeCodexModal(); refresh(); }, 2500);
|
|
215
|
+
} else if (st.status === 'error') {
|
|
216
|
+
cleanupCodexPolling();
|
|
217
|
+
authRunning = false;
|
|
218
|
+
removeCodexModal();
|
|
219
|
+
refresh();
|
|
220
|
+
}
|
|
221
|
+
}).catch(function() {});
|
|
222
|
+
}, 1500);
|
|
223
|
+
codexPollTimeout = setTimeout(function() {
|
|
224
|
+
cleanupCodexPolling();
|
|
225
|
+
if (authRunning) { authRunning = false; removeCodexModal(); }
|
|
226
|
+
}, 5 * 60 * 1000);
|
|
227
|
+
}
|
|
228
|
+
|
|
138
229
|
function showOAuthWaitingModal() {
|
|
139
230
|
removeOAuthModal();
|
|
140
231
|
var overlay = document.createElement('div');
|
|
@@ -217,6 +308,19 @@
|
|
|
217
308
|
|
|
218
309
|
function triggerAuth(agentId) {
|
|
219
310
|
if (authRunning) return;
|
|
311
|
+
if (agentId === 'codex') {
|
|
312
|
+
authRunning = true;
|
|
313
|
+
window.wsClient.rpc('codex.start')
|
|
314
|
+
.then(function(data) {
|
|
315
|
+
if (data.status === 'success') {
|
|
316
|
+
authRunning = false;
|
|
317
|
+
refresh();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
showCodexDeviceModal(data.authUrl, data.userCode);
|
|
321
|
+
}).catch(function() { authRunning = false; });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
220
324
|
window.wsClient.rpc('agent.auth', { id: agentId })
|
|
221
325
|
.then(function(data) {
|
|
222
326
|
if (data.ok) {
|
|
@@ -271,6 +375,8 @@
|
|
|
271
375
|
authRunning = false;
|
|
272
376
|
removeOAuthModal();
|
|
273
377
|
cleanupOAuthPolling();
|
|
378
|
+
cleanupCodexPolling();
|
|
379
|
+
removeCodexModal();
|
|
274
380
|
var term = getTerminal();
|
|
275
381
|
var msg = data.error ? data.error : ('exited with code ' + (data.code || 0));
|
|
276
382
|
if (term) term.writeln('\r\n\x1b[90m[auth ' + msg + ']\x1b[0m');
|