agent-relay-orchestrator 0.91.3 → 0.91.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-orchestrator",
3
- "version": "0.91.3",
3
+ "version": "0.91.4",
4
4
  "description": "Agent Relay orchestrator — manages agent lifecycle across hosts",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "src/**/*.ts",
11
11
  "!src/**/*.test.ts",
12
+ "vendor/**",
12
13
  "README.md"
13
14
  ],
14
15
  "scripts": {
@@ -16,7 +17,7 @@
16
17
  "test": "bun test"
17
18
  },
18
19
  "dependencies": {
19
- "agent-relay-sdk": "0.2.70"
20
+ "agent-relay-sdk": "0.2.71"
20
21
  },
21
22
  "devDependencies": {
22
23
  "@types/bun": "latest",
@@ -1,6 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { dirname, isAbsolute, join } from "node:path";
3
+ import { dirname, isAbsolute, join, resolve } from "node:path";
4
4
  import { errMessage, isRecord } from "agent-relay-sdk";
5
5
  import type { OrchestratorConfig } from "./config";
6
6
  import { agentRelayHome } from "./config";
@@ -54,6 +54,15 @@ export interface SharedCallmuxSupervisorDeps {
54
54
  setTimeout(fn: () => void, ms: number): Timer;
55
55
  clearTimeout(timer: Timer): void;
56
56
  log(message: string): void;
57
+ report(snapshot: SharedCallmuxHealthSnapshot): void;
58
+ }
59
+
60
+ export interface SharedCallmuxHealthSnapshot {
61
+ state: "disabled" | "missing" | "starting" | "running" | "unhealthy" | "restarting" | "stopped";
62
+ url: string;
63
+ command?: string;
64
+ reason?: string;
65
+ pid?: number;
57
66
  }
58
67
 
59
68
  export function sharedCallmuxOptionsFromEnv(env: Record<string, string | undefined> = process.env): SharedCallmuxOptions {
@@ -63,14 +72,13 @@ export function sharedCallmuxOptionsFromEnv(env: Record<string, string | undefin
63
72
  const port = numberEnv(env[SHARED_CALLMUX_PORT_ENV]) ?? parsed?.port ?? DEFAULT_SHARED_CALLMUX_PORT;
64
73
  const url = explicitUrl ?? `http://${host}:${port}/mcp`;
65
74
  return {
66
- // PATH/env-resolved callmux must be >=0.21.0; generated config uses exposeMetaTools.
67
- command: env[SHARED_CALLMUX_COMMAND_ENV] || "callmux",
75
+ command: env[SHARED_CALLMUX_COMMAND_ENV] || bundledCallmuxCommand() || "callmux",
68
76
  host,
69
77
  port,
70
78
  url,
71
79
  configPath: env[SHARED_CALLMUX_CONFIG_ENV] || join(agentRelayHome(), "callmux", "shared-listener.json"),
72
80
  sourceConfigPath: env[SHARED_CALLMUX_SOURCE_CONFIG_ENV] || env.CALLMUX_CONFIG || join(homedir(), ".config", "callmux", "config.json"),
73
- enabled: env[SHARED_CALLMUX_ENABLE_ENV] === "1",
81
+ enabled: !envOff(env[SHARED_CALLMUX_ENABLE_ENV]),
74
82
  };
75
83
  }
76
84
 
@@ -118,14 +126,17 @@ export class SharedCallmuxSupervisor {
118
126
 
119
127
  start(): void {
120
128
  if (!this.opts.enabled) {
121
- this.deps.log("[orchestrator] shared callmux listener disabled (set AGENT_RELAY_SHARED_CALLMUX_ENABLE=1 to enable)");
129
+ this.deps.log("[orchestrator] shared callmux listener disabled by AGENT_RELAY_SHARED_CALLMUX_ENABLE=0");
130
+ this.report("disabled", "global kill-switch");
122
131
  return;
123
132
  }
124
133
  this.deps.log(`[orchestrator] Shared callmux listener: ${this.opts.url}`);
125
134
  if (!this.deps.which(this.opts.command)) {
126
135
  this.deps.log("[orchestrator] shared callmux not found — shared listener dormant");
136
+ this.report("missing", "command not found");
127
137
  return;
128
138
  }
139
+ this.report("starting");
129
140
  this.spawn();
130
141
  this.healthTimer = this.deps.setInterval(() => {
131
142
  void this.checkHealth();
@@ -156,9 +167,11 @@ export class SharedCallmuxSupervisor {
156
167
  }
157
168
  if (ok) {
158
169
  this.backoffMs = this.timing.restartBaseMs ?? 1_000;
170
+ this.report("running", undefined, this.proc?.pid);
159
171
  return true;
160
172
  }
161
173
  this.deps.log(`[orchestrator] Shared callmux readiness failed at ${readyUrl}; restarting`);
174
+ this.report("unhealthy", "readiness failed", this.proc?.pid);
162
175
  this.restart("readiness failed");
163
176
  return false;
164
177
  }
@@ -177,20 +190,24 @@ export class SharedCallmuxSupervisor {
177
190
  proc = this.deps.spawn(this.opts.command, args, { env, cwd: homedir() });
178
191
  } catch (err) {
179
192
  this.deps.log(`[orchestrator] Shared callmux listener failed to start: ${errMessage(err)}; scheduling restart`);
193
+ this.report("restarting", errMessage(err));
180
194
  this.scheduleRestart();
181
195
  return;
182
196
  }
183
197
  this.proc = proc;
184
198
  this.deps.log(`[orchestrator] Started shared callmux listener pid=${proc.pid ?? "unknown"}`);
199
+ this.report("running", undefined, proc.pid);
185
200
  proc.exited.then((code) => {
186
201
  if (this.proc !== proc || this.stopping) return;
187
202
  this.proc = null;
188
203
  this.deps.log(`[orchestrator] Shared callmux listener exited (${code ?? "signal"}); scheduling restart`);
204
+ this.report("restarting", `exited ${code ?? "signal"}`);
189
205
  this.scheduleRestart();
190
206
  }).catch((err) => {
191
207
  if (this.proc !== proc || this.stopping) return;
192
208
  this.proc = null;
193
209
  this.deps.log(`[orchestrator] Shared callmux listener exit watcher failed: ${err}`);
210
+ this.report("restarting", errMessage(err));
194
211
  this.scheduleRestart();
195
212
  });
196
213
  }
@@ -201,9 +218,20 @@ export class SharedCallmuxSupervisor {
201
218
  this.proc.kill("SIGTERM");
202
219
  this.proc = null;
203
220
  }
221
+ this.report("restarting", reason);
204
222
  this.scheduleRestart();
205
223
  }
206
224
 
225
+ private report(state: SharedCallmuxHealthSnapshot["state"], reason?: string, pid?: number): void {
226
+ this.deps.report({
227
+ state,
228
+ url: this.opts.url,
229
+ command: this.opts.command,
230
+ ...(reason ? { reason } : {}),
231
+ ...(pid ? { pid } : {}),
232
+ });
233
+ }
234
+
207
235
  private scheduleRestart(): void {
208
236
  if (this.stopping || this.restartTimer) return;
209
237
  const delay = this.backoffMs;
@@ -238,15 +266,26 @@ function defaultDeps(): SharedCallmuxSupervisorDeps {
238
266
  setTimeout: (fn, ms) => setTimeout(fn, ms),
239
267
  clearTimeout: (timer) => clearTimeout(timer),
240
268
  log: (message) => console.error(message),
269
+ report: (snapshot) => console.error(`[orchestrator] shared callmux status ${snapshot.state}${snapshot.reason ? `: ${snapshot.reason}` : ""}`),
241
270
  };
242
271
  }
243
272
 
273
+ export function bundledCallmuxCommand(): string | null {
274
+ const candidate = resolve(import.meta.dir, "../vendor/callmux/bin/callmux.js");
275
+ return existsSync(candidate) ? candidate : null;
276
+ }
277
+
244
278
  function resolveCommand(command: string): string | null {
245
279
  if (isAbsolute(command)) return existsSync(command) ? command : null;
246
280
  if (command.includes("/")) return existsSync(command) ? command : null;
247
281
  return Bun.which(command);
248
282
  }
249
283
 
284
+ function envOff(value: string | undefined): boolean {
285
+ if (value === undefined || value === null || value === "") return false;
286
+ return ["0", "false", "off", "no"].includes(value.trim().toLowerCase());
287
+ }
288
+
250
289
  function readJsonObject(path: string): Record<string, unknown> {
251
290
  if (!existsSync(path)) return {};
252
291
  const parsed = JSON.parse(readFileSync(path, "utf8"));