agent-relay 1.2.3 → 1.3.0
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/.trajectories/agent-relay-322-324.md +17 -0
- package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.json +49 -0
- package/.trajectories/completed/2026-01/traj_03zupyv1s7b9.md +31 -0
- package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.json +125 -0
- package/.trajectories/completed/2026-01/traj_0zacdjl1g4ht.md +62 -0
- package/.trajectories/completed/2026-01/traj_33iuy72sezbk.json +49 -0
- package/.trajectories/completed/2026-01/traj_33iuy72sezbk.md +31 -0
- package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.json +77 -0
- package/.trajectories/completed/2026-01/traj_5ammh5qtvklq.md +42 -0
- package/.trajectories/completed/2026-01/traj_6mieijqyvaag.json +77 -0
- package/.trajectories/completed/2026-01/traj_6mieijqyvaag.md +42 -0
- package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.json +77 -0
- package/.trajectories/completed/2026-01/traj_78ffm31jn3uk.md +42 -0
- package/.trajectories/completed/2026-01/traj_94gnp3k30goq.json +66 -0
- package/.trajectories/completed/2026-01/traj_94gnp3k30goq.md +36 -0
- package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.json +40 -0
- package/.trajectories/completed/2026-01/traj_avqeghu6pz5a.md +22 -0
- package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.json +121 -0
- package/.trajectories/completed/2026-01/traj_dcsp9s8y01ra.md +29 -0
- package/.trajectories/completed/2026-01/traj_fhx9irlckht6.json +53 -0
- package/.trajectories/completed/2026-01/traj_fhx9irlckht6.md +32 -0
- package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.json +101 -0
- package/.trajectories/completed/2026-01/traj_fqduidx3xbtp.md +52 -0
- package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.json +49 -0
- package/.trajectories/completed/2026-01/traj_hf81ey93uz6t.md +31 -0
- package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.json +65 -0
- package/.trajectories/completed/2026-01/traj_hfmki2jr9d4r.md +37 -0
- package/.trajectories/completed/2026-01/traj_lq450ly148uw.json +49 -0
- package/.trajectories/completed/2026-01/traj_lq450ly148uw.md +31 -0
- package/.trajectories/completed/2026-01/traj_multi_server_arch.md +101 -0
- package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.json +27 -0
- package/.trajectories/completed/2026-01/traj_psd9ob0j2ru3.md +14 -0
- package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.json +53 -0
- package/.trajectories/completed/2026-01/traj_ub8csuv3lcv4.md +32 -0
- package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.json +186 -0
- package/.trajectories/completed/2026-01/traj_uc29tlso8i9s.md +86 -0
- package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.json +77 -0
- package/.trajectories/completed/2026-01/traj_ui9b4tqxoa7j.md +42 -0
- package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.json +89 -0
- package/.trajectories/completed/2026-01/traj_v9dkdoxylyid.md +47 -0
- package/.trajectories/completed/2026-01/traj_xy9vifpqet80.json +65 -0
- package/.trajectories/completed/2026-01/traj_xy9vifpqet80.md +37 -0
- package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.json +49 -0
- package/.trajectories/completed/2026-01/traj_y7aiwijyfmmv.md +31 -0
- package/.trajectories/consolidate-settings-panel.md +24 -0
- package/.trajectories/gh-cli-user-token.md +26 -0
- package/.trajectories/index.json +155 -1
- package/deploy/workspace/codex.config.toml +15 -0
- package/deploy/workspace/entrypoint.sh +167 -7
- package/deploy/workspace/git-credential-relay +17 -2
- package/dist/bridge/spawner.d.ts +7 -0
- package/dist/bridge/spawner.js +40 -9
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.js +210 -168
- package/dist/cloud/api/admin.d.ts +8 -0
- package/dist/cloud/api/admin.js +212 -0
- package/dist/cloud/api/auth.js +8 -0
- package/dist/cloud/api/billing.d.ts +0 -10
- package/dist/cloud/api/billing.js +248 -58
- package/dist/cloud/api/codex-auth-helper.d.ts +10 -4
- package/dist/cloud/api/codex-auth-helper.js +215 -8
- package/dist/cloud/api/coordinators.js +402 -0
- package/dist/cloud/api/daemons.js +15 -11
- package/dist/cloud/api/git.js +104 -17
- package/dist/cloud/api/github-app.js +42 -8
- package/dist/cloud/api/nango-auth.js +297 -16
- package/dist/cloud/api/onboarding.js +97 -33
- package/dist/cloud/api/providers.js +12 -16
- package/dist/cloud/api/repos.js +200 -124
- package/dist/cloud/api/test-helpers.js +40 -0
- package/dist/cloud/api/usage.js +13 -0
- package/dist/cloud/api/webhooks.js +1 -1
- package/dist/cloud/api/workspaces.d.ts +18 -0
- package/dist/cloud/api/workspaces.js +945 -15
- package/dist/cloud/config.d.ts +8 -0
- package/dist/cloud/config.js +15 -0
- package/dist/cloud/db/drizzle.d.ts +5 -2
- package/dist/cloud/db/drizzle.js +27 -20
- package/dist/cloud/db/schema.d.ts +19 -51
- package/dist/cloud/db/schema.js +5 -4
- package/dist/cloud/index.d.ts +0 -1
- package/dist/cloud/index.js +0 -1
- package/dist/cloud/provisioner/index.d.ts +93 -1
- package/dist/cloud/provisioner/index.js +608 -63
- package/dist/cloud/server.js +156 -16
- package/dist/cloud/services/compute-enforcement.d.ts +57 -0
- package/dist/cloud/services/compute-enforcement.js +175 -0
- package/dist/cloud/services/index.d.ts +2 -0
- package/dist/cloud/services/index.js +4 -0
- package/dist/cloud/services/intro-expiration.d.ts +55 -0
- package/dist/cloud/services/intro-expiration.js +211 -0
- package/dist/cloud/services/nango.d.ts +14 -0
- package/dist/cloud/services/nango.js +74 -14
- package/dist/cloud/services/ssh-security.d.ts +31 -0
- package/dist/cloud/services/ssh-security.js +63 -0
- package/dist/continuity/manager.d.ts +5 -0
- package/dist/continuity/manager.js +56 -2
- package/dist/daemon/api.d.ts +2 -0
- package/dist/daemon/api.js +214 -5
- package/dist/daemon/cli-auth.d.ts +13 -1
- package/dist/daemon/cli-auth.js +166 -47
- package/dist/daemon/connection.d.ts +7 -1
- package/dist/daemon/connection.js +15 -0
- package/dist/daemon/orchestrator.d.ts +2 -0
- package/dist/daemon/orchestrator.js +26 -0
- package/dist/daemon/repo-manager.d.ts +116 -0
- package/dist/daemon/repo-manager.js +384 -0
- package/dist/daemon/router.d.ts +60 -1
- package/dist/daemon/router.js +281 -20
- package/dist/daemon/user-directory.d.ts +111 -0
- package/dist/daemon/user-directory.js +233 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/T1tgCqVWHFIkV7ClEtzD7/_ssgManifest.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +9 -0
- package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/899-bb19a9b3d9b39ea6.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8939b0fc700f7eca.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/page-5af1b6b439858aa6.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-f45ecbc3e06134fc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/history/{page-abb9ab2d329f56e9.js → page-8c8bed33beb2bf1c.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-2433bb48965f4333.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/{page-c22d080201cbd9fb.js → page-16f3b49e55b1e0ed.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-ac39dc0cc3c26fa7.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/{page-77e9c65420a06cfb.js → page-4a5938c18a11a654.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-982a7000fee44014.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-ac3a6ac433fd6001.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-09f9caae98a18c09.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/signup/{page-68d34f50baa8ab6b.js → page-547dd0ca55ecd0ba.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/{main-ed4e1fb6f29c34cf.js → main-2ee6beb2ae96d210.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/{main-app-6e8e8d3ef4e0192a.js → main-app-5d692157a8eb1fd9.js} +1 -1
- package/dist/dashboard/out/_next/static/css/85d2af9c7ac74d62.css +1 -0
- package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +1 -0
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +3 -3
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +3 -3
- package/dist/dashboard/out/apple-icon.png +0 -0
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +3 -3
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +3 -3
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +3 -3
- package/dist/dashboard/out/login.html +2 -2
- package/dist/dashboard/out/login.txt +3 -3
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +3 -3
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +3 -3
- package/dist/dashboard/out/providers/setup/claude.html +1 -0
- package/dist/dashboard/out/providers/setup/claude.txt +8 -0
- package/dist/dashboard/out/providers/setup/codex.html +1 -0
- package/dist/dashboard/out/providers/setup/codex.txt +8 -0
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +3 -3
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +3 -3
- package/dist/dashboard-server/server.js +316 -12
- package/dist/dashboard-server/user-bridge.d.ts +103 -0
- package/dist/dashboard-server/user-bridge.js +189 -0
- package/dist/protocol/channels.d.ts +205 -0
- package/dist/protocol/channels.js +154 -0
- package/dist/protocol/types.d.ts +13 -1
- package/dist/resiliency/provider-context.js +2 -0
- package/dist/shared/cli-auth-config.d.ts +19 -0
- package/dist/shared/cli-auth-config.js +58 -2
- package/dist/utils/agent-config.js +1 -1
- package/dist/wrapper/auth-detection.d.ts +49 -0
- package/dist/wrapper/auth-detection.js +192 -0
- package/dist/wrapper/base-wrapper.d.ts +153 -0
- package/dist/wrapper/base-wrapper.js +393 -0
- package/dist/wrapper/client.d.ts +7 -1
- package/dist/wrapper/client.js +3 -0
- package/dist/wrapper/index.d.ts +1 -0
- package/dist/wrapper/index.js +4 -3
- package/dist/wrapper/pty-wrapper.d.ts +62 -84
- package/dist/wrapper/pty-wrapper.js +154 -180
- package/dist/wrapper/tmux-wrapper.d.ts +41 -66
- package/dist/wrapper/tmux-wrapper.js +90 -134
- package/package.json +4 -2
- package/scripts/postinstall.js +11 -155
- package/scripts/test-interactive-terminal.sh +248 -0
- package/dist/cloud/vault/index.d.ts +0 -76
- package/dist/cloud/vault/index.js +0 -219
- package/dist/dashboard/out/_next/static/chunks/699-3b1cd6618a45d259.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/724-2dae7627550ab88f.js +0 -9
- package/dist/dashboard/out/_next/static/chunks/766-1f2dd8cb7f766b0b.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-3fdfa60e53f2810d.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/app/page-e6381e5a6e1fbcfd.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-3538dfe0ffe984b8.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/metrics/page-67a3e98d9a43a6ed.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-b08ed1c34d14434a.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-e88bc117ef7671c3.js +0 -1
- package/dist/dashboard/out/_next/static/css/29852f26181969a0.css +0 -1
- package/dist/dashboard/out/_next/static/css/7c3ae9e8617d42a5.css +0 -1
- package/dist/dashboard/out/_next/static/wPgKJtcOmTFLpUncDg16A/_ssgManifest.js +0 -1
- /package/dist/dashboard/out/_next/static/{wPgKJtcOmTFLpUncDg16A → T1tgCqVWHFIkV7ClEtzD7}/_buildManifest.js +0 -0
package/dist/daemon/api.js
CHANGED
|
@@ -9,7 +9,8 @@ import { createLogger } from '../resiliency/logger.js';
|
|
|
9
9
|
import { metrics } from '../resiliency/metrics.js';
|
|
10
10
|
import { getWorkspaceManager } from './workspace-manager.js';
|
|
11
11
|
import { getAgentManager } from './agent-manager.js';
|
|
12
|
-
import { startCLIAuth, getAuthSession, cancelAuthSession, getSupportedProviders, } from './cli-auth.js';
|
|
12
|
+
import { startCLIAuth, getAuthSession, submitAuthCode, cancelAuthSession, getSupportedProviders, } from './cli-auth.js';
|
|
13
|
+
import { getRepoManager, initRepoManager } from './repo-manager.js';
|
|
13
14
|
const logger = createLogger('daemon-api');
|
|
14
15
|
export class DaemonApi extends EventEmitter {
|
|
15
16
|
server;
|
|
@@ -21,6 +22,9 @@ export class DaemonApi extends EventEmitter {
|
|
|
21
22
|
config;
|
|
22
23
|
allowedOrigins;
|
|
23
24
|
allowAllOrigins;
|
|
25
|
+
// Track alive status for ping/pong keepalive
|
|
26
|
+
clientAlive = new WeakMap();
|
|
27
|
+
pingInterval;
|
|
24
28
|
constructor(config) {
|
|
25
29
|
super();
|
|
26
30
|
this.config = config;
|
|
@@ -80,11 +84,28 @@ export class DaemonApi extends EventEmitter {
|
|
|
80
84
|
* Start the API server
|
|
81
85
|
*/
|
|
82
86
|
async start() {
|
|
87
|
+
// Initialize repo manager (scans for existing repos, syncs from env)
|
|
88
|
+
// This runs in background - don't block server startup
|
|
89
|
+
initRepoManager().catch((err) => {
|
|
90
|
+
logger.warn('Failed to initialize repo manager', { error: String(err) });
|
|
91
|
+
});
|
|
83
92
|
return new Promise((resolve) => {
|
|
84
93
|
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
85
|
-
// Setup WebSocket server
|
|
86
|
-
this.wss = new WebSocketServer({ server: this.server });
|
|
94
|
+
// Setup WebSocket server (disable compression for compatibility)
|
|
95
|
+
this.wss = new WebSocketServer({ server: this.server, perMessageDeflate: false });
|
|
87
96
|
this.wss.on('connection', (ws, req) => this.handleWebSocketConnection(ws, req));
|
|
97
|
+
// Setup ping/pong keepalive (30 second interval)
|
|
98
|
+
this.pingInterval = setInterval(() => {
|
|
99
|
+
this.wss?.clients.forEach((ws) => {
|
|
100
|
+
if (this.clientAlive.get(ws) === false) {
|
|
101
|
+
logger.info('WebSocket client unresponsive, closing');
|
|
102
|
+
ws.terminate();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.clientAlive.set(ws, false);
|
|
106
|
+
ws.ping();
|
|
107
|
+
});
|
|
108
|
+
}, 30000);
|
|
88
109
|
this.server.listen(this.config.port, this.config.host, () => {
|
|
89
110
|
logger.info('Daemon API started', { port: this.config.port, host: this.config.host });
|
|
90
111
|
resolve();
|
|
@@ -95,6 +116,11 @@ export class DaemonApi extends EventEmitter {
|
|
|
95
116
|
* Stop the API server
|
|
96
117
|
*/
|
|
97
118
|
async stop() {
|
|
119
|
+
// Clear ping interval
|
|
120
|
+
if (this.pingInterval) {
|
|
121
|
+
clearInterval(this.pingInterval);
|
|
122
|
+
this.pingInterval = undefined;
|
|
123
|
+
}
|
|
98
124
|
// Close all WebSocket connections
|
|
99
125
|
if (this.wss) {
|
|
100
126
|
for (const ws of this.wss.clients) {
|
|
@@ -295,6 +321,8 @@ export class DaemonApi extends EventEmitter {
|
|
|
295
321
|
status: session.status,
|
|
296
322
|
authUrl: session.authUrl,
|
|
297
323
|
error: session.error,
|
|
324
|
+
errorHint: session.errorHint,
|
|
325
|
+
recoverable: session.recoverable,
|
|
298
326
|
promptsHandled: session.promptsHandled,
|
|
299
327
|
},
|
|
300
328
|
};
|
|
@@ -306,17 +334,109 @@ export class DaemonApi extends EventEmitter {
|
|
|
306
334
|
if (!session) {
|
|
307
335
|
return { status: 404, body: { error: 'Session not found' } };
|
|
308
336
|
}
|
|
309
|
-
|
|
310
|
-
|
|
337
|
+
// Check for error state first
|
|
338
|
+
if (session.status === 'error') {
|
|
339
|
+
return {
|
|
340
|
+
status: 400,
|
|
341
|
+
body: {
|
|
342
|
+
error: session.error || 'Authentication failed',
|
|
343
|
+
errorHint: session.errorHint,
|
|
344
|
+
recoverable: session.recoverable,
|
|
345
|
+
status: session.status,
|
|
346
|
+
},
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// Check if auth is complete AND we have credentials
|
|
350
|
+
// Status can be 'success' before credentials are extracted (race condition)
|
|
351
|
+
if (session.status !== 'success' || !session.token) {
|
|
352
|
+
return {
|
|
353
|
+
status: 400,
|
|
354
|
+
body: {
|
|
355
|
+
error: 'Auth not complete or credentials not yet available',
|
|
356
|
+
status: session.status,
|
|
357
|
+
hasToken: !!session.token,
|
|
358
|
+
},
|
|
359
|
+
};
|
|
311
360
|
}
|
|
312
361
|
return {
|
|
313
362
|
status: 200,
|
|
314
363
|
body: {
|
|
315
364
|
token: session.token,
|
|
365
|
+
refreshToken: session.refreshToken,
|
|
366
|
+
tokenExpiresAt: session.tokenExpiresAt,
|
|
316
367
|
provider: session.provider,
|
|
317
368
|
},
|
|
318
369
|
};
|
|
319
370
|
});
|
|
371
|
+
// Submit auth code to PTY session
|
|
372
|
+
this.routes.set('POST /auth/cli/:provider/code/:sessionId', async (req) => {
|
|
373
|
+
const { sessionId } = req.params;
|
|
374
|
+
const { code, state } = req.body;
|
|
375
|
+
if (!code || typeof code !== 'string') {
|
|
376
|
+
return { status: 400, body: { error: 'Auth code is required' } };
|
|
377
|
+
}
|
|
378
|
+
const result = await submitAuthCode(sessionId, code, state);
|
|
379
|
+
if (!result.success) {
|
|
380
|
+
return {
|
|
381
|
+
status: 400,
|
|
382
|
+
body: {
|
|
383
|
+
error: result.error || 'Failed to submit auth code',
|
|
384
|
+
needsRestart: result.needsRestart,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return { status: 200, body: { success: true, message: 'Auth code submitted' } };
|
|
389
|
+
});
|
|
390
|
+
// Complete auth and wait for credentials
|
|
391
|
+
this.routes.set('POST /auth/cli/:provider/complete/:sessionId', async (req) => {
|
|
392
|
+
const { sessionId } = req.params;
|
|
393
|
+
const { authCode, state } = req.body;
|
|
394
|
+
// For Codex, we need to forward the authCode to the CLI's callback server
|
|
395
|
+
// The Codex CLI starts a callback server at localhost:1455
|
|
396
|
+
if (authCode) {
|
|
397
|
+
try {
|
|
398
|
+
// Forward the OAuth callback to the Codex CLI's callback server
|
|
399
|
+
const callbackUrl = `http://localhost:1455/auth/callback?code=${encodeURIComponent(authCode)}${state ? `&state=${encodeURIComponent(state)}` : ''}`;
|
|
400
|
+
logger.info('Forwarding OAuth callback to Codex CLI', { callbackUrl: callbackUrl.replace(authCode, '[REDACTED]') });
|
|
401
|
+
const callbackResponse = await fetch(callbackUrl, {
|
|
402
|
+
signal: AbortSignal.timeout(5000),
|
|
403
|
+
});
|
|
404
|
+
if (!callbackResponse.ok) {
|
|
405
|
+
logger.error('Failed to forward callback to Codex CLI', { status: callbackResponse.status });
|
|
406
|
+
return {
|
|
407
|
+
status: 400,
|
|
408
|
+
body: {
|
|
409
|
+
error: 'Failed to deliver OAuth callback to Codex CLI. The CLI may have timed out.',
|
|
410
|
+
needsRestart: true,
|
|
411
|
+
},
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
logger.info('Successfully forwarded OAuth callback to Codex CLI');
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
logger.error('Error forwarding callback to Codex CLI', { error: String(err) });
|
|
418
|
+
return {
|
|
419
|
+
status: 500,
|
|
420
|
+
body: {
|
|
421
|
+
error: 'Failed to reach Codex CLI callback server. The CLI may not be running.',
|
|
422
|
+
needsRestart: true,
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// Wait for credentials to be available (polls for up to 15 seconds)
|
|
428
|
+
const { completeAuthSession } = await import('./cli-auth.js');
|
|
429
|
+
const completeResult = await completeAuthSession(sessionId);
|
|
430
|
+
if (!completeResult.success) {
|
|
431
|
+
return {
|
|
432
|
+
status: 400,
|
|
433
|
+
body: {
|
|
434
|
+
error: completeResult.error || 'Authentication failed',
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return { status: 200, body: { success: true, token: completeResult.token } };
|
|
439
|
+
});
|
|
320
440
|
// Cancel auth session
|
|
321
441
|
this.routes.set('POST /auth/cli/:provider/cancel/:sessionId', async (req) => {
|
|
322
442
|
const { sessionId } = req.params;
|
|
@@ -326,6 +446,89 @@ export class DaemonApi extends EventEmitter {
|
|
|
326
446
|
}
|
|
327
447
|
return { status: 200, body: { success: true } };
|
|
328
448
|
});
|
|
449
|
+
// Check if provider is authenticated (credentials exist)
|
|
450
|
+
this.routes.set('GET /auth/cli/:provider/check', async (req) => {
|
|
451
|
+
const { provider } = req.params;
|
|
452
|
+
const { checkProviderAuth } = await import('./cli-auth.js');
|
|
453
|
+
const authenticated = await checkProviderAuth(provider);
|
|
454
|
+
return { status: 200, body: { authenticated } };
|
|
455
|
+
});
|
|
456
|
+
// === Repository Management ===
|
|
457
|
+
// Dynamic repo management without workspace restart
|
|
458
|
+
// List all repos
|
|
459
|
+
this.routes.set('GET /repos', async () => {
|
|
460
|
+
try {
|
|
461
|
+
const repoManager = getRepoManager();
|
|
462
|
+
const repos = repoManager.getRepos();
|
|
463
|
+
return { status: 200, body: { repos } };
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
logger.error('Failed to list repos', { error: String(err) });
|
|
467
|
+
return { status: 500, body: { error: 'Failed to list repositories' } };
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
// Get a specific repo
|
|
471
|
+
this.routes.set('GET /repos/:name', async (req) => {
|
|
472
|
+
try {
|
|
473
|
+
const repoManager = getRepoManager();
|
|
474
|
+
// Handle encoded slashes (e.g., "owner%2Frepo" -> "owner/repo")
|
|
475
|
+
const fullName = decodeURIComponent(req.params.name);
|
|
476
|
+
const repo = repoManager.getRepo(fullName);
|
|
477
|
+
if (!repo) {
|
|
478
|
+
return { status: 404, body: { error: 'Repository not found' } };
|
|
479
|
+
}
|
|
480
|
+
return { status: 200, body: repo };
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
logger.error('Failed to get repo', { error: String(err) });
|
|
484
|
+
return { status: 500, body: { error: 'Failed to get repository' } };
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
// Sync (clone or update) a repo
|
|
488
|
+
this.routes.set('POST /repos/sync', async (req) => {
|
|
489
|
+
const body = req.body;
|
|
490
|
+
// Support single repo or batch
|
|
491
|
+
const reposToSync = [];
|
|
492
|
+
if (body.repo) {
|
|
493
|
+
reposToSync.push(body.repo);
|
|
494
|
+
}
|
|
495
|
+
if (body.repos && Array.isArray(body.repos)) {
|
|
496
|
+
reposToSync.push(...body.repos);
|
|
497
|
+
}
|
|
498
|
+
if (reposToSync.length === 0) {
|
|
499
|
+
return { status: 400, body: { error: 'repo or repos field is required' } };
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
const repoManager = getRepoManager();
|
|
503
|
+
const results = await repoManager.syncRepos(reposToSync);
|
|
504
|
+
const allSuccess = results.every(r => r.success);
|
|
505
|
+
return {
|
|
506
|
+
status: allSuccess ? 200 : 207, // 207 Multi-Status if partial success
|
|
507
|
+
body: { results },
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
logger.error('Failed to sync repos', { error: String(err) });
|
|
512
|
+
return { status: 500, body: { error: 'Failed to sync repositories' } };
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
// Remove a repo
|
|
516
|
+
this.routes.set('DELETE /repos/:name', async (req) => {
|
|
517
|
+
try {
|
|
518
|
+
const repoManager = getRepoManager();
|
|
519
|
+
const fullName = decodeURIComponent(req.params.name);
|
|
520
|
+
const deleteFiles = req.query.deleteFiles === 'true';
|
|
521
|
+
const removed = await repoManager.removeRepo(fullName, deleteFiles);
|
|
522
|
+
if (!removed) {
|
|
523
|
+
return { status: 404, body: { error: 'Repository not found' } };
|
|
524
|
+
}
|
|
525
|
+
return { status: 200, body: { success: true, deleted: deleteFiles } };
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
logger.error('Failed to remove repo', { error: String(err) });
|
|
529
|
+
return { status: 500, body: { error: 'Failed to remove repository' } };
|
|
530
|
+
}
|
|
531
|
+
});
|
|
329
532
|
}
|
|
330
533
|
/**
|
|
331
534
|
* Handle HTTP request
|
|
@@ -461,6 +664,12 @@ export class DaemonApi extends EventEmitter {
|
|
|
461
664
|
*/
|
|
462
665
|
handleWebSocketConnection(ws, req) {
|
|
463
666
|
logger.info('WebSocket client connected', { url: req.url });
|
|
667
|
+
// Mark client as alive for ping/pong keepalive
|
|
668
|
+
this.clientAlive.set(ws, true);
|
|
669
|
+
// Handle pong responses
|
|
670
|
+
ws.on('pong', () => {
|
|
671
|
+
this.clientAlive.set(ws, true);
|
|
672
|
+
});
|
|
464
673
|
// Create session
|
|
465
674
|
const session = {
|
|
466
675
|
userId: 'anonymous', // Would be set from auth
|
|
@@ -20,6 +20,10 @@ interface AuthSession {
|
|
|
20
20
|
refreshToken?: string;
|
|
21
21
|
tokenExpiresAt?: Date;
|
|
22
22
|
error?: string;
|
|
23
|
+
/** User-friendly hint for resolving the error */
|
|
24
|
+
errorHint?: string;
|
|
25
|
+
/** Whether the error can be resolved by retrying */
|
|
26
|
+
recoverable?: boolean;
|
|
23
27
|
output: string;
|
|
24
28
|
promptsHandled: string[];
|
|
25
29
|
createdAt: Date;
|
|
@@ -44,9 +48,12 @@ export declare function getAuthSession(sessionId: string): AuthSession | null;
|
|
|
44
48
|
* Submit auth code to a waiting session
|
|
45
49
|
* This writes the code to the PTY process stdin
|
|
46
50
|
*
|
|
51
|
+
* @param sessionId - The auth session ID
|
|
52
|
+
* @param code - The OAuth authorization code
|
|
53
|
+
* @param state - Optional OAuth state parameter for CSRF validation (used by Codex)
|
|
47
54
|
* @returns Object with success status and optional error message
|
|
48
55
|
*/
|
|
49
|
-
export declare function submitAuthCode(sessionId: string, code: string): Promise<{
|
|
56
|
+
export declare function submitAuthCode(sessionId: string, code: string, state?: string): Promise<{
|
|
50
57
|
success: boolean;
|
|
51
58
|
error?: string;
|
|
52
59
|
needsRestart?: boolean;
|
|
@@ -64,4 +71,9 @@ export declare function completeAuthSession(sessionId: string): Promise<{
|
|
|
64
71
|
* Cancel auth session
|
|
65
72
|
*/
|
|
66
73
|
export declare function cancelAuthSession(sessionId: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Check if a provider is authenticated (credentials exist)
|
|
76
|
+
* Used by the auth check endpoint for SSH tunnel flow
|
|
77
|
+
*/
|
|
78
|
+
export declare function checkProviderAuth(provider: string): Promise<boolean>;
|
|
67
79
|
//# sourceMappingURL=cli-auth.d.ts.map
|
package/dist/daemon/cli-auth.js
CHANGED
|
@@ -9,7 +9,7 @@ import * as crypto from 'crypto';
|
|
|
9
9
|
import * as fs from 'fs/promises';
|
|
10
10
|
import * as os from 'os';
|
|
11
11
|
import { createLogger } from '../resiliency/logger.js';
|
|
12
|
-
import { CLI_AUTH_CONFIG, stripAnsiCodes, matchesSuccessPattern, findMatchingPrompt, getSupportedProviders, } from '../shared/cli-auth-config.js';
|
|
12
|
+
import { CLI_AUTH_CONFIG, stripAnsiCodes, matchesSuccessPattern, findMatchingPrompt, findMatchingError, getSupportedProviders, } from '../shared/cli-auth-config.js';
|
|
13
13
|
const logger = createLogger('cli-auth');
|
|
14
14
|
// Re-export for consumers
|
|
15
15
|
export { CLI_AUTH_CONFIG, getSupportedProviders };
|
|
@@ -123,46 +123,49 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
123
123
|
session.error = 'Timeout waiting for auth completion (5 minutes). Please try again.';
|
|
124
124
|
}
|
|
125
125
|
}, config.waitTimeout + OAUTH_COMPLETION_TIMEOUT);
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
if (session.status === 'waiting_auth' && session.process) {
|
|
130
|
-
try {
|
|
131
|
-
// Send space then backspace - appears as user typing but no net effect
|
|
132
|
-
session.process.write(' \b');
|
|
133
|
-
logger.debug('Keep-alive ping sent', {
|
|
134
|
-
sessionId,
|
|
135
|
-
status: session.status,
|
|
136
|
-
ageSeconds: Math.round((Date.now() - session.createdAt.getTime()) / 1000),
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
// Process may have exited
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}, 20000);
|
|
126
|
+
// Note: Removed keep-alive mechanism that sent ' \b' every 20 seconds
|
|
127
|
+
// It was interfering with OAuth code paste, causing "invalid code" errors
|
|
128
|
+
// CLIs like Claude don't actually need stdin keep-alive during auth wait
|
|
144
129
|
proc.onData((data) => {
|
|
145
130
|
session.output += data;
|
|
146
|
-
// Handle prompts
|
|
147
|
-
const matchingPrompt = findMatchingPrompt(data, config.prompts, respondedPrompts);
|
|
148
|
-
if (matchingPrompt) {
|
|
149
|
-
respondedPrompts.add(matchingPrompt.description);
|
|
150
|
-
session.promptsHandled.push(matchingPrompt.description);
|
|
151
|
-
logger.info('Auto-responding to prompt', { description: matchingPrompt.description });
|
|
152
|
-
const delay = matchingPrompt.delay ?? 100;
|
|
153
|
-
setTimeout(() => {
|
|
154
|
-
try {
|
|
155
|
-
proc.write(matchingPrompt.response);
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
// Process may have exited
|
|
159
|
-
}
|
|
160
|
-
}, delay);
|
|
161
|
-
}
|
|
162
|
-
// Extract auth URL
|
|
163
131
|
const cleanText = stripAnsiCodes(data);
|
|
132
|
+
// Check for error patterns FIRST - if error detected, don't auto-respond to prompts
|
|
133
|
+
// This prevents us from auto-responding to "Press Enter to retry" in error messages
|
|
134
|
+
const matchedError = findMatchingError(data, config.errorPatterns);
|
|
135
|
+
if (matchedError && session.status !== 'error') {
|
|
136
|
+
logger.warn('Auth error detected', {
|
|
137
|
+
provider,
|
|
138
|
+
sessionId,
|
|
139
|
+
errorMessage: matchedError.message,
|
|
140
|
+
recoverable: matchedError.recoverable,
|
|
141
|
+
});
|
|
142
|
+
session.status = 'error';
|
|
143
|
+
session.error = matchedError.message;
|
|
144
|
+
session.errorHint = matchedError.hint;
|
|
145
|
+
session.recoverable = matchedError.recoverable;
|
|
146
|
+
}
|
|
147
|
+
// Don't auto-respond to prompts if we're in error state
|
|
148
|
+
// This prevents responding to "Press Enter to retry" after an error
|
|
149
|
+
if (session.status !== 'error') {
|
|
150
|
+
const matchingPrompt = findMatchingPrompt(data, config.prompts, respondedPrompts);
|
|
151
|
+
if (matchingPrompt) {
|
|
152
|
+
respondedPrompts.add(matchingPrompt.description);
|
|
153
|
+
session.promptsHandled.push(matchingPrompt.description);
|
|
154
|
+
logger.info('Auto-responding to prompt', { description: matchingPrompt.description });
|
|
155
|
+
const delay = matchingPrompt.delay ?? 100;
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
try {
|
|
158
|
+
proc.write(matchingPrompt.response);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Process may have exited
|
|
162
|
+
}
|
|
163
|
+
}, delay);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Extract auth URL (only if not in error state and don't have URL yet)
|
|
164
167
|
const match = cleanText.match(config.urlPattern);
|
|
165
|
-
if (match && match[1] && !session.authUrl) {
|
|
168
|
+
if (match && match[1] && !session.authUrl && session.status !== 'error') {
|
|
166
169
|
session.authUrl = match[1];
|
|
167
170
|
session.status = 'waiting_auth';
|
|
168
171
|
logger.info('Auth URL captured', { provider, url: session.authUrl });
|
|
@@ -172,7 +175,7 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
172
175
|
}
|
|
173
176
|
// Log all output after auth URL is captured (for debugging)
|
|
174
177
|
if (session.authUrl) {
|
|
175
|
-
const trimmedData =
|
|
178
|
+
const trimmedData = cleanText.trim();
|
|
176
179
|
if (trimmedData.length > 0) {
|
|
177
180
|
logger.info('PTY output after auth URL', {
|
|
178
181
|
provider,
|
|
@@ -182,12 +185,18 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
// Check for success and try to extract credentials
|
|
185
|
-
if
|
|
188
|
+
// Don't override error status - if there was an error, keep it
|
|
189
|
+
if (session.status !== 'error' && matchesSuccessPattern(data, config.successPatterns)) {
|
|
186
190
|
session.status = 'success';
|
|
187
191
|
logger.info('Success pattern detected, attempting credential extraction', { provider });
|
|
188
192
|
// Try to extract credentials immediately (CLI may not exit after success)
|
|
189
193
|
// Use a small delay to let the CLI finish writing the file
|
|
190
194
|
setTimeout(async () => {
|
|
195
|
+
// Don't extract if status changed to error (e.g., error detected after success pattern)
|
|
196
|
+
if (session.status === 'error') {
|
|
197
|
+
logger.info('Skipping credential extraction - session is in error state', { provider });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
191
200
|
try {
|
|
192
201
|
const creds = await extractCredentials(provider, config);
|
|
193
202
|
if (creds) {
|
|
@@ -206,7 +215,6 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
206
215
|
proc.onExit(async ({ exitCode }) => {
|
|
207
216
|
clearTimeout(timeout);
|
|
208
217
|
clearTimeout(authUrlTimeout);
|
|
209
|
-
clearInterval(keepAliveInterval);
|
|
210
218
|
// Clear process reference so submitAuthCode knows PTY is gone
|
|
211
219
|
session.process = undefined;
|
|
212
220
|
// Log full output for debugging PTY exit issues
|
|
@@ -221,8 +229,9 @@ export async function startCLIAuth(provider, options = {}) {
|
|
|
221
229
|
// Last 500 chars of output for debugging
|
|
222
230
|
outputTail: cleanOutput.slice(-500),
|
|
223
231
|
});
|
|
224
|
-
// Try to extract credentials
|
|
225
|
-
|
|
232
|
+
// Try to extract credentials (but don't override error status)
|
|
233
|
+
// CLI might exit cleanly (code 0) even after an OAuth error
|
|
234
|
+
if ((session.authUrl || exitCode === 0) && session.status !== 'error') {
|
|
226
235
|
try {
|
|
227
236
|
const creds = await extractCredentials(provider, config);
|
|
228
237
|
if (creds) {
|
|
@@ -265,9 +274,12 @@ export function getAuthSession(sessionId) {
|
|
|
265
274
|
* Submit auth code to a waiting session
|
|
266
275
|
* This writes the code to the PTY process stdin
|
|
267
276
|
*
|
|
277
|
+
* @param sessionId - The auth session ID
|
|
278
|
+
* @param code - The OAuth authorization code
|
|
279
|
+
* @param state - Optional OAuth state parameter for CSRF validation (used by Codex)
|
|
268
280
|
* @returns Object with success status and optional error message
|
|
269
281
|
*/
|
|
270
|
-
export async function submitAuthCode(sessionId, code) {
|
|
282
|
+
export async function submitAuthCode(sessionId, code, state) {
|
|
271
283
|
// Log all active sessions for debugging
|
|
272
284
|
const activeSessionIds = Array.from(sessions.keys());
|
|
273
285
|
logger.info('submitAuthCode called', {
|
|
@@ -305,11 +317,14 @@ export async function submitAuthCode(sessionId, code) {
|
|
|
305
317
|
outputTail: session.output ? stripAnsiCodes(session.output).slice(-500) : 'no output',
|
|
306
318
|
});
|
|
307
319
|
// Try to extract credentials as a fallback - maybe auth completed in browser
|
|
320
|
+
// But don't override error status
|
|
308
321
|
const config = CLI_AUTH_CONFIG[session.provider];
|
|
309
|
-
if (config) {
|
|
322
|
+
if (config && session.status !== 'error') {
|
|
310
323
|
try {
|
|
311
324
|
const creds = await extractCredentials(session.provider, config);
|
|
312
|
-
|
|
325
|
+
// Re-check status after async operation (race condition protection)
|
|
326
|
+
// Use type assertion because TypeScript narrowing doesn't account for async race conditions
|
|
327
|
+
if (creds && session.status !== 'error') {
|
|
313
328
|
session.token = creds.token;
|
|
314
329
|
session.refreshToken = creds.refreshToken;
|
|
315
330
|
session.tokenExpiresAt = creds.expiresAt;
|
|
@@ -331,8 +346,78 @@ export async function submitAuthCode(sessionId, code) {
|
|
|
331
346
|
};
|
|
332
347
|
}
|
|
333
348
|
try {
|
|
334
|
-
// Clean the code - trim whitespace
|
|
335
|
-
|
|
349
|
+
// Clean the code - trim whitespace and strip state parameter if present
|
|
350
|
+
// Claude OAuth codes come as "CODE#STATE" - we only need the code part
|
|
351
|
+
let cleanCode = code.trim();
|
|
352
|
+
if (cleanCode.includes('#')) {
|
|
353
|
+
const originalCode = cleanCode;
|
|
354
|
+
cleanCode = cleanCode.split('#')[0];
|
|
355
|
+
logger.info('Stripped state parameter from auth code', {
|
|
356
|
+
sessionId,
|
|
357
|
+
originalLength: originalCode.length,
|
|
358
|
+
cleanLength: cleanCode.length,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
// For Codex (openai), forward the callback to the CLI's localhost server
|
|
362
|
+
// instead of writing to PTY stdin. The CLI spawns a localhost server
|
|
363
|
+
// waiting for the OAuth callback.
|
|
364
|
+
if (session.provider === 'openai' && session.authUrl) {
|
|
365
|
+
// Extract the redirect port from the auth URL (usually 1455)
|
|
366
|
+
const redirectMatch = session.authUrl.match(/redirect_uri=http%3A%2F%2Flocalhost%3A(\d+)/);
|
|
367
|
+
const port = redirectMatch ? redirectMatch[1] : '1455';
|
|
368
|
+
logger.info('Forwarding OAuth callback to Codex CLI localhost server', {
|
|
369
|
+
sessionId,
|
|
370
|
+
port,
|
|
371
|
+
codeLength: cleanCode.length,
|
|
372
|
+
hasState: !!state,
|
|
373
|
+
});
|
|
374
|
+
try {
|
|
375
|
+
// Forward the callback to the CLI's localhost server
|
|
376
|
+
// Include state parameter for CSRF validation if provided
|
|
377
|
+
let callbackUrl = `http://localhost:${port}/auth/callback?code=${encodeURIComponent(cleanCode)}`;
|
|
378
|
+
if (state) {
|
|
379
|
+
callbackUrl += `&state=${encodeURIComponent(state)}`;
|
|
380
|
+
}
|
|
381
|
+
const response = await fetch(callbackUrl, {
|
|
382
|
+
method: 'GET',
|
|
383
|
+
signal: AbortSignal.timeout(5000),
|
|
384
|
+
});
|
|
385
|
+
if (response.ok) {
|
|
386
|
+
logger.info('OAuth callback forwarded successfully to Codex CLI', { sessionId, status: response.status });
|
|
387
|
+
// Start polling for credentials
|
|
388
|
+
const config = CLI_AUTH_CONFIG[session.provider];
|
|
389
|
+
if (config) {
|
|
390
|
+
pollForCredentials(session, config);
|
|
391
|
+
}
|
|
392
|
+
return { success: true };
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
// Try to get error details from response body
|
|
396
|
+
let errorBody = '';
|
|
397
|
+
try {
|
|
398
|
+
errorBody = await response.text();
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
// Ignore
|
|
402
|
+
}
|
|
403
|
+
logger.warn('Codex CLI localhost server returned error', {
|
|
404
|
+
sessionId,
|
|
405
|
+
status: response.status,
|
|
406
|
+
statusText: response.statusText,
|
|
407
|
+
errorBody: errorBody.substring(0, 500), // Limit log size
|
|
408
|
+
callbackUrl: callbackUrl.replace(/code=[^&]+/, 'code=***'), // Redact code
|
|
409
|
+
});
|
|
410
|
+
// Fall through to PTY write as fallback
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
logger.warn('Failed to forward callback to Codex CLI localhost server', {
|
|
415
|
+
sessionId,
|
|
416
|
+
error: String(err),
|
|
417
|
+
});
|
|
418
|
+
// Fall through to PTY write as fallback
|
|
419
|
+
}
|
|
420
|
+
}
|
|
336
421
|
logger.info('Writing auth code to PTY', {
|
|
337
422
|
sessionId,
|
|
338
423
|
originalLength: code.length,
|
|
@@ -382,6 +467,14 @@ async function pollForCredentials(session, config) {
|
|
|
382
467
|
try {
|
|
383
468
|
const creds = await extractCredentials(session.provider, config);
|
|
384
469
|
if (creds) {
|
|
470
|
+
// Double-check we're not in error state (race condition protection)
|
|
471
|
+
// Use type assertion because TypeScript narrowing doesn't account for async race conditions
|
|
472
|
+
if (session.status === 'error') {
|
|
473
|
+
logger.info('Credentials found but session is in error state, not overriding', {
|
|
474
|
+
provider: session.provider,
|
|
475
|
+
});
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
385
478
|
session.token = creds.token;
|
|
386
479
|
session.refreshToken = creds.refreshToken;
|
|
387
480
|
session.tokenExpiresAt = creds.expiresAt;
|
|
@@ -424,9 +517,18 @@ export async function completeAuthSession(sessionId) {
|
|
|
424
517
|
const maxAttempts = 15;
|
|
425
518
|
const pollInterval = 1000;
|
|
426
519
|
for (let i = 0; i < maxAttempts; i++) {
|
|
520
|
+
// Check if session went into error state
|
|
521
|
+
if (session.status === 'error') {
|
|
522
|
+
return { success: false, error: session.error || 'Authentication failed' };
|
|
523
|
+
}
|
|
427
524
|
try {
|
|
428
525
|
const creds = await extractCredentials(session.provider, config);
|
|
429
526
|
if (creds) {
|
|
527
|
+
// Double-check we're not in error state (race condition protection)
|
|
528
|
+
// Use type assertion because TypeScript narrowing doesn't account for async race conditions
|
|
529
|
+
if (session.status === 'error') {
|
|
530
|
+
return { success: false, error: session.error || 'Authentication failed' };
|
|
531
|
+
}
|
|
430
532
|
session.token = creds.token;
|
|
431
533
|
session.refreshToken = creds.refreshToken;
|
|
432
534
|
session.tokenExpiresAt = creds.expiresAt;
|
|
@@ -534,4 +636,21 @@ async function extractCredentials(provider, config) {
|
|
|
534
636
|
return null;
|
|
535
637
|
}
|
|
536
638
|
}
|
|
639
|
+
/**
|
|
640
|
+
* Check if a provider is authenticated (credentials exist)
|
|
641
|
+
* Used by the auth check endpoint for SSH tunnel flow
|
|
642
|
+
*/
|
|
643
|
+
export async function checkProviderAuth(provider) {
|
|
644
|
+
const config = CLI_AUTH_CONFIG[provider];
|
|
645
|
+
if (!config) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
const creds = await extractCredentials(provider, config);
|
|
650
|
+
return !!creds?.token;
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
537
656
|
//# sourceMappingURL=cli-auth.js.map
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* ERROR -------> CLOSED
|
|
9
9
|
*/
|
|
10
10
|
import net from 'node:net';
|
|
11
|
-
import { type Envelope, type AckPayload } from '../protocol/types.js';
|
|
11
|
+
import { type Envelope, type AckPayload, type EntityType } from '../protocol/types.js';
|
|
12
12
|
export type ConnectionState = 'CONNECTING' | 'HANDSHAKING' | 'ACTIVE' | 'CLOSING' | 'CLOSED' | 'ERROR';
|
|
13
13
|
export interface ConnectionConfig {
|
|
14
14
|
maxFrameBytes: number;
|
|
@@ -39,11 +39,14 @@ export declare class Connection {
|
|
|
39
39
|
private config;
|
|
40
40
|
private _state;
|
|
41
41
|
private _agentName?;
|
|
42
|
+
private _entityType?;
|
|
42
43
|
private _cli?;
|
|
43
44
|
private _program?;
|
|
44
45
|
private _model?;
|
|
45
46
|
private _task?;
|
|
46
47
|
private _workingDirectory?;
|
|
48
|
+
private _displayName?;
|
|
49
|
+
private _avatarUrl?;
|
|
47
50
|
private _sessionId;
|
|
48
51
|
private _resumeToken;
|
|
49
52
|
private _isResumed;
|
|
@@ -59,11 +62,14 @@ export declare class Connection {
|
|
|
59
62
|
constructor(socket: net.Socket, config?: Partial<ConnectionConfig>);
|
|
60
63
|
get state(): ConnectionState;
|
|
61
64
|
get agentName(): string | undefined;
|
|
65
|
+
get entityType(): EntityType | undefined;
|
|
62
66
|
get cli(): string | undefined;
|
|
63
67
|
get program(): string | undefined;
|
|
64
68
|
get model(): string | undefined;
|
|
65
69
|
get task(): string | undefined;
|
|
66
70
|
get workingDirectory(): string | undefined;
|
|
71
|
+
get displayName(): string | undefined;
|
|
72
|
+
get avatarUrl(): string | undefined;
|
|
67
73
|
get sessionId(): string;
|
|
68
74
|
get resumeToken(): string;
|
|
69
75
|
get isResumed(): boolean;
|