agent-relay 3.0.1 → 3.1.0

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 (142) hide show
  1. package/README.md +37 -244
  2. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  3. package/bin/agent-relay-broker-darwin-x64 +0 -0
  4. package/bin/agent-relay-broker-linux-arm64 +0 -0
  5. package/bin/agent-relay-broker-linux-x64 +0 -0
  6. package/dist/index.cjs +342 -60
  7. package/dist/src/cli/commands/core.d.ts +2 -0
  8. package/dist/src/cli/commands/core.d.ts.map +1 -1
  9. package/dist/src/cli/commands/core.js +9 -2
  10. package/dist/src/cli/commands/core.js.map +1 -1
  11. package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -1
  12. package/dist/src/cli/lib/broker-lifecycle.js +87 -28
  13. package/dist/src/cli/lib/broker-lifecycle.js.map +1 -1
  14. package/package.json +9 -8
  15. package/packages/acp-bridge/README.md +50 -67
  16. package/packages/acp-bridge/package.json +2 -2
  17. package/packages/config/package.json +1 -1
  18. package/packages/hooks/package.json +4 -4
  19. package/packages/memory/package.json +2 -2
  20. package/packages/policy/package.json +2 -2
  21. package/packages/sdk/README.md +169 -64
  22. package/packages/sdk/dist/__tests__/contract-fixtures.test.js +76 -9
  23. package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +1 -1
  24. package/packages/sdk/dist/__tests__/facade.test.js +48 -0
  25. package/packages/sdk/dist/__tests__/facade.test.js.map +1 -1
  26. package/packages/sdk/dist/__tests__/integration.test.js +11 -5
  27. package/packages/sdk/dist/__tests__/integration.test.js.map +1 -1
  28. package/packages/sdk/dist/__tests__/unit.test.js +36 -0
  29. package/packages/sdk/dist/__tests__/unit.test.js.map +1 -1
  30. package/packages/sdk/dist/client.d.ts +36 -3
  31. package/packages/sdk/dist/client.d.ts.map +1 -1
  32. package/packages/sdk/dist/client.js +142 -9
  33. package/packages/sdk/dist/client.js.map +1 -1
  34. package/packages/sdk/dist/protocol.d.ts +7 -1
  35. package/packages/sdk/dist/protocol.d.ts.map +1 -1
  36. package/packages/sdk/dist/relay.d.ts +74 -11
  37. package/packages/sdk/dist/relay.d.ts.map +1 -1
  38. package/packages/sdk/dist/relay.js +175 -27
  39. package/packages/sdk/dist/relay.js.map +1 -1
  40. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  41. package/packages/sdk/dist/workflows/runner.js +71 -36
  42. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  43. package/packages/sdk/dist/workflows/types.d.ts +1 -1
  44. package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
  45. package/packages/sdk/package.json +2 -2
  46. package/packages/sdk/src/__tests__/contract-fixtures.test.ts +88 -9
  47. package/packages/sdk/src/__tests__/error-scenarios.test.ts +1 -1
  48. package/packages/sdk/src/__tests__/facade.test.ts +68 -0
  49. package/packages/sdk/src/__tests__/idle-nudge.test.ts +205 -257
  50. package/packages/sdk/src/__tests__/integration.test.ts +11 -5
  51. package/packages/sdk/src/__tests__/orchestration-upgrades.test.ts +277 -13
  52. package/packages/sdk/src/__tests__/swarm-coordinator.test.ts +1 -0
  53. package/packages/sdk/src/__tests__/unit.test.ts +44 -0
  54. package/packages/sdk/src/__tests__/workflow-runner.test.ts +67 -7
  55. package/packages/sdk/src/__tests__/workflow-trajectory.test.ts +4 -5
  56. package/packages/sdk/src/client.ts +195 -14
  57. package/packages/sdk/src/examples/workflows/runner-idle-refactor.yaml +306 -0
  58. package/packages/sdk/src/protocol.ts +7 -2
  59. package/packages/sdk/src/relay.ts +271 -38
  60. package/packages/sdk/src/workflows/runner.ts +73 -42
  61. package/packages/sdk/src/workflows/schema.json +1 -1
  62. package/packages/sdk/src/workflows/types.ts +1 -1
  63. package/packages/sdk/vitest.config.ts +1 -0
  64. package/packages/sdk-py/README.md +89 -102
  65. package/packages/sdk-py/agent_relay/__init__.py +16 -19
  66. package/packages/sdk-py/pyproject.toml +6 -2
  67. package/packages/sdk-py/src/agent_relay/__init__.py +35 -1
  68. package/packages/sdk-py/src/agent_relay/client.py +776 -0
  69. package/packages/sdk-py/src/agent_relay/models.py +27 -0
  70. package/packages/sdk-py/src/agent_relay/protocol.py +114 -0
  71. package/packages/sdk-py/src/agent_relay/relay.py +860 -0
  72. package/packages/sdk-py/tests/test_relay_lifecycle_hooks.py +250 -0
  73. package/packages/telemetry/package.json +1 -1
  74. package/packages/trajectory/package.json +2 -2
  75. package/packages/user-directory/package.json +2 -2
  76. package/packages/utils/package.json +2 -2
  77. package/scripts/postinstall.js +35 -162
  78. package/packages/sdk/.trajectories/active/traj_1771875803391_84ca57b2.json +0 -50
  79. package/packages/sdk/.trajectories/active/traj_1771891934534_06504121.json +0 -50
  80. package/packages/sdk/.trajectories/active/traj_1771891957929_211afc4e.json +0 -50
  81. package/packages/sdk/.trajectories/active/traj_1771891982509_38c84638.json +0 -50
  82. package/packages/sdk/.trajectories/completed/traj_1771875803188_cd6d181c.json +0 -80
  83. package/packages/sdk/.trajectories/completed/traj_1771875803204_f2aeb8c8.json +0 -80
  84. package/packages/sdk/.trajectories/completed/traj_1771875803210_d65f3f1a.json +0 -80
  85. package/packages/sdk/.trajectories/completed/traj_1771875803218_e454a25d.json +0 -80
  86. package/packages/sdk/.trajectories/completed/traj_1771875803223_d7a64815.json +0 -80
  87. package/packages/sdk/.trajectories/completed/traj_1771875803227_7e56da5b.json +0 -80
  88. package/packages/sdk/.trajectories/completed/traj_1771875803235_4fbf93b4.json +0 -80
  89. package/packages/sdk/.trajectories/completed/traj_1771875803243_47931c71.json +0 -80
  90. package/packages/sdk/.trajectories/completed/traj_1771875803258_3816f3fe.json +0 -80
  91. package/packages/sdk/.trajectories/completed/traj_1771875803268_8061140e.json +0 -80
  92. package/packages/sdk/.trajectories/completed/traj_1771875803326_ae6f9c78.json +0 -80
  93. package/packages/sdk/.trajectories/completed/traj_1771875808396_cbde0a6c.json +0 -91
  94. package/packages/sdk/.trajectories/completed/traj_1771875812026_aa2442bb.json +0 -91
  95. package/packages/sdk/.trajectories/completed/traj_1771875815431_c2c656c5.json +0 -91
  96. package/packages/sdk/.trajectories/completed/traj_1771875818645_3a4dbf02.json +0 -91
  97. package/packages/sdk/.trajectories/completed/traj_1771891934403_24923c03.json +0 -80
  98. package/packages/sdk/.trajectories/completed/traj_1771891934421_dca16e24.json +0 -80
  99. package/packages/sdk/.trajectories/completed/traj_1771891934430_057706f7.json +0 -80
  100. package/packages/sdk/.trajectories/completed/traj_1771891934442_faf97382.json +0 -80
  101. package/packages/sdk/.trajectories/completed/traj_1771891934454_5542ecd5.json +0 -80
  102. package/packages/sdk/.trajectories/completed/traj_1771891934464_12202a08.json +0 -80
  103. package/packages/sdk/.trajectories/completed/traj_1771891934487_94378275.json +0 -80
  104. package/packages/sdk/.trajectories/completed/traj_1771891934503_ca728c13.json +0 -80
  105. package/packages/sdk/.trajectories/completed/traj_1771891934519_100af69a.json +0 -80
  106. package/packages/sdk/.trajectories/completed/traj_1771891934536_62ad39d9.json +0 -80
  107. package/packages/sdk/.trajectories/completed/traj_1771891934553_d6798a52.json +0 -80
  108. package/packages/sdk/.trajectories/completed/traj_1771891939537_541c8096.json +0 -91
  109. package/packages/sdk/.trajectories/completed/traj_1771891942985_36ab9a4d.json +0 -91
  110. package/packages/sdk/.trajectories/completed/traj_1771891946453_e8a6e05f.json +0 -91
  111. package/packages/sdk/.trajectories/completed/traj_1771891949838_5de0de84.json +0 -91
  112. package/packages/sdk/.trajectories/completed/traj_1771891957807_0ecfb4f4.json +0 -80
  113. package/packages/sdk/.trajectories/completed/traj_1771891957827_c4539239.json +0 -80
  114. package/packages/sdk/.trajectories/completed/traj_1771891957836_91168b48.json +0 -80
  115. package/packages/sdk/.trajectories/completed/traj_1771891957848_8c5cad0b.json +0 -80
  116. package/packages/sdk/.trajectories/completed/traj_1771891957857_0986b293.json +0 -80
  117. package/packages/sdk/.trajectories/completed/traj_1771891957872_8a3113af.json +0 -80
  118. package/packages/sdk/.trajectories/completed/traj_1771891957884_0bb85208.json +0 -80
  119. package/packages/sdk/.trajectories/completed/traj_1771891957892_86c75e2e.json +0 -80
  120. package/packages/sdk/.trajectories/completed/traj_1771891957907_98ca0e6f.json +0 -80
  121. package/packages/sdk/.trajectories/completed/traj_1771891957918_d9091231.json +0 -80
  122. package/packages/sdk/.trajectories/completed/traj_1771891957931_dcaf77ed.json +0 -80
  123. package/packages/sdk/.trajectories/completed/traj_1771891962931_eb1fdee2.json +0 -91
  124. package/packages/sdk/.trajectories/completed/traj_1771891966262_9061a93f.json +0 -91
  125. package/packages/sdk/.trajectories/completed/traj_1771891969915_1adaba19.json +0 -91
  126. package/packages/sdk/.trajectories/completed/traj_1771891973588_f08b79e9.json +0 -91
  127. package/packages/sdk/.trajectories/completed/traj_1771891982421_f1985bce.json +0 -80
  128. package/packages/sdk/.trajectories/completed/traj_1771891982432_e7a84163.json +0 -80
  129. package/packages/sdk/.trajectories/completed/traj_1771891982447_369b842a.json +0 -80
  130. package/packages/sdk/.trajectories/completed/traj_1771891982469_5fc45199.json +0 -80
  131. package/packages/sdk/.trajectories/completed/traj_1771891982495_454c7cb3.json +0 -80
  132. package/packages/sdk/.trajectories/completed/traj_1771891982514_08098e03.json +0 -80
  133. package/packages/sdk/.trajectories/completed/traj_1771891982526_b351d778.json +0 -80
  134. package/packages/sdk/.trajectories/completed/traj_1771891982533_fa542d83.json +0 -80
  135. package/packages/sdk/.trajectories/completed/traj_1771891982540_18ab24dc.json +0 -80
  136. package/packages/sdk/.trajectories/completed/traj_1771891982544_5b4fa163.json +0 -80
  137. package/packages/sdk/.trajectories/completed/traj_1771891982548_c13f089a.json +0 -80
  138. package/packages/sdk/.trajectories/completed/traj_1771891987510_23f6da1f.json +0 -91
  139. package/packages/sdk/.trajectories/completed/traj_1771891991466_912c2e04.json +0 -91
  140. package/packages/sdk/.trajectories/completed/traj_1771891994891_60604be2.json +0 -91
  141. package/packages/sdk/.trajectories/completed/traj_1771891998370_cfaf9b8b.json +0 -91
  142. package/packages/sdk/bin/agent-relay-broker +0 -0
