agent-relay 3.2.1 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) 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 +1201 -869
  6. package/dist/src/cli/commands/agent-management.d.ts +2 -2
  7. package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
  8. package/dist/src/cli/commands/agent-management.js +41 -240
  9. package/dist/src/cli/commands/agent-management.js.map +1 -1
  10. package/dist/src/cli/commands/core.d.ts +1 -0
  11. package/dist/src/cli/commands/core.d.ts.map +1 -1
  12. package/dist/src/cli/commands/core.js +18 -0
  13. package/dist/src/cli/commands/core.js.map +1 -1
  14. package/dist/src/cli/commands/messaging.d.ts +1 -1
  15. package/dist/src/cli/commands/messaging.d.ts.map +1 -1
  16. package/dist/src/cli/commands/messaging.js +14 -5
  17. package/dist/src/cli/commands/messaging.js.map +1 -1
  18. package/dist/src/cli/lib/agent-management-listing.d.ts +4 -1
  19. package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -1
  20. package/dist/src/cli/lib/agent-management-listing.js +27 -2
  21. package/dist/src/cli/lib/agent-management-listing.js.map +1 -1
  22. package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -1
  23. package/dist/src/cli/lib/broker-lifecycle.js +16 -13
  24. package/dist/src/cli/lib/broker-lifecycle.js.map +1 -1
  25. package/package.json +8 -8
  26. package/packages/acp-bridge/package.json +2 -2
  27. package/packages/config/package.json +1 -1
  28. package/packages/hooks/package.json +4 -4
  29. package/packages/memory/package.json +2 -2
  30. package/packages/openclaw/README.md +2 -2
  31. package/packages/openclaw/dist/identity/files.js +2 -2
  32. package/packages/openclaw/dist/identity/files.js.map +1 -1
  33. package/packages/openclaw/dist/setup.js +2 -2
  34. package/packages/openclaw/package.json +2 -2
  35. package/packages/openclaw/skill/SKILL.md +8 -8
  36. package/packages/openclaw/src/identity/files.ts +2 -2
  37. package/packages/openclaw/src/setup.ts +2 -2
  38. package/packages/openclaw/templates/SOUL.md.template +2 -2
  39. package/packages/policy/package.json +2 -2
  40. package/packages/sdk/dist/client.d.ts +66 -0
  41. package/packages/sdk/dist/client.d.ts.map +1 -1
  42. package/packages/sdk/dist/client.js +230 -0
  43. package/packages/sdk/dist/client.js.map +1 -1
  44. package/packages/sdk/dist/examples/example.js +1 -1
  45. package/packages/sdk/dist/examples/example.js.map +1 -1
  46. package/packages/sdk/dist/relay-adapter.js +4 -4
  47. package/packages/sdk/dist/relay-adapter.js.map +1 -1
  48. package/packages/sdk/dist/workflows/builder.d.ts +18 -3
  49. package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
  50. package/packages/sdk/dist/workflows/builder.js +24 -12
  51. package/packages/sdk/dist/workflows/builder.js.map +1 -1
  52. package/packages/sdk/dist/workflows/runner.d.ts +2 -0
  53. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  54. package/packages/sdk/dist/workflows/runner.js +103 -24
  55. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  56. package/packages/sdk/dist/workflows/validator.js +1 -1
  57. package/packages/sdk/dist/workflows/validator.js.map +1 -1
  58. package/packages/sdk/package.json +2 -2
  59. package/packages/sdk/src/client.ts +301 -0
  60. package/packages/sdk/src/examples/example.ts +1 -1
  61. package/packages/sdk/src/relay-adapter.ts +4 -4
  62. package/packages/sdk/src/workflows/builder.ts +38 -11
  63. package/packages/sdk/src/workflows/runner.ts +108 -32
  64. package/packages/sdk/src/workflows/validator.ts +1 -1
  65. package/packages/sdk-py/pyproject.toml +1 -1
  66. package/packages/telemetry/package.json +1 -1
  67. package/packages/trajectory/package.json +2 -2
  68. package/packages/user-directory/package.json +2 -2
  69. package/packages/utils/package.json +2 -2
  70. package/relay-snippets/agent-relay-protocol.md +4 -4
  71. package/relay-snippets/agent-relay-snippet.md +9 -9
package/dist/index.cjs CHANGED
@@ -35,13 +35,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
35
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
36
 
37
37
  // packages/config/src/bridge-utils.ts
38
- var import_node_child_process2, import_node_util, execAsync;
38
+ var import_node_child_process, import_node_util, execAsync;
39
39
  var init_bridge_utils = __esm({
40
40
  "packages/config/src/bridge-utils.ts"() {
41
41
  "use strict";
42
- import_node_child_process2 = require("node:child_process");
42
+ import_node_child_process = require("node:child_process");
43
43
  import_node_util = require("node:util");
44
- execAsync = (0, import_node_util.promisify)(import_node_child_process2.exec);
44
+ execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
45
45
  }
46
46
  });
47
47
 
