agent-relay 2.0.12 → 2.0.14

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 (143) hide show
  1. package/bin/relay-pty-darwin-arm64 +0 -0
  2. package/bin/relay-pty-darwin-x64 +0 -0
  3. package/bin/relay-pty-linux-x64 +0 -0
  4. package/deploy/workspace/codex.config.toml +5 -0
  5. package/deploy/workspace/entrypoint.sh +10 -2
  6. package/dist/dashboard/out/404.html +1 -1
  7. package/dist/dashboard/out/app/onboarding.html +1 -1
  8. package/dist/dashboard/out/app/onboarding.txt +1 -1
  9. package/dist/dashboard/out/app.html +1 -1
  10. package/dist/dashboard/out/app.txt +1 -1
  11. package/dist/dashboard/out/cloud/link.html +1 -1
  12. package/dist/dashboard/out/cloud/link.txt +1 -1
  13. package/dist/dashboard/out/connect-repos.html +1 -1
  14. package/dist/dashboard/out/connect-repos.txt +1 -1
  15. package/dist/dashboard/out/history.html +1 -1
  16. package/dist/dashboard/out/history.txt +1 -1
  17. package/dist/dashboard/out/index.html +1 -1
  18. package/dist/dashboard/out/index.txt +1 -1
  19. package/dist/dashboard/out/login.html +1 -1
  20. package/dist/dashboard/out/login.txt +1 -1
  21. package/dist/dashboard/out/metrics.html +1 -1
  22. package/dist/dashboard/out/metrics.txt +1 -1
  23. package/dist/dashboard/out/pricing.html +1 -1
  24. package/dist/dashboard/out/pricing.txt +1 -1
  25. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  26. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  27. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  28. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  29. package/dist/dashboard/out/providers/setup/cursor.html +1 -1
  30. package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
  31. package/dist/dashboard/out/providers.html +1 -1
  32. package/dist/dashboard/out/providers.txt +1 -1
  33. package/dist/dashboard/out/signup.html +1 -1
  34. package/dist/dashboard/out/signup.txt +1 -1
  35. package/dist/src/cli/index.js +131 -21
  36. package/package.json +20 -19
  37. package/packages/api-types/package.json +1 -1
  38. package/packages/bridge/dist/index.d.ts +1 -1
  39. package/packages/bridge/dist/index.js +1 -1
  40. package/packages/bridge/dist/spawner.d.ts +18 -0
  41. package/packages/bridge/dist/spawner.js +144 -39
  42. package/packages/bridge/package.json +8 -7
  43. package/packages/cloud/package.json +6 -6
  44. package/packages/config/package.json +2 -2
  45. package/packages/continuity/package.json +1 -1
  46. package/packages/daemon/dist/connection.js +5 -1
  47. package/packages/daemon/dist/relay-ledger.d.ts +3 -1
  48. package/packages/daemon/dist/relay-ledger.js +8 -2
  49. package/packages/daemon/dist/router.js +13 -0
  50. package/packages/daemon/dist/server.d.ts +7 -0
  51. package/packages/daemon/dist/server.js +338 -4
  52. package/packages/daemon/package.json +12 -12
  53. package/packages/dashboard/dist/server.js +29 -5
  54. package/packages/dashboard/package.json +13 -12
  55. package/packages/dashboard/ui-dist/404.html +1 -1
  56. package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
  57. package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
  58. package/packages/dashboard/ui-dist/app.html +1 -1
  59. package/packages/dashboard/ui-dist/app.txt +1 -1
  60. package/packages/dashboard/ui-dist/cloud/link.html +1 -1
  61. package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
  62. package/packages/dashboard/ui-dist/connect-repos.html +1 -1
  63. package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
  64. package/packages/dashboard/ui-dist/history.html +1 -1
  65. package/packages/dashboard/ui-dist/history.txt +1 -1
  66. package/packages/dashboard/ui-dist/index.html +1 -1
  67. package/packages/dashboard/ui-dist/index.txt +1 -1
  68. package/packages/dashboard/ui-dist/login.html +1 -1
  69. package/packages/dashboard/ui-dist/login.txt +1 -1
  70. package/packages/dashboard/ui-dist/metrics.html +1 -1
  71. package/packages/dashboard/ui-dist/metrics.txt +1 -1
  72. package/packages/dashboard/ui-dist/pricing.html +1 -1
  73. package/packages/dashboard/ui-dist/pricing.txt +1 -1
  74. package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
  75. package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
  76. package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
  77. package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
  78. package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
  79. package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
  80. package/packages/dashboard/ui-dist/providers.html +1 -1
  81. package/packages/dashboard/ui-dist/providers.txt +1 -1
  82. package/packages/dashboard/ui-dist/signup.html +1 -1
  83. package/packages/dashboard/ui-dist/signup.txt +1 -1
  84. package/packages/dashboard-server/dist/server.js +29 -5
  85. package/packages/dashboard-server/package.json +12 -12
  86. package/packages/hooks/package.json +4 -4
  87. package/packages/mcp/README.md +24 -3
  88. package/packages/mcp/dist/bin.js +13 -5
  89. package/packages/mcp/dist/client.d.ts +54 -1
  90. package/packages/mcp/dist/client.js +132 -18
  91. package/packages/mcp/dist/cloud.d.ts +12 -0
  92. package/packages/mcp/dist/cloud.js +125 -1
  93. package/packages/mcp/dist/file-transport.d.ts +97 -0
  94. package/packages/mcp/dist/file-transport.js +197 -0
  95. package/packages/mcp/dist/hybrid-client.d.ts +28 -0
  96. package/packages/mcp/dist/hybrid-client.js +159 -0
  97. package/packages/mcp/dist/index.d.ts +4 -2
  98. package/packages/mcp/dist/index.js +6 -2
  99. package/packages/mcp/dist/install.d.ts +23 -1
  100. package/packages/mcp/dist/install.js +229 -31
  101. package/packages/mcp/dist/server.js +7 -1
  102. package/packages/mcp/dist/simple.d.ts +1 -1
  103. package/packages/mcp/dist/tools/index.d.ts +1 -0
  104. package/packages/mcp/dist/tools/index.js +1 -0
  105. package/packages/mcp/dist/tools/relay-continuity.d.ts +35 -0
  106. package/packages/mcp/dist/tools/relay-continuity.js +101 -0
  107. package/packages/mcp/dist/tools/relay-health.d.ts +1 -4
  108. package/packages/mcp/dist/tools/relay-health.js +7 -15
  109. package/packages/mcp/dist/tools/relay-logs.js +4 -2
  110. package/packages/mcp/dist/tools/relay-metrics.d.ts +1 -4
  111. package/packages/mcp/dist/tools/relay-metrics.js +4 -15
  112. package/packages/mcp/dist/tools/relay-send.d.ts +2 -2
  113. package/packages/mcp/package.json +3 -2
  114. package/packages/memory/package.json +2 -2
  115. package/packages/policy/package.json +2 -2
  116. package/packages/protocol/dist/relay-pty-schemas.d.ts +14 -0
  117. package/packages/protocol/dist/types.d.ts +152 -2
  118. package/packages/protocol/package.json +1 -1
  119. package/packages/resiliency/package.json +1 -1
  120. package/packages/sdk/dist/client.js +7 -0
  121. package/packages/sdk/package.json +2 -2
  122. package/packages/spawner/package.json +1 -1
  123. package/packages/state/package.json +1 -1
  124. package/packages/storage/package.json +2 -2
  125. package/packages/telemetry/package.json +1 -1
  126. package/packages/trajectory/package.json +2 -2
  127. package/packages/user-directory/package.json +2 -2
  128. package/packages/utils/dist/logger.js +3 -1
  129. package/packages/utils/package.json +1 -1
  130. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +28 -1
  131. package/packages/wrapper/dist/relay-pty-orchestrator.js +358 -43
  132. package/packages/wrapper/package.json +6 -6
  133. package/scripts/demos/README.md +79 -0
  134. package/scripts/demos/server-capacity.sh +69 -0
  135. package/scripts/demos/sprint-planning.sh +73 -0
  136. /package/dist/dashboard/out/_next/static/{h1U3qU5XIfQSy46M_SDsz → RgEj_9Y-mWbLaxggzni-X}/_buildManifest.js +0 -0
  137. /package/dist/dashboard/out/_next/static/{h1U3qU5XIfQSy46M_SDsz → RgEj_9Y-mWbLaxggzni-X}/_ssgManifest.js +0 -0
  138. /package/packages/dashboard/ui-dist/_next/static/{4WryIM4xHT22BbJ46YITr → RgEj_9Y-mWbLaxggzni-X}/_buildManifest.js +0 -0
  139. /package/packages/dashboard/ui-dist/_next/static/{4WryIM4xHT22BbJ46YITr → RgEj_9Y-mWbLaxggzni-X}/_ssgManifest.js +0 -0
  140. /package/packages/dashboard/ui-dist/_next/static/{dS0EgrS-iG-_pkUVhBypz → UkLmDJOkaPWU2PaNQnkx5}/_buildManifest.js +0 -0
  141. /package/packages/dashboard/ui-dist/_next/static/{dS0EgrS-iG-_pkUVhBypz → UkLmDJOkaPWU2PaNQnkx5}/_ssgManifest.js +0 -0
  142. /package/packages/dashboard/ui-dist/_next/static/{h1U3qU5XIfQSy46M_SDsz → bv9xidgU2pXi7xxPoCAK-}/_buildManifest.js +0 -0
  143. /package/packages/dashboard/ui-dist/_next/static/{h1U3qU5XIfQSy46M_SDsz → bv9xidgU2pXi7xxPoCAK-}/_ssgManifest.js +0 -0