package/dist/index.cjs CHANGED
@@ -8975,6 +8975,9 @@ var AgentRelayProcessError = class extends Error {
8975
8975
  this.name = "AgentRelayProcessError";
8976
8976
  }
8977
8977
  };
8978
+ function isHeadlessProvider(value) {
8979
+ return value === "claude" || value === "opencode";
8980
+ }
8978
8981
  var AgentRelayClient = class _AgentRelayClient {
8979
8982
  options;
8980
8983
  child;
@@ -8989,6 +8992,8 @@ var AgentRelayClient = class _AgentRelayClient {
8989
8992
  eventBuffer = [];
8990
8993
  maxBufferSize = 1e3;
8991
8994
  exitPromise;
8995
+ /** The workspace key returned by the broker in its hello_ack response. */
8996
+ workspaceKey;
8992
8997
  constructor(options = {}) {
8993
8998
  this.options = {
8994
8999
  binaryPath: options.binaryPath ?? resolveDefaultBinaryPath(),
@@ -9098,11 +9103,12 @@ var AgentRelayClient = class _AgentRelayClient {
9098
9103
  });
9099
9104
  return result;
9100
9105
  }
9101
- async spawnHeadlessClaude(input) {
9106
+ async spawnHeadless(input) {
9102
9107
  await this.start();
9103
9108
  const agent = {
9104
9109
  name: input.name,
9105
- runtime: "headless_claude",
9110
+ runtime: "headless",
9111
+ provider: input.provider,
9106
9112
  args: input.args ?? [],
9107
9113
  channels: input.channels ?? []
9108
9114
  };
@@ -9112,6 +9118,42 @@ var AgentRelayClient = class _AgentRelayClient {
9112
9118
  });
9113
9119
  return result;
9114
9120
  }
9121
+ async spawnProvider(input) {
9122
+ const transport = input.transport ?? (input.provider === "opencode" ? "headless" : "pty");
9123
+ if (transport === "headless") {
9124
+ if (!isHeadlessProvider(input.provider)) {
9125
+ throw new AgentRelayProcessError(`provider '${input.provider}' does not support headless transport (supported: claude, opencode)`);
9126
+ }
9127
+ return this.spawnHeadless({
9128
+ name: input.name,
9129
+ provider: input.provider,
9130
+ args: input.args,
9131
+ channels: input.channels,
9132
+ task: input.task
9133
+ });
9134
+ }
9135
+ return this.spawnPty({
9136
+ name: input.name,
9137
+ cli: input.provider,
9138
+ args: input.args,
9139
+ channels: input.channels,
9140
+ task: input.task,
9141
+ model: input.model,
9142
+ cwd: input.cwd,
9143
+ team: input.team,
9144
+ shadowOf: input.shadowOf,
9145
+ shadowMode: input.shadowMode,
9146
+ idleThresholdSecs: input.idleThresholdSecs,
9147
+ restartPolicy: input.restartPolicy,
9148
+ continueFrom: input.continueFrom
9149
+ });
9150
+ }
9151
+ async spawnClaude(input) {
9152
+ return this.spawnProvider({ ...input, provider: "claude" });
9153
+ }
9154
+ async spawnOpencode(input) {
9155
+ return this.spawnProvider({ ...input, provider: "opencode" });
9156
+ }
9115
9157
  async release(name, reason) {
9116
9158
  await this.start();
9117
9159
  return this.requestOk("release_agent", { name, reason });
@@ -9250,8 +9292,11 @@ var AgentRelayClient = class _AgentRelayClient {
9250
9292
  resolve3();
9251
9293
  });
9252
9294
  });
