agent-relay-server 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/upgrade.ts CHANGED
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { VERSION } from "./config";
5
5
 
6
- export type UpgradeProvider = "auto" | "all" | "codex" | "claude";
6
+ export type UpgradeProvider = "auto" | "all" | "codex" | "claude" | "orchestrator";
7
7
 
8
8
  type UpgradeOptions = {
9
9
  targetVersion?: string;
@@ -28,12 +28,16 @@ export type UpgradeSnapshot = {
28
28
  targetVersion: string;
29
29
  serverPackage?: InstalledPackage;
30
30
  codexPackage?: InstalledPackage;
31
+ orchestratorPackage?: InstalledPackage;
31
32
  codexCopiedPackage?: InstalledPackage;
32
33
  claudePluginInstalls: ClaudePluginInstall[];
33
34
  hasCodexCommand: boolean;
35
+ hasOrchestratorCommand: boolean;
34
36
  hasClaudeCommand: boolean;
35
37
  hasSystemdUserService: boolean;
38
+ hasSystemdUserOrchestratorService: boolean;
36
39
  runningServerVersion?: string;
40
+ runningOrchestrators?: Array<{ id: string; version?: string; protocolVersion?: number; health?: { status?: string; restartRequired?: boolean } }>;
37
41
  packageManager: "bun" | "npm" | "none";
38
42
  };
39
43
 
@@ -46,7 +50,7 @@ type UpgradeAction = {
46
50
 
47
51
  type UpgradePlan = {
48
52
  targetVersion: string;
49
- providers: { codex: boolean; claude: boolean };
53
+ providers: { codex: boolean; claude: boolean; orchestrator: boolean };
50
54
  snapshot: UpgradeSnapshot;
51
55
  actions: UpgradeAction[];
52
56
  warnings: string[];
@@ -67,9 +71,9 @@ export async function detectUpgradeSnapshot(options: UpgradeOptions = {}): Promi
67
71
  const codexCopiedPackage = readPackageVersion(join(homeDir(), ".agent-relay", "codex", "package", "package.json"));
68
72
  const claudePluginInstalls = readClaudePluginInstalls(join(homeDir(), ".claude", "plugins", "installed_plugins.json"));
69
73
 
70
- const packageManager = bunPackages.has("agent-relay-server") || bunPackages.has("agent-relay-codex")
74
+ const packageManager = bunPackages.has("agent-relay-server") || bunPackages.has("agent-relay-codex") || bunPackages.has("agent-relay-orchestrator")
71
75
  ? "bun"
72
- : npmPackages.has("agent-relay-server") || npmPackages.has("agent-relay-codex")
76
+ : npmPackages.has("agent-relay-server") || npmPackages.has("agent-relay-codex") || npmPackages.has("agent-relay-orchestrator")
73
77
  ? "npm"
74
78
  : commandExists("bun")
75
79
  ? "bun"
@@ -81,14 +85,18 @@ export async function detectUpgradeSnapshot(options: UpgradeOptions = {}): Promi
81
85
  targetVersion,
82
86
  serverPackage: installedPackage("agent-relay-server", bunPackages, npmPackages),
83
87
  codexPackage: installedPackage("agent-relay-codex", bunPackages, npmPackages),
88
+ orchestratorPackage: installedPackage("agent-relay-orchestrator", bunPackages, npmPackages),
84
89
  codexCopiedPackage: codexCopiedPackage
85
90
  ? { version: codexCopiedPackage, source: "copied", path: join(homeDir(), ".agent-relay", "codex", "package") }
86
91
  : undefined,
87
92
  claudePluginInstalls,
88
93
  hasCodexCommand: commandExists("agent-relay-codex") || commandExists("codex-relay") || existsSync(join(homeDir(), ".agent-relay", "codex", "package")),
94
+ hasOrchestratorCommand: commandExists("agent-relay-orchestrator"),
89
95
  hasClaudeCommand: commandExists("claude"),
90
96
  hasSystemdUserService: hasSystemdUserService("agent-relay.service"),
97
+ hasSystemdUserOrchestratorService: hasSystemdUserService("agent-relay-orchestrator.service"),
91
98
  runningServerVersion: await runningServerVersion(),
99
+ runningOrchestrators: await runningOrchestrators(),
92
100
  packageManager,
93
101
  };
94
102
  }
@@ -99,6 +107,7 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
99
107
  const providerSet = new Set(requestedProviders);
100
108
  const codexRequested = providerSet.has("all") || providerSet.has("codex") || (providerSet.has("auto") && isCodexDetected(snapshot));
101
109
  const claudeRequested = providerSet.has("all") || providerSet.has("claude") || (providerSet.has("auto") && isClaudeRelayDetected(snapshot));
110
+ const orchestratorRequested = providerSet.has("all") || providerSet.has("orchestrator") || (providerSet.has("auto") && isOrchestratorDetected(snapshot));
102
111
  const actions: UpgradeAction[] = [];
103
112
  const warnings: string[] = [];
104
113
 
@@ -107,13 +116,14 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
107
116
  } else {
108
117
  const packages = [`agent-relay-server@${targetVersion}`];
109
118
  if (codexRequested) packages.push(`agent-relay-codex@${targetVersion}`);
119
+ if (orchestratorRequested) packages.push(`agent-relay-orchestrator@${targetVersion}`);
110
120
  const command = snapshot.packageManager === "bun"
111
121
  ? ["bun", "add", "-g", ...packages]
112
122
  : ["npm", "install", "-g", ...packages];
113
123
  actions.push({
114
124
  label: "Upgrade global packages",
115
125
  command,
116
- reason: `Update server${codexRequested ? " and Codex integration" : ""} packages to ${targetVersion}.`,
126
+ reason: `Update server${codexRequested ? ", Codex integration" : ""}${orchestratorRequested ? ", and orchestrator" : ""} packages to ${targetVersion}.`,
117
127
  mutates: true,
118
128
  });
119
129
  }
