agent-relay 3.2.15 → 3.2.16

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 (133) hide show
  1. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  2. package/bin/agent-relay-broker-darwin-x64 +0 -0
  3. package/bin/agent-relay-broker-linux-arm64 +0 -0
  4. package/bin/agent-relay-broker-linux-x64 +0 -0
  5. package/dist/index.cjs +3853 -17163
  6. package/package.json +8 -8
  7. package/packages/acp-bridge/package.json +2 -2
  8. package/packages/config/package.json +1 -1
  9. package/packages/hooks/package.json +4 -4
  10. package/packages/memory/package.json +2 -2
  11. package/packages/openclaw/package.json +2 -2
  12. package/packages/policy/package.json +2 -2
  13. package/packages/sdk/dist/broker-path.d.ts +19 -0
  14. package/packages/sdk/dist/broker-path.d.ts.map +1 -0
  15. package/packages/sdk/dist/broker-path.js +71 -0
  16. package/packages/sdk/dist/broker-path.js.map +1 -0
  17. package/packages/sdk/dist/cli-registry.d.ts.map +1 -1
  18. package/packages/sdk/dist/cli-registry.js +4 -0
  19. package/packages/sdk/dist/cli-registry.js.map +1 -1
  20. package/packages/sdk/dist/client.d.ts +6 -1
  21. package/packages/sdk/dist/client.d.ts.map +1 -1
  22. package/packages/sdk/dist/client.js +18 -0
  23. package/packages/sdk/dist/client.js.map +1 -1
  24. package/packages/sdk/dist/communicate/adapters/index.d.ts +0 -5
  25. package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -1
  26. package/packages/sdk/dist/communicate/adapters/index.js +0 -5
  27. package/packages/sdk/dist/communicate/adapters/index.js.map +1 -1
  28. package/packages/sdk/dist/communicate/adapters/pi.d.ts +0 -1
  29. package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -1
  30. package/packages/sdk/dist/communicate/adapters/pi.js +0 -4
  31. package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -1
  32. package/packages/sdk/dist/communicate/core.d.ts.map +1 -1
  33. package/packages/sdk/dist/communicate/core.js +2 -3
  34. package/packages/sdk/dist/communicate/core.js.map +1 -1
  35. package/packages/sdk/dist/communicate/index.d.ts +17 -1
  36. package/packages/sdk/dist/communicate/index.d.ts.map +1 -1
  37. package/packages/sdk/dist/communicate/index.js +40 -1
  38. package/packages/sdk/dist/communicate/index.js.map +1 -1
  39. package/packages/sdk/dist/communicate/transport.d.ts +0 -1
  40. package/packages/sdk/dist/communicate/transport.d.ts.map +1 -1
  41. package/packages/sdk/dist/communicate/transport.js +42 -134
  42. package/packages/sdk/dist/communicate/transport.js.map +1 -1
  43. package/packages/sdk/dist/http.d.ts +38 -0
  44. package/packages/sdk/dist/http.d.ts.map +1 -0
  45. package/packages/sdk/dist/http.js +60 -0
  46. package/packages/sdk/dist/http.js.map +1 -0
  47. package/packages/sdk/dist/protocol.d.ts +25 -0
  48. package/packages/sdk/dist/protocol.d.ts.map +1 -1
  49. package/packages/sdk/dist/relay.d.ts +26 -3
  50. package/packages/sdk/dist/relay.d.ts.map +1 -1
  51. package/packages/sdk/dist/relay.js +62 -4
  52. package/packages/sdk/dist/relay.js.map +1 -1
  53. package/packages/sdk/dist/workflows/api-executor.d.ts +16 -0
  54. package/packages/sdk/dist/workflows/api-executor.d.ts.map +1 -0
  55. package/packages/sdk/dist/workflows/api-executor.js +94 -0
  56. package/packages/sdk/dist/workflows/api-executor.js.map +1 -0
  57. package/packages/sdk/dist/workflows/builder.d.ts +14 -0
  58. package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
  59. package/packages/sdk/dist/workflows/builder.js +26 -0
  60. package/packages/sdk/dist/workflows/builder.js.map +1 -1
  61. package/packages/sdk/dist/workflows/cloud-runner.d.ts +15 -0
  62. package/packages/sdk/dist/workflows/cloud-runner.d.ts.map +1 -0
  63. package/packages/sdk/dist/workflows/cloud-runner.js +41 -0
  64. package/packages/sdk/dist/workflows/cloud-runner.js.map +1 -0
  65. package/packages/sdk/dist/workflows/index.d.ts +2 -0
  66. package/packages/sdk/dist/workflows/index.d.ts.map +1 -1
  67. package/packages/sdk/dist/workflows/index.js +1 -0
  68. package/packages/sdk/dist/workflows/index.js.map +1 -1
  69. package/packages/sdk/dist/workflows/run.d.ts.map +1 -1
  70. package/packages/sdk/dist/workflows/run.js +4 -0
  71. package/packages/sdk/dist/workflows/run.js.map +1 -1
  72. package/packages/sdk/dist/workflows/runner.d.ts +14 -0
  73. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  74. package/packages/sdk/dist/workflows/runner.js +154 -10
  75. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  76. package/packages/sdk/dist/workflows/types.d.ts +13 -3
  77. package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
  78. package/packages/sdk/dist/workflows/types.js +5 -1
  79. package/packages/sdk/dist/workflows/types.js.map +1 -1
  80. package/packages/sdk/dist/workflows/validator.d.ts.map +1 -1
  81. package/packages/sdk/dist/workflows/validator.js +12 -0
  82. package/packages/sdk/dist/workflows/validator.js.map +1 -1
  83. package/packages/sdk/package.json +13 -3
  84. package/packages/sdk/src/__tests__/channel-management.test.ts +131 -0
  85. package/packages/sdk/src/__tests__/communicate/core.test.ts +36 -88
  86. package/packages/sdk/src/__tests__/communicate/transport.test.ts +41 -80
  87. package/packages/sdk/src/__tests__/orchestration-upgrades.test.ts +120 -0
  88. package/packages/sdk/src/__tests__/relay-channel-ops.test.ts +121 -0
  89. package/packages/sdk/src/broker-path.ts +74 -0
  90. package/packages/sdk/src/cli-registry.ts +4 -0
  91. package/packages/sdk/src/client.ts +28 -0
  92. package/packages/sdk/src/communicate/adapters/index.ts +0 -5
  93. package/packages/sdk/src/communicate/adapters/pi.ts +1 -5
  94. package/packages/sdk/src/communicate/core.ts +6 -10
  95. package/packages/sdk/src/communicate/index.ts +57 -1
  96. package/packages/sdk/src/communicate/transport.ts +46 -177
  97. package/packages/sdk/src/http.ts +96 -0
  98. package/packages/sdk/src/protocol.ts +24 -0
  99. package/packages/sdk/src/relay.ts +93 -8
  100. package/packages/sdk/src/workflows/README.md +5 -2
  101. package/packages/sdk/src/workflows/api-executor.ts +108 -0
  102. package/packages/sdk/src/workflows/builder.ts +40 -0
  103. package/packages/sdk/src/workflows/cloud-runner.ts +56 -0
  104. package/packages/sdk/src/workflows/index.ts +2 -0
  105. package/packages/sdk/src/workflows/run.ts +5 -0
  106. package/packages/sdk/src/workflows/runner.ts +181 -11
  107. package/packages/sdk/src/workflows/types.ts +19 -4
  108. package/packages/sdk/src/workflows/validator.ts +15 -0
  109. package/packages/sdk-py/README.md +7 -0
  110. package/packages/sdk-py/pyproject.toml +1 -1
  111. package/packages/sdk-py/src/agent_relay/__init__.py +2 -0
  112. package/packages/sdk-py/src/agent_relay/client.py +4 -0
  113. package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +0 -9
  114. package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +5 -9
  115. package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +5 -7
  116. package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +3 -13
  117. package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +5 -2
  118. package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +5 -9
  119. package/packages/sdk-py/src/agent_relay/communicate/core.py +7 -24
  120. package/packages/sdk-py/src/agent_relay/communicate/transport.py +35 -212
  121. package/packages/sdk-py/src/agent_relay/communicate/types.py +1 -1
  122. package/packages/sdk-py/src/agent_relay/protocol.py +1 -0
  123. package/packages/sdk-py/src/agent_relay/relay.py +9 -1
  124. package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +6 -6
  125. package/packages/sdk-py/tests/communicate/conftest.py +86 -233
  126. package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +2 -2
  127. package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +14 -24
  128. package/packages/sdk-py/tests/communicate/test_transport.py +65 -54
  129. package/packages/sdk-py/tests/test_send_message_mode.py +91 -0
  130. package/packages/telemetry/package.json +1 -1
  131. package/packages/trajectory/package.json +2 -2
  132. package/packages/user-directory/package.json +2 -2
  133. package/packages/utils/package.json +2 -2