9253
- await this.requestHello();
9295
+ const helloAck = await this.requestHello();
9254
9296
  console.log("[broker] Broker ready (hello handshake complete)");
9297
+ if (helloAck.workspace_key) {
9298
+ this.workspaceKey = helloAck.workspace_key;
9299
+ }
9255
9300
  }
9256
9301
  disposeProcessHandles() {
9257
9302
  this.stdoutRl?.close();
@@ -9367,12 +9412,16 @@ var AgentRelayClient = class _AgentRelayClient {
9367
9412
  }
9368
9413
  };
9369
9414
  var CLI_MODEL_FLAG_CLIS = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "goose", "aider"]);
9415
+ var CLI_DEFAULT_ARGS = {
9416
+ codex: ["-c", "check_for_update_on_startup=false"]
9417
+ };
9370
9418
  function buildPtyArgsWithModel(cli, args, model) {
9371
- const baseArgs = [...args];
9419
+ const cliName = cli.split(":")[0].trim().toLowerCase();
9420
+ const defaultArgs = CLI_DEFAULT_ARGS[cliName] ?? [];
9421
+ const baseArgs = [...defaultArgs, ...args];
9372
9422
  if (!model) {
9373
9423
  return baseArgs;
9374
9424
  }
9375
- const cliName = cli.split(":")[0].trim().toLowerCase();
9376
9425
  if (!CLI_MODEL_FLAG_CLIS.has(cliName)) {
9377
9426
  return baseArgs;
9378
9427
  }
@@ -9403,6 +9452,71 @@ function expandTilde(p) {
9403
9452
  function isExplicitPath(binaryPath) {
9404
9453
  return binaryPath.includes("/") || binaryPath.includes("\\") || binaryPath.startsWith(".") || binaryPath.startsWith("~");
9405
9454
  }
9455
+ function detectPlatformSuffix() {
9456
+ const platformMap = {
9457
+ darwin: { arm64: "darwin-arm64", x64: "darwin-x64" },
9458
+ linux: { arm64: "linux-arm64", x64: "linux-x64" }
9459
+ };
9460
+ return platformMap[process.platform]?.[process.arch] ?? null;
9461
+ }
9462
+ function getLatestVersionSync() {
9463
+ try {
9464
+ const result = (0, import_node_child_process.execSync)("curl -fsSL https://api.github.com/repos/AgentWorkforce/relay/releases/latest", {
9465
+ timeout: 15e3,
9466
+ stdio: ["pipe", "pipe", "pipe"]
9467
+ }).toString();
9468
+ const match = result.match(/"tag_name"\s*:\s*"v?([^"]+)"/);
9469
+ return match?.[1] ?? null;
9470
+ } catch {
9471
+ return null;
9472
+ }
9473
+ }
9474
+ function installBrokerBinary() {
9475
+ const suffix = detectPlatformSuffix();
9476
+ if (!suffix) {
9477
+ throw new AgentRelayProcessError(`Unsupported platform: ${process.platform}-${process.arch}`);
9478
+ }
9479
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
9480
+ const installDir = import_node_path.default.join(homeDir, ".agent-relay", "bin");
9481
+ const brokerExe = process.platform === "win32" ? "agent-relay-broker.exe" : "agent-relay-broker";
9482
+ const targetPath = import_node_path.default.join(installDir, brokerExe);
9483
+ console.log(`[agent-relay] Broker binary not found, installing for ${suffix}...`);
9484
+ const version3 = getLatestVersionSync();
9485
+ if (!version3) {
9486
+ throw new AgentRelayProcessError("Failed to fetch latest agent-relay version from GitHub.\nInstall manually: curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash");
9487
+ }
9488
+ const binaryName = `agent-relay-broker-${suffix}`;
9489
+ const downloadUrl = `https://github.com/AgentWorkforce/relay/releases/download/v${version3}/${binaryName}`;
9490
+ console.log(`[agent-relay] Downloading v${version3} from ${downloadUrl}`);
9491
+ try {
9492
+ import_node_fs.default.mkdirSync(installDir, { recursive: true });
9493
+ (0, import_node_child_process.execSync)(`curl -fsSL "${downloadUrl}" -o "${targetPath}"`, {
9494
+ timeout: 6e4,
9495
+ stdio: ["pipe", "pipe", "pipe"]
9496
+ });
9497
+ import_node_fs.default.chmodSync(targetPath, 493);
9498
+ if (process.platform === "darwin") {
9499
+ try {
9500
+ (0, import_node_child_process.execSync)(`codesign --force --sign - "${targetPath}"`, {
9501
+ timeout: 1e4,
9502
+ stdio: ["pipe", "pipe", "pipe"]
9503
+ });
9504
+ } catch {
9505
+ }
9506
+ }
9507
+ (0, import_node_child_process.execSync)(`"${targetPath}" --help`, { timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] });
9508
+ } catch (err) {
9509
+ try {
9510
+ import_node_fs.default.unlinkSync(targetPath);
9511
+ } catch {
9512
+ }
9513
+ const message = err instanceof Error ? err.message : String(err);
9514
+ throw new AgentRelayProcessError(`Failed to install broker binary: ${message}
9515
+ Install manually: curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash`);
9516
+ }
9517
+ console.log(`[agent-relay] Broker installed to ${targetPath}`);
9518
+ return targetPath;
9519
+ }
9406
9520
  function resolveDefaultBinaryPath() {
9407
9521
  const brokerExe = process.platform === "win32" ? "agent-relay-broker.exe" : "agent-relay-broker";
9408
9522
  const moduleDir = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta_url));
