agent-relay 2.1.9 → 2.1.10

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 (54) hide show
  1. package/dist/index.cjs +33 -17
  2. package/dist/src/cli/index.d.ts +7 -4
  3. package/dist/src/cli/index.d.ts.map +1 -1
  4. package/dist/src/cli/index.js +110 -182
  5. package/dist/src/cli/index.js.map +1 -1
  6. package/install.sh +64 -4
  7. package/package.json +18 -18
  8. package/packages/api-types/package.json +1 -1
  9. package/packages/benchmark/package.json +4 -4
  10. package/packages/bridge/package.json +8 -8
  11. package/packages/cli-tester/package.json +1 -1
  12. package/packages/config/package.json +2 -2
  13. package/packages/continuity/package.json +2 -2
  14. package/packages/daemon/dist/server.d.ts.map +1 -1
  15. package/packages/daemon/dist/server.js +10 -2
  16. package/packages/daemon/dist/server.js.map +1 -1
  17. package/packages/daemon/dist/spawn-manager.d.ts.map +1 -1
  18. package/packages/daemon/dist/spawn-manager.js +3 -0
  19. package/packages/daemon/dist/spawn-manager.js.map +1 -1
  20. package/packages/daemon/package.json +12 -12
  21. package/packages/daemon/src/server.ts +9 -2
  22. package/packages/daemon/src/spawn-manager.ts +3 -0
  23. package/packages/hooks/package.json +4 -4
  24. package/packages/mcp/package.json +4 -4
  25. package/packages/memory/package.json +2 -2
  26. package/packages/policy/package.json +2 -2
  27. package/packages/protocol/dist/types.d.ts +6 -0
  28. package/packages/protocol/dist/types.d.ts.map +1 -1
  29. package/packages/protocol/package.json +1 -1
  30. package/packages/protocol/src/types.ts +6 -0
  31. package/packages/resiliency/package.json +1 -1
  32. package/packages/sdk/dist/client.d.ts +6 -0
  33. package/packages/sdk/dist/client.d.ts.map +1 -1
  34. package/packages/sdk/dist/client.js +6 -0
  35. package/packages/sdk/dist/client.js.map +1 -1
  36. package/packages/sdk/package.json +3 -3
  37. package/packages/sdk/src/client.test.ts +48 -0
  38. package/packages/sdk/src/client.ts +9 -0
  39. package/packages/spawner/package.json +1 -1
  40. package/packages/state/package.json +1 -1
  41. package/packages/storage/dist/adapter.js +1 -1
  42. package/packages/storage/dist/adapter.js.map +1 -1
  43. package/packages/storage/package.json +2 -2
  44. package/packages/storage/src/adapter.ts +1 -1
  45. package/packages/telemetry/package.json +1 -1
  46. package/packages/trajectory/package.json +2 -2
  47. package/packages/user-directory/package.json +2 -2
  48. package/packages/utils/dist/cjs/logger.js +20 -11
  49. package/packages/utils/dist/logger.d.ts.map +1 -1
  50. package/packages/utils/dist/logger.js +24 -14
  51. package/packages/utils/dist/logger.js.map +1 -1
  52. package/packages/utils/package.json +3 -3
  53. package/packages/utils/src/logger.ts +27 -14
  54. package/packages/wrapper/package.json +6 -6
package/dist/index.cjs CHANGED
@@ -3078,11 +3078,27 @@ var init_dist = __esm({
3078
3078
  });
3079
3079
 
3080
3080
  // packages/utils/dist/logger.js