@@ -148,6 +158,10 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
148
158
  warnings.push("Claude Agent Relay plugin not detected; skipping Claude plugin upgrade.");
149
159
  }
150
160
 
161
+ if (!orchestratorRequested && providerSet.has("auto") && !isOrchestratorDetected(snapshot)) {
162
+ warnings.push("Agent Relay orchestrator not detected; skipping orchestrator package upgrade.");
163
+ }
164
+
151
165
  if (snapshot.hasSystemdUserService) {
152
166
  if (options.noRestart) {
153
167
  warnings.push("agent-relay.service detected but --no-restart was set; restart manually to run the upgraded server.");
@@ -163,9 +177,24 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
163
177
  warnings.push("No systemd user service detected; restart any manually running Agent Relay server yourself.");
164
178
  }
165
179
 
180
+ if (snapshot.hasSystemdUserOrchestratorService) {
181
+ if (options.noRestart) {
182
+ warnings.push("agent-relay-orchestrator.service detected but --no-restart was set; restart manually to run the upgraded orchestrator.");
183
+ } else {
184
+ actions.push({
185
+ label: "Restart Agent Relay orchestrator service",
186
+ command: ["systemctl", "--user", "restart", "agent-relay-orchestrator.service"],
187
+ reason: "Restart orchestrator daemon so host lifecycle control uses the upgraded protocol.",
188
+ mutates: true,
189
+ });
190
+ }
191
+ } else if (orchestratorRequested) {
192
+ warnings.push("No agent-relay-orchestrator.service detected; restart any manually running orchestrator yourself.");
193
+ }
194
+
166
195
  return {
167
196
  targetVersion,
168
- providers: { codex: codexRequested, claude: claudeRequested },
197
+ providers: { codex: codexRequested, claude: claudeRequested, orchestrator: orchestratorRequested },
169
198
  snapshot: { ...snapshot, targetVersion },
170
199
  actions,
171
200
  warnings,
@@ -194,6 +223,15 @@ export async function executeUpgradePlan(plan: UpgradePlan, options: { dryRun?:
194
223
  }
195
224
  if (serverVersion) lines.push(`Running server: ${serverVersion}`);
196
225
  }
226
+ if (plan.actions.some((action) => action.command.join(" ") === "systemctl --user restart agent-relay-orchestrator.service")) {
227
+ await new Promise((resolve) => setTimeout(resolve, 1000));
228
+ const orchestrators = await runningOrchestrators() ?? [];
229
+ const mismatched = orchestrators.filter((orch) => orch.version && orch.version !== plan.targetVersion);
230
+ if (mismatched.length > 0) {
231
+ throw new Error(`agent-relay-orchestrator.service restarted but ${mismatched.map((orch) => `${orch.id} reports ${orch.version}`).join(", ")}, expected ${plan.targetVersion}`);
232
+ }
233
+ if (orchestrators.length > 0) lines.push(`Running orchestrator(s): ${orchestrators.map((orch) => `${orch.id}${orch.version ? ` ${orch.version}` : ""}`).join(", ")}`);
234
+ }
197
235
  lines.push("\nUpgrade commands completed.");
198
236
  if (plan.warnings.length > 0) {
199
237
  lines.push("\nWarnings:");
@@ -211,11 +249,14 @@ export function formatUpgradePlan(plan: UpgradePlan, options: { dryRun?: boolean
211
249
  `- running server: ${plan.snapshot.runningServerVersion ?? "unknown"}`,
212
250
  `- codex package: ${formatPackage(plan.snapshot.codexPackage)}`,
213
251
  `- codex copied package: ${formatPackage(plan.snapshot.codexCopiedPackage)}`,
252
+ `- orchestrator package: ${formatPackage(plan.snapshot.orchestratorPackage)}`,
253
+ `- running orchestrators: ${formatRunningOrchestrators(plan.snapshot.runningOrchestrators)}`,
214
254
  `- claude command: ${plan.snapshot.hasClaudeCommand ? "yes" : "no"}`,
215
255
  `- claude agent-relay plugin: ${formatClaudePlugins(plan.snapshot.claudePluginInstalls)}`,
216
256
  `- systemd user service: ${plan.snapshot.hasSystemdUserService ? "yes" : "no"}`,
257
+ `- orchestrator systemd user service: ${plan.snapshot.hasSystemdUserOrchestratorService ? "yes" : "no"}`,
217
258
  "",
218
- `Providers: codex=${plan.providers.codex ? "yes" : "no"}, claude=${plan.providers.claude ? "yes" : "no"}`,
259
+ `Providers: codex=${plan.providers.codex ? "yes" : "no"}, claude=${plan.providers.claude ? "yes" : "no"}, orchestrator=${plan.providers.orchestrator ? "yes" : "no"}`,
219
260
  "",
220
261
  "Actions:",
221
262
  ];
@@ -239,6 +280,10 @@ function isClaudeRelayDetected(snapshot: UpgradeSnapshot): boolean {
239
280
  return snapshot.claudePluginInstalls.length > 0;
240
281
  }
241
282
 
283
+ function isOrchestratorDetected(snapshot: UpgradeSnapshot): boolean {
284
+ return Boolean(snapshot.orchestratorPackage || snapshot.hasOrchestratorCommand || snapshot.hasSystemdUserOrchestratorService || (snapshot.runningOrchestrators?.length ?? 0) > 0);
285
+ }
286
+
242
287
  function installedPackage(name: string, bunPackages: Map<string, string>, npmPackages: Map<string, string>): InstalledPackage | undefined {
243
288
  const bunVersion = bunPackages.get(name);
244
289
  if (bunVersion) return { version: bunVersion, source: "bun" };
@@ -292,6 +337,27 @@ async function runningServerVersion(): Promise<string | undefined> {
292
337
  }
293
338
  }
294
339
 
340
+ async function runningOrchestrators(): Promise<UpgradeSnapshot["runningOrchestrators"]> {
341
+ try {
342
+ const headers: Record<string, string> = {};
343
+ if (process.env.AGENT_RELAY_TOKEN) headers["X-Agent-Relay-Token"] = process.env.AGENT_RELAY_TOKEN;
344
+ const relayUrl = (process.env.AGENT_RELAY_URL || "http://127.0.0.1:4850").replace(/\/+$/, "");
345
+ const response = await fetch(`${relayUrl}/api/orchestrators`, { headers });
346
+ if (!response.ok) return [];
347
+ const payload = await response.json() as Array<{ id?: string; version?: string; protocolVersion?: number; health?: { status?: string; restartRequired?: boolean } }>;
348
+ return payload
349
+ .filter((orch) => typeof orch.id === "string")
350
+ .map((orch) => ({
351
+ id: orch.id!,
352
+ version: orch.version,
353
+ protocolVersion: orch.protocolVersion,
354
+ health: orch.health,
355
+ }));
356
+ } catch {
357
+ return [];
358
+ }
359
+ }
360
+
295
361
  function hasSystemdUserService(name: string): boolean {
296
362
  if (!commandExists("systemctl")) return false;
297
363
  const result = runCommand(["systemctl", "--user", "status", name, "--no-pager"]);
@@ -372,6 +438,13 @@ function formatClaudePlugins(installs: ClaudePluginInstall[]): string {
372
438
  return installs.map((install) => `${install.version ?? "unknown"} (${install.scope ?? "user"})`).join(", ");
373
439
  }
374
440
 
441
+ function formatRunningOrchestrators(orchestrators: UpgradeSnapshot["runningOrchestrators"]): string {
442
+ if (!orchestrators?.length) return "not detected";
443
+ return orchestrators
444
+ .map((orch) => `${orch.id}: ${orch.version ?? "unknown"}${orch.health?.restartRequired ? " (restart required)" : ""}`)
445
+ .join(", ");
446
+ }
447
+
375
448
  function shellQuote(value: string): string {
376
449
  if (/^[a-zA-Z0-9_@%+=:,./-]+$/.test(value)) return value;
377
450
  return `'${value.replaceAll("'", "'\\''")}'`;