@@ -6885,14 +6885,14 @@ var require_parser = __commonJS({
6885
6885
  case "scalar":
6886
6886
  case "single-quoted-scalar":
6887
6887
  case "double-quoted-scalar": {
6888
- const fs9 = this.flowScalar(this.type);
6888
+ const fs10 = this.flowScalar(this.type);
6889
6889
  if (atNextItem || it.value) {
6890
- map2.items.push({ start, key: fs9, sep: [] });
6890
+ map2.items.push({ start, key: fs10, sep: [] });
6891
6891
  this.onKeyLine = true;
6892
6892
  } else if (it.sep) {
6893
- this.stack.push(fs9);
6893
+ this.stack.push(fs10);
6894
6894
  } else {
6895
- Object.assign(it, { key: fs9, sep: [] });
6895
+ Object.assign(it, { key: fs10, sep: [] });
6896
6896
  this.onKeyLine = true;
6897
6897
  }
6898
6898
  return;
@@ -7020,13 +7020,13 @@ var require_parser = __commonJS({
7020
7020
  case "scalar":
7021
7021
  case "single-quoted-scalar":
7022
7022
  case "double-quoted-scalar": {
7023
- const fs9 = this.flowScalar(this.type);
7023
+ const fs10 = this.flowScalar(this.type);
7024
7024
  if (!it || it.value)
7025
- fc.items.push({ start: [], key: fs9, sep: [] });
7025
+ fc.items.push({ start: [], key: fs10, sep: [] });
7026
7026
  else if (it.sep)
7027
- this.stack.push(fs9);
7027
+ this.stack.push(fs10);
7028
7028
  else
7029
- Object.assign(it, { key: fs9, sep: [] });
7029
+ Object.assign(it, { key: fs10, sep: [] });
7030
7030
  return;
7031
7031
  }
7032
7032
  case "flow-map-end":
@@ -7467,11 +7467,11 @@ var init_types = __esm({
7467
7467
  });
7468
7468
 
7469
7469
  // packages/memory/dist/adapters/inmemory.js
7470
- var import_node_crypto10, InMemoryAdapter;
7470
+ var import_node_crypto11, InMemoryAdapter;
7471
7471
  var init_inmemory = __esm({
7472
7472
  "packages/memory/dist/adapters/inmemory.js"() {
7473
7473
  "use strict";
7474
- import_node_crypto10 = require("node:crypto");
7474
+ import_node_crypto11 = require("node:crypto");
7475
7475
  InMemoryAdapter = class {
7476
7476
  type = "inmemory";
7477
7477
  memories = /* @__PURE__ */ new Map();
@@ -7486,7 +7486,7 @@ var init_inmemory = __esm({
7486
7486
  async init() {
7487
7487
  }
7488
7488
  async add(content, options) {
7489
- const id = (0, import_node_crypto10.randomUUID)();
7489
+ const id = (0, import_node_crypto11.randomUUID)();
7490
7490
  const now = Date.now();
7491
7491
  const entry = {
7492
7492
  id,
@@ -8825,6 +8825,7 @@ __export(index_exports, {
8825
8825
  HOOK_ABI_VERSION: () => HOOK_ABI_VERSION,
8826
8826
  HookEmitter: () => HookEmitter,
8827
8827
  HookRegistry: () => HookRegistry,
8828
+ HttpAgentRelayClient: () => HttpAgentRelayClient,
8828
8829
  InMemoryAdapter: () => InMemoryAdapter,
8829
8830
  InMemoryWorkflowDb: () => InMemoryWorkflowDb,
8830
8831
  JsonFileWorkflowDb: () => JsonFileWorkflowDb,
@@ -8951,623 +8952,12 @@ var PROTOCOL_VERSION = 1;
8951
8952
 
8952
8953
  // packages/sdk/dist/client.js
8953
8954
  var import_node_events = require("node:events");
8954
- var import_node_child_process = require("node:child_process");
8955
+ var import_node_child_process2 = require("node:child_process");
8955
8956
  var import_node_readline = require("node:readline");
8956
- var import_node_fs = __toESM(require("node:fs"), 1);
8957
- var import_node_os = __toESM(require("node:os"), 1);
8958
- var import_node_path = __toESM(require("node:path"), 1);
8957
+ var import_node_fs2 = __toESM(require("node:fs"), 1);
8958
+ var import_node_os3 = __toESM(require("node:os"), 1);
8959
+ var import_node_path3 = __toESM(require("node:path"), 1);
8959
8960
  var import_node_url = require("node:url");
8960
- var AgentRelayProtocolError = class extends Error {
8961
- code;
8962
- retryable;
8963
- data;
8964
- constructor(payload) {
8965
- super(payload.message);
8966
- this.name = "AgentRelayProtocolError";
8967
- this.code = payload.code;
8968
- this.retryable = payload.retryable;
8969
- this.data = payload.data;
8970
- }
8971
- };
8972
- var AgentRelayProcessError = class extends Error {
8973
- constructor(message) {
8974
- super(message);
8975
- this.name = "AgentRelayProcessError";
8976
- }
8977
- };
8978
- function isHeadlessProvider(value) {
8979
- return value === "claude" || value === "opencode";
8980
- }
8981
- var AgentRelayClient = class _AgentRelayClient {
8982
- options;
8983
- child;
8984
- stdoutRl;
8985
- stderrRl;
8986
- lastStderrLine;
8987
- requestSeq = 0;
8988
- pending = /* @__PURE__ */ new Map();
8989
- startingPromise;
8990
- eventListeners = /* @__PURE__ */ new Set();
8991
- stderrListeners = /* @__PURE__ */ new Set();
8992
- eventBuffer = [];
8993
- maxBufferSize = 1e3;
8994
- exitPromise;
8995
- /** The workspace key returned by the broker in its hello_ack response. */
8996
- workspaceKey;
8997
- constructor(options = {}) {
8998
- this.options = {
8999
- binaryPath: options.binaryPath ?? resolveDefaultBinaryPath(),
9000
- binaryArgs: options.binaryArgs ?? [],
9001
- brokerName: options.brokerName ?? (import_node_path.default.basename(options.cwd ?? process.cwd()) || "project"),
9002
- channels: options.channels ?? ["general"],
9003
- cwd: options.cwd ?? process.cwd(),
9004
- env: options.env ?? process.env,
9005
- requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
9006
- shutdownTimeoutMs: options.shutdownTimeoutMs ?? 3e3,
9007
- clientName: options.clientName ?? "@agent-relay/sdk",
9008
- clientVersion: options.clientVersion ?? "0.1.0"
9009
- };
9010
- }
9011
- static async start(options = {}) {
9012
- const client = new _AgentRelayClient(options);
9013
- await client.start();
9014
- return client;
9015
- }
9016
- onEvent(listener) {
9017
- this.eventListeners.add(listener);
9018
- return () => {
9019
- this.eventListeners.delete(listener);
9020
- };
9021
- }
9022
- queryEvents(filter) {
9023
- let events = [...this.eventBuffer];
9024
- if (filter?.kind) {
9025
- events = events.filter((event) => event.kind === filter.kind);
9026
- }
9027
- if (filter?.name) {
9028
- events = events.filter((event) => "name" in event && event.name === filter.name);
9029
- }
9030
- const since = filter?.since;
9031
- if (since !== void 0) {
9032
- events = events.filter((event) => "timestamp" in event && typeof event.timestamp === "number" && event.timestamp >= since);
9033
- }
9034
- const limit = filter?.limit;
9035
- if (limit !== void 0) {
9036
- events = events.slice(-limit);
9037
- }
9038
- return events;
9039
- }
9040
- getLastEvent(kind, name) {
9041
- for (let i = this.eventBuffer.length - 1; i >= 0; i -= 1) {
9042
- const event = this.eventBuffer[i];
9043
- if (event.kind === kind && (!name || "name" in event && event.name === name)) {
9044
- return event;
9045
- }
9046
- }
9047
- return void 0;
9048
- }
9049
- onBrokerStderr(listener) {
9050
- this.stderrListeners.add(listener);
9051
- return () => {
9052
- this.stderrListeners.delete(listener);
9053
- };
9054
- }
9055
- get brokerPid() {
9056
- return this.child?.pid;
9057
- }
9058
- async start() {
9059
- if (this.child) {
9060
- return;
9061
- }
9062
- if (this.startingPromise) {
9063
- return this.startingPromise;
9064
- }
9065
- this.startingPromise = this.startInternal();
9066
- try {
9067
- await this.startingPromise;
9068
- } finally {
9069
- this.startingPromise = void 0;
9070
- }
9071
- }
9072
- /**
9073
- * Pre-register a batch of agents with Relaycast before their steps execute.
9074
- * The broker warms its token cache in parallel; subsequent spawn_agent calls
9075
- * hit the cache rather than waiting on individual HTTP registrations.
9076
- * Fire-and-forget from the caller's perspective — broker responds immediately
9077
- * and registers in the background.
9078
- */
9079
- async preflightAgents(agents) {
9080
- if (agents.length === 0)
9081
- return;
9082
- await this.start();
9083
- await this.requestOk("preflight_agents", { agents });
9084
- }
9085
- async spawnPty(input) {
9086
- await this.start();
9087
- const args = buildPtyArgsWithModel(input.cli, input.args ?? [], input.model);
9088
- const agent = {
9089
- name: input.name,
9090
- runtime: "pty",
9091
- cli: input.cli,
9092
- args,
9093
- channels: input.channels ?? [],
9094
- model: input.model,
9095
- cwd: input.cwd ?? this.options.cwd,
9096
- team: input.team,
9097
- shadow_of: input.shadowOf,
9098
- shadow_mode: input.shadowMode,
9099
- restart_policy: input.restartPolicy
9100
- };
9101
- const result = await this.requestOk("spawn_agent", {
9102
- agent,
9103
- ...input.task != null ? { initial_task: input.task } : {},
9104
- ...input.idleThresholdSecs != null ? { idle_threshold_secs: input.idleThresholdSecs } : {},
9105
- ...input.continueFrom != null ? { continue_from: input.continueFrom } : {},
9106
- ...input.skipRelayPrompt != null ? { skip_relay_prompt: input.skipRelayPrompt } : {}
9107
- });
9108
- return result;
9109
- }
9110
- async spawnHeadless(input) {
9111
- await this.start();
9112
- const agent = {
9113
- name: input.name,
9114
- runtime: "headless",
9115
- provider: input.provider,
9116
- args: input.args ?? [],
9117
- channels: input.channels ?? []
9118
- };
9119
- const result = await this.requestOk("spawn_agent", {
9120
- agent,
9121
- ...input.task != null ? { initial_task: input.task } : {},
9122
- ...input.skipRelayPrompt != null ? { skip_relay_prompt: input.skipRelayPrompt } : {}
9123
- });
9124
- return result;
9125
- }
9126
- async spawnProvider(input) {
9127
- const transport = input.transport ?? (input.provider === "opencode" ? "headless" : "pty");
9128
- if (transport === "headless") {
9129
- if (!isHeadlessProvider(input.provider)) {
9130
- throw new AgentRelayProcessError(`provider '${input.provider}' does not support headless transport (supported: claude, opencode)`);
9131
- }
9132
- return this.spawnHeadless({
9133
- name: input.name,
9134
- provider: input.provider,
9135
- args: input.args,
9136
- channels: input.channels,
9137
- task: input.task,
9138
- skipRelayPrompt: input.skipRelayPrompt
9139
- });
9140
- }
9141
- return this.spawnPty({
9142
- name: input.name,
9143
- cli: input.provider,
9144
- args: input.args,
9145
- channels: input.channels,
9146
- task: input.task,
9147
- model: input.model,
9148
- cwd: input.cwd,
9149
- team: input.team,
9150
- shadowOf: input.shadowOf,
9151
- shadowMode: input.shadowMode,
9152
- idleThresholdSecs: input.idleThresholdSecs,
9153
- restartPolicy: input.restartPolicy,
9154
- continueFrom: input.continueFrom,
9155
- skipRelayPrompt: input.skipRelayPrompt
9156
- });
9157
- }
9158
- async spawnClaude(input) {
9159
- return this.spawnProvider({ ...input, provider: "claude" });
9160
- }
9161
- async spawnOpencode(input) {
9162
- return this.spawnProvider({ ...input, provider: "opencode" });
9163
- }
9164
- async release(name, reason) {
9165
- await this.start();
9166
- return this.requestOk("release_agent", { name, reason });
9167
- }
9168
- async sendInput(name, data) {
9169
- await this.start();
9170
- return this.requestOk("send_input", { name, data });
9171
- }
9172
- async setModel(name, model, opts) {
9173
- await this.start();
9174
- return this.requestOk("set_model", {
9175
- name,
9176
- model,
9177
- timeout_ms: opts?.timeoutMs
9178
- });
9179
- }
9180
- async getMetrics(agent) {
9181
- await this.start();
9182
- return this.requestOk("get_metrics", { agent });
9183
- }
9184
- async getCrashInsights() {
9185
- await this.start();
9186
- return this.requestOk("get_crash_insights", {});
9187
- }
9188
- async sendMessage(input) {
9189
- await this.start();
9190
- try {
9191
- return await this.requestOk("send_message", {
9192
- to: input.to,
9193
- text: input.text,
9194
- from: input.from,
9195
- thread_id: input.threadId,
9196
- workspace_id: input.workspaceId,
9197
- workspace_alias: input.workspaceAlias,
9198
- priority: input.priority,
9199
- data: input.data
9200
- });
9201
- } catch (error48) {
9202
- if (error48 instanceof AgentRelayProtocolError && error48.code === "unsupported_operation") {
9203
- return { event_id: "unsupported_operation", targets: [] };
9204
- }
9205
- throw error48;
9206
- }
9207
- }
9208
- async listAgents() {
9209
- await this.start();
9210
- const result = await this.requestOk("list_agents", {});
9211
- return result.agents;
9212
- }
9213
- async getStatus() {
9214
- await this.start();
9215
- return this.requestOk("get_status", {});
9216
- }
9217
- async shutdown() {
9218
- if (!this.child) {
9219
- return;
9220
- }
9221
- void this.requestOk("shutdown", {}).catch(() => {
9222
- });
9223
- const child = this.child;
9224
- const wait = this.exitPromise ?? Promise.resolve();
9225
- const waitForExit = async (timeoutMs) => {
9226
- let timer;
9227
- const result = await Promise.race([
9228
- wait.then(() => true),
9229
- new Promise((resolve3) => {
9230
- timer = setTimeout(() => resolve3(false), timeoutMs);
9231
- })
9232
- ]);
9233
- if (timer !== void 0)
9234
- clearTimeout(timer);
9235
- return result;
9236
- };
9237
- if (await waitForExit(this.options.shutdownTimeoutMs)) {
9238
- return;
9239
- }
9240
- if (child.exitCode === null && child.signalCode === null) {
9241
- child.kill("SIGTERM");
9242
- }
9243
- if (await waitForExit(1e3)) {
9244
- return;
9245
- }
9246
- if (child.exitCode === null && child.signalCode === null) {
9247
- child.kill("SIGKILL");
9248
- }
9249
- await waitForExit(1e3);
9250
- }
9251
- async waitForExit() {
9252
- if (!this.child) {
9253
- return;
9254
- }
9255
- await this.exitPromise;
9256
- }
9257
- async startInternal() {
9258
- const resolvedBinary = expandTilde(this.options.binaryPath);
9259
- if (isExplicitPath(this.options.binaryPath) && !import_node_fs.default.existsSync(resolvedBinary)) {
9260
- throw new AgentRelayProcessError(`broker binary not found: ${this.options.binaryPath}`);
9261
- }
9262
- this.lastStderrLine = void 0;
9263
- const args = [
9264
- "init",
9265
- "--name",
9266
- this.options.brokerName,
9267
- ...this.options.channels.length > 0 ? ["--channels", this.options.channels.join(",")] : [],
9268
- ...this.options.binaryArgs
9269
- ];
9270
- const env = { ...this.options.env };
9271
- if (isExplicitPath(this.options.binaryPath)) {
9272
- const binDir = import_node_path.default.dirname(import_node_path.default.resolve(resolvedBinary));
9273
- const currentPath = env.PATH ?? env.Path ?? "";
9274
- if (!currentPath.split(import_node_path.default.delimiter).includes(binDir)) {
9275
- env.PATH = `${binDir}${import_node_path.default.delimiter}${currentPath}`;
9276
- }
9277
- }
9278
- console.error(`[broker] Starting: ${resolvedBinary} ${args.join(" ")}`);
9279
- const child = (0, import_node_child_process.spawn)(resolvedBinary, args, {
9280
- cwd: this.options.cwd,
9281
- env,
9282
- stdio: "pipe"
9283
- });
9284
- this.child = child;
9285
- this.stdoutRl = (0, import_node_readline.createInterface)({ input: child.stdout, crlfDelay: Infinity });
9286
- this.stderrRl = (0, import_node_readline.createInterface)({ input: child.stderr, crlfDelay: Infinity });
9287
- this.stdoutRl.on("line", (line) => {
9288
- this.handleStdoutLine(line);
9289
- });
9290
- this.stderrRl.on("line", (line) => {
9291
- const trimmed = line.trim();
9292
- if (trimmed) {
9293
- this.lastStderrLine = trimmed;
9294
- }
9295
- for (const listener of this.stderrListeners) {
9296
- listener(line);
9297
- }
9298
- });
9299
- this.exitPromise = new Promise((resolve3) => {
9300
- child.once("close", (code, signal) => {
9301
- const detail = this.lastStderrLine ? `: ${this.lastStderrLine}` : "";
9302
- const error48 = new AgentRelayProcessError(`broker exited (code=${code ?? "null"}, signal=${signal ?? "null"})${detail}`);
9303
- this.failAllPending(error48);
9304
- this.disposeProcessHandles();
9305
- resolve3();
9306
- });
9307
- child.once("error", (error48) => {
9308
- this.failAllPending(error48);
9309
- this.disposeProcessHandles();
9310
- resolve3();
9311
- });
9312
- });
9313
- const helloAck = await this.requestHello();
9314
- console.error("[broker] Broker ready (hello handshake complete)");
9315
- if (helloAck.workspace_key) {
9316
- this.workspaceKey = helloAck.workspace_key;
9317
- }
9318
- }
9319
- disposeProcessHandles() {
9320
- this.stdoutRl?.close();
9321
- this.stderrRl?.close();
9322
- this.stdoutRl = void 0;
9323
- this.stderrRl = void 0;
9324
- this.lastStderrLine = void 0;
9325
- this.child = void 0;
9326
- this.exitPromise = void 0;
9327
- }
9328
- failAllPending(error48) {
9329
- for (const pending of this.pending.values()) {
9330
- clearTimeout(pending.timeout);
9331
- pending.reject(error48);
9332
- }
9333
- this.pending.clear();
9334
- }
9335
- handleStdoutLine(line) {
9336
- let parsed;
9337
- try {
9338
- parsed = JSON.parse(line);
9339
- } catch {
9340
- return;
9341
- }
9342
- if (!parsed || typeof parsed !== "object") {
9343
- return;
9344
- }
9345
- if (parsed.v !== PROTOCOL_VERSION || typeof parsed.type !== "string") {
9346
- return;
9347
- }
9348
- const envelope = {
9349
- v: parsed.v,
9350
- type: parsed.type,
9351
- request_id: parsed.request_id,
9352
- payload: parsed.payload
9353
- };
9354
- if (envelope.type === "event") {
9355
- const payload = envelope.payload;
9356
- this.eventBuffer.push(payload);
9357
- if (this.eventBuffer.length > this.maxBufferSize) {
9358
- this.eventBuffer.shift();
9359
- }
9360
- for (const listener of this.eventListeners) {
9361
- listener(payload);
9362
- }
9363
- return;
9364
- }
9365
- if (!envelope.request_id) {
9366
- return;
9367
- }
9368
- const pending = this.pending.get(envelope.request_id);
9369
- if (!pending) {
9370
- return;
9371
- }
9372
- if (envelope.type === "error") {
9373
- clearTimeout(pending.timeout);
9374
- this.pending.delete(envelope.request_id);
9375
- pending.reject(new AgentRelayProtocolError(envelope.payload));
9376
- return;
9377
- }
9378
- if (envelope.type !== pending.expectedType) {
9379
- clearTimeout(pending.timeout);
9380
- this.pending.delete(envelope.request_id);
9381
- pending.reject(new AgentRelayProcessError(`unexpected response type '${envelope.type}' for request '${envelope.request_id}' (expected '${pending.expectedType}')`));
9382
- return;
9383
- }
9384
- clearTimeout(pending.timeout);
9385
- this.pending.delete(envelope.request_id);
9386
- pending.resolve(envelope);
9387
- }
9388
- async requestHello() {
9389
- const payload = {
9390
- client_name: this.options.clientName,
9391
- client_version: this.options.clientVersion
9392
- };
9393
- const frame = await this.sendRequest("hello", payload, "hello_ack");
9394
- return frame.payload;
9395
- }
9396
- async requestOk(type, payload) {
9397
- const frame = await this.sendRequest(type, payload, "ok");
9398
- const result = frame.payload;
9399
- return result.result;
9400
- }
9401
- async sendRequest(type, payload, expectedType) {
9402
- if (!this.child) {
9403
- throw new AgentRelayProcessError("broker is not running");
9404
- }
9405
- const requestId = `req_${++this.requestSeq}`;
9406
- const message = {
9407
- v: PROTOCOL_VERSION,
9408
- type,
9409
- request_id: requestId,
9410
- payload
9411
- };
9412
- const responsePromise = new Promise((resolve3, reject) => {
9413
- const timeout = setTimeout(() => {
9414
- this.pending.delete(requestId);
9415
- reject(new AgentRelayProcessError(`request timed out after ${this.options.requestTimeoutMs}ms (type='${type}', request_id='${requestId}')`));
9416
- }, this.options.requestTimeoutMs);
9417
- this.pending.set(requestId, {
9418
- expectedType,
9419
- resolve: resolve3,
9420
- reject,
9421
- timeout
9422
- });
9423
- });
9424
- const line = `${JSON.stringify(message)}
9425
- `;
9426
- if (!this.child.stdin.write(line)) {
9427
- await (0, import_node_events.once)(this.child.stdin, "drain");
9428
- }
9429
- return responsePromise;
9430
- }
9431
- };
9432
- var CLI_MODEL_FLAG_CLIS = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "goose", "aider"]);
9433
- var CLI_DEFAULT_ARGS = {
9434
- codex: ["-c", "check_for_update_on_startup=false"]
9435
- };
9436
- function buildPtyArgsWithModel(cli, args, model) {
9437
- const cliName = cli.split(":")[0].trim().toLowerCase();
9438
- const defaultArgs = CLI_DEFAULT_ARGS[cliName] ?? [];
9439
- const baseArgs = [...defaultArgs, ...args];
9440
- if (!model) {
9441
- return baseArgs;
9442
- }
9443
- if (!CLI_MODEL_FLAG_CLIS.has(cliName)) {
9444
- return baseArgs;
9445
- }
9446
- if (hasModelArg(baseArgs)) {
9447
- return baseArgs;
9448
- }
9449
- return ["--model", model, ...baseArgs];
9450
- }
9451
- function hasModelArg(args) {
9452
- for (let i = 0; i < args.length; i += 1) {
9453
- const arg = args[i];
9454
- if (arg === "--model") {
9455
- return true;
9456
- }
9457
- if (arg.startsWith("--model=")) {
9458
- return true;
9459
- }
9460
- }
9461
- return false;
9462
- }
9463
- function expandTilde(p) {
9464
- if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) {
9465
- const home = import_node_os.default.homedir();
9466
- return import_node_path.default.join(home, p.slice(2));
9467
- }
9468
- return p;
9469
- }
9470
- function isExplicitPath(binaryPath) {
9471
- return binaryPath.includes("/") || binaryPath.includes("\\") || binaryPath.startsWith(".") || binaryPath.startsWith("~");
9472
- }
9473
- function detectPlatformSuffix() {
9474
- const platformMap = {
9475
- darwin: { arm64: "darwin-arm64", x64: "darwin-x64" },
9476
- linux: { arm64: "linux-arm64", x64: "linux-x64" },
9477
- win32: { x64: "win32-x64" }
9478
- };
9479
- return platformMap[process.platform]?.[process.arch] ?? null;
9480
- }
9481
- function getLatestVersionSync() {
9482
- try {
9483
- const result = (0, import_node_child_process.execSync)("curl -fsSL https://api.github.com/repos/AgentWorkforce/relay/releases/latest", {
9484
- timeout: 15e3,
9485
- stdio: ["pipe", "pipe", "pipe"]
9486
- }).toString();
9487
- const match = result.match(/"tag_name"\s*:\s*"([^"]+)"/);
9488
- if (!match?.[1])
9489
- return null;
9490
- return match[1].replace(/^openclaw-/, "").replace(/^v/, "");
9491
- } catch {
9492
- return null;
9493
- }
9494
- }
9495
- function installBrokerBinary() {
9496
- const suffix = detectPlatformSuffix();
9497
- if (!suffix) {
9498
- throw new AgentRelayProcessError(`Unsupported platform: ${process.platform}-${process.arch}`);
9499
- }
9500
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
9501
- const installDir = import_node_path.default.join(homeDir, ".agent-relay", "bin");
9502
- const brokerExe = process.platform === "win32" ? "agent-relay-broker.exe" : "agent-relay-broker";
9503
- const targetPath = import_node_path.default.join(installDir, brokerExe);
9504
- console.log(`[agent-relay] Broker binary not found, installing for ${suffix}...`);
9505
- const version2 = getLatestVersionSync();
9506
- if (!version2) {
9507
- 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");
9508
- }
9509
- const binaryName = `agent-relay-broker-${suffix}`;
9510
- const downloadUrl = `https://github.com/AgentWorkforce/relay/releases/download/v${version2}/${binaryName}`;
9511
- console.log(`[agent-relay] Downloading v${version2} from ${downloadUrl}`);
9512
- try {
9513
- import_node_fs.default.mkdirSync(installDir, { recursive: true });
9514
- (0, import_node_child_process.execSync)(`curl -fsSL "${downloadUrl}" -o "${targetPath}"`, {
9515
- timeout: 6e4,
9516
- stdio: ["pipe", "pipe", "pipe"]
9517
- });
9518
- import_node_fs.default.chmodSync(targetPath, 493);
9519
- if (process.platform === "darwin") {
9520
- try {
9521
- (0, import_node_child_process.execSync)(`xattr -d com.apple.quarantine "${targetPath}" 2>/dev/null || true`, {
9522
- timeout: 1e4,
9523
- stdio: ["pipe", "pipe", "pipe"]
9524
- });
9525
- } catch {
9526
- }
9527
- try {
9528
- (0, import_node_child_process.execSync)(`codesign --force --sign - "${targetPath}"`, {
9529
- timeout: 1e4,
9530
- stdio: ["pipe", "pipe", "pipe"]
9531
- });
9532
- } catch {
9533
- }
9534
- }
9535
- (0, import_node_child_process.execSync)(`"${targetPath}" --help`, { timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] });
9536
- } catch (err) {
9537
- try {
9538
- import_node_fs.default.unlinkSync(targetPath);
9539
- } catch {
9540
- }
9541
- const message = err instanceof Error ? err.message : String(err);
9542
- throw new AgentRelayProcessError(`Failed to install broker binary: ${message}
9543
- Install manually: curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash`);
9544
- }
9545
- console.log(`[agent-relay] Broker installed to ${targetPath}`);
9546
- return targetPath;
9547
- }
9548
- function resolveDefaultBinaryPath() {
9549
- const brokerExe = process.platform === "win32" ? "agent-relay-broker.exe" : "agent-relay-broker";
9550
- const moduleDir = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta_url));
9551
- const workspaceRelease = import_node_path.default.resolve(moduleDir, "..", "..", "..", "target", "release", brokerExe);
9552
- if (import_node_fs.default.existsSync(workspaceRelease)) {
9553
- return workspaceRelease;
9554
- }
9555
- const binDir = import_node_path.default.resolve(moduleDir, "..", "bin");
9556
- const suffix = detectPlatformSuffix();
9557
- if (suffix) {
9558
- const ext = process.platform === "win32" ? ".exe" : "";
9559
- const platformBinary = import_node_path.default.join(binDir, `agent-relay-broker-${suffix}${ext}`);
9560
- if (import_node_fs.default.existsSync(platformBinary)) {
9561
- return platformBinary;
9562
- }
9563
- }
9564
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
9565
- const standaloneBroker = import_node_path.default.join(homeDir, ".agent-relay", "bin", brokerExe);
9566
- if (import_node_fs.default.existsSync(standaloneBroker)) {
9567
- return standaloneBroker;
9568
- }
9569
- return installBrokerBinary();
9570
- }
9571
8961
 
9572
8962
  // packages/config/src/relay-config.ts
9573
8963
  var DEFAULT_CONNECTION_CONFIG = {
@@ -9580,28 +8970,66 @@ var DEFAULT_CONNECTION_CONFIG = {
9580
8970
  };
9581
8971
 
9582
8972
  // packages/config/src/bridge-config.ts
9583
- var import_node_path3 = __toESM(require("node:path"), 1);
9584
- var import_node_os3 = __toESM(require("node:os"), 1);
9585
-
9586
- // packages/config/src/project-namespace.ts
9587
8973
  var import_node_path2 = __toESM(require("node:path"), 1);
9588
8974
  var import_node_os2 = __toESM(require("node:os"), 1);
8975
+
8976
+ // packages/config/src/project-namespace.ts
8977
+ var import_node_crypto = __toESM(require("node:crypto"), 1);
8978
+ var import_node_path = __toESM(require("node:path"), 1);
8979
+ var import_node_fs = __toESM(require("node:fs"), 1);
8980
+ var import_node_os = __toESM(require("node:os"), 1);
9589
8981
  function getGlobalBaseDir() {
9590
8982
  if (process.env.AGENT_RELAY_DATA_DIR) {
9591
8983
  return process.env.AGENT_RELAY_DATA_DIR;
9592
8984
  }
9593
8985
  const xdgDataHome = process.env.XDG_DATA_HOME;
9594
8986
  if (xdgDataHome) {
9595
- return import_node_path2.default.join(xdgDataHome, "agent-relay");
8987
+ return import_node_path.default.join(xdgDataHome, "agent-relay");
9596
8988
  }
9597
- return import_node_path2.default.join(import_node_os2.default.homedir(), ".agent-relay");
8989
+ return import_node_path.default.join(import_node_os.default.homedir(), ".agent-relay");
9598
8990
  }
9599
8991
  var GLOBAL_BASE_DIR = getGlobalBaseDir();
8992
+ var PROJECT_DATA_DIR = ".agent-relay";
8993
+ function hashPath(projectPath) {
8994
+ const normalized = import_node_path.default.resolve(projectPath);
8995
+ const hash2 = import_node_crypto.default.createHash("sha256").update(normalized).digest("hex");
8996
+ return hash2.substring(0, 12);
8997
+ }
8998
+ function findProjectRoot(startDir = process.cwd()) {
8999
+ if (process.env.AGENT_RELAY_PROJECT) {
9000
+ return import_node_path.default.resolve(process.env.AGENT_RELAY_PROJECT);
9001
+ }
9002
+ let current = import_node_path.default.resolve(startDir);
9003
+ const root = import_node_path.default.parse(current).root;
9004
+ const markers = [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".agent-relay"];
9005
+ while (current !== root) {
9006
+ for (const marker of markers) {
9007
+ if (import_node_fs.default.existsSync(import_node_path.default.join(current, marker))) {
9008
+ return current;
9009
+ }
9010
+ }
9011
+ current = import_node_path.default.dirname(current);
9012
+ }
9013
+ return import_node_path.default.resolve(startDir);
9014
+ }
9015
+ function getProjectPaths(projectRoot) {
9016
+ const root = projectRoot ?? findProjectRoot();
9017
+ const projectId = hashPath(root);
9018
+ const dataDir = import_node_path.default.join(root, PROJECT_DATA_DIR);
9019
+ return {
9020
+ dataDir,
9021
+ teamDir: import_node_path.default.join(dataDir, "team"),
9022
+ dbPath: import_node_path.default.join(dataDir, "messages.sqlite"),
9023
+ socketPath: import_node_path.default.join(dataDir, "relay.sock"),
9024
+ projectRoot: root,
9025
+ projectId
9026
+ };
9027
+ }
9600
9028
 
9601
9029
  // packages/config/src/bridge-config.ts
9602
9030
  var CONFIG_PATHS = [
9603
- import_node_path3.default.join(import_node_os3.default.homedir(), ".agent-relay", "bridge.json"),
9604
- import_node_path3.default.join(import_node_os3.default.homedir(), ".config", "agent-relay", "bridge.json")
9031
+ import_node_path2.default.join(import_node_os2.default.homedir(), ".agent-relay", "bridge.json"),
9032
+ import_node_path2.default.join(import_node_os2.default.homedir(), ".config", "agent-relay", "bridge.json")
9605
9033
  ];
9606
9034
 
9607
9035
  // packages/config/src/index.ts
@@ -15501,93 +14929,924 @@ var ModelOptions = {
15501
14929
  Droid: DROID_MODEL_OPTIONS,
15502
14930
  Opencode: OPENCODE_MODEL_OPTIONS
15503
14931
  };
15504
- var SwarmPatterns = {
15505
- /** Central coordinator distributes tasks to workers */
15506
- HUB_SPOKE: "hub-spoke",
15507
- /** Directed acyclic graph with dependencies */
15508
- DAG: "dag",
15509
- /** Parallel execution across multiple agents */
15510
- FAN_OUT: "fan-out",
15511
- /** Sequential processing through stages */
15512
- PIPELINE: "pipeline",
15513
- /** Agents reach agreement before proceeding */
15514
- CONSENSUS: "consensus",
15515
- /** Fully connected peer-to-peer communication */
15516
- MESH: "mesh",
15517
- /** Sequential handoff between agents */
15518
- HANDOFF: "handoff",
15519
- /** Cascading delegation */
15520
- CASCADE: "cascade",
15521
- /** Agents debate to reach conclusion */
15522
- DEBATE: "debate",
15523
- /** Tree-structured coordination */
15524
- HIERARCHICAL: "hierarchical"
14932
+ var SwarmPatterns = {
14933
+ /** Central coordinator distributes tasks to workers */
14934
+ HUB_SPOKE: "hub-spoke",
14935
+ /** Directed acyclic graph with dependencies */
14936
+ DAG: "dag",
14937
+ /** Parallel execution across multiple agents */
14938
+ FAN_OUT: "fan-out",
14939
+ /** Sequential processing through stages */
14940
+ PIPELINE: "pipeline",
14941
+ /** Agents reach agreement before proceeding */
14942
+ CONSENSUS: "consensus",
14943
+ /** Fully connected peer-to-peer communication */
14944
+ MESH: "mesh",
14945
+ /** Sequential handoff between agents */
14946
+ HANDOFF: "handoff",
14947
+ /** Cascading delegation */
14948
+ CASCADE: "cascade",
14949
+ /** Agents debate to reach conclusion */
14950
+ DEBATE: "debate",
14951
+ /** Tree-structured coordination */
14952
+ HIERARCHICAL: "hierarchical"
14953
+ };
14954
+ var CLIRegistry = {
14955
+ claude: {
14956
+ name: "Claude Code",
14957
+ package: "@anthropic-ai/claude-code",
14958
+ version: "2.1.72",
14959
+ install: "npm install -g @anthropic-ai/claude-code",
14960
+ npmLink: "https://www.npmjs.com/package/@anthropic-ai"
14961
+ },
14962
+ codex: {
14963
+ name: "Codex CLI",
14964
+ package: "@openai/codex",
14965
+ version: "0.114.0",
14966
+ install: "npm install -g @openai/codex",
14967
+ npmLink: "https://www.npmjs.com/package/@openai/codex"
14968
+ },
14969
+ gemini: {
14970
+ name: "Gemini CLI",
14971
+ package: "@google/gemini-cli",
14972
+ version: "0.33.0",
14973
+ install: "npm install -g @google/gemini-cli",
14974
+ npmLink: "https://www.npmjs.com/package/@google/gemini-cli"
14975
+ },
14976
+ cursor: {
14977
+ name: "Cursor",
14978
+ package: "cursor",
14979
+ version: "2026.02.27-e7d2ef6",
14980
+ install: "Download from cursor.com",
14981
+ npmLink: void 0
14982
+ },
14983
+ droid: {
14984
+ name: "Droid",
14985
+ package: "droid",
14986
+ version: "0.1.0",
14987
+ install: "Download from droid.dev",
14988
+ npmLink: void 0
14989
+ },
14990
+ opencode: {
14991
+ name: "OpenCode",
14992
+ package: "opencode-ai",
14993
+ version: "1.2.24",
14994
+ install: "npm install -g opencode-ai",
14995
+ npmLink: "https://www.npmjs.com/package/opencode-ai"
14996
+ },
14997
+ aider: {
14998
+ name: "Aider",
14999
+ package: "aider-chat",
15000
+ version: "0.72.1",
15001
+ install: "pip install aider-chat",
15002
+ npmLink: void 0
15003
+ },
15004
+ goose: {
15005
+ name: "Goose",
15006
+ package: "goose-ai",
15007
+ version: "1.0.16",
15008
+ install: "pip install goose-ai",
15009
+ npmLink: void 0
15010
+ }
15011
+ };
15012
+ var DefaultModels = {
15013
+ claude: "sonnet",
15014
+ codex: "gpt-5.4",
15015
+ gemini: "gemini-3.1-pro-preview",
15016
+ cursor: "opus-4.6-thinking",
15017
+ droid: "opus-4.6-fast",
15018
+ opencode: "openai/gpt-5.2"
15019
+ };
15020
+
15021
+ // packages/sdk/dist/client.js
15022
+ var AgentRelayProtocolError = class extends Error {
15023
+ code;
15024
+ retryable;
15025
+ data;
15026
+ constructor(payload) {
15027
+ super(payload.message);
15028
+ this.name = "AgentRelayProtocolError";
15029
+ this.code = payload.code;
15030
+ this.retryable = payload.retryable;
15031
+ this.data = payload.data;
15032
+ }
15033
+ };
15034
+ var AgentRelayProcessError = class extends Error {
15035
+ constructor(message) {
15036
+ super(message);
15037
+ this.name = "AgentRelayProcessError";
15038
+ }
15039
+ };
15040
+ function isHeadlessProvider(value) {
15041
+ return value === "claude" || value === "opencode";
15042
+ }
15043
+ var AgentRelayClient = class _AgentRelayClient {
15044
+ options;
15045
+ child;
15046
+ stdoutRl;
15047
+ stderrRl;
15048
+ lastStderrLine;
15049
+ requestSeq = 0;
15050
+ pending = /* @__PURE__ */ new Map();
15051
+ startingPromise;
15052
+ eventListeners = /* @__PURE__ */ new Set();
15053
+ stderrListeners = /* @__PURE__ */ new Set();
15054
+ eventBuffer = [];
15055
+ maxBufferSize = 1e3;
15056
+ exitPromise;
15057
+ /** The workspace key returned by the broker in its hello_ack response. */
15058
+ workspaceKey;
15059
+ constructor(options = {}) {
15060
+ this.options = {
15061
+ binaryPath: options.binaryPath ?? resolveDefaultBinaryPath(),
15062
+ binaryArgs: options.binaryArgs ?? [],
15063
+ brokerName: options.brokerName ?? (import_node_path3.default.basename(options.cwd ?? process.cwd()) || "project"),
15064
+ channels: options.channels ?? ["general"],
15065
+ cwd: options.cwd ?? process.cwd(),
15066
+ env: options.env ?? process.env,
15067
+ requestTimeoutMs: options.requestTimeoutMs ?? 1e4,
15068
+ shutdownTimeoutMs: options.shutdownTimeoutMs ?? 3e3,
15069
+ clientName: options.clientName ?? "@agent-relay/sdk",
15070
+ clientVersion: options.clientVersion ?? "0.1.0"
15071
+ };
15072
+ }
15073
+ static async start(options = {}) {
15074
+ const client = new _AgentRelayClient(options);
15075
+ await client.start();
15076
+ return client;
15077
+ }
15078
+ onEvent(listener) {
15079
+ this.eventListeners.add(listener);
15080
+ return () => {
15081
+ this.eventListeners.delete(listener);
15082
+ };
15083
+ }
15084
+ queryEvents(filter) {
15085
+ let events = [...this.eventBuffer];
15086
+ if (filter?.kind) {
15087
+ events = events.filter((event) => event.kind === filter.kind);
15088
+ }
15089
+ if (filter?.name) {
15090
+ events = events.filter((event) => "name" in event && event.name === filter.name);
15091
+ }
15092
+ const since = filter?.since;
15093
+ if (since !== void 0) {
15094
+ events = events.filter((event) => "timestamp" in event && typeof event.timestamp === "number" && event.timestamp >= since);
15095
+ }
15096
+ const limit = filter?.limit;
15097
+ if (limit !== void 0) {
15098
+ events = events.slice(-limit);
15099
+ }
15100
+ return events;
15101
+ }
15102
+ getLastEvent(kind, name) {
15103
+ for (let i = this.eventBuffer.length - 1; i >= 0; i -= 1) {
15104
+ const event = this.eventBuffer[i];
15105
+ if (event.kind === kind && (!name || "name" in event && event.name === name)) {
15106
+ return event;
15107
+ }
15108
+ }
15109
+ return void 0;
15110
+ }
15111
+ onBrokerStderr(listener) {
15112
+ this.stderrListeners.add(listener);
15113
+ return () => {
15114
+ this.stderrListeners.delete(listener);
15115
+ };
15116
+ }
15117
+ get brokerPid() {
15118
+ return this.child?.pid;
15119
+ }
15120
+ async start() {
15121
+ if (this.child) {
15122
+ return;
15123
+ }
15124
+ if (this.startingPromise) {
15125
+ return this.startingPromise;
15126
+ }
15127
+ this.startingPromise = this.startInternal();
15128
+ try {
15129
+ await this.startingPromise;
15130
+ } finally {
15131
+ this.startingPromise = void 0;
15132
+ }
15133
+ }
15134
+ /**
15135
+ * Pre-register a batch of agents with Relaycast before their steps execute.
15136
+ * The broker warms its token cache in parallel; subsequent spawn_agent calls
15137
+ * hit the cache rather than waiting on individual HTTP registrations.
15138
+ * Fire-and-forget from the caller's perspective — broker responds immediately
15139
+ * and registers in the background.
15140
+ */
15141
+ async preflightAgents(agents) {
15142
+ if (agents.length === 0)
15143
+ return;
15144
+ await this.start();
15145
+ await this.requestOk("preflight_agents", { agents });
15146
+ }
15147
+ async spawnPty(input) {
15148
+ await this.start();
15149
+ const args = buildPtyArgsWithModel(input.cli, input.args ?? [], input.model);
15150
+ const agent = {
15151
+ name: input.name,
15152
+ runtime: "pty",
15153
+ cli: input.cli,
15154
+ args,
15155
+ channels: input.channels ?? [],
15156
+ model: input.model,
15157
+ cwd: input.cwd ?? this.options.cwd,
15158
+ team: input.team,
15159
+ shadow_of: input.shadowOf,
15160
+ shadow_mode: input.shadowMode,
15161
+ restart_policy: input.restartPolicy
15162
+ };
15163
+ const result = await this.requestOk("spawn_agent", {
15164
+ agent,
15165
+ ...input.task != null ? { initial_task: input.task } : {},
15166
+ ...input.idleThresholdSecs != null ? { idle_threshold_secs: input.idleThresholdSecs } : {},
15167
+ ...input.continueFrom != null ? { continue_from: input.continueFrom } : {},
15168
+ ...input.skipRelayPrompt != null ? { skip_relay_prompt: input.skipRelayPrompt } : {}
15169
+ });
15170
+ return result;
15171
+ }
15172
+ async spawnHeadless(input) {
15173
+ await this.start();
15174
+ const agent = {
15175
+ name: input.name,
15176
+ runtime: "headless",
15177
+ provider: input.provider,
15178
+ args: input.args ?? [],
15179
+ channels: input.channels ?? []
15180
+ };
15181
+ const result = await this.requestOk("spawn_agent", {
15182
+ agent,
15183
+ ...input.task != null ? { initial_task: input.task } : {},
15184
+ ...input.skipRelayPrompt != null ? { skip_relay_prompt: input.skipRelayPrompt } : {}
15185
+ });
15186
+ return result;
15187
+ }
15188
+ async spawnProvider(input) {
15189
+ const transport = input.transport ?? (input.provider === "opencode" ? "headless" : "pty");
15190
+ if (transport === "headless") {
15191
+ if (!isHeadlessProvider(input.provider)) {
15192
+ throw new AgentRelayProcessError(`provider '${input.provider}' does not support headless transport (supported: claude, opencode)`);
15193
+ }
15194
+ return this.spawnHeadless({
15195
+ name: input.name,
15196
+ provider: input.provider,
15197
+ args: input.args,
15198
+ channels: input.channels,
15199
+ task: input.task,
15200
+ skipRelayPrompt: input.skipRelayPrompt
15201
+ });
15202
+ }
15203
+ return this.spawnPty({
15204
+ name: input.name,
15205
+ cli: input.provider,
15206
+ args: input.args,
15207
+ channels: input.channels,
15208
+ task: input.task,
15209
+ model: input.model,
15210
+ cwd: input.cwd,
15211
+ team: input.team,
15212
+ shadowOf: input.shadowOf,
15213
+ shadowMode: input.shadowMode,
15214
+ idleThresholdSecs: input.idleThresholdSecs,
15215
+ restartPolicy: input.restartPolicy,
15216
+ continueFrom: input.continueFrom,
15217
+ skipRelayPrompt: input.skipRelayPrompt
15218
+ });
15219
+ }
15220
+ async spawnClaude(input) {
15221
+ return this.spawnProvider({ ...input, provider: "claude" });
15222
+ }
15223
+ async spawnOpencode(input) {
15224
+ return this.spawnProvider({ ...input, provider: "opencode" });
15225
+ }
15226
+ async release(name, reason) {
15227
+ await this.start();
15228
+ return this.requestOk("release_agent", { name, reason });
15229
+ }
15230
+ async sendInput(name, data) {
15231
+ await this.start();
15232
+ return this.requestOk("send_input", { name, data });
15233
+ }
15234
+ async setModel(name, model, opts) {
15235
+ await this.start();
15236
+ return this.requestOk("set_model", {
15237
+ name,
15238
+ model,
15239
+ timeout_ms: opts?.timeoutMs
15240
+ });
15241
+ }
15242
+ async getMetrics(agent) {
15243
+ await this.start();
15244
+ return this.requestOk("get_metrics", { agent });
15245
+ }
15246
+ async getCrashInsights() {
15247
+ await this.start();
15248
+ return this.requestOk("get_crash_insights", {});
15249
+ }
15250
+ async sendMessage(input) {
15251
+ await this.start();
15252
+ try {
15253
+ return await this.requestOk("send_message", {
15254
+ to: input.to,
15255
+ text: input.text,
15256
+ from: input.from,
15257
+ thread_id: input.threadId,
15258
+ workspace_id: input.workspaceId,
15259
+ workspace_alias: input.workspaceAlias,
15260
+ priority: input.priority,
15261
+ data: input.data
15262
+ });
15263
+ } catch (error48) {
15264
+ if (error48 instanceof AgentRelayProtocolError && error48.code === "unsupported_operation") {
15265
+ return { event_id: "unsupported_operation", targets: [] };
15266
+ }
15267
+ throw error48;
15268
+ }
15269
+ }
15270
+ async listAgents() {
15271
+ await this.start();
15272
+ const result = await this.requestOk("list_agents", {});
15273
+ return result.agents;
15274
+ }
15275
+ async getStatus() {
15276
+ await this.start();
15277
+ return this.requestOk("get_status", {});
15278
+ }
15279
+ async shutdown() {
15280
+ if (!this.child) {
15281
+ return;
15282
+ }
15283
+ void this.requestOk("shutdown", {}).catch(() => {
15284
+ });
15285
+ const child = this.child;
15286
+ const wait = this.exitPromise ?? Promise.resolve();
15287
+ const waitForExit = async (timeoutMs) => {
15288
+ let timer;
15289
+ const result = await Promise.race([
15290
+ wait.then(() => true),
15291
+ new Promise((resolve3) => {
15292
+ timer = setTimeout(() => resolve3(false), timeoutMs);
15293
+ })
15294
+ ]);
15295
+ if (timer !== void 0)
15296
+ clearTimeout(timer);
15297
+ return result;
15298
+ };
15299
+ if (await waitForExit(this.options.shutdownTimeoutMs)) {
15300
+ return;
15301
+ }
15302
+ if (child.exitCode === null && child.signalCode === null) {
15303
+ child.kill("SIGTERM");
15304
+ }
15305
+ if (await waitForExit(1e3)) {
15306
+ return;
15307
+ }
15308
+ if (child.exitCode === null && child.signalCode === null) {
15309
+ child.kill("SIGKILL");
15310
+ }
15311
+ await waitForExit(1e3);
15312
+ }
15313
+ async waitForExit() {
15314
+ if (!this.child) {
15315
+ return;
15316
+ }
15317
+ await this.exitPromise;
15318
+ }
15319
+ async startInternal() {
15320
+ const resolvedBinary = expandTilde(this.options.binaryPath);
15321
+ if (isExplicitPath(this.options.binaryPath) && !import_node_fs2.default.existsSync(resolvedBinary)) {
15322
+ throw new AgentRelayProcessError(`broker binary not found: ${this.options.binaryPath}`);
15323
+ }
15324
+ this.lastStderrLine = void 0;
15325
+ const args = [
15326
+ "init",
15327
+ "--name",
15328
+ this.options.brokerName,
15329
+ ...this.options.channels.length > 0 ? ["--channels", this.options.channels.join(",")] : [],
15330
+ ...this.options.binaryArgs
15331
+ ];
15332
+ const env = { ...this.options.env };
15333
+ if (isExplicitPath(this.options.binaryPath)) {
15334
+ const binDir = import_node_path3.default.dirname(import_node_path3.default.resolve(resolvedBinary));
15335
+ const currentPath = env.PATH ?? env.Path ?? "";
15336
+ if (!currentPath.split(import_node_path3.default.delimiter).includes(binDir)) {
15337
+ env.PATH = `${binDir}${import_node_path3.default.delimiter}${currentPath}`;
15338
+ }
15339
+ }
15340
+ console.error(`[broker] Starting: ${resolvedBinary} ${args.join(" ")}`);
15341
+ const child = (0, import_node_child_process2.spawn)(resolvedBinary, args, {
15342
+ cwd: this.options.cwd,
15343
+ env,
15344
+ stdio: "pipe"
15345
+ });
15346
+ this.child = child;
15347
+ this.stdoutRl = (0, import_node_readline.createInterface)({ input: child.stdout, crlfDelay: Infinity });
15348
+ this.stderrRl = (0, import_node_readline.createInterface)({ input: child.stderr, crlfDelay: Infinity });
15349
+ this.stdoutRl.on("line", (line) => {
15350
+ this.handleStdoutLine(line);
15351
+ });
15352
+ this.stderrRl.on("line", (line) => {
15353
+ const trimmed = line.trim();
15354
+ if (trimmed) {
15355
+ this.lastStderrLine = trimmed;
15356
+ }
15357
+ for (const listener of this.stderrListeners) {
15358
+ listener(line);
15359
+ }
15360
+ });
15361
+ this.exitPromise = new Promise((resolve3) => {
15362
+ child.once("close", (code, signal) => {
15363
+ const detail = this.lastStderrLine ? `: ${this.lastStderrLine}` : "";
15364
+ const error48 = new AgentRelayProcessError(`broker exited (code=${code ?? "null"}, signal=${signal ?? "null"})${detail}`);
15365
+ this.failAllPending(error48);
15366
+ this.disposeProcessHandles();
15367
+ resolve3();
15368
+ });
15369
+ child.once("error", (error48) => {
15370
+ this.failAllPending(error48);
15371
+ this.disposeProcessHandles();
15372
+ resolve3();
15373
+ });
15374
+ });
15375
+ const helloAck = await this.requestHello();
15376
+ console.error("[broker] Broker ready (hello handshake complete)");
15377
+ if (helloAck.workspace_key) {
15378
+ this.workspaceKey = helloAck.workspace_key;
15379
+ }
15380
+ }
15381
+ disposeProcessHandles() {
15382
+ this.stdoutRl?.close();
15383
+ this.stderrRl?.close();
15384
+ this.stdoutRl = void 0;
15385
+ this.stderrRl = void 0;
15386
+ this.lastStderrLine = void 0;
15387
+ this.child = void 0;
15388
+ this.exitPromise = void 0;
15389
+ }
15390
+ failAllPending(error48) {
15391
+ for (const pending of this.pending.values()) {
15392
+ clearTimeout(pending.timeout);
15393
+ pending.reject(error48);
15394
+ }
15395
+ this.pending.clear();
15396
+ }
15397
+ handleStdoutLine(line) {
15398
+ let parsed;
15399
+ try {
15400
+ parsed = JSON.parse(line);
15401
+ } catch {
15402
+ return;
15403
+ }
15404
+ if (!parsed || typeof parsed !== "object") {
15405
+ return;
15406
+ }
15407
+ if (parsed.v !== PROTOCOL_VERSION || typeof parsed.type !== "string") {
15408
+ return;
15409
+ }
15410
+ const envelope = {
15411
+ v: parsed.v,
15412
+ type: parsed.type,
15413
+ request_id: parsed.request_id,
15414
+ payload: parsed.payload
15415
+ };
15416
+ if (envelope.type === "event") {
15417
+ const payload = envelope.payload;
15418
+ this.eventBuffer.push(payload);
15419
+ if (this.eventBuffer.length > this.maxBufferSize) {
15420
+ this.eventBuffer.shift();
15421
+ }
15422
+ for (const listener of this.eventListeners) {
15423
+ listener(payload);
15424
+ }
15425
+ return;
15426
+ }
15427
+ if (!envelope.request_id) {
15428
+ return;
15429
+ }
15430
+ const pending = this.pending.get(envelope.request_id);
15431
+ if (!pending) {
15432
+ return;
15433
+ }
15434
+ if (envelope.type === "error") {
15435
+ clearTimeout(pending.timeout);
15436
+ this.pending.delete(envelope.request_id);
15437
+ pending.reject(new AgentRelayProtocolError(envelope.payload));
15438
+ return;
15439
+ }
15440
+ if (envelope.type !== pending.expectedType) {
15441
+ clearTimeout(pending.timeout);
15442
+ this.pending.delete(envelope.request_id);
15443
+ pending.reject(new AgentRelayProcessError(`unexpected response type '${envelope.type}' for request '${envelope.request_id}' (expected '${pending.expectedType}')`));
15444
+ return;
15445
+ }
15446
+ clearTimeout(pending.timeout);
15447
+ this.pending.delete(envelope.request_id);
15448
+ pending.resolve(envelope);
15449
+ }
15450
+ async requestHello() {
15451
+ const payload = {
15452
+ client_name: this.options.clientName,
15453
+ client_version: this.options.clientVersion
15454
+ };
15455
+ const frame = await this.sendRequest("hello", payload, "hello_ack");
15456
+ return frame.payload;
15457
+ }
15458
+ async requestOk(type, payload) {
15459
+ const frame = await this.sendRequest(type, payload, "ok");
15460
+ const result = frame.payload;
15461
+ return result.result;
15462
+ }
15463
+ async sendRequest(type, payload, expectedType) {
15464
+ if (!this.child) {
15465
+ throw new AgentRelayProcessError("broker is not running");
15466
+ }
15467
+ const requestId = `req_${++this.requestSeq}`;
15468
+ const message = {
15469
+ v: PROTOCOL_VERSION,
15470
+ type,
15471
+ request_id: requestId,
15472
+ payload
15473
+ };
15474
+ const responsePromise = new Promise((resolve3, reject) => {
15475
+ const timeout = setTimeout(() => {
15476
+ this.pending.delete(requestId);
15477
+ reject(new AgentRelayProcessError(`request timed out after ${this.options.requestTimeoutMs}ms (type='${type}', request_id='${requestId}')`));
15478
+ }, this.options.requestTimeoutMs);
15479
+ this.pending.set(requestId, {
15480
+ expectedType,
15481
+ resolve: resolve3,
15482
+ reject,
15483
+ timeout
15484
+ });
15485
+ });
15486
+ const line = `${JSON.stringify(message)}
15487
+ `;
15488
+ if (!this.child.stdin.write(line)) {
15489
+ await (0, import_node_events.once)(this.child.stdin, "drain");
15490
+ }
15491
+ return responsePromise;
15492
+ }
15493
+ };
15494
+ var CLI_MODEL_FLAG_CLIS = /* @__PURE__ */ new Set(["claude", "codex", "gemini", "goose", "aider"]);
15495
+ var CLI_DEFAULT_ARGS = {
15496
+ codex: ["-c", "check_for_update_on_startup=false"]
15525
15497
  };
15526
- var CLIRegistry = {
15527
- claude: {
15528
- name: "Claude Code",
15529
- package: "@anthropic-ai/claude-code",
15530
- version: "2.1.72",
15531
- install: "npm install -g @anthropic-ai/claude-code",
15532
- npmLink: "https://www.npmjs.com/package/@anthropic-ai"
15533
- },
15534
- codex: {
15535
- name: "Codex CLI",
15536
- package: "@openai/codex",
15537
- version: "0.114.0",
15538
- install: "npm install -g @openai/codex",
15539
- npmLink: "https://www.npmjs.com/package/@openai/codex"
15540
- },
15541
- gemini: {
15542
- name: "Gemini CLI",
15543
- package: "@google/gemini-cli",
15544
- version: "0.33.0",
15545
- install: "npm install -g @google/gemini-cli",
15546
- npmLink: "https://www.npmjs.com/package/@google/gemini-cli"
15547
- },
15548
- cursor: {
15549
- name: "Cursor",
15550
- package: "cursor",
15551
- version: "2026.02.27-e7d2ef6",
15552
- install: "Download from cursor.com",
15553
- npmLink: void 0
15554
- },
15555
- droid: {
15556
- name: "Droid",
15557
- package: "droid",
15558
- version: "0.1.0",
15559
- install: "Download from droid.dev",
15560
- npmLink: void 0
15561
- },
15562
- opencode: {
15563
- name: "OpenCode",
15564
- package: "opencode-ai",
15565
- version: "1.2.24",
15566
- install: "npm install -g opencode-ai",
15567
- npmLink: "https://www.npmjs.com/package/opencode-ai"
15568
- },
15569
- aider: {
15570
- name: "Aider",
15571
- package: "aider-chat",
15572
- version: "0.72.1",
15573
- install: "pip install aider-chat",
15574
- npmLink: void 0
15575
- },
15576
- goose: {
15577
- name: "Goose",
15578
- package: "goose-ai",
15579
- version: "1.0.16",
15580
- install: "pip install goose-ai",
15581
- npmLink: void 0
15498
+ function buildPtyArgsWithModel(cli, args, model) {
15499
+ const cliName = cli.split(":")[0].trim().toLowerCase();
15500
+ const defaultArgs = CLI_DEFAULT_ARGS[cliName] ?? [];
15501
+ const baseArgs = [...defaultArgs, ...args];
15502
+ if (!model) {
15503
+ return baseArgs;
15504
+ }
15505
+ if (!CLI_MODEL_FLAG_CLIS.has(cliName)) {
15506
+ return baseArgs;
15507
+ }
15508
+ if (hasModelArg(baseArgs)) {
15509
+ return baseArgs;
15510
+ }
15511
+ return ["--model", model, ...baseArgs];
15512
+ }
15513
+ function hasModelArg(args) {
15514
+ for (let i = 0; i < args.length; i += 1) {
15515
+ const arg = args[i];
15516
+ if (arg === "--model") {
15517
+ return true;
15518
+ }
15519
+ if (arg.startsWith("--model=")) {
15520
+ return true;
15521
+ }
15522
+ }
15523
+ return false;
15524
+ }
15525
+ function expandTilde(p) {
15526
+ if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) {
15527
+ const home = import_node_os3.default.homedir();
15528
+ return import_node_path3.default.join(home, p.slice(2));
15529
+ }
15530
+ return p;
15531
+ }
15532
+ function isExplicitPath(binaryPath) {
15533
+ return binaryPath.includes("/") || binaryPath.includes("\\") || binaryPath.startsWith(".") || binaryPath.startsWith("~");
15534
+ }
15535
+ function detectPlatformSuffix() {
15536
+ const platformMap = {
15537
+ darwin: { arm64: "darwin-arm64", x64: "darwin-x64" },
15538
+ linux: { arm64: "linux-arm64", x64: "linux-x64" },
15539
+ win32: { x64: "win32-x64" }
15540
+ };
15541
+ return platformMap[process.platform]?.[process.arch] ?? null;
15542
+ }
15543
+ function getLatestVersionSync() {
15544
+ try {
15545
+ const result = (0, import_node_child_process2.execSync)("curl -fsSL https://api.github.com/repos/AgentWorkforce/relay/releases/latest", {
15546
+ timeout: 15e3,
15547
+ stdio: ["pipe", "pipe", "pipe"]
15548
+ }).toString();
15549
+ const match = result.match(/"tag_name"\s*:\s*"([^"]+)"/);
15550
+ if (!match?.[1])
15551
+ return null;
15552
+ return match[1].replace(/^openclaw-/, "").replace(/^v/, "");
15553
+ } catch {
15554
+ return null;
15555
+ }
15556
+ }
15557
+ function installBrokerBinary() {
15558
+ const suffix = detectPlatformSuffix();
15559
+ if (!suffix) {
15560
+ throw new AgentRelayProcessError(`Unsupported platform: ${process.platform}-${process.arch}`);
15561
+ }
15562
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
15563
+ const installDir = import_node_path3.default.join(homeDir, ".agent-relay", "bin");
15564
+ const brokerExe = process.platform === "win32" ? "agent-relay-broker.exe" : "agent-relay-broker";
15565
+ const targetPath = import_node_path3.default.join(installDir, brokerExe);
15566
+ console.log(`[agent-relay] Broker binary not found, installing for ${suffix}...`);
15567
+ const version2 = getLatestVersionSync();
15568
+ if (!version2) {
15569
+ 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");
15570
+ }
15571
+ const binaryName = `agent-relay-broker-${suffix}`;
15572
+ const downloadUrl = `https://github.com/AgentWorkforce/relay/releases/download/v${version2}/${binaryName}`;
15573
+ console.log(`[agent-relay] Downloading v${version2} from ${downloadUrl}`);
15574
+ try {
15575
+ import_node_fs2.default.mkdirSync(installDir, { recursive: true });
15576
+ (0, import_node_child_process2.execSync)(`curl -fsSL "${downloadUrl}" -o "${targetPath}"`, {
15577
+ timeout: 6e4,
15578
+ stdio: ["pipe", "pipe", "pipe"]
15579
+ });
15580
+ import_node_fs2.default.chmodSync(targetPath, 493);
15581
+ if (process.platform === "darwin") {
15582
+ try {
15583
+ (0, import_node_child_process2.execSync)(`xattr -d com.apple.quarantine "${targetPath}" 2>/dev/null || true`, {
15584
+ timeout: 1e4,
15585
+ stdio: ["pipe", "pipe", "pipe"]
15586
+ });
15587
+ } catch {
15588
+ }
15589
+ try {
15590
+ (0, import_node_child_process2.execSync)(`codesign --force --sign - "${targetPath}"`, {
15591
+ timeout: 1e4,
15592
+ stdio: ["pipe", "pipe", "pipe"]
15593
+ });
15594
+ } catch {
15595
+ }
15596
+ }
15597
+ (0, import_node_child_process2.execSync)(`"${targetPath}" --help`, { timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] });
15598
+ } catch (err) {
15599
+ try {
15600
+ import_node_fs2.default.unlinkSync(targetPath);
15601
+ } catch {
15602
+ }
15603
+ const message = err instanceof Error ? err.message : String(err);
15604
+ throw new AgentRelayProcessError(`Failed to install broker binary: ${message}
15605
+ Install manually: curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash`);
15606
+ }
15607
+ console.log(`[agent-relay] Broker installed to ${targetPath}`);
15608
+ return targetPath;
15609
+ }
15610
+ function resolveDefaultBinaryPath() {
15611
+ const brokerExe = process.platform === "win32" ? "agent-relay-broker.exe" : "agent-relay-broker";
15612
+ const moduleDir = import_node_path3.default.dirname((0, import_node_url.fileURLToPath)(import_meta_url));
15613
+ const workspaceRelease = import_node_path3.default.resolve(moduleDir, "..", "..", "..", "target", "release", brokerExe);
15614
+ if (import_node_fs2.default.existsSync(workspaceRelease)) {
15615
+ return workspaceRelease;
15616
+ }
15617
+ const binDir = import_node_path3.default.resolve(moduleDir, "..", "bin");
15618
+ const suffix = detectPlatformSuffix();
15619
+ if (suffix) {
15620
+ const ext = process.platform === "win32" ? ".exe" : "";
15621
+ const platformBinary = import_node_path3.default.join(binDir, `agent-relay-broker-${suffix}${ext}`);
15622
+ if (import_node_fs2.default.existsSync(platformBinary)) {
15623
+ return platformBinary;
15624
+ }
15625
+ }
15626
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
15627
+ const standaloneBroker = import_node_path3.default.join(homeDir, ".agent-relay", "bin", brokerExe);
15628
+ if (import_node_fs2.default.existsSync(standaloneBroker)) {
15629
+ return standaloneBroker;
15630
+ }
15631
+ return installBrokerBinary();
15632
+ }
15633
+ var DEFAULT_DASHBOARD_PORT = (() => {
15634
+ const envPort = typeof process !== "undefined" ? process.env.AGENT_RELAY_DASHBOARD_PORT : void 0;
15635
+ if (envPort) {
15636
+ const parsed = Number.parseInt(envPort, 10);
15637
+ if (Number.isFinite(parsed) && parsed > 0)
15638
+ return parsed;
15639
+ }
15640
+ return 3888;
15641
+ })();
15642
+ var HTTP_MAX_PORT_SCAN = 25;
15643
+ var HTTP_AUTOSTART_TIMEOUT_MS = 1e4;
15644
+ var HTTP_AUTOSTART_POLL_MS = 250;
15645
+ function sanitizeBrokerName(name) {
15646
+ return name.replace(/[^\p{L}\p{N}-]/gu, "-");
15647
+ }
15648
+ function brokerPidFilename(projectRoot) {
15649
+ const brokerName = import_node_path3.default.basename(projectRoot) || "project";
15650
+ return `broker-${sanitizeBrokerName(brokerName)}.pid`;
15651
+ }
15652
+ var HttpAgentRelayClient = class _HttpAgentRelayClient {
15653
+ port;
15654
+ apiKey;
15655
+ constructor(options) {
15656
+ this.port = options.port;
15657
+ this.apiKey = options.apiKey;
15658
+ }
15659
+ /**
15660
+ * Connect to an already-running broker on the given port.
15661
+ */
15662
+ static async connectHttp(port, options) {
15663
+ const client = new _HttpAgentRelayClient({ port, apiKey: options?.apiKey });
15664
+ await client.healthCheck();
15665
+ return client;
15666
+ }
15667
+ /**
15668
+ * Discover a running broker for the current project and connect to it.
15669
+ * Reads the broker PID file, verifies the process is alive, scans ports
15670
+ * for the HTTP API, and returns a connected client.
15671
+ */
15672
+ static async discoverAndConnect(options) {
15673
+ const cwd = options?.cwd ?? process.cwd();
15674
+ const apiKey = options?.apiKey ?? process.env.RELAY_BROKER_API_KEY?.trim();
15675
+ const autoStart = options?.autoStart ?? false;
15676
+ const paths = getProjectPaths(cwd);
15677
+ const preferredApiPort = DEFAULT_DASHBOARD_PORT + 1;
15678
+ const pidFilePath = import_node_path3.default.join(paths.dataDir, brokerPidFilename(paths.projectRoot));
15679
+ const legacyPidPath = import_node_path3.default.join(paths.dataDir, "broker.pid");
15680
+ let brokerRunning = false;
15681
+ for (const pidPath of [pidFilePath, legacyPidPath]) {
15682
+ if (import_node_fs2.default.existsSync(pidPath)) {
15683
+ const pidStr = import_node_fs2.default.readFileSync(pidPath, "utf-8").trim();
15684
+ const pid = Number.parseInt(pidStr, 10);
15685
+ if (Number.isFinite(pid) && pid > 0) {
15686
+ try {
15687
+ process.kill(pid, 0);
15688
+ brokerRunning = true;
15689
+ break;
15690
+ } catch {
15691
+ }
15692
+ }
15693
+ }
15694
+ }
15695
+ if (brokerRunning) {
15696
+ const port = await _HttpAgentRelayClient.scanForBrokerPort(preferredApiPort);
15697
+ if (port !== null) {
15698
+ return new _HttpAgentRelayClient({ port, apiKey });
15699
+ }
15700
+ throw new AgentRelayProcessError("broker is running for this project, but its local API is unavailable");
15701
+ }
15702
+ if (!autoStart) {
15703
+ throw new AgentRelayProcessError("broker is not running for this project");
15704
+ }
15705
+ const brokerBinary = options?.brokerBinaryPath ?? resolveDefaultBinaryPath();
15706
+ const child = (0, import_node_child_process2.spawn)(brokerBinary, ["init", "--persist", "--api-port", String(preferredApiPort)], {
15707
+ cwd: paths.projectRoot,
15708
+ env: process.env,
15709
+ detached: true,
15710
+ stdio: "ignore"
15711
+ });
15712
+ child.unref();
15713
+ const startedAt = Date.now();
15714
+ while (Date.now() - startedAt < HTTP_AUTOSTART_TIMEOUT_MS) {
15715
+ const port = await _HttpAgentRelayClient.scanForBrokerPort(preferredApiPort);
15716
+ if (port !== null) {
15717
+ return new _HttpAgentRelayClient({ port, apiKey });
15718
+ }
15719
+ await new Promise((resolve3) => setTimeout(resolve3, HTTP_AUTOSTART_POLL_MS));
15720
+ }
15721
+ throw new AgentRelayProcessError(`broker did not become ready within ${HTTP_AUTOSTART_TIMEOUT_MS}ms`);
15722
+ }
15723
+ static async scanForBrokerPort(startPort) {
15724
+ for (let i = 0; i < HTTP_MAX_PORT_SCAN; i++) {
15725
+ const port = startPort + i;
15726
+ try {
15727
+ const res = await fetch(`http://127.0.0.1:${port}/health`);
15728
+ if (!res.ok)
15729
+ continue;
15730
+ const payload = await res.json().catch(() => null);
15731
+ if (payload?.service === "agent-relay-listen") {
15732
+ return port;
15733
+ }
15734
+ } catch {
15735
+ }
15736
+ }
15737
+ return null;
15738
+ }
15739
+ async request(pathname, init) {
15740
+ const headers = new Headers(init?.headers);
15741
+ if (this.apiKey && !headers.has("x-api-key") && !headers.has("authorization")) {
15742
+ headers.set("x-api-key", this.apiKey);
15743
+ }
15744
+ const response = await fetch(`http://127.0.0.1:${this.port}${pathname}`, {
15745
+ ...init,
15746
+ headers
15747
+ });
15748
+ const text = await response.text();
15749
+ let payload;
15750
+ try {
15751
+ payload = text ? JSON.parse(text) : void 0;
15752
+ } catch {
15753
+ payload = text;
15754
+ }
15755
+ if (!response.ok) {
15756
+ const msg = _HttpAgentRelayClient.extractErrorMessage(response, payload);
15757
+ throw new AgentRelayProcessError(msg);
15758
+ }
15759
+ return payload;
15760
+ }
15761
+ static extractErrorMessage(response, payload) {
15762
+ if (typeof payload === "string" && payload.trim())
15763
+ return payload.trim();
15764
+ const p = payload;
15765
+ if (typeof p?.error === "string")
15766
+ return p.error;
15767
+ if (typeof p?.error?.message === "string")
15768
+ return p.error.message;
15769
+ if (typeof p?.message === "string" && p.message.trim())
15770
+ return p.message.trim();
15771
+ return `${response.status} ${response.statusText}`.trim();
15772
+ }
15773
+ async healthCheck() {
15774
+ return this.request("/health");
15775
+ }
15776
+ /** No-op — broker is already running. */
15777
+ async start() {
15778
+ }
15779
+ /** No-op — don't kill an externally-managed broker. */
15780
+ async shutdown() {
15781
+ }
15782
+ async spawnPty(input) {
15783
+ const payload = await this.request("/api/spawn", {
15784
+ method: "POST",
15785
+ headers: { "content-type": "application/json" },
15786
+ body: JSON.stringify({
15787
+ name: input.name,
15788
+ cli: input.cli,
15789
+ model: input.model,
15790
+ args: input.args ?? [],
15791
+ task: input.task,
15792
+ channels: input.channels ?? [],
15793
+ cwd: input.cwd,
15794
+ team: input.team,
15795
+ shadowOf: input.shadowOf,
15796
+ shadowMode: input.shadowMode,
15797
+ continueFrom: input.continueFrom,
15798
+ idleThresholdSecs: input.idleThresholdSecs,
15799
+ restartPolicy: input.restartPolicy,
15800
+ skipRelayPrompt: input.skipRelayPrompt
15801
+ })
15802
+ });
15803
+ return {
15804
+ name: typeof payload?.name === "string" ? payload.name : input.name,
15805
+ runtime: "pty"
15806
+ };
15807
+ }
15808
+ async sendMessage(input) {
15809
+ return this.request("/api/send", {
15810
+ method: "POST",
15811
+ headers: { "content-type": "application/json" },
15812
+ body: JSON.stringify({
15813
+ to: input.to,
15814
+ text: input.text,
15815
+ from: input.from,
15816
+ threadId: input.threadId,
15817
+ workspaceId: input.workspaceId,
15818
+ workspaceAlias: input.workspaceAlias,
15819
+ priority: input.priority,
15820
+ data: input.data
15821
+ })
15822
+ });
15823
+ }
15824
+ async listAgents() {
15825
+ const payload = await this.request("/api/spawned", { method: "GET" });
15826
+ return Array.isArray(payload?.agents) ? payload.agents : [];
15827
+ }
15828
+ async release(name, reason) {
15829
+ const payload = await this.request(`/api/spawned/${encodeURIComponent(name)}`, {
15830
+ method: "DELETE",
15831
+ ...reason ? { headers: { "content-type": "application/json" }, body: JSON.stringify({ reason }) } : {}
15832
+ });
15833
+ return { name: typeof payload?.name === "string" ? payload.name : name };
15834
+ }
15835
+ async setModel(name, model, opts) {
15836
+ const payload = await this.request(`/api/spawned/${encodeURIComponent(name)}/model`, {
15837
+ method: "POST",
15838
+ headers: { "content-type": "application/json" },
15839
+ body: JSON.stringify({ model, timeoutMs: opts?.timeoutMs })
15840
+ });
15841
+ return {
15842
+ name,
15843
+ model: typeof payload?.model === "string" ? payload.model : model,
15844
+ success: payload?.success !== false
15845
+ };
15846
+ }
15847
+ async getConfig() {
15848
+ return this.request("/api/config");
15582
15849
  }
15583
- };
15584
- var DefaultModels = {
15585
- claude: "sonnet",
15586
- codex: "gpt-5.4",
15587
- gemini: "gemini-3.1-pro-preview",
15588
- cursor: "opus-4.6-thinking",
15589
- droid: "opus-4.6-fast",
15590
- opencode: "openai/gpt-5.2"
15591
15850
  };
15592
15851
 
15593
15852
  // node_modules/@relaycast/sdk/dist/version.js
@@ -31425,7 +31684,7 @@ function cleanLines(raw) {
31425
31684
  }
31426
31685
 
31427
31686
  // packages/sdk/dist/relay.js
31428
- var import_node_crypto = require("node:crypto");
31687
+ var import_node_crypto2 = require("node:crypto");
31429
31688
  var import_node_path5 = __toESM(require("node:path"), 1);
31430
31689
 
31431
31690
  // packages/sdk/dist/logs.js
@@ -31843,7 +32102,7 @@ var AgentRelay = class {
31843
32102
  if (result?.event_id === "unsupported_operation") {
31844
32103
  return buildUnsupportedOperationMessage(opts.name, input);
31845
32104
  }
31846
- const eventId = result?.event_id ?? (0, import_node_crypto.randomBytes)(8).toString("hex");
32105
+ const eventId = result?.event_id ?? (0, import_node_crypto2.randomBytes)(8).toString("hex");
31847
32106
  const msg = {
31848
32107
  eventId,
31849
32108
  from: opts.name,
@@ -32505,7 +32764,7 @@ var AgentRelay = class {
32505
32764
  if (result?.event_id === "unsupported_operation") {
32506
32765
  return buildUnsupportedOperationMessage(name, input);
32507
32766
  }
32508
- const eventId = result?.event_id ?? (0, import_node_crypto.randomBytes)(8).toString("hex");
32767
+ const eventId = result?.event_id ?? (0, import_node_crypto2.randomBytes)(8).toString("hex");
32509
32768
  const msg = {
32510
32769
  eventId,
32511
32770
  from: name,
@@ -32622,7 +32881,7 @@ var AgentRelay = class {
32622
32881
  };
32623
32882
 
32624
32883
  // packages/sdk/dist/consensus.js
32625
- var import_node_crypto2 = require("node:crypto");
32884
+ var import_node_crypto3 = require("node:crypto");
32626
32885
  var import_node_events2 = require("node:events");
32627
32886
 
32628
32887
  // packages/sdk/dist/consensus-helpers.js
@@ -32759,7 +33018,7 @@ var ConsensusEngine = class extends import_node_events2.EventEmitter {
32759
33018
  }
32760
33019
  // ── Proposal management ──────────────────────────────────────────────────
32761
33020
  createProposal(options) {
32762
- const id = `prop_${Date.now()}_${(0, import_node_crypto2.randomBytes)(4).toString("hex")}`;
33021
+ const id = `prop_${Date.now()}_${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`;
32763
33022
  const now = Date.now();
32764
33023
  const timeoutMs = options.timeoutMs ?? this.config.defaultTimeoutMs;
32765
33024
  const proposal = {
@@ -33227,15 +33486,15 @@ var ShadowManager = class {
33227
33486
  var WORKFLOW_BOOTSTRAP_TASK = "You are connected to Agent Relay. Do not reply to this message and wait for relay messages and respond using Relaycast MCP tools.";
33228
33487
  var WORKFLOW_CONVENTIONS = [
33229
33488
  "Messaging requirements:",
33230
- '- When you receive `Relay message from <sender> ...`, reply using `mcp__relaycast__dm_send(to: "<sender>", text: "...")`.',
33489
+ '- When you receive `Relay message from <sender> ...`, reply using `mcp__relaycast__message_dm_send(to: "<sender>", text: "...")`.',
33231
33490
  "- Send `ACK: ...` when you receive a task.",
33232
33491
  "- Send `DONE: ...` when the task is complete.",
33233
- "- Do not reply only in terminal text; send the response via mcp__relaycast__dm_send.",
33234
- "- Use mcp__relaycast__inbox_check() and mcp__relaycast__agent_list() when context is missing."
33492
+ "- Do not reply only in terminal text; send the response via mcp__relaycast__message_dm_send.",
33493
+ "- Use mcp__relaycast__message_inbox_check() and mcp__relaycast__agent_list() when context is missing."
33235
33494
  ].join("\n");
33236
33495
  function hasWorkflowConventions(task) {
33237
33496
  const lower = task.toLowerCase();
33238
- return lower.includes("mcp__relaycast__dm_send(") || lower.includes("relay_send(") || lower.includes("ack:") && lower.includes("done:");
33497
+ return lower.includes("mcp__relaycast__message_dm_send(") || lower.includes("relay_send(") || lower.includes("ack:") && lower.includes("done:");
33239
33498
  }
33240
33499
  function buildSpawnTask(task, includeWorkflowConventions) {
33241
33500
  const normalized = typeof task === "string" ? task.trim() : "";
@@ -33427,8 +33686,8 @@ function isAgentStep(step) {
33427
33686
 
33428
33687
  // packages/sdk/dist/workflows/runner.js
33429
33688
  var import_node_child_process3 = require("node:child_process");
33430
- var import_node_crypto4 = require("node:crypto");
33431
- var import_node_fs4 = require("node:fs");
33689
+ var import_node_crypto5 = require("node:crypto");
33690
+ var import_node_fs5 = require("node:fs");
33432
33691
  var import_promises3 = require("node:fs/promises");
33433
33692
  var import_node_path8 = __toESM(require("node:path"), 1);
33434
33693
  var import_yaml2 = __toESM(require_dist(), 1);
@@ -33559,18 +33818,18 @@ async function spawnFromEnv(options = {}) {
33559
33818
  }
33560
33819
 
33561
33820
  // packages/sdk/dist/workflows/custom-steps.js
33562
- var import_node_fs2 = require("node:fs");
33821
+ var import_node_fs3 = require("node:fs");
33563
33822
  var import_node_path6 = __toESM(require("node:path"), 1);
33564
33823
  var import_yaml = __toESM(require_dist(), 1);
33565
33824
  var CUSTOM_STEPS_FILE = ".relay/steps.yaml";
33566
33825
  function loadCustomSteps(cwd) {
33567
33826
  const stepsPath = import_node_path6.default.join(cwd, CUSTOM_STEPS_FILE);
33568
33827
  const steps = /* @__PURE__ */ new Map();
33569
- if (!(0, import_node_fs2.existsSync)(stepsPath)) {
33828
+ if (!(0, import_node_fs3.existsSync)(stepsPath)) {
33570
33829
  return steps;
33571
33830
  }
33572
33831
  try {
33573
- const content = (0, import_node_fs2.readFileSync)(stepsPath, "utf-8");
33832
+ const content = (0, import_node_fs3.readFileSync)(stepsPath, "utf-8");
33574
33833
  if (!content.trim()) {
33575
33834
  return steps;
33576
33835
  }
@@ -33820,7 +34079,7 @@ function resolveAllCustomSteps(steps, customSteps) {
33820
34079
  return steps.map((step) => resolveCustomStep(step, customSteps));
33821
34080
  }
33822
34081
  function customStepsFileExists(cwd) {
33823
- return (0, import_node_fs2.existsSync)(import_node_path6.default.join(cwd, CUSTOM_STEPS_FILE));
34082
+ return (0, import_node_fs3.existsSync)(import_node_path6.default.join(cwd, CUSTOM_STEPS_FILE));
33824
34083
  }
33825
34084
  function getCustomStepsPath(cwd) {
33826
34085
  return import_node_path6.default.join(cwd, CUSTOM_STEPS_FILE);
@@ -33857,8 +34116,8 @@ var InMemoryWorkflowDb = class {
33857
34116
  };
33858
34117
 
33859
34118
  // packages/sdk/dist/workflows/trajectory.js
33860
- var import_node_crypto3 = require("node:crypto");
33861
- var import_node_fs3 = require("node:fs");
34119
+ var import_node_crypto4 = require("node:crypto");
34120
+ var import_node_fs4 = require("node:fs");
33862
34121
  var import_promises2 = require("node:fs/promises");
33863
34122
  var import_node_path7 = __toESM(require("node:path"), 1);
33864
34123
  function classifyFailure(error48) {
@@ -33921,7 +34180,7 @@ var WorkflowTrajectory = class {
33921
34180
  return;
33922
34181
  this.startTime = Date.now();
33923
34182
  this.swarmPattern = pattern ?? "dag";
33924
- const id = `traj_${Date.now()}_${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`;
34183
+ const id = `traj_${Date.now()}_${(0, import_node_crypto4.randomBytes)(4).toString("hex")}`;
33925
34184
  this.trajectory = {
33926
34185
  id,
33927
34186
  version: 1,
@@ -34264,7 +34523,7 @@ var WorkflowTrajectory = class {
34264
34523
  if (!this.trajectory)
34265
34524
  return;
34266
34525
  const chapter = {
34267
- id: `ch_${(0, import_node_crypto3.randomBytes)(4).toString("hex")}`,
34526
+ id: `ch_${(0, import_node_crypto4.randomBytes)(4).toString("hex")}`,
34268
34527
  title,
34269
34528
  agentName,
34270
34529
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -34337,7 +34596,7 @@ var WorkflowTrajectory = class {
34337
34596
  await (0, import_promises2.mkdir)(completedDir, { recursive: true });
34338
34597
  const activePath = import_node_path7.default.join(activeDir, `${this.trajectory.id}.json`);
34339
34598
  const completedPath = import_node_path7.default.join(completedDir, `${this.trajectory.id}.json`);
34340
- if ((0, import_node_fs3.existsSync)(activePath)) {
34599
+ if ((0, import_node_fs4.existsSync)(activePath)) {
34341
34600
  await (0, import_promises2.rename)(activePath, completedPath);
34342
34601
  }
34343
34602
  } catch {
@@ -34435,6 +34694,8 @@ var WorkflowRunner = class _WorkflowRunner {
34435
34694
  stepSignalParticipants = /* @__PURE__ */ new Map();
34436
34695
  /** Resolved named paths from the top-level `paths` config, keyed by name → absolute directory. */
34437
34696
  resolvedPaths = /* @__PURE__ */ new Map();
34697
+ /** Tracks agent names currently assigned as reviewers (ref-counted to handle concurrent usage). */
34698
+ activeReviewers = /* @__PURE__ */ new Map();
34438
34699
  constructor(options = {}) {
34439
34700
  this.db = options.db ?? new InMemoryWorkflowDb();
34440
34701
  this.workspaceId = options.workspaceId ?? "local";
@@ -34473,7 +34734,7 @@ var WorkflowRunner = class _WorkflowRunner {
34473
34734
  const abs = import_node_path8.default.resolve(baseCwd, expanded);
34474
34735
  resolved.set(pd.name, abs);
34475
34736
  const isRequired = pd.required !== false;
34476
- if (!(0, import_node_fs4.existsSync)(abs)) {
34737
+ if (!(0, import_node_fs5.existsSync)(abs)) {
34477
34738
  if (isRequired) {
34478
34739
  errors.push(`Path "${pd.name}" resolves to "${abs}" which does not exist (required)`);
34479
34740
  } else {
@@ -34807,12 +35068,12 @@ ${next}` : next;
34807
35068
  }
34808
35069
  captureFileSnapshot(root) {
34809
35070
  const snapshot = /* @__PURE__ */ new Map();
34810
- if (!(0, import_node_fs4.existsSync)(root))
35071
+ if (!(0, import_node_fs5.existsSync)(root))
34811
35072
  return snapshot;
34812
35073
  const visit = (currentPath) => {
34813
35074
  let entries;
34814
35075
  try {
34815
- entries = (0, import_node_fs4.readdirSync)(currentPath, { withFileTypes: true });
35076
+ entries = (0, import_node_fs5.readdirSync)(currentPath, { withFileTypes: true });
34816
35077
  } catch {
34817
35078
  return;
34818
35079
  }
@@ -34826,7 +35087,7 @@ ${next}` : next;
34826
35087
  continue;
34827
35088
  }
34828
35089
  try {
34829
- const stats = (0, import_node_fs4.statSync)(fullPath);
35090
+ const stats = (0, import_node_fs5.statSync)(fullPath);
34830
35091
  if (!stats.isFile())
34831
35092
  continue;
34832
35093
  snapshot.set(fullPath, { mtimeMs: stats.mtimeMs, size: stats.size });
@@ -34835,7 +35096,7 @@ ${next}` : next;
34835
35096
  }
34836
35097
  };
34837
35098
  try {
34838
- const stats = (0, import_node_fs4.statSync)(root);
35099
+ const stats = (0, import_node_fs5.statSync)(root);
34839
35100
  if (stats.isFile()) {
34840
35101
  snapshot.set(root, { mtimeMs: stats.mtimeMs, size: stats.size });
34841
35102
  return snapshot;
@@ -34956,7 +35217,7 @@ ${next}` : next;
34956
35217
  this.relayApiKey = envKey;
34957
35218
  return;
34958
35219
  }
34959
- const workspaceName = `relay-${channel}-${(0, import_node_crypto4.randomBytes)(4).toString("hex")}`;
35220
+ const workspaceName = `relay-${channel}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}`;
34960
35221
  const baseUrl = this.relayOptions.env?.RELAYCAST_BASE_URL ?? process.env.RELAYCAST_BASE_URL ?? "https://api.relaycast.dev";
34961
35222
  const res = await fetch(`${baseUrl}/v1/workspaces`, {
34962
35223
  method: "POST",
@@ -35020,7 +35281,7 @@ ${next}` : next;
35020
35281
  } catch (err) {
35021
35282
  if (err instanceof RelayError && err.code === "name_conflict") {
35022
35283
  registration = await rc.agents.register({
35023
- name: `WorkflowRunner-${(0, import_node_crypto4.randomBytes)(4).toString("hex")}`,
35284
+ name: `WorkflowRunner-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}`,
35024
35285
  type: "agent"
35025
35286
  });
35026
35287
  } else {
@@ -35237,14 +35498,14 @@ ${err.suggestion}`);
35237
35498
  for (const agent of resolved.agents) {
35238
35499
  if (agent.cwd) {
35239
35500
  const resolvedCwd = import_node_path8.default.resolve(this.cwd, agent.cwd);
35240
- if (!(0, import_node_fs4.existsSync)(resolvedCwd)) {
35501
+ if (!(0, import_node_fs5.existsSync)(resolvedCwd)) {
35241
35502
  warnings.push(`Agent "${agent.name}" cwd "${agent.cwd}" resolves to "${resolvedCwd}" which does not exist`);
35242
35503
  }
35243
35504
  }
35244
35505
  if (agent.additionalPaths) {
35245
35506
  for (const ap of agent.additionalPaths) {
35246
35507
  const resolvedPath = import_node_path8.default.resolve(this.cwd, ap);
35247
- if (!(0, import_node_fs4.existsSync)(resolvedPath)) {
35508
+ if (!(0, import_node_fs5.existsSync)(resolvedPath)) {
35248
35509
  warnings.push(`Agent "${agent.name}" additionalPath "${ap}" resolves to "${resolvedPath}" which does not exist`);
35249
35510
  }
35250
35511
  }
@@ -35983,6 +36244,7 @@ ${err.suggestion}`);
35983
36244
  this.lastActivity.clear();
35984
36245
  this.supervisedRuntimeAgents.clear();
35985
36246
  this.runtimeStepAgents.clear();
36247
+ this.activeReviewers.clear();
35986
36248
  this.log("Shutting down broker...");
35987
36249
  await this.relay?.shutdown();
35988
36250
  this.relay = void 0;
@@ -36577,9 +36839,11 @@ ${trimmedOutput.slice(0, 200)}`);
36577
36839
  }
36578
36840
  const specialistDef = _WorkflowRunner.resolveAgentDef(rawAgentDef);
36579
36841
  const usesOwnerFlow = specialistDef.interactive !== false;
36580
- const usesAutoHardening = usesOwnerFlow && !this.isExplicitInteractiveWorker(specialistDef);
36842
+ const currentPattern = this.currentConfig?.swarm?.pattern ?? "";
36843
+ const isHubPattern = _WorkflowRunner.HUB_PATTERNS.has(currentPattern);
36844
+ const usesAutoHardening = usesOwnerFlow && isHubPattern && !this.isExplicitInteractiveWorker(specialistDef);
36581
36845
  const ownerDef = usesAutoHardening ? this.resolveAutoStepOwner(specialistDef, agentMap) : specialistDef;
36582
- const reviewDef = usesAutoHardening ? this.resolveAutoReviewAgent(ownerDef, agentMap) : void 0;
36846
+ let reviewDef;
36583
36847
  const supervised = {
36584
36848
  specialist: specialistDef,
36585
36849
  owner: ownerDef,
@@ -36700,6 +36964,7 @@ ${resolvedTask}`;
36700
36964
  const spawnResult = this.executor ? await this.executor.executeAgentStep(resolvedStep, effectiveOwner, ownerTask, timeoutMs) : await this.spawnAndWait(effectiveOwner, resolvedStep, timeoutMs, {
36701
36965
  evidenceStepName: step.name,
36702
36966
  evidenceRole: usesOwnerFlow ? "owner" : "specialist",
36967
+ preserveOnIdle: !isHubPattern || !this.isLeadLikeAgent(effectiveOwner) ? false : void 0,
36703
36968
  logicalName: effectiveOwner.name,
36704
36969
  onSpawned: explicitInteractiveWorker ? ({ agent }) => {
36705
36970
  explicitWorkerHandle = agent;
@@ -36750,11 +37015,24 @@ ${resolvedTask}`;
36750
37015
  if (completionReason === "retry_requested_by_owner") {
36751
37016
  throw new WorkflowCompletionError(`Step "${step.name}" owner requested another attempt`, "retry_requested_by_owner");
36752
37017
  }
37018
+ if (usesAutoHardening && usesDedicatedOwner && !reviewDef) {
37019
+ reviewDef = this.resolveAutoReviewAgent(ownerDef, agentMap);
37020
+ supervised.reviewer = reviewDef;
37021
+ }
36753
37022
  let combinedOutput = specialistOutput;
36754
37023
  if (usesOwnerFlow && reviewDef) {
36755
- const remainingMs = timeoutMs ? Math.max(0, timeoutMs - ownerElapsed) : void 0;
36756
- const reviewOutput = await this.runStepReviewGate(step, resolvedTask, specialistOutput, ownerOutput, ownerDef, reviewDef, remainingMs);
36757
- combinedOutput = this.combineStepAndReviewOutput(specialistOutput, reviewOutput);
37024
+ this.activeReviewers.set(reviewDef.name, (this.activeReviewers.get(reviewDef.name) ?? 0) + 1);
37025
+ try {
37026
+ const remainingMs = timeoutMs ? Math.max(0, timeoutMs - ownerElapsed) : void 0;
37027
+ const reviewOutput = await this.runStepReviewGate(step, resolvedTask, specialistOutput, ownerOutput, ownerDef, reviewDef, remainingMs);
37028
+ combinedOutput = this.combineStepAndReviewOutput(specialistOutput, reviewOutput);
37029
+ } finally {
37030
+ const count = (this.activeReviewers.get(reviewDef.name) ?? 1) - 1;
37031
+ if (count <= 0)
37032
+ this.activeReviewers.delete(reviewDef.name);
37033
+ else
37034
+ this.activeReviewers.set(reviewDef.name, count);
37035
+ }
36758
37036
  }
36759
37037
  state.row.status = "completed";
36760
37038
  state.row.output = combinedOutput;
@@ -37113,10 +37391,13 @@ WORKER COMPLETION CONTRACT:
37113
37391
  return 2;
37114
37392
  return isReviewer(def) ? 1 : 0;
37115
37393
  };
37116
- const dedicated = allDefs.filter((d) => eligible(d) && isReviewer(d)).sort((a, b) => reviewerPriority(b) - reviewerPriority(a) || a.name.localeCompare(b.name))[0];
37394
+ const notBusy = (def) => !this.activeReviewers.has(def.name);
37395
+ const dedicatedCandidates = allDefs.filter((d) => eligible(d) && isReviewer(d)).sort((a, b) => reviewerPriority(b) - reviewerPriority(a) || a.name.localeCompare(b.name));
37396
+ const dedicated = dedicatedCandidates.find(notBusy) ?? dedicatedCandidates[0];
37117
37397
  if (dedicated)
37118
37398
  return dedicated;
37119
- const alternate = allDefs.find((d) => eligible(d) && d.interactive !== false);
37399
+ const alternateCandidates = allDefs.filter((d) => eligible(d) && d.interactive !== false);
37400
+ const alternate = alternateCandidates.find(notBusy) ?? alternateCandidates[0];
37120
37401
  if (alternate)
37121
37402
  return alternate;
37122
37403
  return ownerDef;
@@ -37610,7 +37891,7 @@ ${review}
37610
37891
  buildPresetInjection(preset) {
37611
37892
  switch (preset) {
37612
37893
  case "worker":
37613
- return "You are a non-interactive worker agent. Produce clean, structured output to stdout.\nDo NOT use mcp__relaycast__agent_add, add_agent, or any MCP tool to spawn sub-agents.\nDo NOT use mcp__relaycast__dm_send or any Relaycast messaging tools \u2014 you have no relay connection.\n\n";
37894
+ return "You are a non-interactive worker agent. Produce clean, structured output to stdout.\nDo NOT use mcp__relaycast__agent_add, add_agent, or any MCP tool to spawn sub-agents.\nDo NOT use mcp__relaycast__message_dm_send or any Relaycast messaging tools \u2014 you have no relay connection.\n\n";
37614
37895
  case "reviewer":
37615
37896
  return "You are a non-interactive reviewer agent. Read the specified files/artifacts and produce a clear verdict.\nDo NOT spawn sub-agents or use any Relaycast messaging tools.\n\n";
37616
37897
  case "analyst":
@@ -37650,7 +37931,7 @@ DO NOT:
37650
37931
  const { cmd, args } = _WorkflowRunner.buildNonInteractiveCommand(agentDef.cli, taskWithDeliverable, modelArgs);
37651
37932
  const logsDir = this.getWorkerLogsDir();
37652
37933
  const logPath = import_node_path8.default.join(logsDir, `${agentName}.log`);
37653
- const logStream = (0, import_node_fs4.createWriteStream)(logPath, { flags: "a" });
37934
+ const logStream = (0, import_node_fs5.createWriteStream)(logPath, { flags: "a" });
37654
37935
  this.registerWorker(agentName, agentDef.cli, step.task ?? "", void 0, false);
37655
37936
  let stopHeartbeat;
37656
37937
  if (this.relayApiKey) {
@@ -37786,7 +38067,7 @@ DO NOT:
37786
38067
  const taskWithExit = step.task + (relayRegistrationNote ? "\n\n" + relayRegistrationNote : "") + (delegationGuidance ? "\n\n" + delegationGuidance + "\n" : "") + '\n\n---\nIMPORTANT: When you have fully completed this task, you MUST self-terminate by either: (a) calling remove_agent(name: "<your-agent-name>", reason: "task completed") \u2014 preferred, or (b) outputting the exact text "/exit" on its own line as a fallback. Do not wait for further input \u2014 terminate immediately after finishing. Do NOT spawn sub-agents unless the task explicitly requires it.';
37787
38068
  this.ptyOutputBuffers.set(agentName, []);
37788
38069
  const logsDir = this.getWorkerLogsDir();
37789
- const logStream = (0, import_node_fs4.createWriteStream)(import_node_path8.default.join(logsDir, `${agentName}.log`), { flags: "a" });
38070
+ const logStream = (0, import_node_fs5.createWriteStream)(import_node_path8.default.join(logsDir, `${agentName}.log`), { flags: "a" });
37790
38071
  this.ptyLogStreams.set(agentName, logStream);
37791
38072
  this.ptyListeners.set(agentName, (chunk) => {
37792
38073
  const stripped = _WorkflowRunner.stripAnsi(chunk);
@@ -37828,11 +38109,11 @@ DO NOT:
37828
38109
  oldLogStream.end();
37829
38110
  this.ptyLogStreams.delete(oldName);
37830
38111
  try {
37831
- (0, import_node_fs4.renameSync)(oldLogPath, newLogPath);
38112
+ (0, import_node_fs5.renameSync)(oldLogPath, newLogPath);
37832
38113
  } catch {
37833
38114
  }
37834
38115
  }
37835
- const newLogStream = (0, import_node_fs4.createWriteStream)(newLogPath, { flags: "a" });
38116
+ const newLogStream = (0, import_node_fs5.createWriteStream)(newLogPath, { flags: "a" });
37836
38117
  this.ptyLogStreams.set(agent.name, newLogStream);
37837
38118
  const oldListener = this.ptyListeners.get(oldName);
37838
38119
  if (oldListener) {
@@ -37937,7 +38218,7 @@ DO NOT:
37937
38218
  output = ptyChunks.join("");
37938
38219
  } else {
37939
38220
  const summaryPath = import_node_path8.default.join(this.summaryDir, `${step.name}.md`);
37940
- output = (0, import_node_fs4.existsSync)(summaryPath) ? await (0, import_promises3.readFile)(summaryPath, "utf-8") : exitResult === "timeout" ? "Agent completed (released after idle timeout)" : exitResult === "released" ? "Agent completed (idle \u2014 treated as done)" : `Agent exited (${exitResult})`;
38221
+ output = (0, import_node_fs5.existsSync)(summaryPath) ? await (0, import_promises3.readFile)(summaryPath, "utf-8") : exitResult === "timeout" ? "Agent completed (released after idle timeout)" : exitResult === "released" ? "Agent completed (idle \u2014 treated as done)" : `Agent exited (${exitResult})`;
37941
38222
  }
37942
38223
  if (ptyChunks.length === 0) {
37943
38224
  this.captureStepTerminalEvidence(evidenceStepName, { stdout: output, combined: output }, { exitCode: agent?.exitCode, exitSignal: agent?.exitSignal }, {
@@ -38001,17 +38282,56 @@ DO NOT:
38001
38282
  this.log(`[${step.name}] Supervising agent "${agent.name}" may idle while waiting \u2014 using exit-only completion`);
38002
38283
  return agent.waitForExit(timeoutMs);
38003
38284
  }
38004
- const result = await Promise.race([
38005
- agent.waitForExit(timeoutMs).then((r) => ({ kind: "exit", result: r })),
38006
- agent.waitForIdle(timeoutMs).then((r) => ({ kind: "idle", result: r }))
38007
- ]);
38008
- if (result.kind === "idle" && result.result === "idle") {
38009
- this.log(`[${step.name}] Agent "${agent.name}" went idle \u2014 treating as complete`);
38010
- this.postToChannel(`**[${step.name}]** Agent \`${agent.name}\` idle \u2014 treating as complete`);
38011
- await agent.release();
38012
- return "released";
38285
+ const idleLoopStart = Date.now();
38286
+ while (true) {
38287
+ const elapsed = Date.now() - idleLoopStart;
38288
+ const remaining = timeoutMs != null ? Math.max(0, timeoutMs - elapsed) : void 0;
38289
+ if (remaining != null && remaining <= 0) {
38290
+ return "timeout";
38291
+ }
38292
+ const result = await Promise.race([
38293
+ agent.waitForExit(remaining).then((r) => ({ kind: "exit", result: r })),
38294
+ agent.waitForIdle(remaining).then((r) => ({ kind: "idle", result: r }))
38295
+ ]);
38296
+ if (result.kind === "idle" && result.result === "idle") {
38297
+ if (step.verification && step.verification.type === "output_contains") {
38298
+ const token = step.verification.value;
38299
+ const ptyOutput = (this.ptyOutputBuffers.get(agent.name) ?? []).join("");
38300
+ const taskText = step.task ?? "";
38301
+ const taskHasToken = taskText.includes(token);
38302
+ let verificationPassed = true;
38303
+ if (taskHasToken) {
38304
+ const first = ptyOutput.indexOf(token);
38305
+ verificationPassed = first !== -1 && ptyOutput.includes(token, first + token.length);
38306
+ } else {
38307
+ verificationPassed = ptyOutput.includes(token);
38308
+ }
38309
+ if (!verificationPassed) {
38310
+ this.log(`[${step.name}] Agent "${agent.name}" went idle but verification not yet passed \u2014 waiting for more output`);
38311
+ const idleGraceSecs = 15;
38312
+ const graceResult = await Promise.race([
38313
+ agent.waitForExit(idleGraceSecs * 1e3).then((r) => ({ kind: "exit", result: r })),
38314
+ agent.waitForIdle(idleGraceSecs * 1e3).then((r) => ({ kind: "idle", result: r }))
38315
+ ]);
38316
+ if (graceResult.kind === "idle" && graceResult.result === "idle") {
38317
+ continue;
38318
+ }
38319
+ if (graceResult.kind === "exit") {
38320
+ return graceResult.result;
38321
+ }
38322
+ this.log(`[${step.name}] Agent "${agent.name}" still idle after ${idleGraceSecs}s grace \u2014 releasing`);
38323
+ this.postToChannel(`**[${step.name}]** Agent \`${agent.name}\` idle \u2014 releasing (verification pending)`);
38324
+ await agent.release();
38325
+ return "released";
38326
+ }
38327
+ }
38328
+ this.log(`[${step.name}] Agent "${agent.name}" went idle \u2014 treating as complete`);
38329
+ this.postToChannel(`**[${step.name}]** Agent \`${agent.name}\` idle \u2014 treating as complete`);
38330
+ await agent.release();
38331
+ return "released";
38332
+ }
38333
+ return result.result;
38013
38334
  }
38014
- return result.result;
38015
38335
  }
38016
38336
  const nudgeAfterMs = nudgeConfig.nudgeAfterMs ?? 12e4;
38017
38337
  const escalateAfterMs = nudgeConfig.escalateAfterMs ?? 12e4;
@@ -38149,7 +38469,7 @@ DO NOT:
38149
38469
  case "exit_code":
38150
38470
  break;
38151
38471
  case "file_exists":
38152
- if (!(0, import_node_fs4.existsSync)(import_node_path8.default.resolve(this.cwd, check2.value))) {
38472
+ if (!(0, import_node_fs5.existsSync)(import_node_path8.default.resolve(this.cwd, check2.value))) {
38153
38473
  return fail(`Verification failed for "${stepName}": file "${check2.value}" does not exist`);
38154
38474
  }
38155
38475
  break;
@@ -38303,7 +38623,7 @@ DO NOT:
38303
38623
  RELAY SETUP \u2014 do this FIRST before any other relay tool:
38304
38624
  1. Call: register(name="${agentName}")
38305
38625
  This authenticates you in the Relaycast workspace.
38306
- ALL relay tools (mcp__relaycast__dm_send, mcp__relaycast__inbox_check, mcp__relaycast__message_post, etc.) require
38626
+ ALL relay tools (mcp__relaycast__message_dm_send, mcp__relaycast__message_inbox_check, mcp__relaycast__message_post, etc.) require
38307
38627
  registration first \u2014 they will fail with "Not registered" otherwise.
38308
38628
  2. Your agent name is "${agentName}" \u2014 use this exact name when registering.`;
38309
38629
  }
@@ -38312,7 +38632,7 @@ RELAY SETUP \u2014 do this FIRST before any other relay tool:
38312
38632
 
38313
38633
  ` : "";
38314
38634
  const subAgentOption = cli === "claude" ? "Option 2 \u2014 Use built-in sub-agents (Task tool) for research or scoped work:\n - Good for exploring code, reading files, or making targeted changes\n - Can run multiple sub-agents in parallel\n\n" : "";
38315
- return "---\nAUTONOMOUS DELEGATION \u2014 READ THIS BEFORE STARTING:\n" + timeoutNote + 'Before diving in, assess whether this task is too large or complex for a single agent. If it involves multiple independent subtasks, touches many files, or could take a long time, you should break it down and delegate to helper agents to avoid timeouts.\n\nOption 1 \u2014 Spawn relay agents (for real parallel coding work):\n - mcp__relaycast__agent_add(name="helper-1", cli="claude", task="Specific subtask description")\n - Coordinate via mcp__relaycast__dm_send(to="helper-1", text="...")\n - Check on them with mcp__relaycast__inbox_check()\n - Clean up when done: mcp__relaycast__agent_remove(name="helper-1")\n\n' + subAgentOption + `Guidelines:
38635
+ return "---\nAUTONOMOUS DELEGATION \u2014 READ THIS BEFORE STARTING:\n" + timeoutNote + 'Before diving in, assess whether this task is too large or complex for a single agent. If it involves multiple independent subtasks, touches many files, or could take a long time, you should break it down and delegate to helper agents to avoid timeouts.\n\nOption 1 \u2014 Spawn relay agents (for real parallel coding work):\n - mcp__relaycast__agent_add(name="helper-1", cli="claude", task="Specific subtask description")\n - Coordinate via mcp__relaycast__message_dm_send(to="helper-1", text="...")\n - Check on them with mcp__relaycast__message_inbox_check()\n - Clean up when done: mcp__relaycast__agent_remove(name="helper-1")\n\n' + subAgentOption + `Guidelines:
38316
38636
  - You are the lead \u2014 delegate but stay in control, track progress, integrate results
38317
38637
  - Give each helper a clear, self-contained task with enough context to work independently
38318
38638
  - For simple or quick work, just do it yourself \u2014 don't over-delegate
@@ -38488,10 +38808,10 @@ ${excerpt}` : "";
38488
38808
  }
38489
38809
  // ── ID generation ─────────────────────────────────────────────────────
38490
38810
  generateId() {
38491
- return (0, import_node_crypto4.randomBytes)(12).toString("hex");
38811
+ return (0, import_node_crypto5.randomBytes)(12).toString("hex");
38492
38812
  }
38493
38813
  generateShortId() {
38494
- return (0, import_node_crypto4.randomBytes)(4).toString("hex");
38814
+ return (0, import_node_crypto5.randomBytes)(4).toString("hex");
38495
38815
  }
38496
38816
  /** Strip ANSI escape codes from terminal output — delegates to pty.ts canonical regex. */
38497
38817
  static stripAnsi(text) {
@@ -38592,7 +38912,7 @@ ${excerpt}` : "";
38592
38912
  const outputPath = import_node_path8.default.join(this.getStepOutputDir(runId), `${stepName}.md`);
38593
38913
  try {
38594
38914
  const dir = this.getStepOutputDir(runId);
38595
- (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
38915
+ (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
38596
38916
  const cleaned = _WorkflowRunner.stripAnsi(output);
38597
38917
  await (0, import_promises3.writeFile)(outputPath, cleaned);
38598
38918
  } catch {
@@ -38618,9 +38938,9 @@ ${preview}
38618
38938
  loadStepOutput(runId, stepName) {
38619
38939
  try {
38620
38940
  const filePath = import_node_path8.default.join(this.getStepOutputDir(runId), `${stepName}.md`);
38621
- if (!(0, import_node_fs4.existsSync)(filePath))
38941
+ if (!(0, import_node_fs5.existsSync)(filePath))
38622
38942
  return void 0;
38623
- return (0, import_node_fs4.readFileSync)(filePath, "utf-8");
38943
+ return (0, import_node_fs5.readFileSync)(filePath, "utf-8");
38624
38944
  } catch {
38625
38945
  return void 0;
38626
38946
  }
@@ -38628,7 +38948,7 @@ ${preview}
38628
38948
  /** Get or create the worker logs directory (.agent-relay/team/worker-logs) */
38629
38949
  getWorkerLogsDir() {
38630
38950
  const logsDir = import_node_path8.default.join(this.cwd, ".agent-relay", "team", "worker-logs");
38631
- (0, import_node_fs4.mkdirSync)(logsDir, { recursive: true });
38951
+ (0, import_node_fs5.mkdirSync)(logsDir, { recursive: true });
38632
38952
  return logsDir;
38633
38953
  }
38634
38954
  /** Register a spawned agent in workers.json so `agents:kill` can find it. */
@@ -38644,7 +38964,7 @@ ${preview}
38644
38964
  this.activeWorkers.set(agentName, workerEntry);
38645
38965
  this.workersFileLock = this.workersFileLock.then(() => {
38646
38966
  try {
38647
- (0, import_node_fs4.mkdirSync)(import_node_path8.default.dirname(this.workersPath), { recursive: true });
38967
+ (0, import_node_fs5.mkdirSync)(import_node_path8.default.dirname(this.workersPath), { recursive: true });
38648
38968
  const existing = this.readWorkers().filter((w) => w.name !== agentName);
38649
38969
  existing.push({ name: agentName, ...workerEntry });
38650
38970
  this.writeWorkers(existing);
@@ -38666,21 +38986,21 @@ ${preview}
38666
38986
  }
38667
38987
  readWorkers() {
38668
38988
  try {
38669
- if (!(0, import_node_fs4.existsSync)(this.workersPath))
38989
+ if (!(0, import_node_fs5.existsSync)(this.workersPath))
38670
38990
  return [];
38671
- const raw = JSON.parse((0, import_node_fs4.readFileSync)(this.workersPath, "utf-8"));
38991
+ const raw = JSON.parse((0, import_node_fs5.readFileSync)(this.workersPath, "utf-8"));
38672
38992
  return Array.isArray(raw?.workers) ? raw.workers : [];
38673
38993
  } catch {
38674
38994
  return [];
38675
38995
  }
38676
38996
  }
38677
38997
  writeWorkers(workers) {
38678
- (0, import_node_fs4.writeFileSync)(this.workersPath, JSON.stringify({ workers }, null, 2));
38998
+ (0, import_node_fs5.writeFileSync)(this.workersPath, JSON.stringify({ workers }, null, 2));
38679
38999
  }
38680
39000
  };
38681
39001
 
38682
39002
  // packages/sdk/dist/workflows/file-db.js
38683
- var import_node_fs5 = require("node:fs");
39003
+ var import_node_fs6 = require("node:fs");
38684
39004
  var import_node_path9 = __toESM(require("node:path"), 1);
38685
39005
  var JsonFileWorkflowDb = class {
38686
39006
  filePath;
@@ -38690,7 +39010,7 @@ var JsonFileWorkflowDb = class {
38690
39010
  this.filePath = filePath;
38691
39011
  let writable = false;
38692
39012
  try {
38693
- (0, import_node_fs5.mkdirSync)(import_node_path9.default.dirname(filePath), { recursive: true });
39013
+ (0, import_node_fs6.mkdirSync)(import_node_path9.default.dirname(filePath), { recursive: true });
38694
39014
  writable = true;
38695
39015
  } catch {
38696
39016
  }
@@ -38705,7 +39025,7 @@ var JsonFileWorkflowDb = class {
38705
39025
  if (!this.writable)
38706
39026
  return;
38707
39027
  try {
38708
- (0, import_node_fs5.appendFileSync)(this.filePath, JSON.stringify(entry) + "\n", "utf8");
39028
+ (0, import_node_fs6.appendFileSync)(this.filePath, JSON.stringify(entry) + "\n", "utf8");
38709
39029
  } catch {
38710
39030
  }
38711
39031
  }
@@ -38715,7 +39035,7 @@ var JsonFileWorkflowDb = class {
38715
39035
  const steps = /* @__PURE__ */ new Map();
38716
39036
  let raw = "";
38717
39037
  try {
38718
- raw = (0, import_node_fs5.readFileSync)(this.filePath, "utf8");
39038
+ raw = (0, import_node_fs6.readFileSync)(this.filePath, "utf8");
38719
39039
  } catch {
38720
39040
  return { runs, steps };
38721
39041
  }
@@ -38906,6 +39226,8 @@ var WorkflowBuilder = class {
38906
39226
  def.task = options.task;
38907
39227
  if (options.channels !== void 0)
38908
39228
  def.channels = options.channels;
39229
+ if (options.preset !== void 0)
39230
+ def.preset = options.preset;
38909
39231
  if (options.interactive !== void 0)
38910
39232
  def.interactive = options.interactive;
38911
39233
  if (options.model !== void 0 || options.maxTokens !== void 0 || options.timeoutMs !== void 0 || options.retries !== void 0 || options.idleThresholdSecs !== void 0) {
@@ -38924,21 +39246,29 @@ var WorkflowBuilder = class {
38924
39246
  this._agents.push(def);
38925
39247
  return this;
38926
39248
  }
38927
- /** Add a workflow step. */
39249
+ /** Add a workflow step (agent or deterministic). */
38928
39250
  step(name, options) {
38929
- const step = {
38930
- name,
38931
- agent: options.agent,
38932
- task: options.task
38933
- };
39251
+ const step = { name };
39252
+ if ("type" in options && options.type === "deterministic") {
39253
+ step.type = "deterministic";
39254
+ step.command = options.command;
39255
+ if (options.failOnError !== void 0)
39256
+ step.failOnError = options.failOnError;
39257
+ if (options.captureOutput !== void 0)
39258
+ step.captureOutput = options.captureOutput;
39259
+ } else {
39260
+ const agentOpts = options;
39261
+ step.agent = agentOpts.agent;
39262
+ step.task = agentOpts.task;
39263
+ if (agentOpts.verification !== void 0)
39264
+ step.verification = agentOpts.verification;
39265
+ if (agentOpts.retries !== void 0)
39266
+ step.retries = agentOpts.retries;
39267
+ }
38934
39268
  if (options.dependsOn !== void 0)
38935
39269
  step.dependsOn = options.dependsOn;
38936
- if (options.verification !== void 0)
38937
- step.verification = options.verification;
38938
39270
  if (options.timeoutMs !== void 0)
38939
39271
  step.timeoutMs = options.timeoutMs;
38940
- if (options.retries !== void 0)
38941
- step.retries = options.retries;
38942
39272
  this._steps.push(step);
38943
39273
  return this;
38944
39274
  }
@@ -38955,8 +39285,9 @@ var WorkflowBuilder = class {
38955
39285
  }
38956
39286
  /** Build and return the RelayYamlConfig object. */
38957
39287
  toConfig() {
38958
- if (this._agents.length === 0) {
38959
- throw new Error("Workflow must have at least one agent");
39288
+ const hasAgentSteps = this._steps.some((s) => s.type !== "deterministic" && s.type !== "worktree");
39289
+ if (hasAgentSteps && this._agents.length === 0) {
39290
+ throw new Error("Workflow must have at least one agent when using agent steps");
38960
39291
  }
38961
39292
  if (this._steps.length === 0) {
38962
39293
  throw new Error("Workflow must have at least one step");
@@ -39016,7 +39347,7 @@ function workflow(name) {
39016
39347
  }
39017
39348
 
39018
39349
  // packages/sdk/dist/workflows/coordinator.js
39019
- var import_node_crypto5 = require("node:crypto");
39350
+ var import_node_crypto6 = require("node:crypto");
39020
39351
  var import_node_events3 = require("node:events");
39021
39352
  var PATTERN_HEURISTICS = [
39022
39353
  // ── Dependency-based patterns (highest priority) ──────────────────────
@@ -39371,7 +39702,7 @@ var SwarmCoordinator = class extends import_node_events3.EventEmitter {
39371
39702
  }
39372
39703
  // ── Lifecycle: create run ───────────────────────────────────────────────
39373
39704
  async createRun(workspaceId, config2) {
39374
- const id = `run_${Date.now()}_${(0, import_node_crypto5.randomBytes)(4).toString("hex")}`;
39705
+ const id = `run_${Date.now()}_${(0, import_node_crypto6.randomBytes)(4).toString("hex")}`;
39375
39706
  const pattern = this.selectPattern(config2);
39376
39707
  const now = (/* @__PURE__ */ new Date()).toISOString();
39377
39708
  const { rows } = await this.db.query(`INSERT INTO workflow_runs (id, workspace_id, workflow_name, pattern, status, config, started_at, created_at, updated_at)
@@ -39410,7 +39741,7 @@ var SwarmCoordinator = class extends import_node_events3.EventEmitter {
39410
39741
  const created = [];
39411
39742
  for (const wf of workflows) {
39412
39743
  for (const step of wf.steps) {
39413
- const id = `step_${Date.now()}_${(0, import_node_crypto5.randomBytes)(4).toString("hex")}`;
39744
+ const id = `step_${Date.now()}_${(0, import_node_crypto6.randomBytes)(4).toString("hex")}`;
39414
39745
  const now = (/* @__PURE__ */ new Date()).toISOString();
39415
39746
  const { rows } = await this.db.query(`INSERT INTO workflow_steps (id, run_id, step_name, agent_name, status, task, depends_on, created_at, updated_at)
39416
39747
  VALUES ($1, $2, $3, $4, 'pending', $5, $6, $7, $7)
@@ -39587,7 +39918,7 @@ var SwarmCoordinator = class extends import_node_events3.EventEmitter {
39587
39918
  };
39588
39919
 
39589
39920
  // packages/sdk/dist/workflows/barrier.js
39590
- var import_node_crypto6 = require("node:crypto");
39921
+ var import_node_crypto7 = require("node:crypto");
39591
39922
  var import_node_events4 = require("node:events");
39592
39923
  var BarrierManager = class extends import_node_events4.EventEmitter {
39593
39924
  db;
@@ -39603,7 +39934,7 @@ var BarrierManager = class extends import_node_events4.EventEmitter {
39603
39934
  * Create a barrier for a workflow run.
39604
39935
  */
39605
39936
  async createBarrier(runId, definition) {
39606
- const id = `bar_${Date.now()}_${(0, import_node_crypto6.randomBytes)(4).toString("hex")}`;
39937
+ const id = `bar_${Date.now()}_${(0, import_node_crypto7.randomBytes)(4).toString("hex")}`;
39607
39938
  const now = (/* @__PURE__ */ new Date()).toISOString();
39608
39939
  const mode = definition.mode ?? "all";
39609
39940
  const { rows } = await this.db.query(`INSERT INTO workflow_barriers (id, run_id, barrier_name, wait_for, resolved, is_satisfied, timeout_ms, created_at, updated_at)
@@ -39737,7 +40068,7 @@ var BarrierManager = class extends import_node_events4.EventEmitter {
39737
40068
  };
39738
40069
 
39739
40070
  // packages/sdk/dist/workflows/state.js
39740
- var import_node_crypto7 = require("node:crypto");
40071
+ var import_node_crypto8 = require("node:crypto");
39741
40072
  var import_node_events5 = require("node:events");
39742
40073
  var StateStore = class extends import_node_events5.EventEmitter {
39743
40074
  db;
@@ -39780,7 +40111,7 @@ var StateStore = class extends import_node_events5.EventEmitter {
39780
40111
  const namespace = options.namespace ?? this.defaultNamespace;
39781
40112
  const ttlMs = options.ttlMs ?? this.defaultTtlMs;
39782
40113
  const expiresAt = ttlMs ? new Date(Date.now() + ttlMs).toISOString() : null;
39783
- const id = `st_${Date.now()}_${(0, import_node_crypto7.randomBytes)(4).toString("hex")}`;
40114
+ const id = `st_${Date.now()}_${(0, import_node_crypto8.randomBytes)(4).toString("hex")}`;
39784
40115
  const now = (/* @__PURE__ */ new Date()).toISOString();
39785
40116
  const { rows } = await this.db.query(`INSERT INTO swarm_state (id, run_id, namespace, key, value, expires_at, created_at, updated_at)
39786
40117
  VALUES ($1, $2, $3, $4, $5, $6, $7, $7)
@@ -39868,7 +40199,7 @@ var StateStore = class extends import_node_events5.EventEmitter {
39868
40199
  };
39869
40200
 
39870
40201
  // packages/sdk/dist/workflows/templates.js
39871
- var import_node_fs6 = require("node:fs");
40202
+ var import_node_fs7 = require("node:fs");
39872
40203
  var import_node_path10 = __toESM(require("node:path"), 1);
39873
40204
  var import_node_url2 = require("node:url");
39874
40205
  var import_yaml4 = __toESM(require_dist(), 1);
@@ -39972,9 +40303,9 @@ var TemplateRegistry = class {
39972
40303
  throw new Error(`Invalid template name: "${templateName}" contains path separators or traversal sequences`);
39973
40304
  }
39974
40305
  this.validateRelayConfig(parsed, url2);
39975
- await import_node_fs6.promises.mkdir(this.customTemplatesDir, { recursive: true });
40306
+ await import_node_fs7.promises.mkdir(this.customTemplatesDir, { recursive: true });
39976
40307
  const targetPath = import_node_path10.default.join(this.customTemplatesDir, `${templateName}.yaml`);
39977
- await import_node_fs6.promises.writeFile(targetPath, (0, import_yaml4.stringify)(parsed), "utf-8");
40308
+ await import_node_fs7.promises.writeFile(targetPath, (0, import_yaml4.stringify)(parsed), "utf-8");
39978
40309
  return targetPath;
39979
40310
  }
39980
40311
  isTemplateShorthand(input) {
@@ -40003,7 +40334,7 @@ var TemplateRegistry = class {
40003
40334
  import_node_path10.default.resolve(currentDir, "../workflows/builtin-templates")
40004
40335
  ];
40005
40336
  for (const candidate of candidates) {
40006
- if ((0, import_node_fs6.existsSync)(candidate)) {
40337
+ if ((0, import_node_fs7.existsSync)(candidate)) {
40007
40338
  return candidate;
40008
40339
  }
40009
40340
  }
@@ -40025,7 +40356,7 @@ var TemplateRegistry = class {
40025
40356
  for (const ext of YAML_EXTENSIONS) {
40026
40357
  const candidate = import_node_path10.default.join(directory, `${templateName}${ext}`);
40027
40358
  try {
40028
- const stat2 = await import_node_fs6.promises.stat(candidate);
40359
+ const stat2 = await import_node_fs7.promises.stat(candidate);
40029
40360
  if (stat2.isFile()) {
40030
40361
  return candidate;
40031
40362
  }
@@ -40035,7 +40366,7 @@ var TemplateRegistry = class {
40035
40366
  return void 0;
40036
40367
  }
40037
40368
  async readTemplateFile(templatePath) {
40038
- const raw = await import_node_fs6.promises.readFile(templatePath, "utf-8");
40369
+ const raw = await import_node_fs7.promises.readFile(templatePath, "utf-8");
40039
40370
  const parsed = (0, import_yaml4.parse)(raw);
40040
40371
  if (!isRecord(parsed)) {
40041
40372
  throw new Error(`Template at ${templatePath} is not a YAML object`);
@@ -40228,7 +40559,7 @@ var TemplateRegistry = class {
40228
40559
  }
40229
40560
  async safeReadDir(directory) {
40230
40561
  try {
40231
- return await import_node_fs6.promises.readdir(directory);
40562
+ return await import_node_fs7.promises.readdir(directory);
40232
40563
  } catch {
40233
40564
  return [];
40234
40565
  }
@@ -40392,7 +40723,7 @@ function isValidAgentName(name) {
40392
40723
  }
40393
40724
 
40394
40725
  // packages/utils/dist/logger.js
40395
- var import_node_fs7 = __toESM(require("node:fs"), 1);
40726
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
40396
40727
  var import_node_path11 = __toESM(require("node:path"), 1);
40397
40728
  function getLogFile() {
40398
40729
  return process.env.AGENT_RELAY_LOG_FILE;
@@ -40412,8 +40743,8 @@ var LEVEL_PRIORITY = {
40412
40743
  var createdLogDirs = /* @__PURE__ */ new Set();
40413
40744
  function ensureLogDir(logFile) {
40414
40745
  const logDir = import_node_path11.default.dirname(logFile);
40415
- if (!createdLogDirs.has(logDir) && !import_node_fs7.default.existsSync(logDir)) {
40416
- import_node_fs7.default.mkdirSync(logDir, { recursive: true });
40746
+ if (!createdLogDirs.has(logDir) && !import_node_fs8.default.existsSync(logDir)) {
40747
+ import_node_fs8.default.mkdirSync(logDir, { recursive: true });
40417
40748
  createdLogDirs.add(logDir);
40418
40749
  }
40419
40750
  }
@@ -40442,7 +40773,7 @@ function log(level, component, msg, extra) {
40442
40773
  const logFile = getLogFile();
40443
40774
  if (logFile) {
40444
40775
  ensureLogDir(logFile);
40445
- import_node_fs7.default.appendFileSync(logFile, formatted + "\n");
40776
+ import_node_fs8.default.appendFileSync(logFile, formatted + "\n");
40446
40777
  return;
40447
40778
  }
40448
40779
  if (level === "ERROR" || level === "WARN") {
@@ -40713,7 +41044,7 @@ function benchmarkPatterns(iterations = 1e4) {
40713
41044
 
40714
41045
  // packages/utils/dist/command-resolver.js
40715
41046
  var import_node_child_process4 = require("node:child_process");
40716
- var import_node_fs8 = __toESM(require("node:fs"), 1);
41047
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
40717
41048
  function resolveCommand(command) {
40718
41049
  if (command.startsWith("/")) {
40719
41050
  return resolveSymlinks(command);
@@ -40739,7 +41070,7 @@ function resolveCommand(command) {
40739
41070
  }
40740
41071
  function resolveSymlinks(filePath) {
40741
41072
  try {
40742
- const resolved = import_node_fs8.default.realpathSync(filePath);
41073
+ const resolved = import_node_fs9.default.realpathSync(filePath);
40743
41074
  if (resolved !== filePath && process.env.DEBUG_SPAWN === "1") {
40744
41075
  console.log(`[command-resolver] Resolved symlink: ${filePath} -> ${resolved}`);
40745
41076
  }
@@ -40764,7 +41095,7 @@ function commandExists(command) {
40764
41095
  }
40765
41096
 
40766
41097
  // packages/utils/dist/git-remote.js
40767
- var fs5 = __toESM(require("node:fs"), 1);
41098
+ var fs6 = __toESM(require("node:fs"), 1);
40768
41099
  var path11 = __toESM(require("node:path"), 1);
40769
41100
  var import_node_child_process5 = require("node:child_process");
40770
41101
  function parseGitRemoteUrl(url2) {
@@ -40783,7 +41114,7 @@ function parseGitRemoteUrl(url2) {
40783
41114
  function getGitRemoteUrl(workingDirectory, remoteName = "origin") {
40784
41115
  try {
40785
41116
  const gitDir = path11.join(workingDirectory, ".git");
40786
- if (!fs5.existsSync(gitDir)) {
41117
+ if (!fs6.existsSync(gitDir)) {
40787
41118
  return null;
40788
41119
  }
40789
41120
  const result = (0, import_node_child_process5.execSync)(`git remote get-url ${remoteName}`, {
@@ -40796,10 +41127,10 @@ function getGitRemoteUrl(workingDirectory, remoteName = "origin") {
40796
41127
  } catch {
40797
41128
  try {
40798
41129
  const configPath = path11.join(workingDirectory, ".git", "config");
40799
- if (!fs5.existsSync(configPath)) {
41130
+ if (!fs6.existsSync(configPath)) {
40800
41131
  return null;
40801
41132
  }
40802
- const config2 = fs5.readFileSync(configPath, "utf-8");
41133
+ const config2 = fs6.readFileSync(configPath, "utf-8");
40803
41134
  const remoteSection = new RegExp(`\\[remote\\s+"${remoteName}"\\][^\\[]*url\\s*=\\s*([^\\n]+)`, "i");
40804
41135
  const match = config2.match(remoteSection);
40805
41136
  return match?.[1]?.trim() || null;
@@ -40819,7 +41150,7 @@ function findGitRoot(startPath) {
40819
41150
  let currentPath = path11.resolve(startPath);
40820
41151
  const root = path11.parse(currentPath).root;
40821
41152
  while (currentPath !== root) {
40822
- if (fs5.existsSync(path11.join(currentPath, ".git"))) {
41153
+ if (fs6.existsSync(path11.join(currentPath, ".git"))) {
40823
41154
  return currentPath;
40824
41155
  }
40825
41156
  currentPath = path11.dirname(currentPath);
@@ -40839,7 +41170,7 @@ function getRepoFullNameFromPath(workingDirectory) {
40839
41170
  }
40840
41171
 
40841
41172
  // packages/utils/dist/update-checker.js
40842
- var import_node_fs9 = __toESM(require("node:fs"), 1);
41173
+ var import_node_fs10 = __toESM(require("node:fs"), 1);
40843
41174
  var import_node_path12 = __toESM(require("node:path"), 1);
40844
41175
  var import_node_https = __toESM(require("node:https"), 1);
40845
41176
  var import_node_os4 = __toESM(require("node:os"), 1);
@@ -40854,9 +41185,9 @@ function getCachePath() {
40854
41185
  function readCache() {
40855
41186
  try {
40856
41187
  const cachePath = getCachePath();
40857
- if (!import_node_fs9.default.existsSync(cachePath))
41188
+ if (!import_node_fs10.default.existsSync(cachePath))
40858
41189
  return null;
40859
- const data = import_node_fs9.default.readFileSync(cachePath, "utf-8");
41190
+ const data = import_node_fs10.default.readFileSync(cachePath, "utf-8");
40860
41191
  return JSON.parse(data);
40861
41192
  } catch {
40862
41193
  return null;
@@ -40866,10 +41197,10 @@ function writeCache(cache) {
40866
41197
  try {
40867
41198
  const cachePath = getCachePath();
40868
41199
  const cacheDir = import_node_path12.default.dirname(cachePath);
40869
- if (!import_node_fs9.default.existsSync(cacheDir)) {
40870
- import_node_fs9.default.mkdirSync(cacheDir, { recursive: true });
41200
+ if (!import_node_fs10.default.existsSync(cacheDir)) {
41201
+ import_node_fs10.default.mkdirSync(cacheDir, { recursive: true });
40871
41202
  }
40872
- import_node_fs9.default.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
41203
+ import_node_fs10.default.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
40873
41204
  } catch {
40874
41205
  }
40875
41206
  }
@@ -40987,10 +41318,10 @@ function checkForUpdatesInBackground(currentVersion) {
40987
41318
  }
40988
41319
 
40989
41320
  // packages/utils/dist/error-tracking.js
40990
- var import_node_crypto8 = require("node:crypto");
41321
+ var import_node_crypto9 = require("node:crypto");
40991
41322
  function generateErrorId() {
40992
41323
  const timestamp = Math.floor(Date.now() / 1e3);
40993
- const random = (0, import_node_crypto8.randomBytes)(2).toString("hex");
41324
+ const random = (0, import_node_crypto9.randomBytes)(2).toString("hex");
40994
41325
  return `ERR-${timestamp}-${random}`;
40995
41326
  }
40996
41327
  function createTraceableError(message, context = {}, originalError) {
@@ -41148,7 +41479,7 @@ function validateModelForCli(cli, model) {
41148
41479
  }
41149
41480
 
41150
41481
  // packages/utils/dist/relay-pty-path.js
41151
- var import_node_fs10 = __toESM(require("node:fs"), 1);
41482
+ var import_node_fs11 = __toESM(require("node:fs"), 1);
41152
41483
  var import_node_os5 = __toESM(require("node:os"), 1);
41153
41484
  var import_node_path13 = __toESM(require("node:path"), 1);
41154
41485
  var SUPPORTED_PLATFORMS = {
@@ -41209,12 +41540,12 @@ function findRelayPtyBinary(callerDirname) {
41209
41540
  const home = process.env.HOME || process.env.USERPROFILE || "";
41210
41541
  if (home) {
41211
41542
  const npxCacheBase = import_node_path13.default.join(home, ".npm", "_npx");
41212
- if (import_node_fs10.default.existsSync(npxCacheBase)) {
41543
+ if (import_node_fs11.default.existsSync(npxCacheBase)) {
41213
41544
  try {
41214
- const entries = import_node_fs10.default.readdirSync(npxCacheBase);
41545
+ const entries = import_node_fs11.default.readdirSync(npxCacheBase);
41215
41546
  for (const entry of entries) {
41216
41547
  const npxPackage = import_node_path13.default.join(npxCacheBase, entry, "node_modules", "agent-relay");
41217
- if (import_node_fs10.default.existsSync(npxPackage)) {
41548
+ if (import_node_fs11.default.existsSync(npxPackage)) {
41218
41549
  packageRoots.push(npxPackage);
41219
41550
  }
41220
41551
  }
@@ -41272,7 +41603,7 @@ function findRelayPtyBinary(callerDirname) {
41272
41603
  }
41273
41604
  function isExecutable(filePath) {
41274
41605
  try {
41275
- import_node_fs10.default.accessSync(filePath, import_node_fs10.default.constants.X_OK);
41606
+ import_node_fs11.default.accessSync(filePath, import_node_fs11.default.constants.X_OK);
41276
41607
  return true;
41277
41608
  } catch {
41278
41609
  return false;
@@ -41281,9 +41612,9 @@ function isExecutable(filePath) {
41281
41612
  function isPlatformCompatibleBinary(filePath) {
41282
41613
  let fd;
41283
41614
  try {
41284
- fd = import_node_fs10.default.openSync(filePath, "r");
41615
+ fd = import_node_fs11.default.openSync(filePath, "r");
41285
41616
  const header = Buffer.alloc(4);
41286
- const bytesRead = import_node_fs10.default.readSync(fd, header, 0, 4, 0);
41617
+ const bytesRead = import_node_fs11.default.readSync(fd, header, 0, 4, 0);
41287
41618
  if (bytesRead < 4) {
41288
41619
  return false;
41289
41620
  }
@@ -41301,7 +41632,7 @@ function isPlatformCompatibleBinary(filePath) {
41301
41632
  } finally {
41302
41633
  if (fd !== void 0) {
41303
41634
  try {
41304
- import_node_fs10.default.closeSync(fd);
41635
+ import_node_fs11.default.closeSync(fd);
41305
41636
  } catch {
41306
41637
  }
41307
41638
  }
@@ -41332,7 +41663,7 @@ function clearBinaryCache() {
41332
41663
 
41333
41664
  // packages/utils/dist/client-helpers.js
41334
41665
  var import_node_net = require("node:net");
41335
- var import_node_crypto9 = require("node:crypto");
41666
+ var import_node_crypto10 = require("node:crypto");
41336
41667
 
41337
41668
  // packages/utils/dist/legacy-protocol.js
41338
41669
  var PROTOCOL_VERSION2 = 1;
@@ -41459,14 +41790,14 @@ function createRequestHandler(socketPath, envelope, options) {
41459
41790
  });
41460
41791
  }
41461
41792
  function generateRequestId(prefix = "") {
41462
- return `${prefix}${Date.now().toString(36)}-${(0, import_node_crypto9.randomUUID)().slice(0, 8)}`;
41793
+ return `${prefix}${Date.now().toString(36)}-${(0, import_node_crypto10.randomUUID)().slice(0, 8)}`;
41463
41794
  }
41464
41795
 
41465
41796
  // packages/hooks/dist/types.js
41466
41797
  var HOOK_ABI_VERSION = PROTOCOL_VERSION;
41467
41798
 
41468
41799
  // packages/hooks/dist/registry.js
41469
- var import_node_crypto11 = require("node:crypto");
41800
+ var import_node_crypto12 = require("node:crypto");
41470
41801
  var InMemoryHookMemory = class {
41471
41802
  store = /* @__PURE__ */ new Map();
41472
41803
  get(key) {
@@ -41515,8 +41846,8 @@ var HookRegistry = class {
41515
41846
  injectFn;
41516
41847
  sendFn;
41517
41848
  constructor(options = {}) {
41518
- this.sessionId = (0, import_node_crypto11.randomUUID)();
41519
- this.agentId = options.agentId ?? (0, import_node_crypto11.randomUUID)();
41849
+ this.sessionId = (0, import_node_crypto12.randomUUID)();
41850
+ this.agentId = options.agentId ?? (0, import_node_crypto12.randomUUID)();
41520
41851
  this.agentName = options.agentName ?? "agent";
41521
41852
  this.workingDir = options.workingDir ?? process.cwd();
41522
41853
  this.projectId = options.projectId ?? "default";
@@ -41920,9 +42251,9 @@ var HookRegistry = class {
41920
42251
  var import_node_child_process6 = require("node:child_process");
41921
42252
 
41922
42253
  // packages/config/dist/project-namespace.js
41923
- var import_node_crypto12 = __toESM(require("node:crypto"), 1);
42254
+ var import_node_crypto13 = __toESM(require("node:crypto"), 1);
41924
42255
  var import_node_path14 = __toESM(require("node:path"), 1);
41925
- var import_node_fs11 = __toESM(require("node:fs"), 1);
42256
+ var import_node_fs12 = __toESM(require("node:fs"), 1);
41926
42257
  var import_node_os6 = __toESM(require("node:os"), 1);
41927
42258
  function getGlobalBaseDir2() {
41928
42259
  if (process.env.AGENT_RELAY_DATA_DIR) {
@@ -41935,13 +42266,13 @@ function getGlobalBaseDir2() {
41935
42266
  return import_node_path14.default.join(import_node_os6.default.homedir(), ".agent-relay");
41936
42267
  }
41937
42268
  var GLOBAL_BASE_DIR2 = getGlobalBaseDir2();
41938
- var PROJECT_DATA_DIR = ".agent-relay";
41939
- function hashPath(projectPath) {
42269
+ var PROJECT_DATA_DIR2 = ".agent-relay";
42270
+ function hashPath2(projectPath) {
41940
42271
  const normalized = import_node_path14.default.resolve(projectPath);
41941
- const hash2 = import_node_crypto12.default.createHash("sha256").update(normalized).digest("hex");
42272
+ const hash2 = import_node_crypto13.default.createHash("sha256").update(normalized).digest("hex");
41942
42273
  return hash2.substring(0, 12);
41943
42274
  }
41944
- function findProjectRoot(startDir = process.cwd()) {
42275
+ function findProjectRoot2(startDir = process.cwd()) {
41945
42276
  if (process.env.AGENT_RELAY_PROJECT) {
41946
42277
  return import_node_path14.default.resolve(process.env.AGENT_RELAY_PROJECT);
41947
42278
  }
@@ -41950,7 +42281,7 @@ function findProjectRoot(startDir = process.cwd()) {
41950
42281
  const markers = [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".agent-relay"];
41951
42282
  while (current !== root) {
41952
42283
  for (const marker of markers) {
41953
- if (import_node_fs11.default.existsSync(import_node_path14.default.join(current, marker))) {
42284
+ if (import_node_fs12.default.existsSync(import_node_path14.default.join(current, marker))) {
41954
42285
  return current;
41955
42286
  }
41956
42287
  }
@@ -41959,9 +42290,9 @@ function findProjectRoot(startDir = process.cwd()) {
41959
42290
  return import_node_path14.default.resolve(startDir);
41960
42291
  }
41961
42292
  function getProjectPaths2(projectRoot) {
41962
- const root = projectRoot ?? findProjectRoot();
41963
- const projectId = hashPath(root);
41964
- const dataDir = import_node_path14.default.join(root, PROJECT_DATA_DIR);
42293
+ const root = projectRoot ?? findProjectRoot2();
42294
+ const projectId = hashPath2(root);
42295
+ const dataDir = import_node_path14.default.join(root, PROJECT_DATA_DIR2);
41965
42296
  return {
41966
42297
  dataDir,
41967
42298
  teamDir: import_node_path14.default.join(dataDir, "team"),
@@ -41973,10 +42304,10 @@ function getProjectPaths2(projectRoot) {
41973
42304
  }
41974
42305
 
41975
42306
  // packages/config/dist/trajectory-config.js
41976
- var import_node_fs12 = require("node:fs");
42307
+ var import_node_fs13 = require("node:fs");
41977
42308
  var import_node_path15 = require("node:path");
41978
42309
  var import_node_os7 = require("node:os");
41979
- var import_node_crypto13 = require("node:crypto");
42310
+ var import_node_crypto14 = require("node:crypto");
41980
42311
  function getAgentRelayConfigDir() {
41981
42312
  return process.env.AGENT_RELAY_CONFIG_DIR ?? (0, import_node_path15.join)((0, import_node_os7.homedir)(), ".config", "agent-relay");
41982
42313
  }
@@ -41988,7 +42319,7 @@ function readRelayConfig(projectRoot) {
41988
42319
  const configPath = getRelayConfigPath(projectRoot);
41989
42320
  if (configCache && configCache.path === configPath) {
41990
42321
  try {
41991
- const stat2 = (0, import_node_fs12.statSync)(configPath);
42322
+ const stat2 = (0, import_node_fs13.statSync)(configPath);
41992
42323
  if (stat2.mtimeMs === configCache.mtime) {
41993
42324
  return configCache.config;
41994
42325
  }
@@ -41996,13 +42327,13 @@ function readRelayConfig(projectRoot) {
41996
42327
  }
41997
42328
  }
41998
42329
  try {
41999
- if (!(0, import_node_fs12.existsSync)(configPath)) {
42330
+ if (!(0, import_node_fs13.existsSync)(configPath)) {
42000
42331
  return {};
42001
42332
  }
42002
- const content = (0, import_node_fs12.readFileSync)(configPath, "utf-8");
42333
+ const content = (0, import_node_fs13.readFileSync)(configPath, "utf-8");
42003
42334
  const config2 = JSON.parse(content);
42004
42335
  try {
42005
- const stat2 = (0, import_node_fs12.statSync)(configPath);
42336
+ const stat2 = (0, import_node_fs13.statSync)(configPath);
42006
42337
  configCache = { path: configPath, config: config2, mtime: stat2.mtimeMs };
42007
42338
  } catch {
42008
42339
  }
@@ -42018,7 +42349,7 @@ function shouldStoreInRepo(projectRoot) {
42018
42349
  }
42019
42350
  function getProjectHash(projectRoot) {
42020
42351
  const root = projectRoot ?? getProjectPaths2().projectRoot;
42021
- return (0, import_node_crypto13.createHash)("sha256").update(root).digest("hex").slice(0, 16);
42352
+ return (0, import_node_crypto14.createHash)("sha256").update(root).digest("hex").slice(0, 16);
42022
42353
  }
42023
42354
  function getUserTrajectoriesDir(projectRoot) {
42024
42355
  const projectHash = getProjectHash(projectRoot);
@@ -42667,7 +42998,7 @@ var HookEmitter = class {
42667
42998
  };
42668
42999
 
42669
43000
  // packages/hooks/dist/inbox-check/utils.js
42670
- var import_node_fs13 = require("node:fs");
43001
+ var import_node_fs14 = require("node:fs");
42671
43002
  var import_node_path16 = require("node:path");
42672
43003
  var DEFAULT_INBOX_DIR = "/tmp/agent-relay";
42673
43004
  function getAgentName() {
@@ -42681,13 +43012,13 @@ function getInboxPath(config2) {
42681
43012
  return (0, import_node_path16.join)(config2.inboxDir, agentName, "inbox.md");
42682
43013
  }
42683
43014
  function inboxExists(inboxPath) {
42684
- return (0, import_node_fs13.existsSync)(inboxPath);
43015
+ return (0, import_node_fs14.existsSync)(inboxPath);
42685
43016
  }
42686
43017
  function readInbox(inboxPath) {
42687
43018
  if (!inboxExists(inboxPath)) {
42688
43019
  return "";
42689
43020
  }
42690
- return (0, import_node_fs13.readFileSync)(inboxPath, "utf-8");
43021
+ return (0, import_node_fs14.readFileSync)(inboxPath, "utf-8");
42691
43022
  }
42692
43023
  function hasUnreadMessages(inboxPath) {
42693
43024
  const content = readInbox(inboxPath);
@@ -42774,6 +43105,7 @@ init_dist();
42774
43105
  HOOK_ABI_VERSION,
42775
43106
  HookEmitter,
42776
43107
  HookRegistry,
43108
+ HttpAgentRelayClient,
42777
43109
  InMemoryAdapter,
42778
43110
  InMemoryWorkflowDb,
42779
43111
  JsonFileWorkflowDb,