@@ -14,6 +14,8 @@
14
14
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
15
  import { dirname, join } from 'node:path';
16
16
  import { homedir, platform } from 'node:os';
17
+ import { execSync } from 'node:child_process';
18
+ import * as TOML from 'smol-toml';
17
19
  /**
18
20
  * Get platform-specific config paths
19
21
  */
@@ -39,7 +41,7 @@ function getConfigPaths() {
39
41
  },
40
42
  'claude-code': {
41
43
  name: 'Claude Code',
42
- configPath: join(home, '.claude.json'),
44
+ configPath: join(home, '.claude', 'settings.json'),
43
45
  configKey: 'mcpServers',
44
46
  format: 'json',
45
47
  supportsLocal: true,
@@ -92,6 +94,13 @@ function getConfigPaths() {
92
94
  format: 'json',
93
95
  supportsLocal: true,
94
96
  },
97
+ codex: {
98
+ name: 'Codex',
99
+ configPath: join(home, '.codex', 'config.toml'),
100
+ configKey: 'mcp_servers', // TOML uses [mcp_servers.agent-relay] tables
101
+ format: 'toml',
102
+ supportsLocal: true,
103
+ },
95
104
  };
96
105
  }
97
106
  /**
@@ -103,6 +112,86 @@ export function getDefaultServerConfig() {
103
112
  args: ['@agent-relay/mcp', 'serve'],
104
113
  };
105
114
  }
115
+ /**
116
+ * Check if node is installed via nvm (Node Version Manager).
117
+ * GUI apps (Claude, Cursor, VS Code) can't use nvm's shell function,
118
+ * so we need to use absolute paths for nvm installations.
119
+ */
120
+ function isUsingNvm() {
121
+ try {
122
+ const nodePath = execSync('which node', { encoding: 'utf-8' }).trim();
123
+ return nodePath.includes('.nvm');
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ }
129
+ /**
130
+ * Get absolute path to node binary
131
+ */
132
+ function getNodePath() {
133
+ try {
134
+ return execSync('which node', { encoding: 'utf-8' }).trim();
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ }
140
+ /**
141
+ * Get path to globally installed @agent-relay/mcp bin.js
142
+ * Returns null if not installed globally.
143
+ */
144
+ function getGlobalMcpBinPath() {
145
+ try {
146
+ // Get npm global prefix
147
+ const npmPrefix = execSync('npm prefix -g', { encoding: 'utf-8' }).trim();
148
+ const binPath = join(npmPrefix, 'lib', 'node_modules', '@agent-relay', 'mcp', 'dist', 'bin.js');
149
+ if (existsSync(binPath)) {
150
+ return binPath;
151
+ }
152
+ }
153
+ catch {
154
+ // npm not available or failed
155
+ }
156
+ return null;
157
+ }
158
+ /**
159
+ * Build MCP server configuration with proper paths.
160
+ *
161
+ * For nvm users: Uses absolute paths (recommended by MCP community)
162
+ * For others: Uses npx (works when node is in standard PATH)
163
+ *
164
+ * See: https://github.com/modelcontextprotocol/servers/issues/64
165
+ */
166
+ function buildServerConfig() {
167
+ // If using nvm, we need absolute paths because GUI apps can't access nvm's shell function
168
+ if (isUsingNvm()) {
169
+ const nodePath = getNodePath();
170
+ const mcpBinPath = getGlobalMcpBinPath();
171
+ if (nodePath && mcpBinPath) {
172
+ // Best option: globally installed package with absolute paths
173
+ return {
174
+ command: nodePath,
175
+ args: [mcpBinPath, 'serve'],
176
+ };
177
+ }
178
+ // Package not installed globally - still try with absolute node path + npx
179
+ if (nodePath) {
180
+ const npxPath = join(dirname(nodePath), 'npx');
181
+ if (existsSync(npxPath)) {
182
+ return {
183
+ command: npxPath,
184
+ args: ['@agent-relay/mcp', 'serve'],
185
+ };
186
+ }
187
+ }
188
+ }
189
+ // Standard case: npx should work
190
+ return {
191
+ command: 'npx',
192
+ args: ['@agent-relay/mcp', 'serve'],
193
+ };
194
+ }
106
195
  /**
107
196
  * Detect which editors are installed by checking for their config directories
108
197
  */
@@ -145,37 +234,45 @@ function stripJsonComments(content) {
145
234
  return result;
146
235
  }
147
236
  /**
148
- * Read and parse config file, handling both JSON and JSONC
237
+ * Read and parse config file, handling JSON, JSONC, and TOML
149
238
  */
150
239
  function readConfigFile(configPath, format) {
151
240
  if (!existsSync(configPath)) {
152
241
  return {};
153
242
  }
154
243
  const content = readFileSync(configPath, 'utf-8');
155
- const jsonContent = format === 'jsonc' ? stripJsonComments(content) : content;
156
244
  try {
157
245
  // Handle empty or whitespace-only files
158
- const trimmed = jsonContent.trim();
246
+ const trimmed = content.trim();
159
247
  if (!trimmed) {
160
248
  return {};
161
249
  }
162
- return JSON.parse(trimmed);
250
+ if (format === 'toml') {
251
+ return TOML.parse(trimmed);
252
+ }
253
+ const jsonContent = format === 'jsonc' ? stripJsonComments(content) : content;
254
+ return JSON.parse(jsonContent.trim());
163
255
  }
164
256
  catch {
165
- // Invalid JSON, start fresh
257
+ // Invalid config, start fresh
166
258
  return {};
167
259
  }
168
260
  }
169
261
  /**
170
262
  * Write config file with proper formatting
171
263
  */
172
- function writeConfigFile(configPath, config) {
264
+ function writeConfigFile(configPath, config, format = 'json') {
173
265
  const configDir = dirname(configPath);
174
266
  // Ensure directory exists
175
267
  if (!existsSync(configDir)) {
176
268
  mkdirSync(configDir, { recursive: true });
177
269
  }
178
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
270
+ if (format === 'toml') {
271
+ writeFileSync(configPath, TOML.stringify(config) + '\n');
272
+ }
273
+ else {
274
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
275
+ }
179
276
  }
180
277
  /**
181
278
  * Get the config path for local (project-specific) installation
@@ -200,6 +297,8 @@ function getLocalConfigPath(editor, projectDir) {
200
297
  return join(projectDir, 'opencode.json');
201
298
  case 'Droid':
202
299
  return join(projectDir, '.factory', 'mcp.json');
300
+ case 'Codex':
301
+ return join(projectDir, 'codex.toml');
203
302
  default:
204
303
  return null;
205
304
  }
@@ -209,7 +308,8 @@ function getLocalConfigPath(editor, projectDir) {
209
308
  */
210
309
  export function installForEditor(editorKey, options = {}) {
211
310
  const editor = getEditorConfig(editorKey);
212
- if (!editor) {
311
+ // Allow custom configPath even without known editor
312
+ if (!editor && !options.configPath) {
213
313
  return {
214
314
  editor: editorKey,
215
315
  configPath: '',
@@ -218,25 +318,63 @@ export function installForEditor(editorKey, options = {}) {
218
318
  created: false,
219
319
  };
220
320
  }
221
- // Determine config path (global vs local)
222
- let configPath = editor.configPath;
223
- if (!options.global && options.projectDir && editor.supportsLocal) {
224
- const localPath = getLocalConfigPath(editor, options.projectDir);
225
- if (localPath) {
226
- configPath = localPath;
321
+ // Determine config path, format, and key
322
+ // Custom configPath overrides everything (for programmatic installation)
323
+ let configPath;
324
+ let configFormat;
325
+ let configKey;
326
+ if (options.configPath) {
327
+ // Use custom path - for programmatic/auto installation
328
+ configPath = options.configPath;
329
+ configFormat = options.configFormat || 'json';
330
+ configKey = options.configKey || 'mcpServers';
331
+ }
332
+ else if (editor) {
333
+ // Use editor's config
334
+ configPath = editor.configPath;
335
+ configFormat = editor.format;
336
+ configKey = editor.configKey;
337
+ // Check for local path override
338
+ if (!options.global && options.projectDir && editor.supportsLocal) {
339
+ const localPath = getLocalConfigPath(editor, options.projectDir);
340
+ if (localPath) {
341
+ configPath = localPath;
342
+ }
227
343
  }
228
344
  }
229
- // Build server config
345
+ else {
346
+ // Should not reach here due to early return above
347
+ return {
348
+ editor: editorKey,
349
+ configPath: '',
350
+ success: false,
351
+ error: `Unknown editor: ${editorKey}`,
352
+ created: false,
353
+ };
354
+ }
355
+ // Build server config - handles nvm users with absolute paths
356
+ const defaultConfig = buildServerConfig();
230
357
  const serverConfig = {
231
- command: options.command || 'npx',
232
- args: options.args || ['@agent-relay/mcp', 'serve'],
358
+ command: options.command || defaultConfig.command,
359
+ args: options.args || defaultConfig.args,
233
360
  };
234
- // Note: We don't set RELAY_PROJECT for local installs because the MCP server
235
- // will auto-discover the socket from the current working directory.
236
- // This makes the config portable across machines.
361
+ // Set environment variables if provided (e.g., RELAY_SOCKET for project-local installs)
362
+ if (options.env) {
363
+ serverConfig.env = { ...options.env };
364
+ }
365
+ // For project-local installs with projectDir, also set RELAY_SOCKET if not already set
366
+ const isProjectLocal = !options.global && options.projectDir;
367
+ if (isProjectLocal && !serverConfig.env?.RELAY_SOCKET) {
368
+ const socketPath = join(options.projectDir, '.agent-relay', 'relay.sock');
369
+ serverConfig.env = {
370
+ ...serverConfig.env,
371
+ RELAY_SOCKET: socketPath,
372
+ };
373
+ }
237
374
  if (options.dryRun) {
375
+ const editorName = editor?.name || editorKey || 'custom';
238
376
  return {
239
- editor: editor.name,
377
+ editor: editorName,
240
378
  configPath,
241
379
  success: true,
242
380
  created: !existsSync(configPath),
@@ -244,28 +382,71 @@ export function installForEditor(editorKey, options = {}) {
244
382
  }
245
383
  try {
246
384
  // Read existing config
247
- const config = readConfigFile(configPath, editor.format);
385
+ const config = readConfigFile(configPath, configFormat);
248
386
  const created = !existsSync(configPath);
249
387
  // Initialize mcpServers if not present
250
- const configKeyValue = config[editor.configKey];
388
+ const configKeyValue = config[configKey];
251
389
  if (!configKeyValue || typeof configKeyValue !== 'object') {
252
- config[editor.configKey] = {};
390
+ config[configKey] = {};
253
391
  }
254
392
  // Add agent-relay server config
255
- const mcpServers = config[editor.configKey];
256
- mcpServers['agent-relay'] = serverConfig;
393
+ const mcpServers = config[configKey];
394
+ // OpenCode uses a different format: { type: "local", command: [...], environment: {...} }
395
+ if (editor?.name === 'OpenCode') {
396
+ const openCodeConfig = {
397
+ type: 'local',
398
+ command: [serverConfig.command, ...serverConfig.args],
399
+ };
400
+ if (serverConfig.env) {
401
+ openCodeConfig.environment = serverConfig.env;
402
+ }
403
+ mcpServers['agent-relay'] = openCodeConfig;
404
+ }
405
+ else {
406
+ mcpServers['agent-relay'] = serverConfig;
407
+ }
408
+ // For Claude Code, also add permissions to auto-approve agent-relay tools
409
+ // Permissions go in settings.json, not .mcp.json
410
+ if (editor?.name === 'Claude Code') {
411
+ // Determine settings path based on where MCP config is being written
412
+ const projectDir = configPath.endsWith('.mcp.json')
413
+ ? dirname(configPath) // project-local: same dir as .mcp.json
414
+ : dirname(configPath); // global: ~/.claude/
415
+ const settingsPath = configPath.endsWith('.mcp.json')
416
+ ? join(projectDir, '.claude', 'settings.json') // project-local
417
+ : configPath; // global uses same file (settings.json)
418
+ // Read existing settings
419
+ const settings = configPath.endsWith('.mcp.json')
420
+ ? readConfigFile(settingsPath, 'json') // separate file for project-local
421
+ : config; // same config object for global
422
+ const permissions = settings.permissions || {};
423
+ const allowList = permissions.allow || [];
424
+ // Add agent-relay permission if not already present
425
+ if (!allowList.includes('mcp__agent-relay__*')) {
426
+ allowList.push('mcp__agent-relay__*');
427
+ }
428
+ permissions.allow = allowList;
429
+ settings.permissions = permissions;
430
+ // Write settings (separate file for project-local)
431
+ if (configPath.endsWith('.mcp.json')) {
432
+ writeConfigFile(settingsPath, settings, 'json');
433
+ }
434
+ // For global, permissions are added to same config object
435
+ }
257
436
  // Write updated config
258
- writeConfigFile(configPath, config);
437
+ writeConfigFile(configPath, config, configFormat);
438
+ const editorName = editor?.name || editorKey || 'custom';
259
439
  return {
260
- editor: editor.name,
440
+ editor: editorName,
261
441
  configPath,
262
442
  success: true,
263
443
  created,
264
444
  };
265
445
  }
266
446
  catch (err) {
447
+ const editorName = editor?.name || editorKey || 'custom';
267
448
  return {
268
- editor: editor.name,
449
+ editor: editorName,
269
450
  configPath,
270
451
  success: false,
271
452
  error: err instanceof Error ? err.message : String(err),
@@ -308,7 +489,7 @@ export function uninstallFromEditor(editorKey, options = {}) {
308
489
  const mcpServers = config[editor.configKey];
309
490
  if (mcpServers && 'agent-relay' in mcpServers) {
310
491
  delete mcpServers['agent-relay'];
311
- writeConfigFile(configPath, config);
492
+ writeConfigFile(configPath, config, editor.format);
312
493
  }
313
494
  return {
314
495
  editor: editor.name,
@@ -379,6 +560,23 @@ export function install(options = {}) {
379
560
  }
380
561
  return results;
381
562
  }
563
+ /**
564
+ * Install MCP server configuration to a specific config file path.
565
+ * This is a simpler API for programmatic/automated installation.
566
+ *
567
+ * @param configPath - Absolute path to config file
568
+ * @param options - Optional settings for format, key, etc.
569
+ */
570
+ export function installMcpConfig(configPath, options = {}) {
571
+ return installForEditor('custom', {
572
+ configPath,
573
+ configFormat: options.format || 'json',
574
+ configKey: options.configKey || 'mcpServers',
575
+ command: options.command,
576
+ args: options.args,
577
+ env: options.env,
578
+ });
579
+ }
382
580
  /**
383
581
  * Uninstall MCP server from all detected editors (or specified editors)
384
582
  */
@@ -1,7 +1,7 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
- import { relaySendTool, relaySendSchema, handleRelaySend, relayInboxTool, relayInboxSchema, handleRelayInbox, relayWhoTool, relayWhoSchema, handleRelayWho, relaySpawnTool, relaySpawnSchema, handleRelaySpawn, relayReleaseTool, relayReleaseSchema, handleRelayRelease, relayStatusTool, relayStatusSchema, handleRelayStatus, relayLogsTool, relayLogsSchema, handleRelayLogs, relayMetricsTool, relayMetricsSchema, handleRelayMetrics, relayHealthTool, relayHealthSchema, handleRelayHealth, } from './tools/index.js';
4
+ import { relaySendTool, relaySendSchema, handleRelaySend, relayInboxTool, relayInboxSchema, handleRelayInbox, relayWhoTool, relayWhoSchema, handleRelayWho, relaySpawnTool, relaySpawnSchema, handleRelaySpawn, relayReleaseTool, relayReleaseSchema, handleRelayRelease, relayStatusTool, relayStatusSchema, handleRelayStatus, relayLogsTool, relayLogsSchema, handleRelayLogs, relayMetricsTool, relayMetricsSchema, handleRelayMetrics, relayHealthTool, relayHealthSchema, handleRelayHealth, relayContinuityTool, relayContinuitySchema, handleRelayContinuity, } from './tools/index.js';
5
5
  import { protocolPrompt, getProtocolPrompt } from './prompts/index.js';
6
6
  import { agentsResource, getAgentsResource, inboxResource, getInboxResource, projectResource, getProjectResource, } from './resources/index.js';
7
7
  /**
@@ -17,6 +17,7 @@ const TOOLS = [
17
17
  relayLogsTool,
18
18
  relayMetricsTool,
19
19
  relayHealthTool,
20
+ relayContinuityTool,
20
21
  ];
21
22
  /**
22
23
  * All available prompts
@@ -97,6 +98,11 @@ export function createMCPServer(client, config) {
97
98
  result = await handleRelayHealth(client, input);
98
99
  break;
99
100
  }
101
+ case 'relay_continuity': {
102
+ const input = relayContinuitySchema.parse(args);
103
+ result = await handleRelayContinuity(client, input);
104
+ break;
105
+ }
100
106
  default:
101
107
  return {
102
108
  content: [
@@ -24,7 +24,7 @@ export interface Message {
24
24
  }
25
25
  export interface Agent {
26
26
  name: string;
27
- cli: string;
27
+ cli?: string;
28
28
  idle?: boolean;
29
29
  parent?: string;
30
30
  }
@@ -7,4 +7,5 @@ export { relayStatusTool, relayStatusSchema, handleRelayStatus, type RelayStatus
7
7
  export { relayLogsTool, relayLogsSchema, handleRelayLogs, type RelayLogsInput, } from './relay-logs.js';
8
8
  export { relayMetricsTool, relayMetricsSchema, handleRelayMetrics, type RelayMetricsInput, } from './relay-metrics.js';
9
9
  export { relayHealthTool, relayHealthSchema, handleRelayHealth, type RelayHealthInput, } from './relay-health.js';
10
+ export { relayContinuityTool, relayContinuitySchema, handleRelayContinuity, type RelayContinuityInput, } from './relay-continuity.js';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -7,4 +7,5 @@ export { relayStatusTool, relayStatusSchema, handleRelayStatus, } from './relay-
7
7
  export { relayLogsTool, relayLogsSchema, handleRelayLogs, } from './relay-logs.js';
8
8
  export { relayMetricsTool, relayMetricsSchema, handleRelayMetrics, } from './relay-metrics.js';
9
9
  export { relayHealthTool, relayHealthSchema, handleRelayHealth, } from './relay-health.js';
10
+ export { relayContinuityTool, relayContinuitySchema, handleRelayContinuity, } from './relay-continuity.js';
10
11
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod';
2
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { RelayClient } from '../client.js';
4
+ export declare const relayContinuitySchema: z.ZodObject<{
5
+ action: z.ZodEnum<["save", "load", "uncertain"]>;
6
+ current_task: z.ZodOptional<z.ZodString>;
7
+ completed: z.ZodOptional<z.ZodString>;
8
+ in_progress: z.ZodOptional<z.ZodString>;
9
+ key_decisions: z.ZodOptional<z.ZodString>;
10
+ files: z.ZodOptional<z.ZodString>;
11
+ item: z.ZodOptional<z.ZodString>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ action: "save" | "load" | "uncertain";
14
+ current_task?: string | undefined;
15
+ completed?: string | undefined;
16
+ in_progress?: string | undefined;
17
+ key_decisions?: string | undefined;
18
+ files?: string | undefined;
19
+ item?: string | undefined;
20
+ }, {
21
+ action: "save" | "load" | "uncertain";
22
+ current_task?: string | undefined;
23
+ completed?: string | undefined;
24
+ in_progress?: string | undefined;
25
+ key_decisions?: string | undefined;
26
+ files?: string | undefined;
27
+ item?: string | undefined;
28
+ }>;
29
+ export type RelayContinuityInput = z.infer<typeof relayContinuitySchema>;
30
+ export declare const relayContinuityTool: Tool;
31
+ /**
32
+ * Handle continuity actions for session recovery.
33
+ */
34
+ export declare function handleRelayContinuity(client: RelayClient, input: RelayContinuityInput): Promise<string>;
35
+ //# sourceMappingURL=relay-continuity.d.ts.map
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod';
2
+ export const relayContinuitySchema = z.object({
3
+ action: z.enum(['save', 'load', 'uncertain']).describe('Action: save state, load previous state, or mark uncertainty'),
4
+ current_task: z.string().optional().describe('Current task being worked on (for save)'),
5
+ completed: z.string().optional().describe('Completed work summary (for save)'),
6
+ in_progress: z.string().optional().describe('In-progress work summary (for save)'),
7
+ key_decisions: z.string().optional().describe('Key decisions made (for save)'),
8
+ files: z.string().optional().describe('Files being worked on (for save)'),
9
+ item: z.string().optional().describe('Item to mark as uncertain (for uncertain action)'),
10
+ });
11
+ export const relayContinuityTool = {
12
+ name: 'relay_continuity',
13
+ description: `Manage session continuity for agent recovery.
14
+
15
+ Use this to:
16
+ - Save your current state before long operations or session end
17
+ - Load previous context when starting a new session
18
+ - Mark items that need future verification
19
+
20
+ Examples:
21
+ - Save state: action="save", current_task="Implementing auth", completed="User model done"
22
+ - Load state: action="load"
23
+ - Mark uncertain: action="uncertain", item="API rate limit handling unclear"`,
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties: {
27
+ action: {
28
+ type: 'string',
29
+ enum: ['save', 'load', 'uncertain'],
30
+ description: 'Action: save state, load previous state, or mark uncertainty',
31
+ },
32
+ current_task: {
33
+ type: 'string',
34
+ description: 'Current task being worked on (for save)',
35
+ },
36
+ completed: {
37
+ type: 'string',
38
+ description: 'Completed work summary (for save)',
39
+ },
40
+ in_progress: {
41
+ type: 'string',
42
+ description: 'In-progress work summary (for save)',
43
+ },
44
+ key_decisions: {
45
+ type: 'string',
46
+ description: 'Key decisions made (for save)',
47
+ },
48
+ files: {
49
+ type: 'string',
50
+ description: 'Files being worked on (for save)',
51
+ },
52
+ item: {
53
+ type: 'string',
54
+ description: 'Item to mark as uncertain (for uncertain action)',
55
+ },
56
+ },
57
+ required: ['action'],
58
+ },
59
+ };
60
+ /**
61
+ * Handle continuity actions for session recovery.
62
+ */
63
+ export async function handleRelayContinuity(client, input) {
64
+ const extClient = client;
65
+ const { action, current_task, completed, in_progress, key_decisions, files, item } = input;
66
+ switch (action) {
67
+ case 'save': {
68
+ if (!extClient.saveContinuity) {
69
+ return 'Continuity save not supported by this client';
70
+ }
71
+ await extClient.saveContinuity({
72
+ currentTask: current_task,
73
+ completed,
74
+ inProgress: in_progress,
75
+ keyDecisions: key_decisions,
76
+ files,
77
+ });
78
+ return 'Session state saved for recovery';
79
+ }
80
+ case 'load': {
81
+ if (!extClient.loadContinuity) {
82
+ return 'Continuity load not supported by this client';
83
+ }
84
+ await extClient.loadContinuity();
85
+ return 'Previous session context loaded';
86
+ }
87
+ case 'uncertain': {
88
+ if (!extClient.markUncertain) {
89
+ return 'Mark uncertain not supported by this client';
90
+ }
91
+ if (!item) {
92
+ return 'Item required for uncertain action';
93
+ }
94
+ await extClient.markUncertain(item);
95
+ return `Marked as uncertain: ${item}`;
96
+ }
97
+ default:
98
+ return `Unknown action: ${action}`;
99
+ }
100
+ }
101
+ //# sourceMappingURL=relay-continuity.js.map
@@ -2,15 +2,12 @@ import { z } from 'zod';
2
2
  import type { Tool } from '@modelcontextprotocol/sdk/types.js';
3
3
  import type { RelayClient } from '../client.js';
4
4
  export declare const relayHealthSchema: z.ZodObject<{
5
- port: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
6
5
  include_crashes: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
7
6
  include_alerts: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
8
7
  }, "strip", z.ZodTypeAny, {
9
- port: number;
10
8
  include_crashes: boolean;
11
9
  include_alerts: boolean;
12
10
  }, {
13
- port?: number | undefined;
14
11
  include_crashes?: boolean | undefined;
15
12
  include_alerts?: boolean | undefined;
16
13
  }>;
@@ -19,5 +16,5 @@ export declare const relayHealthTool: Tool;
19
16
  /**
20
17
  * Get system health, crash insights, and recommendations.
21
18
  */
22
- export declare function handleRelayHealth(_client: RelayClient, input: RelayHealthInput): Promise<string>;
19
+ export declare function handleRelayHealth(client: RelayClient, input: RelayHealthInput): Promise<string>;
23
20
  //# sourceMappingURL=relay-health.d.ts.map
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  export const relayHealthSchema = z.object({
3
- port: z.number().optional().default(3888).describe('Dashboard port (default: 3888)'),
4
3
  include_crashes: z.boolean().optional().default(true).describe('Include recent crash history'),
5
4
  include_alerts: z.boolean().optional().default(true).describe('Include unacknowledged alerts'),
6
5
  });
@@ -24,11 +23,6 @@ Example: Health check without crash history
24
23
  inputSchema: {
25
24
  type: 'object',
26
25
  properties: {
27
- port: {
28
- type: 'number',
29
- description: 'Dashboard port (default: 3888)',
30
- default: 3888,
31
- },
32
26
  include_crashes: {
33
27
  type: 'boolean',
34
28
  description: 'Include recent crash history',
@@ -46,14 +40,12 @@ Example: Health check without crash history
46
40
  /**
47
41
  * Get system health, crash insights, and recommendations.
48
42
  */
49
- export async function handleRelayHealth(_client, input) {
50
- const port = input.port || 3888;
43
+ export async function handleRelayHealth(client, input) {
51
44
  try {
52
- const response = await fetch(`http://localhost:${port}/api/metrics/health`);
53
- if (!response.ok) {
54
- throw new Error(`HTTP ${response.status}`);
55
- }
56
- const data = await response.json();
45
+ const data = await client.getHealth({
46
+ include_crashes: input.include_crashes,
47
+ include_alerts: input.include_alerts,
48
+ });
57
49
  const lines = [];
58
50
  // Health score header
59
51
  const scoreEmoji = data.healthScore >= 80 ? '✅' :
@@ -129,8 +121,8 @@ export async function handleRelayHealth(_client, input) {
129
121
  }
130
122
  catch (err) {
131
123
  const error = err;
132
- if (error.code === 'ECONNREFUSED') {
133
- return `Cannot connect to dashboard at port ${port}. Is the daemon running?\n\nRun 'agent-relay up' to start the daemon.`;
124
+ if (error.code === 'ECONNREFUSED' || error.code === 'ENOENT') {
125
+ return `Cannot connect to daemon. Is the daemon running?\n\nRun 'agent-relay up' to start the daemon.`;
134
126
  }
135
127
  return `Failed to fetch health data: ${error.message || String(err)}`;
136
128
  }
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { promisify } from 'node:util';
5
5
  import { exec } from 'node:child_process';
6
+ import { getProjectPaths } from '@agent-relay/config';
6
7
  const execAsync = promisify(exec);
7
8
  export const relayLogsSchema = z.object({
8
9
  agent: z.string().describe('Name of the agent to get logs for'),
@@ -39,10 +40,11 @@ Example: Get last 100 lines from Worker1
39
40
  };
40
41
  /**
41
42
  * Get the worker logs directory path.
42
- * Mirrors the logic from @agent-relay/bridge.
43
+ * Uses getProjectPaths from @agent-relay/config to match the bridge package.
43
44
  */
44
45
  function getWorkerLogsDir(projectRoot) {
45
- return path.join(projectRoot, '.relay', 'logs');
46
+ const paths = getProjectPaths(projectRoot);
47
+ return path.join(paths.teamDir, 'worker-logs');
46
48
  }
47
49
  /**
48
50
  * Read recent logs from an agent's output file.