@@ -9410,7 +9524,15 @@ function resolveDefaultBinaryPath() {
9410
9524
  if (import_node_fs.default.existsSync(workspaceRelease)) {
9411
9525
  return workspaceRelease;
9412
9526
  }
9413
- const bundled = import_node_path.default.resolve(moduleDir, "..", "bin", brokerExe);
9527
+ const binDir = import_node_path.default.resolve(moduleDir, "..", "bin");
9528
+ const suffix = detectPlatformSuffix();
9529
+ if (suffix) {
9530
+ const platformBinary = import_node_path.default.join(binDir, `agent-relay-broker-${suffix}`);
9531
+ if (import_node_fs.default.existsSync(platformBinary)) {
9532
+ return platformBinary;
9533
+ }
9534
+ }
9535
+ const bundled = import_node_path.default.join(binDir, brokerExe);
9414
9536
  if (import_node_fs.default.existsSync(bundled)) {
9415
9537
  return bundled;
9416
9538
  }
@@ -9419,7 +9541,7 @@ function resolveDefaultBinaryPath() {
9419
9541
  if (import_node_fs.default.existsSync(standaloneBroker)) {
9420
9542
  return standaloneBroker;
9421
9543
  }
9422
- return "agent-relay";
9544
+ return installBrokerBinary();
9423
9545
  }
9424
9546
 
9425
9547
  // packages/config/src/relay-config.ts
@@ -15176,7 +15298,7 @@ var DefaultModels = {
15176
15298
  };
15177
15299
 
15178
15300
  // node_modules/@relaycast/sdk/dist/version.js
15179
- var SDK_VERSION = "0.4.0";
15301
+ var SDK_VERSION = "0.4.2";
15180
15302
 
15181
15303
  // node_modules/@relaycast/types/node_modules/zod/v4/classic/external.js
15182
15304
  var external_exports2 = {};
@@ -45021,12 +45143,26 @@ var AgentRelay = class {
45021
45143
  onDeliveryUpdate = null;
45022
45144
  onAgentExitRequested = null;
45023
45145
  onAgentIdle = null;
45146
+ // ── Public accessors ────────────────────────────────────────────────────
45147
+ /** The resolved Relaycast workspace API key (available after first spawn). */
45148
+ get workspaceKey() {
45149
+ return this.relayApiKey;
45150
+ }
45151
+ /** Observer URL for the auto-created workspace (available after first spawn). */
45152
+ get observerUrl() {
45153
+ if (!this.relayApiKey)
45154
+ return void 0;
45155
+ return `https://observer.relaycast.dev/?key=${this.relayApiKey}`;
45156
+ }
45024
45157
  // Shorthand spawners
45025
45158
  codex;
45026
45159
  claude;
45027
45160
  gemini;
45028
45161
  clientOptions;
45029
45162
  defaultChannels;
45163
+ workspaceName;
45164
+ relaycastBaseUrl;
45165
+ relayApiKey;
45030
45166
  client;
45031
45167
  startPromise;
45032
45168
  unsubEvent;
@@ -45043,6 +45179,8 @@ var AgentRelay = class {
45043
45179
  idleResolverSeq = 0;
45044
45180
  constructor(options = {}) {
45045
45181
  this.defaultChannels = options.channels ?? ["general"];
45182
+ this.workspaceName = options.workspaceName;
45183
+ this.relaycastBaseUrl = options.relaycastBaseUrl;
45046
45184
  this.clientOptions = {
45047
45185
  binaryPath: options.binaryPath,
45048
45186
  binaryArgs: options.binaryArgs,
@@ -45084,26 +45222,44 @@ var AgentRelay = class {
45084
45222
  console.warn(`[AgentRelay] spawnPty("${input.name}"): no channels specified, defaulting to "general". Set explicit channels for workflow isolation.`);
45085
45223
  }
45086
45224
  const channels = input.channels ?? ["general"];
45087
- const result = await client.spawnPty({
45225
+ const lifecycleContext = {
45088
45226
  name: input.name,
45089
45227
  cli: input.cli,
45090
- args: input.args,
45091
45228
  channels,
45092
- task: input.task,
45093
- model: input.model,
45094
- cwd: input.cwd,
45095
- team: input.team,
45096
- shadowOf: input.shadowOf,
45097
- shadowMode: input.shadowMode,
45098
- idleThresholdSecs: input.idleThresholdSecs,
45099
- restartPolicy: input.restartPolicy
45100
- });
45101
- this.readyAgents.delete(result.name);
45102
- this.messageReadyAgents.delete(result.name);
45103
- this.exitedAgents.delete(result.name);
45104
- this.idleAgents.delete(result.name);
45229
+ task: input.task
45230
+ };
45231
+ await this.invokeLifecycleHook(input.onStart, lifecycleContext, `spawnPty("${input.name}") onStart`);
45232
+ let result;
45233
+ try {
45234
+ result = await client.spawnPty({
45235
+ name: input.name,
45236
+ cli: input.cli,
45237
+ args: input.args,
45238
+ channels,
45239
+ task: input.task,
45240
+ model: input.model,
45241
+ cwd: input.cwd,
45242
+ team: input.team,
45243
+ shadowOf: input.shadowOf,
45244
+ shadowMode: input.shadowMode,
45245
+ idleThresholdSecs: input.idleThresholdSecs,
45246
+ restartPolicy: input.restartPolicy
45247
+ });
45248
+ } catch (error95) {
45249
+ await this.invokeLifecycleHook(input.onError, {
45250
+ ...lifecycleContext,
45251
+ error: error95
45252
+ }, `spawnPty("${input.name}") onError`);
45253
+ throw error95;
45254
+ }
45255
+ this.resetAgentLifecycleState(result.name);
45105
45256
  const agent = this.makeAgent(result.name, result.runtime, channels);
45106
45257
  this.knownAgents.set(agent.name, agent);
45258
+ await this.invokeLifecycleHook(input.onSuccess, {
45259
+ ...lifecycleContext,
45260
+ name: result.name,
45261
+ runtime: result.runtime
45262
+ }, `spawnPty("${input.name}") onSuccess`);
45107
45263
  return agent;
45108
45264
  }
45109
45265
  async spawn(name, cli, task, options) {
@@ -45119,7 +45275,10 @@ var AgentRelay = class {
45119
45275
  shadowOf: options?.shadowOf,
45120
45276
  shadowMode: options?.shadowMode,
45121
45277
  idleThresholdSecs: options?.idleThresholdSecs,
45122
- restartPolicy: options?.restartPolicy
45278
+ restartPolicy: options?.restartPolicy,
45279
+ onStart: options?.onStart,
45280
+ onSuccess: options?.onSuccess,
45281
+ onError: options?.onError
45123
45282
  });
45124
45283
  }
45125
45284
  async spawnAndWait(name, cli, task, options) {
@@ -45128,7 +45287,7 @@ var AgentRelay = class {
45128
45287
  if (waitForMessage) {
45129
45288
  return this.waitForAgentMessage(name, timeoutMs ?? 6e4);
45130
45289
  }
45131
- return this.waitForAgentReady(name, timeoutMs ?? 3e4);
45290
+ return this.waitForAgentReady(name, timeoutMs ?? 6e4);
45132
45291
  }
45133
45292
  // ── Human source ────────────────────────────────────────────────────────
45134
45293
  human(opts) {
@@ -45318,7 +45477,7 @@ var AgentRelay = class {
45318
45477
  * The agent's CLI may not yet be ready to receive messages.
45319
45478
  * Use `waitForAgentMessage()` for full readiness.
45320
45479
  */
45321
- async waitForAgentReady(name, timeoutMs = 3e4) {
45480
+ async waitForAgentReady(name, timeoutMs = 6e4) {
45322
45481
  const client = await this.ensureStarted();
45323
45482
  const existing = this.knownAgents.get(name);
45324
45483
  if (existing && this.readyAgents.has(name)) {
@@ -45492,14 +45651,42 @@ var AgentRelay = class {
45492
45651
  }
45493
45652
  }
45494
45653
  }
45654
+ /**
45655
+ * Ensure a Relaycast workspace API key is available.
45656
+ * Resolution order:
45657
+ * 1. Already resolved (cached from a previous call)
45658
+ * 2. RELAY_API_KEY in options.env
45659
+ * 3. RELAY_API_KEY in process.env
45660
+ * 4. Auto-create a fresh workspace via the Relaycast REST API
45661
+ */
45662
+ async ensureRelaycastApiKey() {
45663
+ if (this.relayApiKey)
45664
+ return;
45665
+ const envKey = this.clientOptions.env?.RELAY_API_KEY ?? process.env.RELAY_API_KEY;
45666
+ if (envKey) {
45667
+ this.relayApiKey = envKey;
45668
+ if (!this.clientOptions.env) {
45669
+ this.clientOptions.env = { ...process.env, RELAY_API_KEY: envKey };
45670
+ } else if (!this.clientOptions.env.RELAY_API_KEY) {
45671
+ this.clientOptions.env.RELAY_API_KEY = envKey;
45672
+ }
45673
+ return;
45674
+ }
45675
+ if (!this.clientOptions.env) {
45676
+ this.clientOptions.env = { ...process.env };
45677
+ }
45678
+ }
45495
45679
  async ensureStarted() {
45496
45680
  if (this.client)
45497
45681
  return this.client;
45498
45682
  if (this.startPromise)
45499
45683
  return this.startPromise;
45500
- this.startPromise = AgentRelayClient.start(this.clientOptions).then((c) => {
45684
+ this.startPromise = this.ensureRelaycastApiKey().then(() => AgentRelayClient.start(this.clientOptions)).then((c) => {
45501
45685
  this.client = c;
45502
45686
  this.startPromise = void 0;
45687
+ if (c.workspaceKey) {
45688
+ this.relayApiKey = c.workspaceKey;
45689
+ }
45503
45690
  this.wireEvents(c);
45504
45691
  return c;
45505
45692
  }).catch((err) => {
@@ -45644,11 +45831,26 @@ var AgentRelay = class {
45644
45831
  },
45645
45832
  exitCode: void 0,
45646
45833
  exitSignal: void 0,
45647
- async release(reason) {
45834
+ async release(reasonOrOptions) {
45835
+ const releaseOptions = relay.normalizeReleaseOptions(reasonOrOptions);
45836
+ const releaseContext = {
45837
+ name,
45838
+ reason: releaseOptions.reason
45839
+ };
45648
45840
  const client = await relay.ensureStarted();
45649
- await client.release(name, reason);
45841
+ await relay.invokeLifecycleHook(releaseOptions.onStart, releaseContext, `release("${name}") onStart`);
45842
+ try {
45843
+ await client.release(name, releaseOptions.reason);
45844
+ await relay.invokeLifecycleHook(releaseOptions.onSuccess, releaseContext, `release("${name}") onSuccess`);
45845
+ } catch (error95) {
45846
+ await relay.invokeLifecycleHook(releaseOptions.onError, {
45847
+ ...releaseContext,
45848
+ error: error95
45849
+ }, `release("${name}") onError`);
45850
+ throw error95;
45851
+ }
45650
45852
  },
45651
- async waitForReady(timeoutMs = 3e4) {
45853
+ async waitForReady(timeoutMs = 6e4) {
45652
45854
  await relay.waitForAgentReady(name, timeoutMs);
45653
45855
  },
45654
45856
  waitForExit(timeoutMs) {
@@ -45769,31 +45971,83 @@ var AgentRelay = class {
45769
45971
  createSpawner(cli, defaultName, runtime) {
45770
45972
  return {
45771
45973
  spawn: async (options) => {
45772
- const client = await this.ensureStarted();
45773
45974
  const name = options?.name ?? defaultName;
45774
45975
  const channels = options?.channels ?? ["general"];
45775
45976
  const args = options?.args ?? [];
45776
45977
  const task = options?.task;
45777
- let result;
45778
- if (runtime === "headless_claude") {
45779
- result = await client.spawnHeadlessClaude({ name, args, channels, task });
45780
- } else {
45781
- result = await client.spawnPty({
45978
+ if (runtime === "pty") {
45979
+ return this.spawnPty({
45782
45980
  name,
45783
45981
  cli,
45784
45982
  args,
45785
45983
  channels,
45786
45984
  task,
45787
45985
  model: options?.model,
45788
- cwd: options?.cwd
45986
+ cwd: options?.cwd,
45987
+ onStart: options?.onStart,
45988
+ onSuccess: options?.onSuccess,
45989
+ onError: options?.onError
45789
45990
  });
45790
45991
  }
45992
+ const client = await this.ensureStarted();
45993
+ const lifecycleContext = {
45994
+ name,
45995
+ cli,
45996
+ channels,
45997
+ task
45998
+ };
45999
+ await this.invokeLifecycleHook(options?.onStart, lifecycleContext, `spawn("${name}") onStart`);
46000
+ let result;
46001
+ try {
46002
+ result = await client.spawnProvider({
46003
+ name,
46004
+ provider: cli,
46005
+ transport: "headless",
46006
+ args,
46007
+ channels,
46008
+ task
46009
+ });
46010
+ } catch (error95) {
46011
+ await this.invokeLifecycleHook(options?.onError, {
46012
+ ...lifecycleContext,
46013
+ error: error95
46014
+ }, `spawn("${name}") onError`);
46015
+ throw error95;
46016
+ }
46017
+ this.resetAgentLifecycleState(result.name);
45791
46018
  const agent = this.makeAgent(result.name, result.runtime, channels);
45792
46019
  this.knownAgents.set(agent.name, agent);
46020
+ await this.invokeLifecycleHook(options?.onSuccess, {
46021
+ ...lifecycleContext,
46022
+ name: result.name,
46023
+ runtime: result.runtime
46024
+ }, `spawn("${name}") onSuccess`);
45793
46025
  return agent;
45794
46026
  }
45795
46027
  };
45796
46028
  }
46029
+ async invokeLifecycleHook(hook, context, label) {
46030
+ if (!hook) {
46031
+ return;
46032
+ }
46033
+ try {
46034
+ await hook(context);
46035
+ } catch (error95) {
46036
+ console.warn(`[AgentRelay] ${label} hook threw`, error95);
46037
+ }
46038
+ }
46039
+ resetAgentLifecycleState(name) {
46040
+ this.readyAgents.delete(name);
46041
+ this.messageReadyAgents.delete(name);
46042
+ this.exitedAgents.delete(name);
46043
+ this.idleAgents.delete(name);
46044
+ }
46045
+ normalizeReleaseOptions(reasonOrOptions) {
46046
+ if (typeof reasonOrOptions === "string" || reasonOrOptions === void 0) {
46047
+ return { reason: reasonOrOptions };
46048
+ }
46049
+ return reasonOrOptions;
46050
+ }
45797
46051
  };
45798
46052
 
45799
46053
  // packages/sdk/dist/consensus.js
@@ -47299,6 +47553,22 @@ var WorkflowTrajectory = class {
47299
47553
  };
47300
47554
 
47301
47555
  // packages/sdk/dist/workflows/runner.js
47556
+ var _resolvedCursorCli;
47557
+ function resolveCursorCli() {
47558
+ if (_resolvedCursorCli !== void 0)
47559
+ return _resolvedCursorCli;
47560
+ const candidates = ["cursor-agent", "agent"];
47561
+ for (const candidate of candidates) {
47562
+ try {
47563
+ (0, import_node_child_process3.execFileSync)("which", [candidate], { stdio: "ignore" });
47564
+ _resolvedCursorCli = candidate;
47565
+ return candidate;
47566
+ } catch {
47567
+ }
47568
+ }
47569
+ _resolvedCursorCli = "agent";
47570
+ return _resolvedCursorCli;
47571
+ }
47302
47572
  var WorkflowRunner = class _WorkflowRunner {
47303
47573
  db;
47304
47574
  workspaceId;
@@ -47393,6 +47663,17 @@ var WorkflowRunner = class _WorkflowRunner {
47393
47663
  }
47394
47664
  this.relayApiKey = apiKey;
47395
47665
  this.relayApiKeyAutoCreated = true;
47666
+ const dashboardPort = process.env.AGENT_RELAY_DASHBOARD_PORT || "3888";
47667
+ fetch(`http://127.0.0.1:${dashboardPort}/api/relay-config`, {
47668
+ method: "POST",
47669
+ headers: { "content-type": "application/json" },
47670
+ body: JSON.stringify({ apiKey })
47671
+ }).then((res2) => {
47672
+ if (!res2.ok) {
47673
+ console.warn(`[WorkflowRunner] dashboard key push failed: HTTP ${res2.status}`);
47674
+ }
47675
+ }).catch(() => {
47676
+ });
47396
47677
  }
47397
47678
  getRelayEnv() {
47398
47679
  if (!this.relayApiKey) {
@@ -48075,8 +48356,7 @@ ${err.suggestion}`);
48075
48356
  this.log("API key resolved");
48076
48357
  if (this.relayApiKeyAutoCreated && this.relayApiKey) {
48077
48358
  this.log(`Workspace created \u2014 follow this run in Relaycast:`);
48078
- this.log(` RELAY_API_KEY=${this.relayApiKey}`);
48079
- this.log(` Observer: https://observer.relaycast.dev (paste key above)`);
48359
+ this.log(` Observer: https://observer.relaycast.dev/?key=${this.relayApiKey}`);
48080
48360
  this.log(` Channel: ${channel}`);
48081
48361
  }
48082
48362
  this.log("Starting broker...");
@@ -48182,19 +48462,6 @@ ${err.suggestion}`);
48182
48462
  if (!isResume && workflow2.preflight?.length) {
48183
48463
  await this.runPreflightChecks(workflow2.preflight, runId);
48184
48464
  }
48185
- if (this.relay && !isResume) {
48186
- const agentPreflight = workflow2.steps.filter((s) => s.type !== "deterministic" && s.type !== "worktree" && s.agent).map((s) => {
48187
- const agentDef = agentMap.get(s.agent);
48188
- return agentDef && agentDef.interactive !== false ? { name: `${s.name}-${runId.slice(0, 8)}`, cli: agentDef.cli } : null;
48189
- }).filter((e) => e !== null);
48190
- if (agentPreflight.length > 0) {
48191
- this.log(`Pre-registering ${agentPreflight.length} agents with Relaycast...`);
48192
- await this.relay.preflightAgents(agentPreflight).catch((err) => {
48193
- this.log(`[preflight-agents] warning: ${err.message} \u2014 continuing without pre-registration`);
48194
- });
48195
- this.log("Agent pre-registration complete");
48196
- }
48197
- }
48198
48465
  this.log(`Executing ${workflow2.steps.length} steps (pattern: ${config3.swarm.pattern})`);
48199
48466
  await this.executeSteps(workflow2, stepStates, agentMap, config3.errorHandling, runId);
48200
48467
  const allCompleted = [...stepStates.values()].every((s) => s.row.status === "completed" || s.row.status === "skipped");
@@ -48576,8 +48843,6 @@ ${trimmedOutput.slice(0, 200)}`);
48576
48843
  });
48577
48844
  await this.persistStepOutput(runId, step.name, output);
48578
48845
  this.emit({ type: "step:completed", runId, stepName: step.name, output });
48579
- this.postToChannel(`**[${step.name}]** Completed (deterministic)
48580
- ${output.slice(0, 500)}${output.length > 500 ? "\n...(truncated)" : ""}`);
48581
48846
  } catch (err) {
48582
48847
  const errorMsg = err instanceof Error ? err.message : String(err);
48583
48848
  this.postToChannel(`**[${step.name}]** Failed: ${errorMsg}`);
@@ -48792,8 +49057,6 @@ ${output.slice(0, 500)}${output.length > 500 ? "\n...(truncated)" : ""}`);
48792
49057
  });
48793
49058
  await this.persistStepOutput(runId, step.name, output);
48794
49059
  this.emit({ type: "step:completed", runId, stepName: step.name, output });
48795
- this.postToChannel(`**[${step.name}]** Completed
48796
- ${output.slice(0, 500)}${output.length > 500 ? "\n...(truncated)" : ""}`);
48797
49060
  await this.trajectory?.stepCompleted(step, output, attempt + 1);
48798
49061
  return;
48799
49062
  } catch (err) {
@@ -48831,6 +49094,11 @@ ${output.slice(0, 500)}${output.length > 500 ? "\n...(truncated)" : ""}`);
48831
49094
  return { cmd: "aider", args: ["--message", task, "--yes-always", "--no-git", ...extraArgs] };
48832
49095
  case "goose":
48833
49096
  return { cmd: "goose", args: ["run", "--text", task, "--no-session", ...extraArgs] };
49097
+ case "cursor-agent":
49098
+ case "agent":
49099
+ return { cmd: cli, args: ["--force", "-p", task, ...extraArgs] };
49100
+ case "cursor":
49101
+ return { cmd: resolveCursorCli(), args: ["--force", "-p", task, ...extraArgs] };
48834
49102
  }
48835
49103
  }
48836
49104
  /**
@@ -48838,11 +49106,12 @@ ${output.slice(0, 500)}${output.length > 500 ? "\n...(truncated)" : ""}`);
48838
49106
  * Explicit fields on the definition always win over preset-inferred defaults.
48839
49107
  */
48840
49108
  static resolveAgentDef(def) {
49109
+ const resolvedCli = def.cli === "cursor" ? resolveCursorCli() : def.cli;
48841
49110
  if (!def.preset)
48842
- return def;
49111
+ return resolvedCli !== def.cli ? { ...def, cli: resolvedCli } : def;
48843
49112
  const nonInteractivePresets = ["worker", "reviewer", "analyst"];
48844
49113
  const defaults = nonInteractivePresets.includes(def.preset) ? { interactive: false } : {};
48845
- return { ...defaults, ...def };
49114
+ return { ...defaults, ...def, cli: resolvedCli };
48846
49115
  }
48847
49116
  /**
48848
49117
  * Returns a preset-specific prefix that is prepended to the non-interactive
@@ -49157,7 +49426,17 @@ DO NOT:
49157
49426
  async waitForExitWithIdleNudging(agent, agentDef, step, timeoutMs) {
49158
49427
  const nudgeConfig = this.currentConfig?.swarm.idleNudge;
49159
49428
  if (!nudgeConfig) {
49160
- return agent.waitForExit(timeoutMs);
49429
+ const result = await Promise.race([
49430
+ agent.waitForExit(timeoutMs).then((r) => ({ kind: "exit", result: r })),
49431
+ agent.waitForIdle(timeoutMs).then((r) => ({ kind: "idle", result: r }))
49432
+ ]);
49433
+ if (result.kind === "idle" && result.result === "idle") {
49434
+ this.log(`[${step.name}] Agent "${agent.name}" went idle \u2014 treating as complete`);
49435
+ this.postToChannel(`**[${step.name}]** Agent \`${agent.name}\` idle \u2014 treating as complete`);
49436
+ await agent.release();
49437
+ return "released";
49438
+ }
49439
+ return result.result;
49161
49440
  }
49162
49441
  const nudgeAfterMs = nudgeConfig.nudgeAfterMs ?? 12e4;
49163
49442
  const escalateAfterMs = nudgeConfig.escalateAfterMs ?? 12e4;
@@ -49572,7 +49851,7 @@ ${excerpt}` : "";
49572
49851
  const withoutSystemReminders = text.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/giu, "").replace(/<system-reminder>[\s\S]*/giu, "");
49573
49852
  const normalized = withoutSystemReminders.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
49574
49853
  const ansiStripped = stripAnsi(normalized);
49575
- const SPINNER = "\\u2756\\u2738\\u2739\\u273a\\u273b\\u273c\\u273d\\u2731\\u2732\\u2733\\u2734\\u2735\\u2736\\u2737\\u2743\\u2745\\u2746\\u25d6\\u25d7\\u25d8\\u25d9\\u2022\\u25cf\\u25cb\\u25a0\\u25a1\\u25b6\\u25c0\\u23f5\\u23f6\\u23f7\\u23f8\\u23f9\\u25e2\\u25e3\\u25e4\\u25e5\\u2597\\u2596\\u2598\\u259d\\u2bc8\\u2bc7\\u2bc5\\u2bc6\\u00b7\\u2590\\u258c\\u2588\\u2584\\u2580\\u259a\\u259e";
49854
+ const SPINNER = "\\u2756\\u2738\\u2739\\u273a\\u273b\\u273c\\u273d\\u2731\\u2732\\u2733\\u2734\\u2735\\u2736\\u2737\\u2743\\u2745\\u2746\\u25d6\\u25d7\\u25d8\\u25d9\\u2022\\u25cf\\u25cb\\u25a0\\u25a1\\u25b6\\u25c0\\u23f5\\u23f6\\u23f7\\u23f8\\u23f9\\u25e2\\u25e3\\u25e4\\u25e5\\u2597\\u2596\\u2598\\u259d\\u2bc8\\u2bc7\\u2bc5\\u2bc6\\u00b7\\u2590\\u258c\\u2588\\u2584\\u2580\\u259a\\u259e\\u2b21\\u2b22";
49576
49855
  const spinnerRe = new RegExp(`[${SPINNER}]`, "gu");
49577
49856
  const spinnerClassRe = new RegExp(`^[\\s${SPINNER}]*$`, "u");
49578
49857
  const boxDrawingOnlyRe = /^[\s\u2500-\u257f\u2580-\u259f\u25a0-\u25ff\-_=~]{3,}$/u;
@@ -49582,6 +49861,7 @@ ${excerpt}` : "";
49582
49861
  const uiHintRe = /\b(?:Press\s+up\s+to\s+edit|tab\s+to\s+queue|bypass\s+permissions|esc\s+to\s+interrupt)\b/iu;
49583
49862
  const thinkingLineRe = new RegExp(`^[\\s${SPINNER}]*\\s*\\w[\\w\\s]*\\u2026\\s*$`, "u");
49584
49863
  const cursorOnlyRe = /^[\s❯⎿›»◀▶←→↑↓⟨⟩⟪⟫·]+$/u;
49864
+ const cursorAgentRe = /^(?:Cursor Agent|[\s⬡⬢]*Generating[.\s]|\[Pasted text|Auto-run all|Add a follow-up|ctrl\+c to stop|shift\+tab|Auto$|\/\s*commands|@\s*files|!\s*shell|follow-ups?\s|The user ha)/iu;
49585
49865
  const slashCommandRe = /^\/\w+\s*$/u;
49586
49866
  const mcpJsonKvRe = /^\s*"(?:type|method|params|result|id|jsonrpc|tool|name|arguments|content|role|metadata)"\s*:/u;
49587
49867
  const meaningfulContentRe = /[a-zA-Z0-9]/u;
@@ -49630,6 +49910,8 @@ ${excerpt}` : "";
49630
49910
  continue;
49631
49911
  if (cursorOnlyRe.test(trimmed))
49632
49912
  continue;
49913
+ if (cursorAgentRe.test(trimmed))
49914
+ continue;
49633
49915
  if (slashCommandRe.test(trimmed))
49634
49916
  continue;
49635
49917
  if (!meaningfulContentRe.test(trimmed))
@@ -45,6 +45,8 @@ export interface CoreRelay {
45
45
  }>;
46
46
  shutdown: () => Promise<unknown>;
47
47
  onBrokerStderr?: (listener: (line: string) => void) => () => void;
48
+ /** Relaycast workspace API key, available after the hello handshake. */
49
+ workspaceKey?: string;
48
50
  }
49
51
  export interface CoreFileSystem {
50
52
  existsSync: (path: string) => boolean;