agent-relay 1.3.1 → 1.3.3
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/active/traj_3yx9dy148mge.json +42 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.json +49 -0
- package/.trajectories/completed/2026-01/traj_1g7yx6qtg4ai.md +31 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.json +49 -0
- package/.trajectories/completed/2026-01/traj_4qwd4zmhfwp4.md +31 -0
- package/.trajectories/completed/2026-01/traj_6unwwmgyj5sq.json +109 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.json +49 -0
- package/.trajectories/completed/2026-01/traj_a0tqx8biw9c4.md +31 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.json +66 -0
- package/.trajectories/completed/2026-01/traj_ax8uungxz2qh.md +36 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.json +49 -0
- package/.trajectories/completed/2026-01/traj_c9izbh2snpzf.md +31 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.json +65 -0
- package/.trajectories/completed/2026-01/traj_cpn70dw066nt.md +37 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.json +36 -0
- package/.trajectories/completed/2026-01/traj_erglv2f8t9eh.md +21 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.json +101 -0
- package/.trajectories/completed/2026-01/traj_he75f24d1xfm.md +52 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.json +61 -0
- package/.trajectories/completed/2026-01/traj_lgtodco7dp1n.md +36 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.json +73 -0
- package/.trajectories/completed/2026-01/traj_oszg9flv74pk.md +41 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.json +77 -0
- package/.trajectories/completed/2026-01/traj_pulomd3y8cvj.md +42 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.json +109 -0
- package/.trajectories/completed/2026-01/traj_rsavt0jipi3c.md +56 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.json +113 -0
- package/.trajectories/completed/2026-01/traj_x721m1j9rzup.md +57 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.json +61 -0
- package/.trajectories/completed/2026-01/traj_xjqvmep5ed3h.md +36 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.json +49 -0
- package/.trajectories/completed/2026-01/traj_y7n6hfbf7dmg.md +31 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.json +49 -0
- package/.trajectories/completed/2026-01/traj_yvfkwnkdiso2.md +31 -0
- package/.trajectories/index.json +140 -1
- package/README.md +23 -9
- package/TRAIL_GIT_AUTH_FIX.md +113 -0
- package/deploy/workspace/codex.config.toml +1 -1
- package/deploy/workspace/entrypoint.sh +20 -79
- package/deploy/workspace/gh-relay +156 -0
- package/deploy/workspace/git-credential-relay +5 -1
- package/dist/bridge/multi-project-client.js +13 -10
- package/dist/bridge/spawner.d.ts +2 -0
- package/dist/bridge/spawner.js +58 -76
- package/dist/bridge/types.d.ts +2 -0
- package/dist/cli/index.d.ts +8 -6
- package/dist/cli/index.js +297 -30
- package/dist/cloud/api/admin.js +16 -3
- package/dist/cloud/api/codex-auth-helper.js +28 -8
- package/dist/cloud/api/consensus.d.ts +13 -0
- package/dist/cloud/api/consensus.js +259 -0
- package/dist/cloud/api/daemons.js +205 -1
- package/dist/cloud/api/git.js +37 -7
- package/dist/cloud/api/onboarding.js +4 -1
- package/dist/cloud/api/provider-env.d.ts +5 -0
- package/dist/cloud/api/provider-env.js +27 -0
- package/dist/cloud/api/providers.js +2 -0
- package/dist/cloud/api/test-helpers.js +130 -0
- package/dist/cloud/api/workspaces.js +38 -3
- package/dist/cloud/db/bulk-ingest.d.ts +88 -0
- package/dist/cloud/db/bulk-ingest.js +268 -0
- package/dist/cloud/db/drizzle.d.ts +33 -0
- package/dist/cloud/db/drizzle.js +174 -2
- package/dist/cloud/db/index.d.ts +24 -5
- package/dist/cloud/db/index.js +19 -4
- package/dist/cloud/db/schema.d.ts +397 -3
- package/dist/cloud/db/schema.js +75 -1
- package/dist/cloud/provisioner/index.d.ts +8 -0
- package/dist/cloud/provisioner/index.js +256 -50
- package/dist/cloud/server.js +47 -3
- package/dist/cloud/services/index.d.ts +1 -0
- package/dist/cloud/services/index.js +2 -0
- package/dist/cloud/services/nango.d.ts +3 -4
- package/dist/cloud/services/nango.js +11 -33
- package/dist/cloud/services/workspace-keepalive.d.ts +76 -0
- package/dist/cloud/services/workspace-keepalive.js +234 -0
- package/dist/config/relay-config.d.ts +23 -0
- package/dist/config/relay-config.js +23 -0
- package/dist/daemon/agent-manager.d.ts +20 -1
- package/dist/daemon/agent-manager.js +51 -0
- package/dist/daemon/agent-registry.js +4 -4
- package/dist/daemon/agent-signing.d.ts +158 -0
- package/dist/daemon/agent-signing.js +523 -0
- package/dist/daemon/api.js +18 -1
- package/dist/daemon/cli-auth.d.ts +4 -1
- package/dist/daemon/cli-auth.js +55 -11
- package/dist/daemon/cloud-sync.d.ts +47 -1
- package/dist/daemon/cloud-sync.js +152 -3
- package/dist/daemon/connection.d.ts +28 -0
- package/dist/daemon/connection.js +113 -22
- package/dist/daemon/consensus-integration.d.ts +167 -0
- package/dist/daemon/consensus-integration.js +371 -0
- package/dist/daemon/consensus.d.ts +271 -0
- package/dist/daemon/consensus.js +632 -0
- package/dist/daemon/delivery-tracker.d.ts +34 -0
- package/dist/daemon/delivery-tracker.js +104 -0
- package/dist/daemon/enhanced-features.d.ts +118 -0
- package/dist/daemon/enhanced-features.js +178 -0
- package/dist/daemon/index.d.ts +4 -0
- package/dist/daemon/index.js +5 -0
- package/dist/daemon/rate-limiter.d.ts +68 -0
- package/dist/daemon/rate-limiter.js +130 -0
- package/dist/daemon/router.d.ts +18 -11
- package/dist/daemon/router.js +57 -113
- package/dist/daemon/server.d.ts +13 -1
- package/dist/daemon/server.js +71 -9
- package/dist/daemon/sync-queue.d.ts +116 -0
- package/dist/daemon/sync-queue.js +361 -0
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/{page-c617745b81344f4f.js → page-7f64824ae7d06707.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/{page-dc786c183425c2ac.js → page-814efc4d77b4191d.js} +1 -1
- package/dist/dashboard/out/_next/static/chunks/{main-2ee6beb2ae96d210.js → main-5a40a5ae29646e1b.js} +1 -1
- package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +2 -2
- package/dist/dashboard/out/cloud/link.html +1 -0
- package/dist/dashboard/out/cloud/link.txt +7 -0
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +2 -2
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +2 -2
- package/dist/dashboard/out/login.html +2 -3
- package/dist/dashboard/out/login.txt +2 -2
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +2 -2
- package/dist/dashboard/out/pricing.html +2 -2
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +1 -1
- package/dist/dashboard/out/signup.html +2 -2
- package/dist/dashboard/out/signup.txt +1 -1
- package/dist/dashboard-server/server.js +244 -28
- package/dist/health-worker-manager.d.ts +62 -0
- package/dist/health-worker-manager.js +144 -0
- package/dist/health-worker.d.ts +9 -0
- package/dist/health-worker.js +79 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -1
- package/dist/memory/context-compaction.d.ts +156 -0
- package/dist/memory/context-compaction.js +453 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/protocol/channels.js +4 -4
- package/dist/protocol/framing.d.ts +72 -10
- package/dist/protocol/framing.js +194 -25
- package/dist/storage/adapter.d.ts +8 -1
- package/dist/storage/adapter.js +11 -0
- package/dist/storage/batched-sqlite-adapter.d.ts +71 -0
- package/dist/storage/batched-sqlite-adapter.js +183 -0
- package/dist/storage/dead-letter-queue.d.ts +196 -0
- package/dist/storage/dead-letter-queue.js +427 -0
- package/dist/storage/dlq-adapter.d.ts +195 -0
- package/dist/storage/dlq-adapter.js +664 -0
- package/dist/trajectory/config.d.ts +32 -14
- package/dist/trajectory/config.js +38 -16
- package/dist/trajectory/integration.js +217 -64
- package/dist/utils/git-remote.d.ts +47 -0
- package/dist/utils/git-remote.js +125 -0
- package/dist/utils/id-generator.d.ts +35 -0
- package/dist/utils/id-generator.js +60 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/precompiled-patterns.d.ts +110 -0
- package/dist/utils/precompiled-patterns.js +322 -0
- package/dist/wrapper/auth-detection.js +1 -1
- package/dist/wrapper/base-wrapper.d.ts +40 -0
- package/dist/wrapper/base-wrapper.js +60 -6
- package/dist/wrapper/client.d.ts +14 -4
- package/dist/wrapper/client.js +89 -31
- package/dist/wrapper/idle-detector.d.ts +102 -0
- package/dist/wrapper/idle-detector.js +279 -0
- package/dist/wrapper/parser.d.ts +4 -0
- package/dist/wrapper/parser.js +19 -1
- package/dist/wrapper/pty-wrapper.d.ts +14 -2
- package/dist/wrapper/pty-wrapper.js +132 -32
- package/dist/wrapper/shared.d.ts +1 -1
- package/dist/wrapper/shared.js +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +20 -2
- package/dist/wrapper/tmux-wrapper.js +163 -40
- package/package.json +3 -1
- package/scripts/run-migrations.js +43 -0
- package/scripts/verify-schema.js +134 -0
- package/tests/benchmarks/protocol.bench.ts +310 -0
- package/dist/dashboard/out/_next/static/chunks/116-2502180def231162.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/899-fc02ed79e3de4302.js +0 -1
- package/dist/dashboard/out/_next/static/chunks/app/login/page-c22d080201cbd9fb.js +0 -1
- package/dist/dashboard/out/_next/static/css/48a8fbe3e659080e.css +0 -1
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{sDcbGRTYLcpPvyTs_rsNb → R-uQOUcOLINtsp6ACeZa9}/_ssgManifest.js +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
* Agent Relay CLI
|
|
4
4
|
*
|
|
5
5
|
* Commands:
|
|
6
|
-
* relay
|
|
7
|
-
* relay -
|
|
8
|
-
* relay
|
|
9
|
-
* relay
|
|
10
|
-
* relay
|
|
11
|
-
* relay
|
|
6
|
+
* relay claude - Start daemon + Dashboard coordinator with Claude
|
|
7
|
+
* relay codex - Start daemon + Dasbboard coordinator with Codex
|
|
8
|
+
* relay create-agent <cmd> - Wrap agent with real-time messaging
|
|
9
|
+
* relay create-agent -n Name cmd - Wrap with specific agent name
|
|
10
|
+
* relay up - Start daemon + dashboard
|
|
11
|
+
* relay read <id> - Read full message by ID
|
|
12
|
+
* relay agents - List connected agents
|
|
13
|
+
* relay who - Show currently active agents
|
|
12
14
|
*/
|
|
13
15
|
import { Command } from 'commander';
|
|
14
16
|
import { config as dotenvConfig } from 'dotenv';
|
|
@@ -36,7 +38,7 @@ const VERSION = packageJson.version;
|
|
|
36
38
|
const execAsync = promisify(exec);
|
|
37
39
|
// Check for updates in background (non-blocking)
|
|
38
40
|
// Only show notification for interactive commands, not when wrapping agents or running update
|
|
39
|
-
const interactiveCommands = ['up', 'down', 'status', 'agents', 'who', 'version', '--version', '-V', '--help', '-h'];
|
|
41
|
+
const interactiveCommands = ['up', 'down', 'status', 'agents', 'who', 'version', '--version', '-V', '--help', '-h', 'create-agent', 'claude', 'codex'];
|
|
40
42
|
const shouldCheckUpdates = process.argv.length > 2 &&
|
|
41
43
|
interactiveCommands.includes(process.argv[2]);
|
|
42
44
|
if (shouldCheckUpdates) {
|
|
@@ -50,21 +52,19 @@ program
|
|
|
50
52
|
.name('agent-relay')
|
|
51
53
|
.description('Agent-to-agent messaging')
|
|
52
54
|
.version(VERSION, '-V, --version', 'Output the version number');
|
|
53
|
-
//
|
|
55
|
+
// create-agent - Wrap agent with real-time messaging
|
|
54
56
|
program
|
|
57
|
+
.command('create-agent')
|
|
58
|
+
.description('Wrap an agent with real-time messaging')
|
|
55
59
|
.option('-n, --name <name>', 'Agent name (auto-generated if not set)')
|
|
56
|
-
.option('-
|
|
60
|
+
.option('-d, --debug', 'Enable debug output')
|
|
57
61
|
.option('--prefix <pattern>', 'Relay prefix pattern (default: ->relay:)')
|
|
58
62
|
.option('--dashboard-port <port>', 'Dashboard port for spawn/release API (auto-detected if not set)')
|
|
59
63
|
.option('--shadow <name>', 'Spawn a shadow agent with this name that monitors the primary')
|
|
60
64
|
.option('--shadow-role <role>', 'Shadow role: reviewer, auditor, or triggers (comma-separated: SESSION_END,CODE_WRITTEN,REVIEW_REQUEST,EXPLICIT_ASK,ALL_MESSAGES)')
|
|
61
|
-
.
|
|
65
|
+
.option('--skip-instructions', 'Skip initial instruction injection (use with --append-system-prompt)')
|
|
66
|
+
.argument('<command...>', 'Command to wrap (e.g., claude)')
|
|
62
67
|
.action(async (commandParts, options) => {
|
|
63
|
-
// If no command provided, show help
|
|
64
|
-
if (!commandParts || commandParts.length === 0) {
|
|
65
|
-
program.help();
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
68
|
const { getProjectPaths } = await import('../utils/project-namespace.js');
|
|
69
69
|
const { findAgentConfig, isClaudeCli, buildClaudeArgs } = await import('../utils/agent-config.js');
|
|
70
70
|
const paths = getProjectPaths();
|
|
@@ -93,20 +93,29 @@ program
|
|
|
93
93
|
dashboardPort = parseInt(options.dashboardPort, 10);
|
|
94
94
|
}
|
|
95
95
|
else {
|
|
96
|
-
// Try to detect if dashboard is running at
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
// Try to detect if dashboard is running at common ports
|
|
97
|
+
const portsToTry = [
|
|
98
|
+
parseInt(DEFAULT_DASHBOARD_PORT, 10),
|
|
99
|
+
3889, 3890, 3891, // Common fallback ports when default is in use
|
|
100
|
+
];
|
|
101
|
+
for (const port of portsToTry) {
|
|
102
|
+
try {
|
|
103
|
+
const response = await fetch(`http://localhost:${port}/api/health`, {
|
|
104
|
+
method: 'GET',
|
|
105
|
+
signal: AbortSignal.timeout(300), // Quick timeout for detection
|
|
106
|
+
});
|
|
107
|
+
if (response.ok) {
|
|
108
|
+
const health = await response.json();
|
|
109
|
+
if (health.status === 'healthy') {
|
|
110
|
+
dashboardPort = port;
|
|
111
|
+
console.error(`Dashboard detected: http://localhost:${dashboardPort}`);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Try next port
|
|
106
118
|
}
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// Dashboard not running - spawn/release will use fallback callbacks
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
// Create spawner as fallback for direct spawn (if dashboard API not available)
|
|
@@ -116,10 +125,11 @@ program
|
|
|
116
125
|
command: mainCommand,
|
|
117
126
|
args: finalArgs,
|
|
118
127
|
socketPath: paths.socketPath,
|
|
119
|
-
debug:
|
|
128
|
+
debug: options.debug ?? false,
|
|
120
129
|
relayPrefix: options.prefix,
|
|
121
130
|
useInbox: true,
|
|
122
131
|
inboxDir: paths.dataDir, // Use the project-specific data directory for the inbox
|
|
132
|
+
skipInstructions: options.skipInstructions,
|
|
123
133
|
// Use dashboard API for spawn/release when available (preferred - works from any context)
|
|
124
134
|
dashboardPort,
|
|
125
135
|
// Wire up spawn/release callbacks as fallback (if no dashboardPort)
|
|
@@ -448,6 +458,162 @@ program
|
|
|
448
458
|
console.log('Cleaned up stale pid');
|
|
449
459
|
}
|
|
450
460
|
});
|
|
461
|
+
// System prompt for Dashboard agent - plain text to avoid shell escaping issues
|
|
462
|
+
const MEGA_SYSTEM_PROMPT = [
|
|
463
|
+
'You are Dashboard, a lead coordinator in agent-relay.',
|
|
464
|
+
'Your PRIMARY job is to delegate - you should almost NEVER do implementation work yourself.',
|
|
465
|
+
'ALWAYS SPAWN AGENTS: For any non-trivial task, spawn specialized workers.',
|
|
466
|
+
].join(' ');
|
|
467
|
+
// Helper function for starting Dashboard coordinator with a specific provider
|
|
468
|
+
async function startDashboardCoordinator(operator) {
|
|
469
|
+
const { spawn } = await import('node:child_process');
|
|
470
|
+
const { getProjectPaths } = await import('../utils/project-namespace.js');
|
|
471
|
+
const { resolveTmux, TmuxNotFoundError } = await import('../utils/tmux-resolver.js');
|
|
472
|
+
// Check for tmux first
|
|
473
|
+
const tmuxInfo = resolveTmux();
|
|
474
|
+
if (!tmuxInfo) {
|
|
475
|
+
const error = new TmuxNotFoundError();
|
|
476
|
+
console.error(`Error: ${error.message}`);
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
479
|
+
const paths = getProjectPaths();
|
|
480
|
+
console.log(`Starting Dashboard with ${operator}...`);
|
|
481
|
+
console.log(`Project: ${paths.projectRoot}`);
|
|
482
|
+
console.log(`Tmux: ${tmuxInfo.path} (v${tmuxInfo.version})`);
|
|
483
|
+
// Step 1: Check if daemon is already running, start if needed
|
|
484
|
+
console.log('\n[1/3] Checking daemon...');
|
|
485
|
+
// Check if socket exists (daemon running)
|
|
486
|
+
const socketExists = fs.existsSync(paths.socketPath);
|
|
487
|
+
// Ports to try for dashboard detection
|
|
488
|
+
const portsToTry = [
|
|
489
|
+
parseInt(DEFAULT_DASHBOARD_PORT, 10),
|
|
490
|
+
3889, 3890, 3891,
|
|
491
|
+
];
|
|
492
|
+
// Check if dashboard is responding for THIS project
|
|
493
|
+
let dashboardReady = false;
|
|
494
|
+
let detectedPort;
|
|
495
|
+
// Helper to check health at a port
|
|
496
|
+
const checkPort = async (port) => {
|
|
497
|
+
try {
|
|
498
|
+
const response = await fetch(`http://localhost:${port}/api/health`, {
|
|
499
|
+
signal: AbortSignal.timeout(500),
|
|
500
|
+
});
|
|
501
|
+
if (response.ok) {
|
|
502
|
+
const health = await response.json();
|
|
503
|
+
return health.status === 'healthy';
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
// Port not responding
|
|
508
|
+
}
|
|
509
|
+
return false;
|
|
510
|
+
};
|
|
511
|
+
if (socketExists) {
|
|
512
|
+
for (const port of portsToTry) {
|
|
513
|
+
if (await checkPort(port)) {
|
|
514
|
+
dashboardReady = true;
|
|
515
|
+
detectedPort = port;
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (dashboardReady && detectedPort) {
|
|
521
|
+
console.log(`Daemon already running at port ${detectedPort}, reusing...`);
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
console.log('Starting daemon...');
|
|
525
|
+
const daemonProc = spawn(process.execPath, [process.argv[1], 'up'], {
|
|
526
|
+
stdio: 'ignore',
|
|
527
|
+
detached: true,
|
|
528
|
+
});
|
|
529
|
+
daemonProc.unref();
|
|
530
|
+
// Wait for dashboard to be ready (up to 10 seconds)
|
|
531
|
+
const maxWait = 10000;
|
|
532
|
+
const startTime = Date.now();
|
|
533
|
+
while (Date.now() - startTime < maxWait) {
|
|
534
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
535
|
+
for (const port of portsToTry) {
|
|
536
|
+
if (await checkPort(port)) {
|
|
537
|
+
dashboardReady = true;
|
|
538
|
+
detectedPort = port;
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (dashboardReady)
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
if (!dashboardReady) {
|
|
546
|
+
console.error('Warning: Dashboard may not be fully ready. Spawn might not work.');
|
|
547
|
+
detectedPort = parseInt(DEFAULT_DASHBOARD_PORT, 10); // Fallback
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const dashboardPort = detectedPort || parseInt(DEFAULT_DASHBOARD_PORT, 10);
|
|
551
|
+
// Step 2: Install prpm snippet via npx
|
|
552
|
+
console.log('[2/3] Installing agent-relay snippet...');
|
|
553
|
+
const prpmArgs = operator.toLowerCase() === 'claude'
|
|
554
|
+
? ['prpm', 'install', '@agent-relay/agent-relay-snippet', '--location', 'CLAUDE.md']
|
|
555
|
+
: ['prpm', 'install', '@agent-relay/agent-relay-snippet'];
|
|
556
|
+
try {
|
|
557
|
+
await new Promise((resolve, reject) => {
|
|
558
|
+
const prpmProc = spawn('npx', prpmArgs, {
|
|
559
|
+
stdio: 'inherit',
|
|
560
|
+
});
|
|
561
|
+
prpmProc.on('close', (code) => {
|
|
562
|
+
if (code === 0)
|
|
563
|
+
resolve();
|
|
564
|
+
else
|
|
565
|
+
reject(new Error(`npx prpm exited with code ${code}`));
|
|
566
|
+
});
|
|
567
|
+
prpmProc.on('error', reject);
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
catch (err) {
|
|
571
|
+
console.warn(`Warning: prpm install failed: ${err.message}`);
|
|
572
|
+
console.warn('Continuing without snippet installation...');
|
|
573
|
+
}
|
|
574
|
+
// Step 3: Start Dashboard agent with system prompt
|
|
575
|
+
console.log(`[3/3] Starting Dashboard agent with ${operator}...`);
|
|
576
|
+
console.log('');
|
|
577
|
+
const op = operator.toLowerCase();
|
|
578
|
+
// Build CLI-specific arguments for system prompt
|
|
579
|
+
// These args go AFTER the operator command, passed through to the CLI
|
|
580
|
+
let cliArgs = [];
|
|
581
|
+
if (op === 'claude') {
|
|
582
|
+
// Claude: --append-system-prompt <content> (takes content directly, not file)
|
|
583
|
+
cliArgs = ['--append-system-prompt', MEGA_SYSTEM_PROMPT];
|
|
584
|
+
}
|
|
585
|
+
else if (op === 'codex') {
|
|
586
|
+
// Codex: --config developer_instructions="<content>"
|
|
587
|
+
cliArgs = ['--config', `developer_instructions=${MEGA_SYSTEM_PROMPT}`];
|
|
588
|
+
}
|
|
589
|
+
// Use '--' to separate agent-relay options from the command + its args
|
|
590
|
+
// Format: agent-relay create-agent -n Dashboard --skip-instructions --dashboard-port <port> -- claude --append-system-prompt "..."
|
|
591
|
+
const agentProc = spawn(process.execPath, [process.argv[1], 'create-agent', '-n', 'Dashboard', '--skip-instructions', '--dashboard-port', String(dashboardPort), '--', operator, ...cliArgs], { stdio: 'inherit' });
|
|
592
|
+
// Forward signals to agent process
|
|
593
|
+
process.on('SIGINT', () => {
|
|
594
|
+
agentProc.kill('SIGINT');
|
|
595
|
+
});
|
|
596
|
+
process.on('SIGTERM', () => {
|
|
597
|
+
agentProc.kill('SIGTERM');
|
|
598
|
+
});
|
|
599
|
+
agentProc.on('close', (code) => {
|
|
600
|
+
process.exit(code ?? 0);
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
// claude - Start daemon and spawn Dashboard coordinator with Claude
|
|
604
|
+
program
|
|
605
|
+
.command('claude')
|
|
606
|
+
.description('Start daemon and Dashboard coordinator with Claude')
|
|
607
|
+
.action(async () => {
|
|
608
|
+
await startDashboardCoordinator('claude');
|
|
609
|
+
});
|
|
610
|
+
// codex - Start daemon and spawn Dashboard coordinator with Codex
|
|
611
|
+
program
|
|
612
|
+
.command('codex')
|
|
613
|
+
.description('Start daemon and Dashboard coordinator with Codex')
|
|
614
|
+
.action(async () => {
|
|
615
|
+
await startDashboardCoordinator('codex');
|
|
616
|
+
});
|
|
451
617
|
// status - Check daemon status
|
|
452
618
|
program
|
|
453
619
|
.command('status')
|
|
@@ -1341,6 +1507,105 @@ program
|
|
|
1341
1507
|
}
|
|
1342
1508
|
}
|
|
1343
1509
|
});
|
|
1510
|
+
// spawn - Spawn an agent via API (works from any context, no tmux required)
|
|
1511
|
+
program
|
|
1512
|
+
.command('spawn', { hidden: true })
|
|
1513
|
+
.description('Spawn an agent via dashboard API (no tmux required, works in containers)')
|
|
1514
|
+
.argument('<name>', 'Agent name')
|
|
1515
|
+
.argument('<cli>', 'CLI to use (claude, codex, gemini, etc.)')
|
|
1516
|
+
.argument('[task]', 'Task description (can also be piped via stdin)')
|
|
1517
|
+
.option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
|
|
1518
|
+
.option('--team <team>', 'Team name for the agent')
|
|
1519
|
+
.option('--spawner <name>', 'Name of the agent requesting the spawn (for policy enforcement)')
|
|
1520
|
+
.option('--interactive', 'Disable auto-accept of permission prompts (for auth setup flows)')
|
|
1521
|
+
.option('--cwd <path>', 'Working directory for the agent')
|
|
1522
|
+
.option('--shadow-mode <mode>', 'Shadow execution mode: subagent or process')
|
|
1523
|
+
.option('--shadow-of <name>', 'Primary agent to shadow (if this agent is a shadow)')
|
|
1524
|
+
.option('--shadow-agent <profile>', 'Shadow agent profile to use')
|
|
1525
|
+
.option('--shadow-triggers <triggers>', 'When to trigger shadow (comma-separated: SESSION_END,CODE_WRITTEN,REVIEW_REQUEST,EXPLICIT_ASK,ALL_MESSAGES)')
|
|
1526
|
+
.option('--shadow-speak-on <triggers>', 'When shadow should speak (comma-separated, same values as --shadow-triggers)')
|
|
1527
|
+
.action(async (name, cli, task, options) => {
|
|
1528
|
+
const port = options.port || DEFAULT_DASHBOARD_PORT;
|
|
1529
|
+
// Read task from stdin if not provided as argument
|
|
1530
|
+
let finalTask = task;
|
|
1531
|
+
if (!finalTask && !process.stdin.isTTY) {
|
|
1532
|
+
const chunks = [];
|
|
1533
|
+
for await (const chunk of process.stdin) {
|
|
1534
|
+
chunks.push(chunk);
|
|
1535
|
+
}
|
|
1536
|
+
finalTask = Buffer.concat(chunks).toString('utf-8').trim();
|
|
1537
|
+
}
|
|
1538
|
+
if (!finalTask) {
|
|
1539
|
+
console.error('Error: Task description required (as argument or via stdin)');
|
|
1540
|
+
process.exit(1);
|
|
1541
|
+
}
|
|
1542
|
+
// Validate shadow mode if provided
|
|
1543
|
+
if (options.shadowMode && !['subagent', 'process'].includes(options.shadowMode)) {
|
|
1544
|
+
console.error('Error: --shadow-mode must be "subagent" or "process"');
|
|
1545
|
+
process.exit(1);
|
|
1546
|
+
}
|
|
1547
|
+
// Parse comma-separated trigger lists
|
|
1548
|
+
const parseTriggers = (value) => {
|
|
1549
|
+
if (!value)
|
|
1550
|
+
return undefined;
|
|
1551
|
+
const validTriggers = ['SESSION_END', 'CODE_WRITTEN', 'REVIEW_REQUEST', 'EXPLICIT_ASK', 'ALL_MESSAGES'];
|
|
1552
|
+
const triggers = value.split(',').map(t => t.trim().toUpperCase());
|
|
1553
|
+
const invalid = triggers.filter(t => !validTriggers.includes(t));
|
|
1554
|
+
if (invalid.length > 0) {
|
|
1555
|
+
console.error(`Error: Invalid triggers: ${invalid.join(', ')}`);
|
|
1556
|
+
console.error(`Valid triggers: ${validTriggers.join(', ')}`);
|
|
1557
|
+
process.exit(1);
|
|
1558
|
+
}
|
|
1559
|
+
return triggers;
|
|
1560
|
+
};
|
|
1561
|
+
try {
|
|
1562
|
+
// Build spawn request using the SpawnRequest type for consistency
|
|
1563
|
+
const spawnRequest = {
|
|
1564
|
+
name,
|
|
1565
|
+
cli,
|
|
1566
|
+
task: finalTask,
|
|
1567
|
+
team: options.team,
|
|
1568
|
+
spawnerName: options.spawner,
|
|
1569
|
+
interactive: options.interactive,
|
|
1570
|
+
cwd: options.cwd,
|
|
1571
|
+
shadowMode: options.shadowMode,
|
|
1572
|
+
shadowOf: options.shadowOf,
|
|
1573
|
+
shadowAgent: options.shadowAgent,
|
|
1574
|
+
shadowTriggers: parseTriggers(options.shadowTriggers),
|
|
1575
|
+
shadowSpeakOn: parseTriggers(options.shadowSpeakOn),
|
|
1576
|
+
};
|
|
1577
|
+
const response = await fetch(`http://localhost:${port}/api/spawn`, {
|
|
1578
|
+
method: 'POST',
|
|
1579
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1580
|
+
body: JSON.stringify(spawnRequest),
|
|
1581
|
+
});
|
|
1582
|
+
const result = await response.json();
|
|
1583
|
+
if (result.success) {
|
|
1584
|
+
console.log(`Spawned agent: ${name} (pid: ${result.pid})`);
|
|
1585
|
+
process.exit(0);
|
|
1586
|
+
}
|
|
1587
|
+
else {
|
|
1588
|
+
if (result.policyDecision) {
|
|
1589
|
+
console.error(`Policy denied spawn: ${result.policyDecision.reason}`);
|
|
1590
|
+
console.error(`Policy source: ${result.policyDecision.policySource}`);
|
|
1591
|
+
}
|
|
1592
|
+
else {
|
|
1593
|
+
console.error(`Failed to spawn ${name}: ${result.error || 'Unknown error'}`);
|
|
1594
|
+
}
|
|
1595
|
+
process.exit(1);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
catch (err) {
|
|
1599
|
+
if (err.code === 'ECONNREFUSED') {
|
|
1600
|
+
console.error(`Cannot connect to dashboard at port ${port}. Is the daemon running?`);
|
|
1601
|
+
console.log(`Run 'agent-relay up' to start the daemon.`);
|
|
1602
|
+
}
|
|
1603
|
+
else {
|
|
1604
|
+
console.error(`Failed to spawn ${name}: ${err.message}`);
|
|
1605
|
+
}
|
|
1606
|
+
process.exit(1);
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1344
1609
|
// release - Release a spawned agent via API (works from any context, no terminal required)
|
|
1345
1610
|
program
|
|
1346
1611
|
.command('release')
|
|
@@ -1439,7 +1704,7 @@ cloudCommand
|
|
|
1439
1704
|
.command('link')
|
|
1440
1705
|
.description('Link this machine to your Agent Relay Cloud account')
|
|
1441
1706
|
.option('--name <name>', 'Name for this machine')
|
|
1442
|
-
.option('--cloud-url <url>', 'Cloud API URL', process.env.AGENT_RELAY_CLOUD_URL || 'https://
|
|
1707
|
+
.option('--cloud-url <url>', 'Cloud API URL', process.env.AGENT_RELAY_CLOUD_URL || 'https://agent-relay.com')
|
|
1443
1708
|
.action(async (options) => {
|
|
1444
1709
|
const os = await import('node:os');
|
|
1445
1710
|
const crypto = await import('node:crypto');
|
|
@@ -1720,6 +1985,8 @@ program
|
|
|
1720
1985
|
stdio: 'inherit',
|
|
1721
1986
|
env: {
|
|
1722
1987
|
...process.env,
|
|
1988
|
+
// Trajectory env vars override parent shell settings
|
|
1989
|
+
// This ensures config-based TRAJECTORIES_DATA_DIR takes precedence
|
|
1723
1990
|
TRAJECTORIES_PROJECT: paths.projectId,
|
|
1724
1991
|
TRAJECTORIES_DATA_DIR: trajectoriesDir,
|
|
1725
1992
|
},
|
package/dist/cloud/api/admin.js
CHANGED
|
@@ -43,7 +43,7 @@ adminRouter.use(requireAdminAuth);
|
|
|
43
43
|
* - batchSize?: Number of concurrent updates (default: 5)
|
|
44
44
|
*
|
|
45
45
|
* Response:
|
|
46
|
-
* - summary: { total, updated, pendingRestart, skippedActiveAgents, skippedNotRunning, errors }
|
|
46
|
+
* - summary: { total, updated, pendingRestart, skippedActiveAgents, skippedVerificationFailed, skippedNotRunning, errors }
|
|
47
47
|
* - results: Array of per-workspace results
|
|
48
48
|
*/
|
|
49
49
|
adminRouter.post('/workspaces/update-image', async (req, res) => {
|
|
@@ -159,7 +159,9 @@ adminRouter.get('/workspaces/:id/agents', async (req, res) => {
|
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
try {
|
|
162
|
-
|
|
162
|
+
// Use /api/data endpoint which returns { agents: [...], ... }
|
|
163
|
+
// Note: /api/agents doesn't exist on the workspace dashboard-server
|
|
164
|
+
const response = await fetch(`${baseUrl}/api/data`, {
|
|
163
165
|
method: 'GET',
|
|
164
166
|
headers: { 'Accept': 'application/json' },
|
|
165
167
|
signal: AbortSignal.timeout(10_000),
|
|
@@ -170,7 +172,18 @@ adminRouter.get('/workspaces/:id/agents', async (req, res) => {
|
|
|
170
172
|
}
|
|
171
173
|
const data = await response.json();
|
|
172
174
|
const agents = data.agents || [];
|
|
173
|
-
const activeAgents = agents.filter(a =>
|
|
175
|
+
const activeAgents = agents.filter(a => {
|
|
176
|
+
const status = (a.status ?? '').toLowerCase();
|
|
177
|
+
const activityState = (a.activityState ?? '').toLowerCase();
|
|
178
|
+
const isProcessing = a.isProcessing === true;
|
|
179
|
+
if (activityState === 'active' || activityState === 'idle')
|
|
180
|
+
return true;
|
|
181
|
+
if (status && status !== 'disconnected' && status !== 'offline')
|
|
182
|
+
return true;
|
|
183
|
+
if (isProcessing)
|
|
184
|
+
return true;
|
|
185
|
+
return false;
|
|
186
|
+
});
|
|
174
187
|
res.json({
|
|
175
188
|
hasActiveAgents: activeAgents.length > 0,
|
|
176
189
|
agentCount: activeAgents.length,
|
|
@@ -19,6 +19,7 @@ import { requireAuth } from './auth.js';
|
|
|
19
19
|
import { db } from '../db/index.js';
|
|
20
20
|
import { deriveSshPassword } from '../services/ssh-security.js';
|
|
21
21
|
export const codexAuthHelperRouter = Router();
|
|
22
|
+
const WORKSPACE_SSH_PORT = 3022;
|
|
22
23
|
const pendingAuthSessions = new Map();
|
|
23
24
|
const pendingCliTokens = new Map();
|
|
24
25
|
// Clean up old sessions every minute
|
|
@@ -201,18 +202,18 @@ codexAuthHelperRouter.get('/tunnel-info/:workspaceId', async (req, res) => {
|
|
|
201
202
|
const host = url.hostname;
|
|
202
203
|
const apiPort = parseInt(url.port, 10) || 80;
|
|
203
204
|
// SSH connection info varies by environment:
|
|
204
|
-
// - Fly.io: Use public fly.dev hostname with port
|
|
205
|
+
// - Fly.io: Use public fly.dev hostname with port 3022 (exposed via TCP service)
|
|
205
206
|
// - Local Docker: Use localhost with derived SSH port (22000 + apiPort - 3000)
|
|
206
207
|
const isOnFly = !!process.env.FLY_APP_NAME;
|
|
207
208
|
const isLocalDocker = (host === 'localhost' || host === '127.0.0.1') && apiPort >= 3000;
|
|
208
209
|
let sshHost;
|
|
209
210
|
let sshPort;
|
|
210
211
|
if (isOnFly) {
|
|
211
|
-
// Fly.io public hostname - SSH is exposed as a public TCP service on port
|
|
212
|
-
// Users can SSH directly from their machine to {app}.fly.dev:
|
|
212
|
+
// Fly.io public hostname - SSH is exposed as a public TCP service on port 3022
|
|
213
|
+
// Users can SSH directly from their machine to {app}.fly.dev:3022
|
|
213
214
|
const appName = `ar-${workspace.id.substring(0, 8)}`;
|
|
214
215
|
sshHost = `${appName}.fly.dev`;
|
|
215
|
-
sshPort =
|
|
216
|
+
sshPort = WORKSPACE_SSH_PORT;
|
|
216
217
|
}
|
|
217
218
|
else if (isLocalDocker) {
|
|
218
219
|
// Local Docker: SSH port is derived from API port
|
|
@@ -223,7 +224,7 @@ codexAuthHelperRouter.get('/tunnel-info/:workspaceId', async (req, res) => {
|
|
|
223
224
|
else {
|
|
224
225
|
// Default fallback
|
|
225
226
|
sshHost = host;
|
|
226
|
-
sshPort =
|
|
227
|
+
sshPort = WORKSPACE_SSH_PORT;
|
|
227
228
|
}
|
|
228
229
|
// SSH password is derived per-workspace for security
|
|
229
230
|
// Each workspace gets a unique password based on its ID + secret salt
|
|
@@ -288,18 +289,37 @@ codexAuthHelperRouter.get('/auth-status/:workspaceId', async (req, res) => {
|
|
|
288
289
|
if (!workspace.publicUrl) {
|
|
289
290
|
return res.status(400).json({ error: 'Workspace URL not available' });
|
|
290
291
|
}
|
|
291
|
-
// Check with workspace daemon if Codex is authenticated
|
|
292
|
-
|
|
292
|
+
// Check with workspace daemon if Codex is authenticated for this specific user
|
|
293
|
+
// Pass userId to enable per-user credential checking (multiple users can share a workspace)
|
|
294
|
+
const checkUrl = new URL(`${workspace.publicUrl}/auth/cli/openai/check`);
|
|
295
|
+
checkUrl.searchParams.set('userId', userId);
|
|
296
|
+
const response = await fetch(checkUrl.toString(), {
|
|
293
297
|
method: 'GET',
|
|
294
298
|
signal: AbortSignal.timeout(5000),
|
|
295
299
|
});
|
|
296
300
|
if (response.ok) {
|
|
297
301
|
const data = await response.json();
|
|
302
|
+
// When authentication is detected, mark the provider as connected in the database
|
|
303
|
+
// This ensures the dashboard shows correct per-user connection status
|
|
304
|
+
if (data.authenticated && userId) {
|
|
305
|
+
try {
|
|
306
|
+
await db.credentials.upsert({
|
|
307
|
+
userId,
|
|
308
|
+
provider: 'codex', // Codex provider for OpenAI
|
|
309
|
+
scopes: [],
|
|
310
|
+
});
|
|
311
|
+
console.log(`[codex-helper] Marked codex as connected for user ${userId}`);
|
|
312
|
+
}
|
|
313
|
+
catch (dbError) {
|
|
314
|
+
console.error('[codex-helper] Failed to mark provider as connected:', dbError);
|
|
315
|
+
// Don't fail the request if DB update fails
|
|
316
|
+
}
|
|
317
|
+
}
|
|
298
318
|
return res.json({ authenticated: data.authenticated });
|
|
299
319
|
}
|
|
300
320
|
res.json({ authenticated: false });
|
|
301
321
|
}
|
|
302
|
-
catch (
|
|
322
|
+
catch (_error) {
|
|
303
323
|
// Workspace might not be reachable, return false
|
|
304
324
|
res.json({ authenticated: false });
|
|
305
325
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consensus API Routes (Read-Only)
|
|
3
|
+
*
|
|
4
|
+
* Provides API endpoints for observing multi-agent consensus decisions.
|
|
5
|
+
* The dashboard is read-only - agents handle all consensus activity via relay messages.
|
|
6
|
+
*
|
|
7
|
+
* Architecture:
|
|
8
|
+
* - Agents create proposals and vote via ->relay:_consensus messages
|
|
9
|
+
* - The daemon processes these and syncs state to cloud via /sync endpoint
|
|
10
|
+
* - Dashboard reads consensus state for display only
|
|
11
|
+
*/
|
|
12
|
+
export declare const consensusRouter: import("express-serve-static-core").Router;
|
|
13
|
+
//# sourceMappingURL=consensus.d.ts.map
|