@@ -16,6 +16,7 @@ import { getCliDefinition } from '../cli-registry.js';
16
16
  import { resolveCliSync } from '../cli-resolver.js';
17
17
  import { loadCustomSteps, resolveAllCustomSteps, validateCustomStepsUsage, CustomStepsParseError, CustomStepResolutionError, } from './custom-steps.js';
18
18
  import { collectCliSession } from './cli-session-collector.js';
19
+ import { executeApiStep } from './api-executor.js';
19
20
  import { InMemoryWorkflowDb } from './memory-db.js';
20
21
  import { formatRunSummaryTable } from './run-summary-table.js';
21
22
  import { WorkflowTrajectory } from './trajectory.js';
@@ -60,6 +61,7 @@ export class WorkflowRunner {
60
61
  cwd;
61
62
  summaryDir;
62
63
  executor;
64
+ envSecrets;
63
65
  /** @internal exposed for CLI signal-handler shutdown only */
64
66
  relay;
65
67
  relaycast;
@@ -120,6 +122,7 @@ export class WorkflowRunner {
120
122
  this.summaryDir = options.summaryDir ?? path.join(this.cwd, '.relay', 'summaries');
121
123
  this.workersPath = path.join(this.cwd, '.agent-relay', 'team', 'workers.json');
122
124
  this.executor = options.executor;
125
+ this.envSecrets = options.envSecrets;
123
126
  }
124
127
  // ── Path resolution ─────────────────────────────────────────────────────
125
128
  /** Expand environment variables like $HOME or $VAR in a path string. */
@@ -1089,7 +1092,7 @@ export class WorkflowRunner {
1089
1092
  }
1090
1093
  // 6. Resource estimation
1091
1094
  const peakConcurrency = Math.max(...waves.map((w) => w.steps.length), 0);
1092
- const totalAgentSteps = resolvedSteps.filter((s) => s.type !== 'deterministic' && s.type !== 'worktree').length;
1095
+ const totalAgentSteps = resolvedSteps.filter((s) => s.type !== 'deterministic' && s.type !== 'worktree' && s.type !== 'integration').length;
1093
1096
  // 7. Check maxConcurrency against wave widths
1094
1097
  const maxConcurrency = resolved.swarm.maxConcurrency;
1095
1098
  if (maxConcurrency !== undefined) {
@@ -1140,6 +1143,15 @@ export class WorkflowRunner {
1140
1143
  throw new Error(`${source}: deterministic step "${s.name}" must have a "command" field`);
1141
1144
  }
1142
1145
  }
1146
+ else if (s.type === 'integration') {
1147
+ // Integration steps require integration and action
1148
+ if (typeof s.integration !== 'string') {
1149
+ throw new Error(`${source}: integration step "${s.name}" must have an "integration" string field`);
1150
+ }
1151
+ if (typeof s.action !== 'string') {
1152
+ throw new Error(`${source}: integration step "${s.name}" must have an "action" string field`);
1153
+ }
1154
+ }
1143
1155
  else {
1144
1156
  // Agent steps (type undefined or 'agent') require agent and task
1145
1157
  if (typeof s.agent !== 'string' || typeof s.task !== 'string') {
@@ -1162,7 +1174,7 @@ export class WorkflowRunner {
1162
1174
  this.detectLeadWorkerDeadlock(w.steps, agents, source, w.name);
1163
1175
  // Warn if non-interactive agent task is excessively large before interpolation
1164
1176
  for (const step of w.steps) {
1165
- if (step.type === 'deterministic' || step.type === 'worktree')
1177
+ if (step.type === 'deterministic' || step.type === 'worktree' || step.type === 'integration')
1166
1178
  continue;
1167
1179
  const agentDef = agents.find((a) => a.name === step.agent);
1168
1180
  const isNonInteractive = agentDef?.interactive === false || ['worker', 'reviewer', 'analyst'].includes(agentDef?.preset ?? '');
@@ -1208,7 +1220,7 @@ export class WorkflowRunner {
1208
1220
  }
1209
1221
  for (const step of steps) {
1210
1222
  // Only check interactive agent steps (leads)
1211
- if (step.type === 'deterministic' || step.type === 'worktree')
1223
+ if (step.type === 'deterministic' || step.type === 'worktree' || step.type === 'integration')
1212
1224
  continue;
1213
1225
  const agentDef = agents.find((a) => a.name === step.agent);
1214
1226
  // Skip non-interactive agents — they can't wait for channel signals
@@ -1255,6 +1267,15 @@ export class WorkflowRunner {
1255
1267
  if (step.command) {
1256
1268
  step.command = this.interpolate(step.command, vars);
1257
1269
  }
1270
+ // Resolve variables in integration step params
1271
+ if (step.params && typeof step.params === 'object') {
1272
+ for (const key of Object.keys(step.params)) {
1273
+ const val = step.params[key];
1274
+ if (typeof val === 'string') {
1275
+ step.params[key] = this.interpolate(val, vars);
1276
+ }
1277
+ }
1278
+ }
1258
1279
  }
1259
1280
  }
1260
1281
  }
@@ -1372,8 +1393,8 @@ export class WorkflowRunner {
1372
1393
  // Build step rows
1373
1394
  const stepStates = new Map();
1374
1395
  for (const step of resolvedWorkflow.steps) {
1375
- // Handle agent, deterministic, and worktree steps
1376
- const isNonAgent = step.type === 'deterministic' || step.type === 'worktree';
1396
+ // Handle agent, deterministic, worktree, and integration steps
1397
+ const isNonAgent = step.type === 'deterministic' || step.type === 'worktree' || step.type === 'integration';
1377
1398
  const stepRow = {
1378
1399
  id: this.generateId(),
1379
1400
  runId,
@@ -1385,7 +1406,9 @@ export class WorkflowRunner {
1385
1406
  ? (step.command ?? '')
1386
1407
  : step.type === 'worktree'
1387
1408
  ? (step.branch ?? '')
1388
- : (step.task ?? ''),
1409
+ : step.type === 'integration'
1410
+ ? (`${step.integration}.${step.action}`)
1411
+ : (step.task ?? ''),
1389
1412
  dependsOn: step.dependsOn ?? [],
1390
1413
  retryCount: 0,
1391
1414
  createdAt: now,
@@ -1525,7 +1548,7 @@ export class WorkflowRunner {
1525
1548
  }
1526
1549
  const relaycastDisabled = this.relayOptions.env?.AGENT_RELAY_WORKFLOW_DISABLE_RELAYCAST === '1';
1527
1550
  const requiresBroker = !this.executor &&
1528
- workflow.steps.some((step) => step.type !== 'deterministic' && step.type !== 'worktree');
1551
+ workflow.steps.some((step) => step.type !== 'deterministic' && step.type !== 'worktree' && step.type !== 'integration');
1529
1552
  // Skip broker/relay init when an external executor handles agent spawning
1530
1553
  if (requiresBroker) {
1531
1554
  if (!relaycastDisabled) {
@@ -1710,8 +1733,16 @@ export class WorkflowRunner {
1710
1733
  };
1711
1734
  this.relaycast = undefined;
1712
1735
  this.relaycastAgent = undefined;
1713
- // Wire broker stderr to console for observability
1736
+ // Wire broker stderr to console for observability — skip empty and
1737
+ // JSON event lines (already surfaced via the broker:event emitter).
1714
1738
  this.unsubBrokerStderr = this.relay.onBrokerStderr((line) => {
1739
+ const trimmed = line.trim();
1740
+ if (!trimmed)
1741
+ return;
1742
+ // JSON event lines from the Rust EventEmitter are already parsed
1743
+ // and emitted as broker:event — no need to double-log them.
1744
+ if (trimmed.startsWith('{') && trimmed.endsWith('}'))
1745
+ return;
1715
1746
  console.log(`${chalk.dim.yellow('[broker]')} ${line}`);
1716
1747
  });
1717
1748
  if (!relaycastDisabled) {
@@ -2086,6 +2117,10 @@ export class WorkflowRunner {
2086
2117
  isWorktreeStep(step) {
2087
2118
  return step.type === 'worktree';
2088
2119
  }
2120
+ /** Check if a step is an integration (external service) step. */
2121
+ isIntegrationStep(step) {
2122
+ return step.type === 'integration';
2123
+ }
2089
2124
  async executeStep(step, stepStates, agentMap, errorHandling, runId) {
2090
2125
  // Branch: deterministic steps execute shell commands
2091
2126
  if (this.isDeterministicStep(step)) {
@@ -2095,6 +2130,10 @@ export class WorkflowRunner {
2095
2130
  if (this.isWorktreeStep(step)) {
2096
2131
  return this.executeWorktreeStep(step, stepStates, runId);
2097
2132
  }
2133
+ // Branch: integration steps interact with external services
2134
+ if (this.isIntegrationStep(step)) {
2135
+ return this.executeIntegrationStep(step, stepStates, runId);
2136
+ }
2098
2137
  // Agent step execution
2099
2138
  return this.executeAgentStep(step, stepStates, agentMap, errorHandling, runId);
2100
2139
  }
@@ -2489,6 +2528,64 @@ export class WorkflowRunner {
2489
2528
  throw new Error(`Step "${step.name}" failed: ${errorMsg}`);
2490
2529
  }
2491
2530
  }
2531
+ /**
2532
+ * Execute an integration step (external service interaction via executor).
2533
+ */
2534
+ async executeIntegrationStep(step, stepStates, runId) {
2535
+ const state = stepStates.get(step.name);
2536
+ if (!state)
2537
+ throw new Error(`Step state not found: ${step.name}`);
2538
+ this.checkAborted();
2539
+ // Mark step as running
2540
+ state.row.status = 'running';
2541
+ state.row.error = undefined;
2542
+ state.row.completionReason = undefined;
2543
+ state.row.startedAt = new Date().toISOString();
2544
+ await this.db.updateStep(state.row.id, {
2545
+ status: 'running',
2546
+ error: undefined,
2547
+ completionReason: undefined,
2548
+ startedAt: state.row.startedAt,
2549
+ updatedAt: new Date().toISOString(),
2550
+ });
2551
+ this.emit({ type: 'step:started', runId, stepName: step.name });
2552
+ this.postToChannel(`**[${step.name}]** Started (integration: ${step.integration}.${step.action})`);
2553
+ // Resolve {{steps.X.output}} in params
2554
+ const stepOutputContext = this.buildStepOutputContext(stepStates, runId);
2555
+ const resolvedParams = {};
2556
+ for (const [key, value] of Object.entries(step.params ?? {})) {
2557
+ resolvedParams[key] = this.interpolateStepTask(value, stepOutputContext);
2558
+ }
2559
+ try {
2560
+ if (!this.executor?.executeIntegrationStep) {
2561
+ throw new Error(`Integration steps require a cloud executor. Step "${step.name}" cannot run locally. ` +
2562
+ `Use "cloud run" to execute workflows with integration steps.`);
2563
+ }
2564
+ const result = await this.executor.executeIntegrationStep(step, resolvedParams, { workspaceId: this.workspaceId });
2565
+ if (!result.success) {
2566
+ throw new Error(`Integration step "${step.name}" failed: ${result.output}`);
2567
+ }
2568
+ // Mark completed
2569
+ state.row.status = 'completed';
2570
+ state.row.output = result.output;
2571
+ state.row.completedAt = new Date().toISOString();
2572
+ await this.db.updateStep(state.row.id, {
2573
+ status: 'completed',
2574
+ output: result.output,
2575
+ completedAt: state.row.completedAt,
2576
+ updatedAt: new Date().toISOString(),
2577
+ });
2578
+ await this.persistStepOutput(runId, step.name, result.output);
2579
+ this.emit({ type: 'step:completed', runId, stepName: step.name, output: result.output });
2580
+ this.postToChannel(`**[${step.name}]** Completed (integration: ${step.integration}.${step.action})`);
2581
+ }
2582
+ catch (err) {
2583
+ const errorMsg = err instanceof Error ? err.message : String(err);
2584
+ this.postToChannel(`**[${step.name}]** Failed: ${errorMsg}`);
2585
+ await this.markStepFailed(state, errorMsg, runId);
2586
+ throw new Error(`Step "${step.name}" failed: ${errorMsg}`);
2587
+ }
2588
+ }
2492
2589
  /**
2493
2590
  * Execute an agent step (LLM-powered).
2494
2591
  */
@@ -2505,6 +2602,50 @@ export class WorkflowRunner {
2505
2602
  throw new Error(`Agent "${agentName}" not found in config`);
2506
2603
  }
2507
2604
  const specialistDef = WorkflowRunner.resolveAgentDef(rawAgentDef);
2605
+ // API-mode agents: execute via direct API call instead of spawning a PTY/subprocess.
2606
+ if (specialistDef.cli === 'api') {
2607
+ const stepOutputContext = this.buildStepOutputContext(stepStates, runId);
2608
+ const resolvedTask = this.interpolateStepTask(step.task ?? '', stepOutputContext);
2609
+ state.row.status = 'running';
2610
+ state.row.startedAt = new Date().toISOString();
2611
+ await this.db.updateStep(state.row.id, {
2612
+ status: 'running',
2613
+ startedAt: state.row.startedAt,
2614
+ updatedAt: new Date().toISOString(),
2615
+ });
2616
+ this.emit({ type: 'step:started', runId, stepName: step.name });
2617
+ this.postToChannel(`**[${step.name}]** Started (api)`);
2618
+ try {
2619
+ const output = await executeApiStep(specialistDef.constraints?.model ?? 'claude-sonnet-4-20250514', resolvedTask, { envSecrets: this.envSecrets, skills: specialistDef.skills, defaultMaxTokens: specialistDef.constraints?.maxTokens });
2620
+ state.row.status = 'completed';
2621
+ state.row.output = output;
2622
+ state.row.completedAt = new Date().toISOString();
2623
+ await this.db.updateStep(state.row.id, {
2624
+ status: 'completed',
2625
+ output,
2626
+ completedAt: state.row.completedAt,
2627
+ updatedAt: new Date().toISOString(),
2628
+ });
2629
+ await this.persistStepOutput(runId, step.name, output);
2630
+ this.emit({ type: 'step:completed', runId, stepName: step.name, output });
2631
+ }
2632
+ catch (apiError) {
2633
+ const errorMessage = apiError instanceof Error ? apiError.message : String(apiError);
2634
+ state.row.status = 'failed';
2635
+ state.row.error = errorMessage;
2636
+ state.row.completedAt = new Date().toISOString();
2637
+ await this.db.updateStep(state.row.id, {
2638
+ status: 'failed',
2639
+ error: errorMessage,
2640
+ completedAt: state.row.completedAt,
2641
+ updatedAt: new Date().toISOString(),
2642
+ });
2643
+ this.emit({ type: 'step:failed', runId, stepName: step.name, error: errorMessage });
2644
+ this.postToChannel(`**[${step.name}]** Failed (api): ${errorMessage}`);
2645
+ throw apiError;
2646
+ }
2647
+ return;
2648
+ }
2508
2649
  const usesOwnerFlow = specialistDef.interactive !== false;
2509
2650
  const currentPattern = this.currentConfig?.swarm?.pattern ?? '';
2510
2651
  const isHubPattern = WorkflowRunner.HUB_PATTERNS.has(currentPattern);
@@ -3649,10 +3790,13 @@ export class WorkflowRunner {
3649
3790
  * Delegates to the consolidated CLI registry for per-CLI arg formats.
3650
3791
  */
3651
3792
  static buildNonInteractiveCommand(cli, task, extraArgs = []) {
3793
+ if (cli === 'api') {
3794
+ throw new Error('cli "api" uses direct API calls, not a subprocess command');
3795
+ }
3652
3796
  const resolvedCli = cli === 'cursor' ? resolveCursorCli() : cli;
3653
3797
  const def = getCliDefinition(resolvedCli);
3654
- if (!def) {
3655
- throw new Error(`Unknown CLI: ${resolvedCli}`);
3798
+ if (!def || def.binaries.length === 0) {
3799
+ throw new Error(`Unknown or non-executable CLI: ${resolvedCli}`);
3656
3800
  }
3657
3801
  return {
3658
3802
  cmd: def.binaries[0],