agentgui 1.0.702 → 1.0.703
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/CLAUDE.md +27 -0
- package/lib/ws-handlers-util.js +33 -18
- package/package.json +1 -1
- package/server.js +344 -144
- package/static/js/agent-auth.js +0 -106
- package/static/js/client.js +0 -7
- package/acp-queries.js +0 -182
- package/static/js/codec.js +0 -2
- package/static/js/sync-debug.js +0 -146
- package/test-cli-detection.mjs +0 -37
- package/test-fixes.mjs +0 -103
- package/test-thread-steering.mjs +0 -100
package/CLAUDE.md
CHANGED
|
@@ -82,6 +82,7 @@ XState v5 machines provide formal state tracking alongside (not replacing) the e
|
|
|
82
82
|
- `BASE_URL` - URL prefix (default: /gm)
|
|
83
83
|
- `STARTUP_CWD` - Working directory passed to agents
|
|
84
84
|
- `HOT_RELOAD` - Set to "false" to disable watch mode
|
|
85
|
+
- `CODEX_HOME` - Override Codex CLI home directory (default: `~/.codex`)
|
|
85
86
|
|
|
86
87
|
## ACP Tool Lifecycle
|
|
87
88
|
|
|
@@ -123,6 +124,11 @@ All routes are prefixed with `BASE_URL` (default `/gm`).
|
|
|
123
124
|
- `GET /api/tools/:id/history` - Get tool install/update history (query: limit, offset)
|
|
124
125
|
- `POST /api/tools/update` - Batch update all tools with available updates
|
|
125
126
|
- `POST /api/tools/refresh-all` - Refresh all tool statuses from package manager
|
|
127
|
+
- `POST /api/codex-oauth/start` - Start Codex CLI OAuth flow (returns `{ authUrl, mode }`)
|
|
128
|
+
- `GET /api/codex-oauth/status` - Get current Codex OAuth state `{ status, email, error }`
|
|
129
|
+
- `POST /api/codex-oauth/relay` - Relay OAuth code+state from remote browser (body: `{ code, state }`)
|
|
130
|
+
- `POST /api/codex-oauth/complete` - Complete OAuth by pasting redirect URL (body: `{ url }`)
|
|
131
|
+
- `GET /codex-oauth2callback` - OAuth callback endpoint (redirect_uri for local flows)
|
|
126
132
|
|
|
127
133
|
## Tool Update System
|
|
128
134
|
|
|
@@ -286,6 +292,27 @@ Speech models (~470MB total) are downloaded automatically on server startup. No
|
|
|
286
292
|
- **Client init:** `loadAgents()`, `loadConversations()`, `checkSpeechStatus()` run in parallel via `Promise.all()`.
|
|
287
293
|
- **`perMessageDeflate: false`** on WebSocket server — msgpack binary doesn't compress well, and zlib was blocking the event loop on every streaming_progress send.
|
|
288
294
|
|
|
295
|
+
## Codex CLI OAuth
|
|
296
|
+
|
|
297
|
+
OpenAI Codex CLI uses PKCE authorization code flow against `https://auth.openai.com`.
|
|
298
|
+
|
|
299
|
+
**Flow:**
|
|
300
|
+
1. `POST /api/codex-oauth/start` generates PKCE (SHA-256 S256 challenge), CSRF state, returns `authUrl`
|
|
301
|
+
2. User opens `authUrl` in browser and authenticates via OpenAI/ChatGPT
|
|
302
|
+
3. **Local**: Browser redirects to `http://localhost:1455/auth/callback` — but since agentgui's server is on a different port, the redirect goes to `GET /codex-oauth2callback` (agentgui intercepts via matching route). Token exchange happens server-side.
|
|
303
|
+
4. **Remote**: Redirect goes to `/codex-oauth2callback` which serves a relay page. Relay POSTs `{ code, state }` to `/api/codex-oauth/relay`. Token exchange happens on the server.
|
|
304
|
+
5. Tokens saved to `$CODEX_HOME/auth.json` (default: `~/.codex/auth.json`) as `{ auth_mode: "chatgpt", tokens: { id_token, access_token, refresh_token }, last_refresh }`
|
|
305
|
+
|
|
306
|
+
**Constants (in server.js):**
|
|
307
|
+
- Issuer: `https://auth.openai.com`
|
|
308
|
+
- Client ID: `app_EMoamEEZ73f0CkXaXp7hrann`
|
|
309
|
+
- Scopes: `openid profile email offline_access api.connectors.read api.connectors.invoke`
|
|
310
|
+
- Redirect URI (local): `http://localhost:1455/auth/callback` (actual callback goes to agentgui's `/codex-oauth2callback`)
|
|
311
|
+
|
|
312
|
+
**WebSocket handlers** (in `lib/ws-handlers-util.js`): `codex.start`, `codex.status`, `codex.relay`, `codex.complete`
|
|
313
|
+
|
|
314
|
+
**Agent auth**: `POST /api/agents/codex/auth` starts OAuth flow same as Gemini — broadcasts `script_started`/`script_output`/`script_stopped` events as OAuth progresses.
|
|
315
|
+
|
|
289
316
|
## ACP SDK Integration
|
|
290
317
|
|
|
291
318
|
- **@agentclientprotocol/sdk** (`^0.4.1`) added to dependencies
|
package/lib/ws-handlers-util.js
CHANGED
|
@@ -9,7 +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
|
-
|
|
12
|
+
startCodexOAuth, exchangeCodexOAuthCode, codexOAuthState,
|
|
13
13
|
STARTUP_CWD, activeScripts, voiceCacheManager, toolManager, discoveredAgents } = deps;
|
|
14
14
|
|
|
15
15
|
router.handle('home', () => ({ home: os.homedir(), cwd: STARTUP_CWD }));
|
|
@@ -158,31 +158,46 @@ export function register(router, deps) {
|
|
|
158
158
|
} catch (e) { err(400, e.message); }
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
+
router.handle('gemini.complete', async (p) => {
|
|
162
|
+
const pastedUrl = (p.url || '').trim();
|
|
163
|
+
if (!pastedUrl) err(400, 'No URL provided');
|
|
164
|
+
let parsed;
|
|
165
|
+
try { parsed = new URL(pastedUrl); } catch { err(400, 'Invalid URL. Paste the full URL from the browser address bar.'); }
|
|
166
|
+
const urlError = parsed.searchParams.get('error');
|
|
167
|
+
if (urlError) {
|
|
168
|
+
const desc = parsed.searchParams.get('error_description') || urlError;
|
|
169
|
+
return { error: desc };
|
|
170
|
+
}
|
|
171
|
+
const code = parsed.searchParams.get('code');
|
|
172
|
+
const state = parsed.searchParams.get('state');
|
|
173
|
+
try {
|
|
174
|
+
const email = await exchangeGeminiOAuthCode(code, state);
|
|
175
|
+
return { success: true, email };
|
|
176
|
+
} catch (e) { err(400, e.message); }
|
|
177
|
+
});
|
|
178
|
+
|
|
161
179
|
router.handle('codex.start', async () => {
|
|
162
180
|
try {
|
|
163
|
-
const result =
|
|
164
|
-
|
|
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 };
|
|
181
|
+
const result = await startCodexOAuth();
|
|
182
|
+
return { authUrl: result.authUrl, mode: result.mode };
|
|
177
183
|
} catch (e) { err(500, e.message); }
|
|
178
184
|
});
|
|
179
185
|
|
|
180
186
|
router.handle('codex.status', () => {
|
|
181
|
-
const st = typeof
|
|
187
|
+
const st = typeof codexOAuthState === 'function' ? codexOAuthState() : codexOAuthState;
|
|
182
188
|
return st;
|
|
183
189
|
});
|
|
184
190
|
|
|
185
|
-
router.handle('
|
|
191
|
+
router.handle('codex.relay', async (p) => {
|
|
192
|
+
const { code, state } = p;
|
|
193
|
+
if (!code || !state) err(400, 'Missing code or state');
|
|
194
|
+
try {
|
|
195
|
+
const email = await exchangeCodexOAuthCode(code, state);
|
|
196
|
+
return { success: true, email };
|
|
197
|
+
} catch (e) { err(400, e.message); }
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
router.handle('codex.complete', async (p) => {
|
|
186
201
|
const pastedUrl = (p.url || '').trim();
|
|
187
202
|
if (!pastedUrl) err(400, 'No URL provided');
|
|
188
203
|
let parsed;
|
|
@@ -195,7 +210,7 @@ export function register(router, deps) {
|
|
|
195
210
|
const code = parsed.searchParams.get('code');
|
|
196
211
|
const state = parsed.searchParams.get('state');
|
|
197
212
|
try {
|
|
198
|
-
const email = await
|
|
213
|
+
const email = await exchangeCodexOAuthCode(code, state);
|
|
199
214
|
return { success: true, email };
|
|
200
215
|
} catch (e) { err(400, e.message); }
|
|
201
216
|
});
|