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/bridge/spawner.js
CHANGED
|
@@ -11,6 +11,7 @@ import { resolveCommand } from '../utils/command-resolver.js';
|
|
|
11
11
|
import { PtyWrapper } from '../wrapper/pty-wrapper.js';
|
|
12
12
|
import { selectShadowCli } from './shadow-cli.js';
|
|
13
13
|
import { AgentPolicyService } from '../policy/agent-policy.js';
|
|
14
|
+
import { buildClaudeArgs } from '../utils/agent-config.js';
|
|
14
15
|
/**
|
|
15
16
|
* Get a minimal relay reminder.
|
|
16
17
|
* Agents already have full relay docs via CLAUDE.md - this is just a brief reminder.
|
|
@@ -192,6 +193,16 @@ export class AgentSpawner {
|
|
|
192
193
|
if (isClaudeCli && !args.includes('--dangerously-skip-permissions')) {
|
|
193
194
|
args.push('--dangerously-skip-permissions');
|
|
194
195
|
}
|
|
196
|
+
// Apply agent config (model, --agent flag) from .claude/agents/ if available
|
|
197
|
+
// This ensures spawned agents respect their profile settings
|
|
198
|
+
if (isClaudeCli) {
|
|
199
|
+
const configuredArgs = buildClaudeArgs(name, args, this.projectRoot);
|
|
200
|
+
// Replace args with configured version (includes --model and --agent if found)
|
|
201
|
+
args.length = 0;
|
|
202
|
+
args.push(...configuredArgs);
|
|
203
|
+
if (debug)
|
|
204
|
+
console.log(`[spawner:debug] Applied agent config for ${name}: ${args.join(' ')}`);
|
|
205
|
+
}
|
|
195
206
|
// Add --dangerously-bypass-approvals-and-sandbox for Codex agents
|
|
196
207
|
const isCodexCli = commandName.startsWith('codex');
|
|
197
208
|
if (isCodexCli && !args.includes('--dangerously-bypass-approvals-and-sandbox')) {
|
|
@@ -216,6 +227,8 @@ export class AgentSpawner {
|
|
|
216
227
|
cwd: agentCwd,
|
|
217
228
|
logsDir: this.logsDir,
|
|
218
229
|
dashboardPort: this.dashboardPort,
|
|
230
|
+
// Interactive mode - disables auto-accept for auth setup flows
|
|
231
|
+
interactive: request.interactive,
|
|
219
232
|
// Shadow agent configuration
|
|
220
233
|
shadowOf: request.shadowOf,
|
|
221
234
|
shadowSpeakOn: request.shadowSpeakOn,
|
|
@@ -302,17 +315,22 @@ export class AgentSpawner {
|
|
|
302
315
|
};
|
|
303
316
|
}
|
|
304
317
|
// Build the full message: minimal relay reminder + policy instructions (if any) + task
|
|
318
|
+
// Only build message if there's an actual task - empty task means interactive mode
|
|
305
319
|
let fullMessage = task || '';
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
320
|
+
// Only prepend relay reminder if we have an actual task
|
|
321
|
+
// Empty task = interactive mode, user will respond to prompts directly
|
|
322
|
+
if (fullMessage.trim()) {
|
|
323
|
+
// Prepend a brief relay reminder (agents have full docs via CLAUDE.md)
|
|
324
|
+
// Note: Previously loaded full 400+ line docs which overwhelmed agents
|
|
325
|
+
const relayReminder = getMinimalRelayReminder();
|
|
326
|
+
if (relayReminder) {
|
|
327
|
+
fullMessage = `${relayReminder}\n\n---\n\n${fullMessage}`;
|
|
328
|
+
if (debug)
|
|
329
|
+
console.log(`[spawner:debug] Prepended relay reminder for ${name}`);
|
|
330
|
+
}
|
|
313
331
|
}
|
|
314
|
-
// Prepend policy instructions if enforcement is enabled
|
|
315
|
-
if (this.policyEnforcementEnabled && this.policyService) {
|
|
332
|
+
// Prepend policy instructions if enforcement is enabled (only if we have a task)
|
|
333
|
+
if (fullMessage.trim() && this.policyEnforcementEnabled && this.policyService) {
|
|
316
334
|
const policyInstruction = await this.policyService.getPolicyInstruction(name);
|
|
317
335
|
if (policyInstruction) {
|
|
318
336
|
fullMessage = `${policyInstruction}\n\n${fullMessage}`;
|
|
@@ -601,6 +619,19 @@ export class AgentSpawner {
|
|
|
601
619
|
return null;
|
|
602
620
|
return worker.pty.getRawOutput();
|
|
603
621
|
}
|
|
622
|
+
/**
|
|
623
|
+
* Send input to a worker's PTY (for interactive terminal support)
|
|
624
|
+
* @param name - Worker name
|
|
625
|
+
* @param data - Input data to send (keystrokes, text, etc.)
|
|
626
|
+
* @returns true if input was sent, false if worker not found
|
|
627
|
+
*/
|
|
628
|
+
sendWorkerInput(name, data) {
|
|
629
|
+
const worker = this.activeWorkers.get(name);
|
|
630
|
+
if (!worker)
|
|
631
|
+
return false;
|
|
632
|
+
worker.pty.write(data);
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
604
635
|
/**
|
|
605
636
|
* Wait for an agent to appear in the registry (agents.json)
|
|
606
637
|
*/
|
package/dist/bridge/types.d.ts
CHANGED
|
@@ -41,6 +41,8 @@ export interface SpawnRequest {
|
|
|
41
41
|
cwd?: string;
|
|
42
42
|
/** Name of the agent requesting the spawn (for policy enforcement) */
|
|
43
43
|
spawnerName?: string;
|
|
44
|
+
/** Interactive mode - disables auto-accept of permission prompts (for auth setup flows) */
|
|
45
|
+
interactive?: boolean;
|
|
44
46
|
/** Shadow execution mode (subagent = no extra process) */
|
|
45
47
|
shadowMode?: 'subagent' | 'process';
|
|
46
48
|
/** Primary agent to shadow (if this agent is a shadow) */
|
package/dist/cli/index.js
CHANGED
|
@@ -2226,214 +2226,256 @@ program
|
|
|
2226
2226
|
console.log(`Profiling ${agentName}... Press Ctrl+C to stop.`);
|
|
2227
2227
|
});
|
|
2228
2228
|
// ============================================================================
|
|
2229
|
-
// codex-auth -
|
|
2229
|
+
// codex-auth - SSH tunnel helper for Codex/OpenAI authentication
|
|
2230
2230
|
// ============================================================================
|
|
2231
2231
|
program
|
|
2232
2232
|
.command('codex-auth')
|
|
2233
|
-
.description('
|
|
2234
|
-
.option('--
|
|
2233
|
+
.description('Connect Codex via SSH tunnel to workspace (run this when connecting Codex in Agent Relay)')
|
|
2234
|
+
.option('--workspace <id>', 'Workspace ID to connect to')
|
|
2235
2235
|
.option('--cloud-url <url>', 'Cloud API URL', process.env.AGENT_RELAY_CLOUD_URL || 'https://agent-relay.com')
|
|
2236
|
-
.option('--
|
|
2236
|
+
.option('--token <token>', 'CLI authentication token (from dashboard)')
|
|
2237
|
+
.option('--session-cookie <cookie>', 'Session cookie for authentication (deprecated, use --token)')
|
|
2237
2238
|
.option('--timeout <seconds>', 'Timeout in seconds (default: 300)', '300')
|
|
2238
2239
|
.action(async (options) => {
|
|
2239
|
-
const http = await import('node:http');
|
|
2240
|
-
const { URL } = await import('node:url');
|
|
2241
|
-
const CALLBACK_PORT = parseInt(options.port, 10);
|
|
2242
2240
|
const TIMEOUT_MS = parseInt(options.timeout, 10) * 1000;
|
|
2243
2241
|
const CLOUD_URL = options.cloudUrl.replace(/\/$/, '');
|
|
2242
|
+
const TUNNEL_PORT = 1455;
|
|
2244
2243
|
// Colors for terminal output
|
|
2245
2244
|
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
2246
2245
|
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
2247
2246
|
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
2248
2247
|
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
2248
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
2249
2249
|
console.log('');
|
|
2250
2250
|
console.log(cyan('═══════════════════════════════════════════════════'));
|
|
2251
2251
|
console.log(cyan(' Codex Authentication Helper'));
|
|
2252
2252
|
console.log(cyan('═══════════════════════════════════════════════════'));
|
|
2253
2253
|
console.log('');
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
console.log('
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2254
|
+
if (!options.workspace) {
|
|
2255
|
+
console.log(red('Missing --workspace parameter.'));
|
|
2256
|
+
console.log('');
|
|
2257
|
+
console.log('To connect Codex, follow these steps:');
|
|
2258
|
+
console.log('');
|
|
2259
|
+
console.log(' 1. Go to the Agent Relay dashboard');
|
|
2260
|
+
console.log(' 2. Click "Connect with Codex" (Settings → AI Providers)');
|
|
2261
|
+
console.log(' 3. Copy the command shown (it includes the workspace ID and token)');
|
|
2262
|
+
console.log(' 4. Run the command in your terminal');
|
|
2263
|
+
console.log('');
|
|
2264
|
+
console.log('The command will look like:');
|
|
2265
|
+
console.log(cyan(' npx agent-relay codex-auth --workspace=<ID> --token=<TOKEN>'));
|
|
2266
|
+
console.log('');
|
|
2267
|
+
process.exit(1);
|
|
2268
|
+
}
|
|
2269
|
+
const workspaceId = options.workspace;
|
|
2270
|
+
console.log(`Workspace: ${workspaceId.slice(0, 8)}...`);
|
|
2271
|
+
// Get tunnel info from cloud API
|
|
2272
|
+
console.log('Getting workspace connection info...');
|
|
2273
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
2274
|
+
if (options.sessionCookie) {
|
|
2275
|
+
headers['Cookie'] = options.sessionCookie;
|
|
2276
|
+
}
|
|
2277
|
+
// Validate token is provided
|
|
2278
|
+
if (!options.token && !options.sessionCookie) {
|
|
2279
|
+
console.log(red('Missing --token parameter.'));
|
|
2280
|
+
console.log('');
|
|
2281
|
+
console.log('The token is provided by the dashboard when you click "Connect with Codex".');
|
|
2282
|
+
console.log('Copy the complete command from the dashboard and paste it here.');
|
|
2283
|
+
console.log('');
|
|
2284
|
+
process.exit(1);
|
|
2285
|
+
}
|
|
2286
|
+
let tunnelInfo;
|
|
2287
|
+
try {
|
|
2288
|
+
// Build URL with token query parameter
|
|
2289
|
+
const tunnelInfoUrl = new URL(`${CLOUD_URL}/api/auth/codex-helper/tunnel-info/${workspaceId}`);
|
|
2290
|
+
if (options.token) {
|
|
2291
|
+
tunnelInfoUrl.searchParams.set('token', options.token);
|
|
2292
|
+
}
|
|
2293
|
+
const response = await fetch(tunnelInfoUrl.toString(), {
|
|
2294
|
+
method: 'GET',
|
|
2295
|
+
headers,
|
|
2296
|
+
credentials: 'include',
|
|
2297
|
+
});
|
|
2298
|
+
if (!response.ok) {
|
|
2299
|
+
const errorData = await response.json();
|
|
2300
|
+
console.log(red(`Failed to get tunnel info: ${errorData.error || response.statusText}`));
|
|
2301
|
+
process.exit(1);
|
|
2302
|
+
}
|
|
2303
|
+
tunnelInfo = await response.json();
|
|
2304
|
+
}
|
|
2305
|
+
catch (err) {
|
|
2306
|
+
console.log(red(`Failed to connect to cloud API: ${err instanceof Error ? err.message : String(err)}`));
|
|
2307
|
+
process.exit(1);
|
|
2308
|
+
}
|
|
2309
|
+
console.log(`Workspace: ${cyan(tunnelInfo.workspaceName)}`);
|
|
2310
|
+
console.log('');
|
|
2311
|
+
// Establish SSH tunnel using ssh2 library (no external tools needed)
|
|
2312
|
+
console.log(yellow('Establishing SSH tunnel...'));
|
|
2313
|
+
console.log(dim(` SSH: ${tunnelInfo.host}:${tunnelInfo.port}`));
|
|
2314
|
+
console.log(dim(` Tunnel: localhost:${TUNNEL_PORT} → workspace:${tunnelInfo.tunnelPort}`));
|
|
2315
|
+
console.log('');
|
|
2316
|
+
const { Client } = await import('ssh2');
|
|
2317
|
+
const net = await import('node:net');
|
|
2318
|
+
const sshClient = new Client();
|
|
2319
|
+
// Use object to hold server reference (avoids TypeScript narrowing issues)
|
|
2320
|
+
const tunnel = { server: null };
|
|
2321
|
+
let tunnelReady = false;
|
|
2322
|
+
let tunnelError = null;
|
|
2323
|
+
// Create a promise that resolves when tunnel is ready or rejects on error
|
|
2324
|
+
const tunnelPromise = new Promise((resolve, reject) => {
|
|
2325
|
+
sshClient.on('ready', () => {
|
|
2326
|
+
// Create local server that forwards connections through SSH
|
|
2327
|
+
tunnel.server = net.createServer((localSocket) => {
|
|
2328
|
+
sshClient.forwardOut('127.0.0.1', TUNNEL_PORT, 'localhost', tunnelInfo.tunnelPort, (err, stream) => {
|
|
2329
|
+
if (err) {
|
|
2330
|
+
localSocket.end();
|
|
2331
|
+
return;
|
|
2332
|
+
}
|
|
2333
|
+
localSocket.pipe(stream).pipe(localSocket);
|
|
2334
|
+
});
|
|
2263
2335
|
});
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
console.log(red('Failed to create auth session.'));
|
|
2268
|
-
console.log('');
|
|
2269
|
-
if (response.status === 401) {
|
|
2270
|
-
console.log('To use this command, run it with a token from the Agent Relay dashboard:');
|
|
2271
|
-
console.log('');
|
|
2272
|
-
console.log(cyan(' npx agent-relay codex-auth --token=<TOKEN>'));
|
|
2273
|
-
console.log('');
|
|
2274
|
-
console.log('Get the token from: Settings → Connect Codex → "Use CLI helper"');
|
|
2336
|
+
tunnel.server.on('error', (err) => {
|
|
2337
|
+
if (err.code === 'EADDRINUSE') {
|
|
2338
|
+
tunnelError = `Port ${TUNNEL_PORT} is already in use. Close any other applications using this port.`;
|
|
2275
2339
|
}
|
|
2276
2340
|
else {
|
|
2277
|
-
|
|
2341
|
+
tunnelError = err.message;
|
|
2278
2342
|
}
|
|
2279
|
-
|
|
2343
|
+
reject(new Error(tunnelError));
|
|
2344
|
+
});
|
|
2345
|
+
tunnel.server.listen(TUNNEL_PORT, '127.0.0.1', () => {
|
|
2346
|
+
tunnelReady = true;
|
|
2347
|
+
resolve();
|
|
2348
|
+
});
|
|
2349
|
+
});
|
|
2350
|
+
sshClient.on('error', (err) => {
|
|
2351
|
+
if (err.message.includes('Authentication')) {
|
|
2352
|
+
tunnelError = 'SSH authentication failed. Check the password.';
|
|
2280
2353
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
}
|
|
2284
|
-
catch (err) {
|
|
2285
|
-
console.log(red('Failed to connect to Agent Relay Cloud.'));
|
|
2286
|
-
console.log(`URL: ${CLOUD_URL}`);
|
|
2287
|
-
console.log(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2288
|
-
process.exit(1);
|
|
2289
|
-
}
|
|
2290
|
-
}
|
|
2291
|
-
console.log(`Session: ${authSessionId?.slice(0, 8)}...`);
|
|
2292
|
-
console.log(`Listening on port: ${cyan(String(CALLBACK_PORT))}`);
|
|
2293
|
-
console.log('');
|
|
2294
|
-
// Success HTML page
|
|
2295
|
-
const successHtml = `<!DOCTYPE html>
|
|
2296
|
-
<html>
|
|
2297
|
-
<head>
|
|
2298
|
-
<title>Authentication Successful</title>
|
|
2299
|
-
<style>
|
|
2300
|
-
body { font-family: -apple-system, sans-serif; background: #0a0f1a; color: #e2e8f0;
|
|
2301
|
-
display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
2302
|
-
.card { background: #1a1f2e; padding: 2rem; border-radius: 12px; text-align: center;
|
|
2303
|
-
border: 1px solid rgba(0, 212, 255, 0.3); max-width: 400px; }
|
|
2304
|
-
h1 { color: #00d4ff; margin-bottom: 1rem; }
|
|
2305
|
-
p { color: #94a3b8; margin: 0.5rem 0; }
|
|
2306
|
-
</style>
|
|
2307
|
-
</head>
|
|
2308
|
-
<body>
|
|
2309
|
-
<div class="card">
|
|
2310
|
-
<h1>✓ Authentication Successful!</h1>
|
|
2311
|
-
<p>Your Codex account is now connected.</p>
|
|
2312
|
-
<p style="margin-top: 1rem;">You can close this window.</p>
|
|
2313
|
-
</div>
|
|
2314
|
-
</body>
|
|
2315
|
-
</html>`;
|
|
2316
|
-
// Create HTTP server to capture callback
|
|
2317
|
-
let authCode = null;
|
|
2318
|
-
let serverClosed = false;
|
|
2319
|
-
const server = http.createServer(async (req, res) => {
|
|
2320
|
-
const url = new URL(req.url || '/', `http://localhost:${CALLBACK_PORT}`);
|
|
2321
|
-
if (url.pathname === '/auth/callback') {
|
|
2322
|
-
const code = url.searchParams.get('code');
|
|
2323
|
-
const error = url.searchParams.get('error');
|
|
2324
|
-
if (error) {
|
|
2325
|
-
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
2326
|
-
res.end(`OAuth Error: ${error}`);
|
|
2327
|
-
console.log(red(`OAuth error: ${error}`));
|
|
2328
|
-
return;
|
|
2354
|
+
else if (err.message.includes('ECONNREFUSED')) {
|
|
2355
|
+
tunnelError = `Cannot connect to SSH server at ${tunnelInfo.host}:${tunnelInfo.port}. Is the workspace running and SSH enabled?`;
|
|
2329
2356
|
}
|
|
2330
|
-
if (
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
console.log(green('✓ Received OAuth callback!'));
|
|
2357
|
+
else if (err.message.includes('ENOTFOUND') || err.message.includes('getaddrinfo')) {
|
|
2358
|
+
tunnelError = `Cannot resolve hostname: ${tunnelInfo.host}. Check network connectivity.`;
|
|
2359
|
+
}
|
|
2360
|
+
else if (err.message.includes('ETIMEDOUT')) {
|
|
2361
|
+
tunnelError = `Connection timed out to ${tunnelInfo.host}:${tunnelInfo.port}. Is the workspace running?`;
|
|
2336
2362
|
}
|
|
2337
2363
|
else {
|
|
2338
|
-
|
|
2339
|
-
res.end('Missing authorization code');
|
|
2364
|
+
tunnelError = `SSH error: ${err.message}`;
|
|
2340
2365
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
});
|
|
2362
|
-
// Start listening
|
|
2363
|
-
await new Promise((resolve) => {
|
|
2364
|
-
server.listen(CALLBACK_PORT, () => {
|
|
2365
|
-
resolve();
|
|
2366
|
+
reject(new Error(tunnelError));
|
|
2367
|
+
});
|
|
2368
|
+
sshClient.on('close', () => {
|
|
2369
|
+
if (!tunnelReady) {
|
|
2370
|
+
// Only set error if not already set by error handler
|
|
2371
|
+
if (!tunnelError) {
|
|
2372
|
+
tunnelError = `SSH connection to ${tunnelInfo.host}:${tunnelInfo.port} closed unexpectedly. The workspace may not have SSH enabled or the port may be blocked.`;
|
|
2373
|
+
}
|
|
2374
|
+
reject(new Error(tunnelError));
|
|
2375
|
+
}
|
|
2376
|
+
});
|
|
2377
|
+
// Connect to SSH server
|
|
2378
|
+
sshClient.connect({
|
|
2379
|
+
host: tunnelInfo.host,
|
|
2380
|
+
port: tunnelInfo.port,
|
|
2381
|
+
username: tunnelInfo.user,
|
|
2382
|
+
password: tunnelInfo.password,
|
|
2383
|
+
readyTimeout: 10000,
|
|
2384
|
+
// Disable host key checking for simplicity (workspace containers)
|
|
2385
|
+
hostVerifier: () => true,
|
|
2366
2386
|
});
|
|
2367
2387
|
});
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2388
|
+
// Wait for tunnel to establish
|
|
2389
|
+
try {
|
|
2390
|
+
await Promise.race([
|
|
2391
|
+
tunnelPromise,
|
|
2392
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('SSH connection timeout')), 15000)),
|
|
2393
|
+
]);
|
|
2394
|
+
}
|
|
2395
|
+
catch (err) {
|
|
2396
|
+
console.log(red(`Failed to establish tunnel: ${err instanceof Error ? err.message : String(err)}`));
|
|
2397
|
+
sshClient.end();
|
|
2398
|
+
process.exit(1);
|
|
2399
|
+
}
|
|
2400
|
+
console.log(green('✓ SSH tunnel established!'));
|
|
2375
2401
|
console.log('');
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
if (elapsed > 0 && elapsed % 30 === 0) {
|
|
2383
|
-
console.log(` Still waiting... (${elapsed}s)`);
|
|
2402
|
+
// Handle Ctrl+C gracefully
|
|
2403
|
+
const cleanup = () => {
|
|
2404
|
+
console.log('');
|
|
2405
|
+
console.log(dim('Shutting down...'));
|
|
2406
|
+
if (tunnel.server) {
|
|
2407
|
+
tunnel.server.close();
|
|
2384
2408
|
}
|
|
2409
|
+
sshClient.end();
|
|
2410
|
+
process.exit(0);
|
|
2411
|
+
};
|
|
2412
|
+
process.on('SIGINT', cleanup);
|
|
2413
|
+
process.on('SIGTERM', cleanup);
|
|
2414
|
+
// Display the OAuth URL
|
|
2415
|
+
if (tunnelInfo.authUrl) {
|
|
2416
|
+
console.log('');
|
|
2417
|
+
console.log(green('Ready! Open this URL in your browser to complete authentication:'));
|
|
2418
|
+
console.log('');
|
|
2419
|
+
console.log(cyan(tunnelInfo.authUrl));
|
|
2420
|
+
console.log('');
|
|
2421
|
+
console.log(dim('The browser will redirect to localhost:1455, which tunnels to the workspace.'));
|
|
2422
|
+
console.log(dim('The Codex CLI in the workspace will receive the callback and complete auth.'));
|
|
2423
|
+
console.log('');
|
|
2385
2424
|
}
|
|
2386
|
-
|
|
2387
|
-
server.close();
|
|
2388
|
-
serverClosed = true;
|
|
2389
|
-
if (!authCode) {
|
|
2425
|
+
else {
|
|
2390
2426
|
console.log('');
|
|
2391
|
-
console.log(
|
|
2427
|
+
console.log(yellow('OAuth URL not available. Please start authentication from the dashboard.'));
|
|
2392
2428
|
console.log('');
|
|
2393
|
-
console.log('If you completed sign-in, the callback may have failed.');
|
|
2394
|
-
console.log('Try copying the localhost URL from your browser and pasting');
|
|
2395
|
-
console.log('it into the Agent Relay dashboard manually.');
|
|
2396
|
-
process.exit(1);
|
|
2397
2429
|
}
|
|
2398
|
-
//
|
|
2399
|
-
console.log(
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
console.log('You can close this terminal and return to the dashboard.');
|
|
2418
|
-
console.log('');
|
|
2430
|
+
// Poll for authentication completion
|
|
2431
|
+
console.log(cyan(`Waiting for authentication... (timeout: ${options.timeout}s)`));
|
|
2432
|
+
const startTime = Date.now();
|
|
2433
|
+
let authenticated = false;
|
|
2434
|
+
while (!authenticated && (Date.now() - startTime) < TIMEOUT_MS) {
|
|
2435
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
2436
|
+
try {
|
|
2437
|
+
// Build URL with token for authentication
|
|
2438
|
+
const authStatusUrl = new URL(`${CLOUD_URL}/api/auth/codex-helper/auth-status/${workspaceId}`);
|
|
2439
|
+
if (options.token) {
|
|
2440
|
+
authStatusUrl.searchParams.set('token', options.token);
|
|
2441
|
+
}
|
|
2442
|
+
const statusResponse = await fetch(authStatusUrl.toString(), { method: 'GET', headers, credentials: 'include' });
|
|
2443
|
+
if (statusResponse.ok) {
|
|
2444
|
+
const statusData = await statusResponse.json();
|
|
2445
|
+
if (statusData.authenticated) {
|
|
2446
|
+
authenticated = true;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2419
2449
|
}
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
console.log(
|
|
2426
|
-
console.log(cyan(authCode));
|
|
2427
|
-
process.exit(1);
|
|
2450
|
+
catch {
|
|
2451
|
+
// Ignore polling errors
|
|
2452
|
+
}
|
|
2453
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
2454
|
+
if (!authenticated && elapsed > 0 && elapsed % 30 === 0) {
|
|
2455
|
+
console.log(` Still waiting... (${elapsed}s)`);
|
|
2428
2456
|
}
|
|
2429
2457
|
}
|
|
2430
|
-
|
|
2458
|
+
// Cleanup SSH tunnel
|
|
2459
|
+
if (tunnel.server) {
|
|
2460
|
+
tunnel.server.close();
|
|
2461
|
+
}
|
|
2462
|
+
sshClient.end();
|
|
2463
|
+
if (authenticated) {
|
|
2464
|
+
console.log('');
|
|
2465
|
+
console.log(green('═══════════════════════════════════════════════════'));
|
|
2466
|
+
console.log(green(' Authentication Complete!'));
|
|
2467
|
+
console.log(green('═══════════════════════════════════════════════════'));
|
|
2468
|
+
console.log('');
|
|
2469
|
+
console.log('Your Codex account is now connected to the workspace.');
|
|
2470
|
+
console.log('You can close this terminal and return to the dashboard.');
|
|
2471
|
+
console.log('');
|
|
2472
|
+
}
|
|
2473
|
+
else {
|
|
2431
2474
|
console.log('');
|
|
2432
|
-
console.log(red('
|
|
2433
|
-
console.log(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2475
|
+
console.log(red('Timeout waiting for authentication.'));
|
|
2434
2476
|
console.log('');
|
|
2435
|
-
console.log('
|
|
2436
|
-
console.log(
|
|
2477
|
+
console.log('If you completed sign-in, the workspace may not have received');
|
|
2478
|
+
console.log('the callback. Check if the SSH tunnel was working correctly.');
|
|
2437
2479
|
process.exit(1);
|
|
2438
2480
|
}
|
|
2439
2481
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin API Routes
|
|
3
|
+
*
|
|
4
|
+
* Administrative endpoints for managing workspaces at scale.
|
|
5
|
+
* Protected by admin secret (ADMIN_API_SECRET environment variable).
|
|
6
|
+
*/
|
|
7
|
+
export declare const adminRouter: import("express-serve-static-core").Router;
|
|
8
|
+
//# sourceMappingURL=admin.d.ts.map
|