3081
+ function getLogFile() {
3082
+ return process.env.AGENT_RELAY_LOG_FILE;
3083
+ }
3084
+ function getLogLevel() {
3085
+ return (process.env.AGENT_RELAY_LOG_LEVEL ?? "INFO").toUpperCase();
3086
+ }
3087
+ function isLogJson() {
3088
+ return process.env.AGENT_RELAY_LOG_JSON === "1";
3089
+ }
3090
+ function ensureLogDir(logFile) {
3091
+ const logDir = import_node_path12.default.dirname(logFile);
3092
+ if (!createdLogDirs.has(logDir) && !import_node_fs9.default.existsSync(logDir)) {
3093
+ import_node_fs9.default.mkdirSync(logDir, { recursive: true });
3094
+ createdLogDirs.add(logDir);
3095
+ }
3096
+ }
3081
3097
  function shouldLog(level) {
3082
- return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[LOG_LEVEL];
3098
+ return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[getLogLevel()];
3083
3099
  }
3084
3100
  function formatMessage(entry) {
3085
- if (LOG_JSON) {
3101
+ if (isLogJson()) {
3086
3102
  return JSON.stringify(entry);
3087
3103
  }
3088
3104
  const { ts, level, component, msg, ...extra } = entry;
@@ -3100,8 +3116,10 @@ function log(level, component, msg, extra) {
3100
3116
  ...extra
3101
3117
  };
3102
3118
  const formatted = formatMessage(entry);
3103
- if (LOG_FILE) {
3104
- import_node_fs9.default.appendFileSync(LOG_FILE, formatted + "\n");
3119
+ const logFile = getLogFile();
3120
+ if (logFile) {
3121
+ ensureLogDir(logFile);
3122
+ import_node_fs9.default.appendFileSync(logFile, formatted + "\n");
3105
3123
  return;
3106
3124
  }
3107
3125
  if (level === "ERROR" || level === "WARN") {
@@ -3118,28 +3136,19 @@ function createLogger2(component) {
3118
3136
  error: (msg, extra) => log("ERROR", component, msg, extra)
3119
3137
  };
3120
3138
  }
3121
- var import_node_fs9, import_node_path12, LOG_FILE, LOG_LEVEL, LOG_JSON, _DEBUG, LEVEL_PRIORITY, daemonLog, routerLog, connectionLog;
3139
+ var import_node_fs9, import_node_path12, LEVEL_PRIORITY, createdLogDirs, daemonLog, routerLog, connectionLog;
3122
3140
  var init_logger = __esm({
3123
3141
  "packages/utils/dist/logger.js"() {
3124
3142
  "use strict";
3125
3143
  import_node_fs9 = __toESM(require("node:fs"), 1);
3126
3144
  import_node_path12 = __toESM(require("node:path"), 1);
3127
- LOG_FILE = process.env.AGENT_RELAY_LOG_FILE;
3128
- LOG_LEVEL = (process.env.AGENT_RELAY_LOG_LEVEL ?? "INFO").toUpperCase();
3129
- LOG_JSON = process.env.AGENT_RELAY_LOG_JSON === "1";
3130
- _DEBUG = process.env.DEBUG === "1" || LOG_LEVEL === "DEBUG";
3131
3145
  LEVEL_PRIORITY = {
3132
3146
  DEBUG: 0,
3133
3147
  INFO: 1,
3134
3148
  WARN: 2,
3135
3149
  ERROR: 3
3136
3150
  };
3137
- if (LOG_FILE) {
3138
- const logDir = import_node_path12.default.dirname(LOG_FILE);
3139
- if (!import_node_fs9.default.existsSync(logDir)) {
3140
- import_node_fs9.default.mkdirSync(logDir, { recursive: true });
3141
- }
3142
- }
3151
+ createdLogDirs = /* @__PURE__ */ new Set();
3143
3152
  daemonLog = createLogger2("daemon");
3144
3153
  routerLog = createLogger2("router");
3145
3154
  connectionLog = createLogger2("connection");
@@ -70197,7 +70206,10 @@ var SpawnManager = class {
70197
70206
  cwd: payload.cwd,
70198
70207
  spawnerName: payload.spawnerName ?? spawnerName,
70199
70208
  interactive: payload.interactive,
70209
+ shadowMode: payload.shadowMode,
70200
70210
  shadowOf: payload.shadowOf,
70211
+ shadowAgent: payload.shadowAgent,
70212
+ shadowTriggers: payload.shadowTriggers,
70201
70213
  shadowSpeakOn: payload.shadowSpeakOn,
70202
70214
  userId: payload.userId,
70203
70215
  includeWorkflowConventions: payload.includeWorkflowConventions
@@ -70523,7 +70535,6 @@ async function createStorageAdapter(dbPath, config2) {
70523
70535
  case "jsonl": {
70524
70536
  const { JsonlStorageAdapter: JsonlStorageAdapter2 } = await Promise.resolve().then(() => (init_jsonl_adapter(), jsonl_adapter_exports));
70525
70537
  const baseDir = import_node_path23.default.dirname(finalConfig.path);
70526
- console.error("[storage] Using JSONL storage");
70527
70538
  const adapter = new JsonlStorageAdapter2({
70528
70539
  baseDir,
70529
70540
  watchForChanges: finalConfig.watchForChanges
@@ -72650,7 +72661,12 @@ var Daemon = class _Daemon {
72650
72661
  this.config.pidFilePath = `${config2.socketPath}.pid`;
72651
72662
  }
72652
72663
  if (!this.config.teamDir) {
72653
- this.config.teamDir = import_node_path25.default.dirname(this.config.socketPath);
72664
+ const socketDir = import_node_path25.default.dirname(this.config.socketPath);
72665
+ if (socketDir === "/tmp" || socketDir === "/private/tmp") {
72666
+ this.config.teamDir = import_node_path25.default.join(socketDir, "agent-relay-state");
72667
+ } else {
72668
+ this.config.teamDir = socketDir;
72669
+ }
72654
72670
  }
72655
72671
  if (this.config.teamDir) {
72656
72672
  this.registry = new AgentRegistry(this.config.teamDir);
@@ -3,14 +3,17 @@
3
3
  * Agent Relay CLI
4
4
  *
5
5
  * Commands:
6
- * relay claude - Start daemon with Claude coordinator
7
- * relay codex - Start daemon with Codex coordinator
6
+ * relay up - Start daemon
7
+ * relay up --dashboard - Start daemon with web dashboard
8
8
  * relay create-agent <cmd> - Wrap agent with real-time messaging
9
9
  * relay create-agent -n Name cmd - Wrap with specific agent name
10
- * relay up - Start daemon
11
- * relay read <id> - Read full message by ID
10
+ * relay spawn <name> <cli> - Spawn a new agent
11
+ * relay release <name> - Release an agent
12
12
  * relay agents - List connected agents
13
13
  * relay who - Show currently active agents
14
+ * relay read <id> - Read full message by ID
15
+ * relay status - Check daemon status
16
+ * relay down - Stop daemon
14
17
  */
15
18
  /**
16
19
  * Install agent-relay-snippet to markdown files using prpm.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AA0JH;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAqC7H"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AA8JH;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAqC7H"}
@@ -3,14 +3,17 @@
3
3
  * Agent Relay CLI
4
4
  *
5
5
  * Commands:
6
- * relay claude - Start daemon with Claude coordinator
7
- * relay codex - Start daemon with Codex coordinator
6
+ * relay up - Start daemon
7
+ * relay up --dashboard - Start daemon with web dashboard
8
8
  * relay create-agent <cmd> - Wrap agent with real-time messaging
9
9
  * relay create-agent -n Name cmd - Wrap with specific agent name
10
- * relay up - Start daemon
11
- * relay read <id> - Read full message by ID
10
+ * relay spawn <name> <cli> - Spawn a new agent
11
+ * relay release <name> - Release an agent
12
12
  * relay agents - List connected agents
13
13
  * relay who - Show currently active agents
14
+ * relay read <id> - Read full message by ID
15
+ * relay status - Check daemon status
16
+ * relay down - Stop daemon
14
17
  */
15
18
  import { Command } from 'commander';
16
19
  import { config as dotenvConfig } from 'dotenv';
@@ -82,6 +85,9 @@ function startDashboardViaNpx(options) {
82
85
  '--team-dir', options.teamDir,
83
86
  '--project-root', options.projectRoot,
84
87
  ];
88
+ if (options.verbose) {
89
+ args.push('--verbose');
90
+ }
85
91
  if (dashboardBinary) {
86
92
  console.log(`Starting dashboard using binary: ${dashboardBinary}`);
87
93
  dashboardProcess = spawnProcess(dashboardBinary, args, {
@@ -180,7 +186,7 @@ export async function installRelaySnippets(options) {
180
186
  }
181
187
  return { success: installed.length > 0, installed };
182
188
  }
183
- dotenvConfig();
189
+ dotenvConfig({ quiet: true });
184
190
  const DEFAULT_DASHBOARD_PORT = process.env.AGENT_RELAY_DASHBOARD_PORT || '3888';
185
191
  // Read version from package.json
186
192
  const __filename = fileURLToPath(import.meta.url);
@@ -463,6 +469,7 @@ program
463
469
  .option('--no-spawn', 'Do not auto-spawn agents (just start daemon)')
464
470
  .option('--watch', 'Auto-restart daemon on crash (supervisor mode)')
465
471
  .option('--max-restarts <n>', 'Max restarts in 60s before giving up (default: 5)', '5')
472
+ .option('--verbose', 'Enable verbose logging (show debug output in console)')
466
473
  .action(async (options) => {
467
474
  // If --watch is specified, run in supervisor mode
468
475
  if (options.watch) {
@@ -485,6 +492,8 @@ program
485
492
  args.push('--spawn');
486
493
  if (options.spawn === false)
487
494
  args.push('--no-spawn');
495
+ if (options.verbose)
496
+ args.push('--verbose');
488
497
  console.log(`[supervisor] Starting daemon...`);
489
498
  child = spawnProcess(process.execPath, [process.argv[1], ...args], {
490
499
  stdio: 'inherit',
@@ -562,10 +571,15 @@ program
562
571
  }
563
572
  // Set up log file to avoid console output polluting TUI terminals
564
573
  // Only set if not already configured via environment
565
- if (!process.env.AGENT_RELAY_LOG_FILE) {
574
+ // Skip if --verbose is set (logs go to console in verbose mode)
575
+ if (!process.env.AGENT_RELAY_LOG_FILE && !options.verbose) {
566
576
  const logFile = path.join(paths.dataDir, 'daemon.log');
567
577
  process.env.AGENT_RELAY_LOG_FILE = logFile;
568
578
  }
579
+ // In verbose mode, also set DEBUG level for more detailed output
580
+ if (options.verbose && !process.env.AGENT_RELAY_LOG_LEVEL) {
581
+ process.env.AGENT_RELAY_LOG_LEVEL = 'DEBUG';
582
+ }
569
583
  console.log(`Project: ${paths.projectRoot}`);
570
584
  console.log(`Socket: ${socketPath}`);
571
585
  // Load teams.json if present
@@ -659,6 +673,7 @@ program
659
673
  enableSpawner: true,
660
674
  onMarkSpawning: (name) => daemon.markSpawning(name),
661
675
  onClearSpawning: (name) => daemon.clearSpawning(name),
676
+ verbose: options.verbose,
662
677
  });
663
678
  console.log(`Dashboard: http://localhost:${dashboardPort}`);
664
679
  // Hook daemon log output to dashboard WebSocket broadcast
@@ -671,15 +686,15 @@ program
671
686
  }
672
687
  catch (err) {
673
688
  if (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND') {
674
- // Dashboard package not installed
689
+ // Dashboard package not installed as a dependency - use binary or npx fallback
675
690
  if (dashboardRequested) {
676
- // User explicitly asked for dashboard but it's not installed - start via npx
677
- console.log('Dashboard package not installed. Starting via npx...');
691
+ // User explicitly asked for dashboard - start via binary or npx
678
692
  const { process: dashboardProcess, port: npxPort, ready } = startDashboardViaNpx({
679
693
  port,
680
694
  dataDir: paths.dataDir,
681
695
  teamDir: paths.teamDir,
682
696
  projectRoot: paths.projectRoot,
697
+ verbose: options.verbose,
683
698
  });
684
699
  dashboardPort = npxPort;
685
700
  // Clean up dashboard process on exit
@@ -941,152 +956,6 @@ program
941
956
  }
942
957
  }
943
958
  });
944
- // System prompt for Dashboard agent - plain text to avoid shell escaping issues
945
- const MEGA_SYSTEM_PROMPT = [
946
- 'You are Dashboard, a lead coordinator in agent-relay.',
947
- 'Your PRIMARY job is to delegate - you should almost NEVER do implementation work yourself.',
948
- 'ALWAYS SPAWN AGENTS: For any non-trivial task, spawn specialized workers.',
949
- ].join(' ');
950
- // Helper function for starting Dashboard coordinator with a specific provider
951
- async function startDashboardCoordinator(operator) {
952
- const paths = getProjectPaths();
953
- console.log(`Starting Dashboard with ${operator}...`);
954
- console.log(`Project: ${paths.projectRoot}`);
955
- // Step 1: Check if daemon is already running, start if needed
956
- console.log('\n[1/3] Checking daemon...');
957
- // Check if socket exists (daemon running)
958
- const socketExists = fs.existsSync(paths.socketPath);
959
- // Ports to try for dashboard detection
960
- const portsToTry = [
961
- parseInt(DEFAULT_DASHBOARD_PORT, 10),
962
- 3889, 3890, 3891,
963
- ];
964
- // Check if dashboard is responding for THIS project
965
- let dashboardReady = false;
966
- let detectedPort;
967
- // Helper to check health at a port
968
- const checkPort = async (port) => {
969
- try {
970
- const response = await fetch(`http://localhost:${port}/api/health`, {
971
- signal: AbortSignal.timeout(500),
972
- });
973
- if (response.ok) {
974
- const health = await response.json();
975
- return health.status === 'healthy';
976
- }
977
- }
978
- catch {
979
- // Port not responding
980
- }
981
- return false;
982
- };
983
- if (socketExists) {
984
- for (const port of portsToTry) {
985
- if (await checkPort(port)) {
986
- dashboardReady = true;
987
- detectedPort = port;
988
- break;
989
- }
990
- }
991
- }
992
- if (dashboardReady && detectedPort) {
993
- console.log(`Daemon already running at port ${detectedPort}, reusing...`);
994
- }
995
- else {
996
- console.log('Starting daemon...');
997
- const daemonProc = spawnProcess(process.execPath, [process.argv[1], 'up', '--dashboard'], {
998
- stdio: 'ignore',
999
- detached: true,
1000
- });
1001
- daemonProc.unref();
1002
- // Wait for dashboard to be ready (up to 10 seconds)
1003
- const maxWait = 10000;
1004
- const startTime = Date.now();
1005
- while (Date.now() - startTime < maxWait) {
1006
- await new Promise((resolve) => setTimeout(resolve, 500));
1007
- for (const port of portsToTry) {
1008
- if (await checkPort(port)) {
1009
- dashboardReady = true;
1010
- detectedPort = port;
1011
- break;
1012
- }
1013
- }
1014
- if (dashboardReady)
1015
- break;
1016
- }
1017
- if (!dashboardReady) {
1018
- console.error('Warning: Dashboard may not be fully ready. Spawn might not work.');
1019
- detectedPort = parseInt(DEFAULT_DASHBOARD_PORT, 10); // Fallback
1020
- }
1021
- }
1022
- const dashboardPort = detectedPort || parseInt(DEFAULT_DASHBOARD_PORT, 10);
1023
- // Step 2: Install prpm snippet via npx
1024
- console.log('[2/3] Installing agent-relay snippet...');
1025
- const prpmArgs = operator.toLowerCase() === 'claude'
1026
- ? ['prpm', 'install', '@agent-relay/agent-relay-snippet', '--location', 'CLAUDE.md']
1027
- : ['prpm', 'install', '@agent-relay/agent-relay-snippet'];
1028
- try {
1029
- await new Promise((resolve, reject) => {
1030
- const prpmProc = spawnProcess('npx', prpmArgs, {
1031
- stdio: 'inherit',
1032
- });
1033
- prpmProc.on('close', (code) => {
1034
- if (code === 0)
1035
- resolve();
1036
- else
1037
- reject(new Error(`npx prpm exited with code ${code}`));
1038
- });
1039
- prpmProc.on('error', reject);
1040
- });
1041
- }
1042
- catch (err) {
1043
- console.warn(`Warning: prpm install failed: ${err.message}`);
1044
- console.warn('Continuing without snippet installation...');
1045
- }
1046
- // Step 3: Start Dashboard agent with system prompt
1047
- console.log(`[3/3] Starting Dashboard agent with ${operator}...`);
1048
- console.log('');
1049
- const op = operator.toLowerCase();
1050
- // Build CLI-specific arguments for system prompt
1051
- // These args go AFTER the operator command, passed through to the CLI
1052
- let cliArgs = [];
1053
- if (op === 'claude') {
1054
- // Claude: --append-system-prompt <content> (takes content directly, not file)
1055
- cliArgs = ['--append-system-prompt', MEGA_SYSTEM_PROMPT];
1056
- }
1057
- else if (op === 'codex') {
1058
- // Codex: --config developer_instructions="<content>"
1059
- cliArgs = ['--config', `developer_instructions=${MEGA_SYSTEM_PROMPT}`];
1060
- }
1061
- // Use '--' to separate agent-relay options from the command + its args
1062
- // Format: agent-relay create-agent -n Dashboard --skip-instructions --dashboard-port <port> -- claude --append-system-prompt "..."
1063
- const agentProc = spawnProcess(process.execPath, [process.argv[1], 'create-agent', '-n', 'Dashboard', '--skip-instructions', '--dashboard-port', String(dashboardPort), '--', operator, ...cliArgs], { stdio: 'inherit' });
1064
- // Forward signals to agent process
1065
- process.on('SIGINT', () => {
1066
- agentProc.kill('SIGINT');
1067
- });
1068
- process.on('SIGTERM', () => {
1069
- agentProc.kill('SIGTERM');
1070
- });
1071
- agentProc.on('close', (code) => {
1072
- process.exit(code ?? 0);
1073
- });
1074
- }
1075
- // claude - Start daemon and spawn Dashboard coordinator with Claude
1076
- program
1077
- .command('claude')
1078
- .description('Start daemon and Dashboard coordinator with Claude')
1079
- .action(async () => {
1080
- await startDashboardCoordinator('claude');
1081
- });
1082
- // codex - Start daemon and spawn Dashboard coordinator with Codex
1083
- program
1084
- .command('codex')
1085
- .description('Start daemon and Dashboard coordinator with Codex')
1086
- .action(async () => {
1087
- await startDashboardCoordinator('codex');
1088
- });
1089
- // status - Check daemon status
1090
959
  program
1091
960
  .command('status')
1092
961
  .description('Check daemon status')
@@ -2047,7 +1916,7 @@ program
2047
1916
  // Use this for programmatic spawning from scripts, detached processes, or containers
2048
1917
  program
2049
1918
  .command('spawn')
2050
- .description('Spawn an agent via dashboard API (recommended for programmatic use, no TTY required)')
1919
+ .description('Spawn an agent via daemon (recommended for programmatic use, no TTY or dashboard required)')
2051
1920
  .argument('<name>', 'Agent name')
2052
1921
  .argument('<cli>', 'CLI to use (claude, codex, gemini, etc.)')
2053
1922
  .argument('[task]', 'Task description (can also be piped via stdin)')
@@ -2110,19 +1979,62 @@ program
2110
1979
  shadowTriggers: parseTriggers(options.shadowTriggers),
2111
1980
  shadowSpeakOn: parseTriggers(options.shadowSpeakOn),
2112
1981
  };
2113
- // Try daemon socket first (preferred path)
1982
+ // Try daemon socket first (preferred - no dashboard required)
1983
+ const paths = getProjectPaths();
1984
+ let daemonSpawnSucceeded = false;
2114
1985
  try {
2115
- const _paths = getProjectPaths();
2116
- // TODO: Re-enable daemon-based spawning when client.spawn() is implemented
2117
- // See: docs/SDK-MIGRATION-PLAN.md for planned implementation
2118
- // For now, fall through to HTTP API
2119
- throw new Error('Daemon-based spawn not yet implemented');
1986
+ const client = new RelayClient({
1987
+ socketPath: paths.socketPath,
1988
+ agentName: options.spawner || '__cli_spawner__',
1989
+ quiet: true,
1990
+ reconnect: false,
1991
+ maxReconnectAttempts: 0,
1992
+ reconnectDelayMs: 0,
1993
+ reconnectMaxDelayMs: 0,
1994
+ });
1995
+ await client.connect();
1996
+ const result = await client.spawn({
1997
+ name: spawnRequest.name,
1998
+ cli: spawnRequest.cli,
1999
+ task: spawnRequest.task,
2000
+ team: spawnRequest.team,
2001
+ interactive: spawnRequest.interactive,
2002
+ cwd: spawnRequest.cwd,
2003
+ shadowMode: spawnRequest.shadowMode,
2004
+ shadowOf: spawnRequest.shadowOf,
2005
+ shadowAgent: spawnRequest.shadowAgent,
2006
+ shadowTriggers: spawnRequest.shadowTriggers,
2007
+ shadowSpeakOn: spawnRequest.shadowSpeakOn,
2008
+ }, 30000);
2009
+ // Set success flag BEFORE disconnect - disconnect errors shouldn't mask spawn success
2010
+ daemonSpawnSucceeded = true;
2011
+ await client.disconnect();
2012
+ if (result.success) {
2013
+ console.log(`Spawned agent: ${name} (pid: ${result.pid})`);
2014
+ process.exit(0);
2015
+ }
2016
+ else {
2017
+ if (result.policyDecision) {
2018
+ console.error(`Policy denied spawn: ${result.policyDecision.reason || 'Policy rejected'}`);
2019
+ }
2020
+ else {
2021
+ console.error(`Failed to spawn ${name}: ${result.error || 'Unknown error'}`);
2022
+ }
2023
+ process.exit(1);
2024
+ }
2120
2025
  }
2121
- catch {
2122
- // Fall through to HTTP API
2123
- // console.log('Daemon not available, trying HTTP API...');
2026
+ catch (daemonErr) {
2027
+ // If daemon connection failed, try HTTP API as fallback
2028
+ if (daemonErr.message?.includes('ENOENT') || daemonErr.message?.includes('ECONNREFUSED') || daemonErr.code === 'ENOENT') {
2029
+ // Socket doesn't exist or daemon not running - try HTTP API
2030
+ }
2031
+ else if (!daemonSpawnSucceeded) {
2032
+ // Other error during spawn - report it
2033
+ console.error(`Failed to spawn ${name}: ${daemonErr.message}`);
2034
+ process.exit(1);
2035
+ }
2124
2036
  }
2125
- // Fall back to HTTP API
2037
+ // Fall back to HTTP API (dashboard) if daemon not available
2126
2038
  try {
2127
2039
  const response = await fetch(`http://localhost:${port}/api/spawn`, {
2128
2040
  method: 'POST',
@@ -2136,8 +2048,7 @@ program
2136
2048
  }
2137
2049
  else {
2138
2050
  if (result.policyDecision) {
2139
- console.error(`Policy denied spawn: ${result.policyDecision.reason}`);
2140
- console.error(`Policy source: ${result.policyDecision.policySource}`);
2051
+ console.error(`Policy denied spawn: ${result.policyDecision.reason || 'Policy rejected'}`);
2141
2052
  }
2142
2053
  else {
2143
2054
  console.error(`Failed to spawn ${name}: ${result.error || 'Unknown error'}`);
@@ -2147,7 +2058,7 @@ program
2147
2058
  }
2148
2059
  catch (err) {
2149
2060
  if (err.code === 'ECONNREFUSED') {
2150
- console.error(`Cannot connect to dashboard at port ${port}. Is the daemon running?`);
2061
+ console.error(`Cannot connect to daemon. Is it running?`);
2151
2062
  console.log(`Run 'agent-relay up' to start the daemon.`);
2152
2063
  }
2153
2064
  else {
@@ -2164,11 +2075,12 @@ program
2164
2075
  .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
2165
2076
  .action(async (name, options) => {
2166
2077
  const port = options.port || DEFAULT_DASHBOARD_PORT;
2167
- // Try daemon socket first (preferred path)
2078
+ // Try daemon socket first (preferred - no dashboard required)
2079
+ const paths = getProjectPaths();
2080
+ let daemonReleaseSucceeded = false;
2168
2081
  try {
2169
- const _paths = getProjectPaths();
2170
- const _client = new RelayClient({
2171
- socketPath: _paths.socketPath,
2082
+ const client = new RelayClient({
2083
+ socketPath: paths.socketPath,
2172
2084
  agentName: '__cli_releaser__',
2173
2085
  quiet: true,
2174
2086
  reconnect: false,
@@ -2176,16 +2088,32 @@ program
2176
2088
  reconnectDelayMs: 0,
2177
2089
  reconnectMaxDelayMs: 0,
2178
2090
  });
2179
- // TODO: Re-enable daemon-based release when client.release() is implemented
2180
- // See: docs/SDK-MIGRATION-PLAN.md for planned implementation
2181
- // For now, fall through to HTTP API
2182
- throw new Error('Daemon-based release not yet implemented');
2091
+ await client.connect();
2092
+ const result = await client.release(name, 10000);
2093
+ // Set success flag BEFORE disconnect - disconnect errors shouldn't mask release success
2094
+ daemonReleaseSucceeded = true;
2095
+ await client.disconnect();
2096
+ if (result.success) {
2097
+ console.log(`Released agent: ${name}`);
2098
+ process.exit(0);
2099
+ }
2100
+ else {
2101
+ console.error(`Failed to release ${name}: ${result.error || 'Unknown error'}`);
2102
+ process.exit(1);
2103
+ }
2183
2104
  }
2184
- catch {
2185
- // Fall through to HTTP API
2186
- // console.log('Daemon not available, trying HTTP API...');
2105
+ catch (daemonErr) {
2106
+ // If daemon connection failed, try HTTP API as fallback
2107
+ if (daemonErr.message?.includes('ENOENT') || daemonErr.message?.includes('ECONNREFUSED') || daemonErr.code === 'ENOENT') {
2108
+ // Socket doesn't exist or daemon not running - try HTTP API
2109
+ }
2110
+ else if (!daemonReleaseSucceeded) {
2111
+ // Other error during release - report it
2112
+ console.error(`Failed to release ${name}: ${daemonErr.message}`);
2113
+ process.exit(1);
2114
+ }
2187
2115
  }
2188
- // Fall back to HTTP API
2116
+ // Fall back to HTTP API (dashboard) if daemon not available
2189
2117
  try {
2190
2118
  const response = await fetch(`http://localhost:${port}/api/spawned/${encodeURIComponent(name)}`, {
2191
2119
  method: 'DELETE',
@@ -2203,7 +2131,7 @@ program
2203
2131
  catch (err) {
2204
2132
  // If API call fails, try to provide helpful error message
2205
2133
  if (err.code === 'ECONNREFUSED') {
2206
- console.error(`Cannot connect to dashboard at port ${port}. Is the daemon running?`);
2134
+ console.error(`Cannot connect to daemon. Is it running?`);
2207
2135
  console.log(`Run 'agent-relay up' to start the daemon.`);
2208
2136
  }
2209
2137
  else {