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.
Files changed (106) hide show
  1. package/README.md +23 -9
  2. package/dist/bridge/spawner.js +39 -75
  3. package/dist/cli/index.d.ts +8 -6
  4. package/dist/cli/index.js +251 -30
  5. package/dist/daemon/agent-manager.js +4 -0
  6. package/dist/daemon/connection.js +17 -9
  7. package/dist/daemon/router.js +2 -2
  8. package/dist/dashboard/out/404.html +1 -0
  9. package/dist/dashboard/out/_next/static/R-uQOUcOLINtsp6ACeZa9/_buildManifest.js +1 -0
  10. package/dist/dashboard/out/_next/static/R-uQOUcOLINtsp6ACeZa9/_ssgManifest.js +1 -0
  11. package/dist/dashboard/out/_next/static/chunks/116-de2a4ac06e5000dc.js +1 -0
  12. package/dist/dashboard/out/_next/static/chunks/117-f7b8ab0809342e77.js +2 -0
  13. package/dist/dashboard/out/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
  14. package/dist/dashboard/out/_next/static/chunks/532-bace199897eeab37.js +9 -0
  15. package/dist/dashboard/out/_next/static/chunks/648-5cc6e1921389a58a.js +1 -0
  16. package/dist/dashboard/out/_next/static/chunks/766-b54f0853794b78c3.js +1 -0
  17. package/dist/dashboard/out/_next/static/chunks/83-b51836037078006c.js +1 -0
  18. package/dist/dashboard/out/_next/static/chunks/847-f1f467060f32afff.js +1 -0
  19. package/dist/dashboard/out/_next/static/chunks/891-6cd50de1224f70bb.js +1 -0
  20. package/dist/dashboard/out/_next/static/chunks/919-87d604a5d76c1fbd.js +1 -0
  21. package/dist/dashboard/out/_next/static/chunks/app/_not-found/page-53b8a69f76db17d0.js +1 -0
  22. package/dist/dashboard/out/_next/static/chunks/app/app/onboarding/page-8553743baca53a00.js +1 -0
  23. package/dist/dashboard/out/_next/static/chunks/app/app/page-7f64824ae7d06707.js +1 -0
  24. package/dist/dashboard/out/_next/static/chunks/app/cloud/link/page-3f559d393902aad2.js +1 -0
  25. package/dist/dashboard/out/_next/static/chunks/app/connect-repos/page-3538dfe0ffe984b8.js +1 -0
  26. package/dist/dashboard/out/_next/static/chunks/app/history/page-abb9ab2d329f56e9.js +1 -0
  27. package/dist/dashboard/out/_next/static/chunks/app/layout-c0d118c0f92d969c.js +1 -0
  28. package/dist/dashboard/out/_next/static/chunks/app/login/page-16d1715ddaa874ee.js +1 -0
  29. package/dist/dashboard/out/_next/static/chunks/app/metrics/page-f829604fb75a831a.js +1 -0
  30. package/dist/dashboard/out/_next/static/chunks/app/page-814efc4d77b4191d.js +1 -0
  31. package/dist/dashboard/out/_next/static/chunks/app/pricing/page-b08ed1c34d14434a.js +1 -0
  32. package/dist/dashboard/out/_next/static/chunks/app/providers/page-84322991d7244499.js +1 -0
  33. package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-05606941a8e2be83.js +1 -0
  34. package/dist/dashboard/out/_next/static/chunks/app/signup/page-68d34f50baa8ab6b.js +1 -0
  35. package/dist/dashboard/out/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
  36. package/dist/dashboard/out/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +1 -0
  37. package/dist/dashboard/out/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
  38. package/dist/dashboard/out/_next/static/chunks/main-5a40a5ae29646e1b.js +1 -0
  39. package/dist/dashboard/out/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +1 -0
  40. package/dist/dashboard/out/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
  41. package/dist/dashboard/out/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
  42. package/dist/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  43. package/dist/dashboard/out/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
  44. package/dist/dashboard/out/_next/static/css/44d2b52637b511bc.css +1 -0
  45. package/dist/dashboard/out/_next/static/css/fe4b28883eeff359.css +1 -0
  46. package/dist/dashboard/out/alt-logos/agent-relay-logo-128.png +0 -0
  47. package/dist/dashboard/out/alt-logos/agent-relay-logo-256.png +0 -0
  48. package/dist/dashboard/out/alt-logos/agent-relay-logo-32.png +0 -0
  49. package/dist/dashboard/out/alt-logos/agent-relay-logo-512.png +0 -0
  50. package/dist/dashboard/out/alt-logos/agent-relay-logo-64.png +0 -0
  51. package/dist/dashboard/out/alt-logos/agent-relay-logo.svg +45 -0
  52. package/dist/dashboard/out/alt-logos/logo.svg +38 -0
  53. package/dist/dashboard/out/alt-logos/monogram-logo-128.png +0 -0
  54. package/dist/dashboard/out/alt-logos/monogram-logo-256.png +0 -0
  55. package/dist/dashboard/out/alt-logos/monogram-logo-32.png +0 -0
  56. package/dist/dashboard/out/alt-logos/monogram-logo-512.png +0 -0
  57. package/dist/dashboard/out/alt-logos/monogram-logo-64.png +0 -0
  58. package/dist/dashboard/out/alt-logos/monogram-logo.svg +38 -0
  59. package/dist/dashboard/out/app/onboarding.html +1 -0
  60. package/dist/dashboard/out/app/onboarding.txt +7 -0
  61. package/dist/dashboard/out/app.html +1 -0
  62. package/dist/dashboard/out/app.txt +7 -0
  63. package/dist/dashboard/out/apple-icon.png +0 -0
  64. package/dist/dashboard/out/cloud/link.html +1 -0
  65. package/dist/dashboard/out/cloud/link.txt +7 -0
  66. package/dist/dashboard/out/connect-repos.html +1 -0
  67. package/dist/dashboard/out/connect-repos.txt +7 -0
  68. package/dist/dashboard/out/history.html +1 -0
  69. package/dist/dashboard/out/history.txt +7 -0
  70. package/dist/dashboard/out/index.html +1 -0
  71. package/dist/dashboard/out/index.txt +7 -0
  72. package/dist/dashboard/out/login.html +5 -0
  73. package/dist/dashboard/out/login.txt +7 -0
  74. package/dist/dashboard/out/metrics.html +1 -0
  75. package/dist/dashboard/out/metrics.txt +7 -0
  76. package/dist/dashboard/out/pricing.html +13 -0
  77. package/dist/dashboard/out/pricing.txt +7 -0
  78. package/dist/dashboard/out/providers/setup/claude.html +1 -0
  79. package/dist/dashboard/out/providers/setup/claude.txt +8 -0
  80. package/dist/dashboard/out/providers/setup/codex.html +1 -0
  81. package/dist/dashboard/out/providers/setup/codex.txt +8 -0
  82. package/dist/dashboard/out/providers.html +1 -0
  83. package/dist/dashboard/out/providers.txt +7 -0
  84. package/dist/dashboard/out/signup.html +6 -0
  85. package/dist/dashboard/out/signup.txt +7 -0
  86. package/dist/dashboard-server/metrics.d.ts +105 -0
  87. package/dist/dashboard-server/metrics.js +193 -0
  88. package/dist/dashboard-server/needs-attention.d.ts +24 -0
  89. package/dist/dashboard-server/needs-attention.js +78 -0
  90. package/dist/dashboard-server/server.d.ts +15 -0
  91. package/dist/dashboard-server/server.js +3992 -0
  92. package/dist/dashboard-server/start.d.ts +6 -0
  93. package/dist/dashboard-server/start.js +13 -0
  94. package/dist/dashboard-server/user-bridge.d.ts +103 -0
  95. package/dist/dashboard-server/user-bridge.js +189 -0
  96. package/dist/wrapper/base-wrapper.d.ts +4 -0
  97. package/dist/wrapper/base-wrapper.js +12 -4
  98. package/dist/wrapper/client.js +5 -0
  99. package/dist/wrapper/parser.js +2 -2
  100. package/dist/wrapper/pty-wrapper.d.ts +7 -1
  101. package/dist/wrapper/pty-wrapper.js +81 -5
  102. package/dist/wrapper/shared.d.ts +1 -1
  103. package/dist/wrapper/shared.js +1 -1
  104. package/dist/wrapper/tmux-wrapper.d.ts +8 -1
  105. package/dist/wrapper/tmux-wrapper.js +103 -28
  106. package/package.json +5 -3
