agent-relay 1.3.2 → 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/README.md +23 -9
- package/dist/bridge/spawner.js +39 -75
- package/dist/cli/index.d.ts +8 -6
- package/dist/cli/index.js +251 -30
- package/dist/daemon/agent-manager.js +4 -0
- package/dist/daemon/connection.js +17 -9
- package/dist/daemon/router.js +2 -2
- package/dist/dashboard/out/404.html +1 -0
- package/dist/dashboard/out/_next/static/R-uQOUcOLINtsp6ACeZa9/_buildManifest.js +1 -0
- package/dist/dashboard/out/_next/static/R-uQOUcOLINtsp6ACeZa9/_ssgManifest.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/117-f7b8ab0809342e77.js +2 -0
- package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +9 -0
- package/dist/dashboard/out/_next/static/chunks/648-5cc6e1921389a58a.js +1 -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/847-f1f467060f32afff.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-53b8a69f76db17d0.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8553743baca53a00.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/app/page-7f64824ae7d06707.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-3538dfe0ffe984b8.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/history/page-abb9ab2d329f56e9.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.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/metrics/page-f829604fb75a831a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/page-814efc4d77b4191d.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/pricing/page-b08ed1c34d14434a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/page-84322991d7244499.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-05606941a8e2be83.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/app/signup/page-68d34f50baa8ab6b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
- package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/main-5a40a5ae29646e1b.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
- package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
- package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +1 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +45 -0
- package/dist/dashboard/out/alt-logos/logo.svg +38 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
- package/dist/dashboard/out/alt-logos/monogram-logo.svg +38 -0
- package/dist/dashboard/out/app/onboarding.html +1 -0
- package/dist/dashboard/out/app/onboarding.txt +7 -0
- package/dist/dashboard/out/app.html +1 -0
- package/dist/dashboard/out/app.txt +7 -0
- package/dist/dashboard/out/apple-icon.png +0 -0
- 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 -0
- package/dist/dashboard/out/connect-repos.txt +7 -0
- package/dist/dashboard/out/history.html +1 -0
- package/dist/dashboard/out/history.txt +7 -0
- package/dist/dashboard/out/index.html +1 -0
- package/dist/dashboard/out/index.txt +7 -0
- package/dist/dashboard/out/login.html +5 -0
- package/dist/dashboard/out/login.txt +7 -0
- package/dist/dashboard/out/metrics.html +1 -0
- package/dist/dashboard/out/metrics.txt +7 -0
- package/dist/dashboard/out/pricing.html +13 -0
- package/dist/dashboard/out/pricing.txt +7 -0
- 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 -0
- package/dist/dashboard/out/providers.txt +7 -0
- package/dist/dashboard/out/signup.html +6 -0
- package/dist/dashboard/out/signup.txt +7 -0
- package/dist/dashboard-server/metrics.d.ts +105 -0
- package/dist/dashboard-server/metrics.js +193 -0
- package/dist/dashboard-server/needs-attention.d.ts +24 -0
- package/dist/dashboard-server/needs-attention.js +78 -0
- package/dist/dashboard-server/server.d.ts +15 -0
- package/dist/dashboard-server/server.js +3992 -0
- package/dist/dashboard-server/start.d.ts +6 -0
- package/dist/dashboard-server/start.js +13 -0
- package/dist/dashboard-server/user-bridge.d.ts +103 -0
- package/dist/dashboard-server/user-bridge.js +189 -0
- package/dist/wrapper/base-wrapper.d.ts +4 -0
- package/dist/wrapper/base-wrapper.js +12 -4
- package/dist/wrapper/client.js +5 -0
- package/dist/wrapper/parser.js +2 -2
- package/dist/wrapper/pty-wrapper.d.ts +7 -1
- package/dist/wrapper/pty-wrapper.js +81 -5
- package/dist/wrapper/shared.d.ts +1 -1
- package/dist/wrapper/shared.js +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +8 -1
- package/dist/wrapper/tmux-wrapper.js +103 -28
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -17,15 +17,27 @@ sudo apt-get update && sudo apt-get install -y build-essential
|
|
|
17
17
|
|
|
18
18
|
## Quick Start
|
|
19
19
|
|
|
20
|
+
```bash
|
|
21
|
+
# Start Mega coordinator with Claude (starts daemon automatically)
|
|
22
|
+
agent-relay claude
|
|
23
|
+
|
|
24
|
+
# Or with Codex
|
|
25
|
+
agent-relay codex
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The `claude` and `codex` commands start a Mega coordinator agent that can spawn and manage worker agents.
|
|
29
|
+
|
|
30
|
+
### Manual Setup
|
|
31
|
+
|
|
20
32
|
```bash
|
|
21
33
|
# Terminal 1: Start daemon
|
|
22
34
|
agent-relay up
|
|
23
35
|
|
|
24
36
|
# Terminal 2: Start an agent
|
|
25
|
-
agent-relay -n Alice claude
|
|
37
|
+
agent-relay create-agent -n Alice claude
|
|
26
38
|
|
|
27
39
|
# Terminal 3: Start another agent
|
|
28
|
-
agent-relay -n Bob codex
|
|
40
|
+
agent-relay create-agent -n Bob codex
|
|
29
41
|
```
|
|
30
42
|
|
|
31
43
|
Agents communicate by outputting `->relay:` patterns. Always use the fenced format:
|
|
@@ -42,8 +54,10 @@ Broadcasting to everyone>>>
|
|
|
42
54
|
|
|
43
55
|
| Command | Description |
|
|
44
56
|
|---------|-------------|
|
|
45
|
-
| `agent-relay
|
|
46
|
-
| `agent-relay
|
|
57
|
+
| `agent-relay claude` | Start daemon + Mega coordinator with Claude |
|
|
58
|
+
| `agent-relay codex` | Start daemon + Mega coordinator with Codex |
|
|
59
|
+
| `agent-relay create-agent <cmd>` | Wrap agent with messaging |
|
|
60
|
+
| `agent-relay create-agent -n Name <cmd>` | Wrap with specific name |
|
|
47
61
|
| `agent-relay up` | Start daemon + dashboard |
|
|
48
62
|
| `agent-relay down` | Stop daemon |
|
|
49
63
|
| `agent-relay status` | Check if running |
|
|
@@ -53,7 +67,7 @@ Broadcasting to everyone>>>
|
|
|
53
67
|
## How It Works
|
|
54
68
|
|
|
55
69
|
1. `agent-relay up` starts a daemon that routes messages via Unix socket
|
|
56
|
-
2. `agent-relay <cmd>` wraps your agent in tmux, parsing output for `->relay:` patterns
|
|
70
|
+
2. `agent-relay create-agent <cmd>` wraps your agent in tmux, parsing output for `->relay:` patterns
|
|
57
71
|
3. Messages are injected into recipient terminals in real-time
|
|
58
72
|
|
|
59
73
|
```
|
|
@@ -109,9 +123,9 @@ Agent names automatically match role definitions (case-insensitive):
|
|
|
109
123
|
|
|
110
124
|
```bash
|
|
111
125
|
# If .claude/agents/lead.md exists:
|
|
112
|
-
agent-relay -n Lead claude # matches lead.md
|
|
113
|
-
agent-relay -n LEAD claude # matches lead.md
|
|
114
|
-
agent-relay -n lead claude # matches lead.md
|
|
126
|
+
agent-relay create-agent -n Lead claude # matches lead.md
|
|
127
|
+
agent-relay create-agent -n LEAD claude # matches lead.md
|
|
128
|
+
agent-relay create-agent -n lead claude # matches lead.md
|
|
115
129
|
|
|
116
130
|
# Supported locations:
|
|
117
131
|
# - .claude/agents/<name>.md
|
|
@@ -140,7 +154,7 @@ agent-relay bridge ~/auth ~/frontend ~/api
|
|
|
140
154
|
### Workflow
|
|
141
155
|
|
|
142
156
|
1. **Start daemons** in each project: `agent-relay up`
|
|
143
|
-
2. **Start agents** in each project: `agent-relay -n Alice claude`
|
|
157
|
+
2. **Start agents** in each project: `agent-relay create-agent -n Alice claude`
|
|
144
158
|
3. **Bridge** from anywhere: `agent-relay bridge ~/project1 ~/project2`
|
|
145
159
|
|
|
146
160
|
### Cross-Project Messaging
|
package/dist/bridge/spawner.js
CHANGED
|
@@ -14,16 +14,33 @@ import { AgentPolicyService } from '../policy/agent-policy.js';
|
|
|
14
14
|
import { buildClaudeArgs } from '../utils/agent-config.js';
|
|
15
15
|
import { getUserDirectoryService } from '../daemon/user-directory.js';
|
|
16
16
|
/**
|
|
17
|
-
* Get a
|
|
18
|
-
*
|
|
19
|
-
* Loading full docs (400+ lines) overwhelms agents and causes "meandering".
|
|
17
|
+
* Get relay protocol instructions for a spawned agent.
|
|
18
|
+
* This provides the agent with the communication protocol it needs to work with the relay.
|
|
20
19
|
*/
|
|
21
|
-
function
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
function getRelayInstructions(agentName) {
|
|
21
|
+
return [
|
|
22
|
+
'# Agent Relay Protocol',
|
|
23
|
+
'',
|
|
24
|
+
`You are agent "${agentName}" connected to Agent Relay for multi-agent coordination.`,
|
|
25
|
+
'',
|
|
26
|
+
'## Sending Messages',
|
|
27
|
+
'',
|
|
28
|
+
'Use fenced format for all messages:',
|
|
29
|
+
'->relay:TargetAgent <<<',
|
|
30
|
+
'Your message here.>>>',
|
|
31
|
+
'',
|
|
32
|
+
'## Communication Rules',
|
|
33
|
+
'',
|
|
34
|
+
'1. **ACK immediately** - When you receive a task:',
|
|
35
|
+
' ->relay:Sender <<<',
|
|
36
|
+
' ACK: Brief description of task received>>>',
|
|
37
|
+
'',
|
|
38
|
+
'2. **Report completion** - When done:',
|
|
39
|
+
' ->relay:Sender <<<',
|
|
40
|
+
' DONE: Brief summary of what was completed>>>',
|
|
41
|
+
'',
|
|
42
|
+
'3. Close >>> must immediately follow content (no blank lines before it)',
|
|
43
|
+
].join('\n');
|
|
27
44
|
}
|
|
28
45
|
export class AgentSpawner {
|
|
29
46
|
activeWorkers = new Map();
|
|
@@ -209,6 +226,14 @@ export class AgentSpawner {
|
|
|
209
226
|
if (isCodexCli && !args.includes('--dangerously-bypass-approvals-and-sandbox')) {
|
|
210
227
|
args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
211
228
|
}
|
|
229
|
+
// Inject relay protocol instructions via CLI-specific system prompt
|
|
230
|
+
const relayInstructions = getRelayInstructions(name);
|
|
231
|
+
if (isClaudeCli && !args.includes('--append-system-prompt')) {
|
|
232
|
+
args.push('--append-system-prompt', relayInstructions);
|
|
233
|
+
}
|
|
234
|
+
else if (isCodexCli && !args.some(a => a.includes('developer_instructions'))) {
|
|
235
|
+
args.push('--config', `developer_instructions=${relayInstructions}`);
|
|
236
|
+
}
|
|
212
237
|
if (debug)
|
|
213
238
|
console.log(`[spawner:debug] Spawning ${name} with: ${command} ${args.join(' ')}`);
|
|
214
239
|
// Create PtyWrapper config
|
|
@@ -247,6 +272,8 @@ export class AgentSpawner {
|
|
|
247
272
|
// Shadow agent configuration
|
|
248
273
|
shadowOf: request.shadowOf,
|
|
249
274
|
shadowSpeakOn: request.shadowSpeakOn,
|
|
275
|
+
// Skip continuity for spawned agents - they're short-lived workers
|
|
276
|
+
skipContinuity: true,
|
|
250
277
|
// Only use callbacks if dashboardPort is not set (for backwards compatibility)
|
|
251
278
|
onSpawn: this.dashboardPort ? undefined : async (workerName, workerCli, workerTask) => {
|
|
252
279
|
// Handle nested spawn requests (legacy path, may fail in non-TTY)
|
|
@@ -330,72 +357,9 @@ export class AgentSpawner {
|
|
|
330
357
|
error,
|
|
331
358
|
};
|
|
332
359
|
}
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
// Only prepend relay reminder if we have an actual task
|
|
337
|
-
// Empty task = interactive mode, user will respond to prompts directly
|
|
338
|
-
if (fullMessage.trim()) {
|
|
339
|
-
// Prepend a brief relay reminder (agents have full docs via CLAUDE.md)
|
|
340
|
-
// Note: Previously loaded full 400+ line docs which overwhelmed agents
|
|
341
|
-
const relayReminder = getMinimalRelayReminder();
|
|
342
|
-
if (relayReminder) {
|
|
343
|
-
fullMessage = `${relayReminder}\n\n---\n\n${fullMessage}`;
|
|
344
|
-
if (debug)
|
|
345
|
-
console.log(`[spawner:debug] Prepended relay reminder for ${name}`);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
// Prepend policy instructions if enforcement is enabled (only if we have a task)
|
|
349
|
-
if (fullMessage.trim() && this.policyEnforcementEnabled && this.policyService) {
|
|
350
|
-
const policyInstruction = await this.policyService.getPolicyInstruction(name);
|
|
351
|
-
if (policyInstruction) {
|
|
352
|
-
fullMessage = `${policyInstruction}\n\n${fullMessage}`;
|
|
353
|
-
if (debug)
|
|
354
|
-
console.log(`[spawner:debug] Prepended policy instructions to task for ${name}`);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
// Send task via relay message if provided (not via direct PTY injection)
|
|
358
|
-
// This ensures the agent is ready to receive before processing the task
|
|
359
|
-
if (fullMessage && fullMessage.trim()) {
|
|
360
|
-
if (debug)
|
|
361
|
-
console.log(`[spawner:debug] Will send task via relay: ${fullMessage.substring(0, 50)}...`);
|
|
362
|
-
// If we have dashboard API, send task as relay message
|
|
363
|
-
if (this.dashboardPort) {
|
|
364
|
-
// Wait a moment for the agent's relay client to be ready
|
|
365
|
-
await sleep(1000);
|
|
366
|
-
try {
|
|
367
|
-
const response = await fetch(`http://localhost:${this.dashboardPort}/api/send`, {
|
|
368
|
-
method: 'POST',
|
|
369
|
-
headers: { 'Content-Type': 'application/json' },
|
|
370
|
-
body: JSON.stringify({
|
|
371
|
-
to: name,
|
|
372
|
-
message: fullMessage,
|
|
373
|
-
from: '__spawner__',
|
|
374
|
-
}),
|
|
375
|
-
});
|
|
376
|
-
const result = await response.json();
|
|
377
|
-
if (result.success) {
|
|
378
|
-
if (debug)
|
|
379
|
-
console.log(`[spawner:debug] Task sent via relay to ${name}`);
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
console.warn(`[spawner] Failed to send task via relay: ${result.error}`);
|
|
383
|
-
// Fall back to direct injection
|
|
384
|
-
pty.write(fullMessage + '\r');
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
catch (err) {
|
|
388
|
-
console.warn(`[spawner] Relay send failed, falling back to direct injection: ${err.message}`);
|
|
389
|
-
pty.write(fullMessage + '\r');
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
// No dashboard API available - use direct injection as fallback
|
|
394
|
-
if (debug)
|
|
395
|
-
console.log(`[spawner:debug] No dashboard API, using direct injection`);
|
|
396
|
-
pty.write(fullMessage + '\r');
|
|
397
|
-
}
|
|
398
|
-
}
|
|
360
|
+
// Note: Task is NOT sent here. The spawning agent (wrapper) waits for the worker
|
|
361
|
+
// to come online and then sends the task via normal relay message.
|
|
362
|
+
// This avoids race conditions with the agent's readyForMessages state.
|
|
399
363
|
// Track the worker
|
|
400
364
|
const workerInfo = {
|
|
401
365
|
name,
|
package/dist/cli/index.d.ts
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
|
export {};
|
|
14
16
|
//# sourceMappingURL=index.d.ts.map
|
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';
|
|
@@ -26,6 +28,7 @@ import { promisify } from 'node:util';
|
|
|
26
28
|
import { exec } from 'node:child_process';
|
|
27
29
|
import { fileURLToPath } from 'node:url';
|
|
28
30
|
dotenvConfig();
|
|
31
|
+
const DEFAULT_DASHBOARD_PORT = process.env.AGENT_RELAY_DASHBOARD_PORT || '3888';
|
|
29
32
|
// Read version from package.json
|
|
30
33
|
const __filename = fileURLToPath(import.meta.url);
|
|
31
34
|
const __dirname = path.dirname(__filename);
|
|
@@ -35,7 +38,7 @@ const VERSION = packageJson.version;
|
|
|
35
38
|
const execAsync = promisify(exec);
|
|
36
39
|
// Check for updates in background (non-blocking)
|
|
37
40
|
// Only show notification for interactive commands, not when wrapping agents or running update
|
|
38
|
-
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'];
|
|
39
42
|
const shouldCheckUpdates = process.argv.length > 2 &&
|
|
40
43
|
interactiveCommands.includes(process.argv[2]);
|
|
41
44
|
if (shouldCheckUpdates) {
|
|
@@ -49,20 +52,19 @@ program
|
|
|
49
52
|
.name('agent-relay')
|
|
50
53
|
.description('Agent-to-agent messaging')
|
|
51
54
|
.version(VERSION, '-V, --version', 'Output the version number');
|
|
52
|
-
//
|
|
55
|
+
// create-agent - Wrap agent with real-time messaging
|
|
53
56
|
program
|
|
57
|
+
.command('create-agent')
|
|
58
|
+
.description('Wrap an agent with real-time messaging')
|
|
54
59
|
.option('-n, --name <name>', 'Agent name (auto-generated if not set)')
|
|
55
|
-
.option('-
|
|
60
|
+
.option('-d, --debug', 'Enable debug output')
|
|
56
61
|
.option('--prefix <pattern>', 'Relay prefix pattern (default: ->relay:)')
|
|
62
|
+
.option('--dashboard-port <port>', 'Dashboard port for spawn/release API (auto-detected if not set)')
|
|
57
63
|
.option('--shadow <name>', 'Spawn a shadow agent with this name that monitors the primary')
|
|
58
64
|
.option('--shadow-role <role>', 'Shadow role: reviewer, auditor, or triggers (comma-separated: SESSION_END,CODE_WRITTEN,REVIEW_REQUEST,EXPLICIT_ASK,ALL_MESSAGES)')
|
|
59
|
-
.
|
|
65
|
+
.option('--skip-instructions', 'Skip initial instruction injection (use with --append-system-prompt)')
|
|
66
|
+
.argument('<command...>', 'Command to wrap (e.g., claude)')
|
|
60
67
|
.action(async (commandParts, options) => {
|
|
61
|
-
// If no command provided, show help
|
|
62
|
-
if (!commandParts || commandParts.length === 0) {
|
|
63
|
-
program.help();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
68
|
const { getProjectPaths } = await import('../utils/project-namespace.js');
|
|
67
69
|
const { findAgentConfig, isClaudeCli, buildClaudeArgs } = await import('../utils/agent-config.js');
|
|
68
70
|
const paths = getProjectPaths();
|
|
@@ -84,18 +86,53 @@ program
|
|
|
84
86
|
}
|
|
85
87
|
const { TmuxWrapper } = await import('../wrapper/tmux-wrapper.js');
|
|
86
88
|
const { AgentSpawner } = await import('../bridge/spawner.js');
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
+
// Determine dashboard port for spawn/release API
|
|
90
|
+
// Priority: CLI flag > env var > auto-detect default port
|
|
91
|
+
let dashboardPort;
|
|
92
|
+
if (options.dashboardPort) {
|
|
93
|
+
dashboardPort = parseInt(options.dashboardPort, 10);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
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
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Create spawner as fallback for direct spawn (if dashboard API not available)
|
|
122
|
+
const spawner = new AgentSpawner(paths.projectRoot, undefined, dashboardPort);
|
|
89
123
|
const wrapper = new TmuxWrapper({
|
|
90
124
|
name: agentName,
|
|
91
125
|
command: mainCommand,
|
|
92
126
|
args: finalArgs,
|
|
93
127
|
socketPath: paths.socketPath,
|
|
94
|
-
debug:
|
|
128
|
+
debug: options.debug ?? false,
|
|
95
129
|
relayPrefix: options.prefix,
|
|
96
130
|
useInbox: true,
|
|
97
131
|
inboxDir: paths.dataDir, // Use the project-specific data directory for the inbox
|
|
98
|
-
|
|
132
|
+
skipInstructions: options.skipInstructions,
|
|
133
|
+
// Use dashboard API for spawn/release when available (preferred - works from any context)
|
|
134
|
+
dashboardPort,
|
|
135
|
+
// Wire up spawn/release callbacks as fallback (if no dashboardPort)
|
|
99
136
|
onSpawn: async (workerName, workerCli, task) => {
|
|
100
137
|
console.error(`[${agentName}] Spawning ${workerName} (${workerCli})...`);
|
|
101
138
|
const result = await spawner.spawn({
|
|
@@ -199,10 +236,12 @@ program
|
|
|
199
236
|
}
|
|
200
237
|
}
|
|
201
238
|
});
|
|
202
|
-
// up - Start daemon
|
|
239
|
+
// up - Start daemon + dashboard
|
|
203
240
|
program
|
|
204
241
|
.command('up')
|
|
205
|
-
.description('Start daemon')
|
|
242
|
+
.description('Start daemon + dashboard')
|
|
243
|
+
.option('--no-dashboard', 'Disable web dashboard')
|
|
244
|
+
.option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
|
|
206
245
|
.option('--spawn', 'Force spawn all agents from teams.json')
|
|
207
246
|
.option('--no-spawn', 'Do not auto-spawn agents (just start daemon)')
|
|
208
247
|
.option('--watch', 'Auto-restart daemon on crash (supervisor mode)')
|
|
@@ -219,6 +258,10 @@ program
|
|
|
219
258
|
const startDaemon = () => {
|
|
220
259
|
// Build args without --watch to prevent infinite recursion
|
|
221
260
|
const args = ['up'];
|
|
261
|
+
if (options.dashboard === false)
|
|
262
|
+
args.push('--no-dashboard');
|
|
263
|
+
if (options.port)
|
|
264
|
+
args.push('--port', options.port);
|
|
222
265
|
if (options.spawn === true)
|
|
223
266
|
args.push('--spawn');
|
|
224
267
|
if (options.spawn === false)
|
|
@@ -330,6 +373,28 @@ program
|
|
|
330
373
|
try {
|
|
331
374
|
await daemon.start();
|
|
332
375
|
console.log('Daemon started.');
|
|
376
|
+
let dashboardPort;
|
|
377
|
+
// Dashboard starts by default (use --no-dashboard to disable)
|
|
378
|
+
if (options.dashboard !== false) {
|
|
379
|
+
const port = parseInt(options.port, 10);
|
|
380
|
+
const { startDashboard } = await import('../dashboard-server/server.js');
|
|
381
|
+
dashboardPort = await startDashboard({
|
|
382
|
+
port,
|
|
383
|
+
dataDir: paths.dataDir,
|
|
384
|
+
teamDir: paths.teamDir,
|
|
385
|
+
dbPath,
|
|
386
|
+
enableSpawner: true,
|
|
387
|
+
projectRoot: paths.projectRoot,
|
|
388
|
+
});
|
|
389
|
+
console.log(`Dashboard: http://localhost:${dashboardPort}`);
|
|
390
|
+
// Hook daemon log output to dashboard WebSocket
|
|
391
|
+
daemon.onLogOutput = (agentName, data, _timestamp) => {
|
|
392
|
+
const broadcast = global.__broadcastLogOutput;
|
|
393
|
+
if (broadcast) {
|
|
394
|
+
broadcast(agentName, data);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
333
398
|
// Determine if we should auto-spawn agents
|
|
334
399
|
// --spawn: force spawn
|
|
335
400
|
// --no-spawn: never spawn
|
|
@@ -342,7 +407,7 @@ program
|
|
|
342
407
|
if (shouldSpawn && teamsConfig && teamsConfig.agents.length > 0) {
|
|
343
408
|
console.log('');
|
|
344
409
|
console.log('Auto-spawning agents from teams.json...');
|
|
345
|
-
spawner = new AgentSpawner(paths.projectRoot);
|
|
410
|
+
spawner = new AgentSpawner(paths.projectRoot, undefined, dashboardPort);
|
|
346
411
|
for (const agent of teamsConfig.agents) {
|
|
347
412
|
console.log(` Spawning ${agent.name} (${agent.cli})...`);
|
|
348
413
|
const result = await spawner.spawn({
|
|
@@ -393,6 +458,162 @@ program
|
|
|
393
458
|
console.log('Cleaned up stale pid');
|
|
394
459
|
}
|
|
395
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
|
+
});
|
|
396
617
|
// status - Check daemon status
|
|
397
618
|
program
|
|
398
619
|
.command('status')
|
|
@@ -1293,7 +1514,7 @@ program
|
|
|
1293
1514
|
.argument('<name>', 'Agent name')
|
|
1294
1515
|
.argument('<cli>', 'CLI to use (claude, codex, gemini, etc.)')
|
|
1295
1516
|
.argument('[task]', 'Task description (can also be piped via stdin)')
|
|
1296
|
-
.option('--port <port>', 'Dashboard port',
|
|
1517
|
+
.option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
|
|
1297
1518
|
.option('--team <team>', 'Team name for the agent')
|
|
1298
1519
|
.option('--spawner <name>', 'Name of the agent requesting the spawn (for policy enforcement)')
|
|
1299
1520
|
.option('--interactive', 'Disable auto-accept of permission prompts (for auth setup flows)')
|
|
@@ -1304,7 +1525,7 @@ program
|
|
|
1304
1525
|
.option('--shadow-triggers <triggers>', 'When to trigger shadow (comma-separated: SESSION_END,CODE_WRITTEN,REVIEW_REQUEST,EXPLICIT_ASK,ALL_MESSAGES)')
|
|
1305
1526
|
.option('--shadow-speak-on <triggers>', 'When shadow should speak (comma-separated, same values as --shadow-triggers)')
|
|
1306
1527
|
.action(async (name, cli, task, options) => {
|
|
1307
|
-
const port = options.port ||
|
|
1528
|
+
const port = options.port || DEFAULT_DASHBOARD_PORT;
|
|
1308
1529
|
// Read task from stdin if not provided as argument
|
|
1309
1530
|
let finalTask = task;
|
|
1310
1531
|
if (!finalTask && !process.stdin.isTTY) {
|
|
@@ -1390,9 +1611,9 @@ program
|
|
|
1390
1611
|
.command('release')
|
|
1391
1612
|
.description('Release a spawned agent via API (no terminal required)')
|
|
1392
1613
|
.argument('<name>', 'Agent name to release')
|
|
1393
|
-
.option('--port <port>', 'Dashboard port',
|
|
1614
|
+
.option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
|
|
1394
1615
|
.action(async (name, options) => {
|
|
1395
|
-
const port = options.port ||
|
|
1616
|
+
const port = options.port || DEFAULT_DASHBOARD_PORT;
|
|
1396
1617
|
try {
|
|
1397
1618
|
const response = await fetch(`http://localhost:${port}/api/spawned/${encodeURIComponent(name)}`, {
|
|
1398
1619
|
method: 'DELETE',
|
|
@@ -1960,12 +2181,12 @@ program
|
|
|
1960
2181
|
.command('metrics')
|
|
1961
2182
|
.description('Show agent memory metrics and resource usage')
|
|
1962
2183
|
.option('--agent <name>', 'Show metrics for specific agent')
|
|
1963
|
-
.option('--port <port>', 'Dashboard port',
|
|
2184
|
+
.option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
|
|
1964
2185
|
.option('--json', 'Output as JSON')
|
|
1965
2186
|
.option('--watch', 'Continuously update metrics')
|
|
1966
2187
|
.option('--interval <ms>', 'Update interval for watch mode', '5000')
|
|
1967
2188
|
.action(async (options) => {
|
|
1968
|
-
const port = options.port ||
|
|
2189
|
+
const port = options.port || DEFAULT_DASHBOARD_PORT;
|
|
1969
2190
|
const fetchMetrics = async () => {
|
|
1970
2191
|
try {
|
|
1971
2192
|
const response = await fetch(`http://localhost:${port}/api/metrics/agents`);
|
|
@@ -2079,12 +2300,12 @@ program
|
|
|
2079
2300
|
program
|
|
2080
2301
|
.command('health')
|
|
2081
2302
|
.description('Show system health, crash insights, and recommendations')
|
|
2082
|
-
.option('--port <port>', 'Dashboard port',
|
|
2303
|
+
.option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
|
|
2083
2304
|
.option('--json', 'Output as JSON')
|
|
2084
2305
|
.option('--crashes', 'Show recent crash history')
|
|
2085
2306
|
.option('--alerts', 'Show unacknowledged alerts')
|
|
2086
2307
|
.action(async (options) => {
|
|
2087
|
-
const port = options.port ||
|
|
2308
|
+
const port = options.port || DEFAULT_DASHBOARD_PORT;
|
|
2088
2309
|
try {
|
|
2089
2310
|
const response = await fetch(`http://localhost:${port}/api/metrics/health`);
|
|
2090
2311
|
if (!response.ok) {
|
|
@@ -97,6 +97,8 @@ export class AgentManager extends EventEmitter {
|
|
|
97
97
|
args,
|
|
98
98
|
cwd: workspacePath,
|
|
99
99
|
logsDir: this.logsDir,
|
|
100
|
+
// Skip continuity for cloud-managed agents
|
|
101
|
+
skipContinuity: true,
|
|
100
102
|
env: {
|
|
101
103
|
CLOUD_API_URL: process.env.CLOUD_API_URL || '',
|
|
102
104
|
WORKSPACE_TOKEN: process.env.WORKSPACE_TOKEN || '',
|
|
@@ -341,6 +343,8 @@ export class AgentManager extends EventEmitter {
|
|
|
341
343
|
args,
|
|
342
344
|
cwd: workspacePath,
|
|
343
345
|
logsDir: this.logsDir,
|
|
346
|
+
// Skip continuity for cloud-managed agents
|
|
347
|
+
skipContinuity: true,
|
|
344
348
|
env: {
|
|
345
349
|
CLOUD_API_URL: process.env.CLOUD_API_URL || '',
|
|
346
350
|
WORKSPACE_TOKEN: process.env.WORKSPACE_TOKEN || '',
|