@@ -218,11 +218,18 @@ export class TmuxWrapper extends BaseWrapper {
218
218
  if (!this.config.args || this.config.args.length === 0) {
219
219
  return this.config.command;
220
220
  }
221
- // Quote any argument that contains spaces, quotes, or special chars
221
+ // Quote any argument that contains spaces, quotes, or shell special chars
222
+ // Must handle: spaces, quotes, $, <, >, |, &, ;, (, ), `, etc.
222
223
  const quotedArgs = this.config.args.map(arg => {
223
- if (arg.includes(' ') || arg.includes('"') || arg.includes("'") || arg.includes('$')) {
224
- // Use double quotes and escape internal quotes
225
- return `"${arg.replace(/"/g, '\\"')}"`;
224
+ if (/[\s"'$<>|&;()`,!\\]/.test(arg)) {
225
+ // Use double quotes and escape internal quotes and special chars
226
+ const escaped = arg
227
+ .replace(/\\/g, '\\\\')
228
+ .replace(/"/g, '\\"')
229
+ .replace(/\$/g, '\\$')
230
+ .replace(/`/g, '\\`')
231
+ .replace(/!/g, '\\!');
232
+ return `"${escaped}"`;
226
233
  }
227
234
  return arg;
228
235
  });
@@ -330,9 +337,13 @@ export class TmuxWrapper extends BaseWrapper {
330
337
  // Wait for shell to be ready (look for prompt)
331
338
  await this.waitForShellReady();
332
339
  // Send the command to run
340
+ this.logStderr('Sending command to tmux...');
333
341
  await this.sendKeysLiteral(fullCommand);
334
- await sleep(100);
342
+ await sleep(300); // Give shell time to process the command literal
343
+ this.logStderr('Sending Enter...');
335
344
  await this.sendKeys('Enter');
345
+ await sleep(500); // Ensure Enter is processed and command starts before we continue
346
+ this.logStderr('Command sent');
336
347
  }
337
348
  catch (err) {
338
349
  throw new Error(`Failed to create tmux session: ${err.message}`);
@@ -452,6 +463,8 @@ export class TmuxWrapper extends BaseWrapper {
452
463
  async injectInstructions() {
453
464
  if (!this.running)
454
465
  return;
466
+ if (this.config.skipInstructions)
467
+ return;
455
468
  // Use escaped prefix (\->relay:) in examples to prevent parser from treating them as real commands
456
469
  const escapedPrefix = '\\' + this.relayPrefix;
457
470
  // Build instructions including relay and trail
@@ -991,20 +1004,23 @@ export class TmuxWrapper extends BaseWrapper {
991
1004
  });
992
1005
  }
993
1006
  /**
994
- * Execute spawn via API (if dashboardPort set) or callback
1007
+ * Execute spawn via API (if dashboardPort set) or callback.
1008
+ * After spawning, waits for the agent to come online and sends the task via relay.
995
1009
  */
996
1010
  async executeSpawn(name, cli, task) {
1011
+ let spawned = false;
997
1012
  if (this.config.dashboardPort) {
998
1013
  // Use dashboard API for spawning (works from any context, no terminal required)
999
1014
  try {
1000
1015
  const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/spawn`, {
1001
1016
  method: 'POST',
1002
1017
  headers: { 'Content-Type': 'application/json' },
1003
- body: JSON.stringify({ name, cli, task }),
1018
+ body: JSON.stringify({ name, cli }), // No task - we send it after agent is online
1004
1019
  });
1005
1020
  const result = await response.json();
1006
1021
  if (result.success) {
1007
1022
  this.logStderr(`Spawned ${name} via API`);
1023
+ spawned = true;
1008
1024
  }
1009
1025
  else {
1010
1026
  this.logStderr(`Spawn failed: ${result.error}`, true);
@@ -1018,11 +1034,57 @@ export class TmuxWrapper extends BaseWrapper {
1018
1034
  // Fall back to callback
1019
1035
  try {
1020
1036
  await this.config.onSpawn(name, cli, task);
1037
+ spawned = true;
1021
1038
  }
1022
1039
  catch (err) {
1023
1040
  this.logStderr(`Spawn failed: ${err.message}`, true);
1024
1041
  }
1025
1042
  }
1043
+ // If spawn succeeded and we have a task, wait for agent to come online and send it
1044
+ if (spawned && task && task.trim() && this.config.dashboardPort) {
1045
+ await this.waitAndSendTask(name, task);
1046
+ }
1047
+ }
1048
+ /**
1049
+ * Wait for a spawned agent to come online, then send the task via relay.
1050
+ * Uses the wrapper's own relay client so the message comes "from" this agent,
1051
+ * not from the dashboard's relay client.
1052
+ */
1053
+ async waitAndSendTask(agentName, task) {
1054
+ const maxWaitMs = 30000;
1055
+ const pollIntervalMs = 500;
1056
+ const startTime = Date.now();
1057
+ this.logStderr(`Waiting for ${agentName} to come online...`);
1058
+ // Poll for agent to be online using dedicated status endpoint
1059
+ while (Date.now() - startTime < maxWaitMs) {
1060
+ try {
1061
+ const response = await fetch(`http://localhost:${this.config.dashboardPort}/api/agents/${encodeURIComponent(agentName)}/online`);
1062
+ const data = await response.json();
1063
+ if (data.online) {
1064
+ this.logStderr(`${agentName} is online, sending task...`);
1065
+ // Send task directly via our relay client (not dashboard API)
1066
+ // This ensures the message comes "from" this agent, not from _DashboardUI
1067
+ if (this.client.state === 'READY') {
1068
+ const sent = this.client.sendMessage(agentName, task, 'message');
1069
+ if (sent) {
1070
+ this.logStderr(`Task sent to ${agentName}`);
1071
+ }
1072
+ else {
1073
+ this.logStderr(`Failed to send task to ${agentName}: sendMessage returned false`, true);
1074
+ }
1075
+ }
1076
+ else {
1077
+ this.logStderr(`Failed to send task to ${agentName}: relay client not ready (state: ${this.client.state})`, true);
1078
+ }
1079
+ return;
1080
+ }
1081
+ }
1082
+ catch (err) {
1083
+ // Ignore poll errors, keep trying
1084
+ }
1085
+ await sleep(pollIntervalMs);
1086
+ }
1087
+ this.logStderr(`Timeout waiting for ${agentName} to come online`, true);
1026
1088
  }
1027
1089
  /**
1028
1090
  * Execute release via API (if dashboardPort set) or callback
@@ -1069,6 +1131,10 @@ export class TmuxWrapper extends BaseWrapper {
1069
1131
  // Only process if we have API or callbacks configured
1070
1132
  const canSpawn = this.config.dashboardPort || this.config.onSpawn;
1071
1133
  const canRelease = this.config.dashboardPort || this.config.onRelease;
1134
+ // Debug: Log spawn capability status
1135
+ if (content.includes('->relay:spawn')) {
1136
+ this.logStderr(`[spawn-debug] canSpawn=${!!canSpawn} dashboardPort=${this.config.dashboardPort} hasOnSpawn=${!!this.config.onSpawn}`);
1137
+ }
1072
1138
  if (!canSpawn && !canRelease)
1073
1139
  return;
1074
1140
  const lines = content.split('\n');
@@ -1079,6 +1145,12 @@ export class TmuxWrapper extends BaseWrapper {
1079
1145
  let trimmed = line.trim();
1080
1146
  // Strip common line prefixes (bullets, prompts) before checking for commands
1081
1147
  trimmed = trimmed.replace(linePrefixPattern, '');
1148
+ // Fix for over-stripping: the linePrefixPattern includes - and > characters,
1149
+ // which can accidentally strip the -> from ->relay:spawn, leaving just relay:spawn.
1150
+ // If we detect this happened, restore the -> prefix.
1151
+ if (/^(relay|thinking|continuity):/.test(trimmed)) {
1152
+ trimmed = '->' + trimmed;
1153
+ }
1082
1154
  // If we're in fenced spawn mode, accumulate lines until we see >>>
1083
1155
  if (this.pendingFencedSpawn) {
1084
1156
  // Check for fence close (>>> at end of line or on its own line)
@@ -1236,11 +1308,6 @@ export class TmuxWrapper extends BaseWrapper {
1236
1308
  setTimeout(() => this.checkForInjectionOpportunity(), retryMs);
1237
1309
  return;
1238
1310
  }
1239
- // Log detection method in debug mode
1240
- if (this.config.debug && idleResult.signals.length > 0) {
1241
- const signalInfo = idleResult.signals.map(s => `${s.source}:${(s.confidence * 100).toFixed(0)}%`).join(', ');
1242
- this.logStderr(`Idle detected (${signalInfo})`);
1243
- }
1244
1311
  this.injectNextMessage();
1245
1312
  }
1246
1313
  /**
@@ -1252,19 +1319,16 @@ export class TmuxWrapper extends BaseWrapper {
1252
1319
  if (!msg)
1253
1320
  return;
1254
1321
  this.isInjecting = true;
1255
- this.logStderr(`Injecting message from ${msg.from} (cli: ${this.cliType})`);
1256
1322
  try {
1257
1323
  const shortId = msg.messageId.substring(0, 8);
1258
1324
  // Wait for input to be clear before injecting
1259
1325
  // If input is not clear (human typing), re-queue and try later - never clear forcefully!
1260
- // Fix for agent-relay-j9z: forceful clearing destroys human input in progress
1261
1326
  const waitTimeoutMs = this.config.inputWaitTimeoutMs ?? 5000;
1262
1327
  const waitPollMs = this.config.inputWaitPollMs ?? 200;
1263
1328
  const inputClear = await this.waitForClearInput(waitTimeoutMs, waitPollMs);
1264
1329
  if (!inputClear) {
1265
1330
  // Input still has text after timeout - DON'T clear forcefully, re-queue instead
1266
- // This preserves any human input in progress
1267
- this.logStderr('Input not clear after waiting, re-queuing injection to preserve human input');
1331
+ this.logStderr('Input not clear, re-queuing injection');
1268
1332
  this.messageQueue.unshift(msg);
1269
1333
  this.isInjecting = false;
1270
1334
  setTimeout(() => this.checkForInjectionOpportunity(), this.config.injectRetryMs ?? 1000);
@@ -1316,7 +1380,9 @@ export class TmuxWrapper extends BaseWrapper {
1316
1380
  }
1317
1381
  },
1318
1382
  performInjection: async (inj) => {
1319
- await this.pasteLiteral(inj);
1383
+ // Use send-keys -l (literal) instead of paste-buffer
1384
+ // paste-buffer causes issues where Claude shows "[Pasted text]" but content doesn't appear
1385
+ await this.sendKeysLiteral(inj);
1320
1386
  await sleep(INJECTION_CONSTANTS.ENTER_DELAY_MS);
1321
1387
  await this.sendKeys('Enter');
1322
1388
  },
@@ -1367,7 +1433,15 @@ export class TmuxWrapper extends BaseWrapper {
1367
1433
  * Send special keys to tmux
1368
1434
  */
1369
1435
  async sendKeys(keys) {
1370
- await execAsync(`"${this.tmuxPath}" send-keys -t ${this.sessionName} ${keys}`);
1436
+ const cmd = `"${this.tmuxPath}" send-keys -t ${this.sessionName} ${keys}`;
1437
+ try {
1438
+ await execAsync(cmd);
1439
+ this.logStderr(`[sendKeys] Sent: ${keys}`);
1440
+ }
1441
+ catch (err) {
1442
+ this.logStderr(`[sendKeys] Failed to send ${keys}: ${err.message}`, true);
1443
+ throw err;
1444
+ }
1371
1445
  }
1372
1446
  /**
1373
1447
  * Send literal text to tmux
@@ -1382,7 +1456,14 @@ export class TmuxWrapper extends BaseWrapper {
1382
1456
  .replace(/\$/g, '\\$')
1383
1457
  .replace(/`/g, '\\`')
1384
1458
  .replace(/!/g, '\\!');
1385
- await execAsync(`"${this.tmuxPath}" send-keys -t ${this.sessionName} -l "${escaped}"`);
1459
+ try {
1460
+ await execAsync(`"${this.tmuxPath}" send-keys -t ${this.sessionName} -l "${escaped}"`);
1461
+ this.logStderr(`[sendKeysLiteral] Sent ${text.length} chars`);
1462
+ }
1463
+ catch (err) {
1464
+ this.logStderr(`[sendKeysLiteral] Failed: ${err.message}`, true);
1465
+ throw err;
1466
+ }
1386
1467
  }
1387
1468
  /**
1388
1469
  * Paste text using tmux buffer with optional bracketed paste to avoid interleaving with ongoing output.
@@ -1398,15 +1479,9 @@ export class TmuxWrapper extends BaseWrapper {
1398
1479
  .replace(/`/g, '\\`')
1399
1480
  .replace(/!/g, '\\!');
1400
1481
  // Set tmux buffer then paste
1401
- // Skip bracketed paste (-p) for CLIs that don't handle it properly (droid, other)
1402
- await execAsync(`"${this.tmuxPath}" set-buffer -- "${escaped}"`);
1403
- const useBracketedPaste = this.cliType === 'claude' || this.cliType === 'codex' || this.cliType === 'gemini' || this.cliType === 'opencode';
1404
- if (useBracketedPaste) {
1405
- await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName} -p`);
1406
- }
1407
- else {
1408
- await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName}`);
1409
- }
1482
+ const setBufferCmd = `"${this.tmuxPath}" set-buffer -- "${escaped}"`;
1483
+ await execAsync(setBufferCmd);
1484
+ await execAsync(`"${this.tmuxPath}" paste-buffer -t ${this.sessionName}`);
1410
1485
  }
1411
1486
  /**
1412
1487
  * Reset session-specific state for wrapper reuse.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Real-time agent-to-agent communication system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,8 +13,9 @@
13
13
  },
14
14
  "scripts": {
15
15
  "postinstall": "npm rebuild better-sqlite3 && node scripts/postinstall.js",
16
- "build": "npm run clean && tsc",
17
- "postbuild": "chmod +x dist/cli/index.js",
16
+ "build": "npm run clean && tsc && npm run build:dashboard",
17
+ "build:dashboard": "cd src/dashboard && npm run build",
18
+ "postbuild": "chmod +x dist/cli/index.js && mkdir -p dist/dashboard && cp -r src/dashboard/out dist/dashboard/",
18
19
  "dev:watch": "tsc -w",
19
20
  "predev": "npm run clean && tsc && chmod +x dist/cli/index.js",
20
21
  "dev": "concurrently -n daemon,next -c blue,magenta \"npm run dev:daemon\" \"npm run dev:next\"",
@@ -26,6 +27,7 @@
26
27
  "dev:rebuild": "npm run build && echo '✓ Rebuilt (linked version updated)'",
27
28
  "start": "node dist/cli/index.js",
28
29
  "daemon": "node dist/daemon/server.js",
30
+ "dashboard": "node dist/dashboard-server/start.js",
29
31
  "pretest": "npm run build",
30
32
  "test": "vitest run",
31
33
  "test:integration": "vitest run test/cloud/*.integration.test.ts",