agent-relay-server 0.6.1 → 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/package.json +3 -1
- package/public/dashboard/actions.js +819 -0
- package/public/dashboard/api.js +336 -0
- package/public/dashboard/app.js +34 -0
- package/public/dashboard/charts.js +128 -0
- package/public/dashboard/computed.js +693 -0
- package/public/dashboard/constants.js +28 -0
- package/public/dashboard/display.js +345 -0
- package/public/dashboard/state.js +129 -0
- package/public/dashboard/utils.js +207 -0
- package/public/index.html +48 -36
- package/scripts/orchestrator-spawn-smoke.ts +140 -0
- package/src/cli.ts +5 -4
- package/src/config.ts +1 -0
- package/src/db.ts +52 -4
- package/src/routes.ts +74 -48
- package/src/types.ts +16 -0
- package/src/upgrade.ts +80 -7
- package/public/dashboard.js +0 -3032
package/src/routes.ts
CHANGED
|
@@ -775,6 +775,44 @@ function auditEvent(input: ActivityEventInput): void {
|
|
|
775
775
|
}
|
|
776
776
|
}
|
|
777
777
|
|
|
778
|
+
type AgentActivityState = {
|
|
779
|
+
key: "available" | "busy" | "not-ready" | "offline";
|
|
780
|
+
title: string;
|
|
781
|
+
icon: string;
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
function agentActivityState(agent?: Pick<AgentCard, "ready" | "status"> | null): AgentActivityState | null {
|
|
785
|
+
if (!agent) return null;
|
|
786
|
+
if (agent.status === "offline") return { key: "offline", title: "Agent offline", icon: "ti-plug-off" };
|
|
787
|
+
if (!agent.ready) return { key: "not-ready", title: "Agent not ready", icon: "ti-loader" };
|
|
788
|
+
if (agent.status === "busy") return { key: "busy", title: "Agent busy", icon: "ti-activity" };
|
|
789
|
+
return { key: "available", title: "Agent available", icon: "ti-circle-check" };
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function auditAgentStateTransition(agentId: string, before: AgentCard | null | undefined, after: AgentCard | null | undefined): void {
|
|
793
|
+
const previous = agentActivityState(before);
|
|
794
|
+
const next = agentActivityState(after);
|
|
795
|
+
if (!next || previous?.key === next.key) return;
|
|
796
|
+
auditEvent({
|
|
797
|
+
clientId: "server-agent-" + agentId + "-state-" + next.key + "-" + Date.now(),
|
|
798
|
+
kind: "state",
|
|
799
|
+
title: next.title,
|
|
800
|
+
meta: agentId,
|
|
801
|
+
icon: next.icon,
|
|
802
|
+
view: "agents",
|
|
803
|
+
agentId,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function orchestratorControlMeta(control: Record<string, unknown>): Record<string, unknown> {
|
|
808
|
+
return {
|
|
809
|
+
delivery: "interrupt",
|
|
810
|
+
priority: "urgent",
|
|
811
|
+
// Legacy orchestrators read control details from message meta.
|
|
812
|
+
orchestratorControl: control,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
|
|
778
816
|
// --- Agent routes ---
|
|
779
817
|
|
|
780
818
|
const postAgent: Handler = async (req) => {
|
|
@@ -836,21 +874,14 @@ const patchAgentStatus: Handler = async (req, params) => {
|
|
|
836
874
|
const guard = normalizeAgentSessionGuard(req, body);
|
|
837
875
|
const session = validateAgentSession(params.id!, guard);
|
|
838
876
|
if (!session.ok) return error(session.error!, agentSessionStatus(session.error));
|
|
877
|
+
const before = getAgent(params.id!);
|
|
839
878
|
if (!setStatus(params.id!, body.status as any, guard)) return error("agent not found", 404);
|
|
879
|
+
auditAgentStateTransition(params.id!, before, getAgent(params.id!));
|
|
840
880
|
} catch (e) {
|
|
841
881
|
if (e instanceof ValidationError) return error(e.message, 400);
|
|
842
882
|
throw e;
|
|
843
883
|
}
|
|
844
884
|
emitAgentStatus(params.id!);
|
|
845
|
-
auditEvent({
|
|
846
|
-
clientId: "server-agent-" + params.id! + "-status-" + body.status + "-" + Date.now(),
|
|
847
|
-
kind: "state",
|
|
848
|
-
title: "Agent " + body.status,
|
|
849
|
-
meta: params.id!,
|
|
850
|
-
icon: body.status === "offline" ? "ti-plug-off" : "ti-activity",
|
|
851
|
-
view: "agents",
|
|
852
|
-
agentId: params.id!,
|
|
853
|
-
});
|
|
854
885
|
return json({ ok: true });
|
|
855
886
|
};
|
|
856
887
|
|
|
@@ -903,21 +934,14 @@ const patchAgentReady: Handler = async (req, params) => {
|
|
|
903
934
|
const guard = normalizeAgentSessionGuard(req, body);
|
|
904
935
|
const session = validateAgentSession(params.id!, guard);
|
|
905
936
|
if (!session.ok) return error(session.error!, agentSessionStatus(session.error));
|
|
937
|
+
const before = getAgent(params.id!);
|
|
906
938
|
if (!markReady(params.id!, body.ready, guard)) return error("agent not found", 404);
|
|
939
|
+
auditAgentStateTransition(params.id!, before, getAgent(params.id!));
|
|
907
940
|
} catch (e) {
|
|
908
941
|
if (e instanceof ValidationError) return error(e.message, 400);
|
|
909
942
|
throw e;
|
|
910
943
|
}
|
|
911
944
|
emitAgentStatus(params.id!);
|
|
912
|
-
auditEvent({
|
|
913
|
-
clientId: "server-agent-" + params.id! + "-ready-" + body.ready + "-" + Date.now(),
|
|
914
|
-
kind: "state",
|
|
915
|
-
title: body.ready ? "Agent ready" : "Agent not ready",
|
|
916
|
-
meta: params.id!,
|
|
917
|
-
icon: body.ready ? "ti-circle-check" : "ti-loader",
|
|
918
|
-
view: "agents",
|
|
919
|
-
agentId: params.id!,
|
|
920
|
-
});
|
|
921
945
|
return json({ ok: true });
|
|
922
946
|
};
|
|
923
947
|
|
|
@@ -1008,17 +1032,15 @@ const postAgentSpawn: Handler = async (req) => {
|
|
|
1008
1032
|
if (orchestrators.length > 0) {
|
|
1009
1033
|
// Route through the first available orchestrator
|
|
1010
1034
|
const orch = orchestrators[0]!;
|
|
1035
|
+
const control = { action: "spawn", provider, cwd, label, approvalMode, requestedBy: "dashboard", requestedAt: Date.now() };
|
|
1011
1036
|
const msg = sendMessage({
|
|
1012
1037
|
from: "system",
|
|
1013
1038
|
to: orch.agentId,
|
|
1014
1039
|
kind: "control",
|
|
1015
1040
|
subject: "Spawn agent",
|
|
1016
1041
|
body: `Spawn ${provider} agent${label ? ` (${label})` : ""}`,
|
|
1017
|
-
payload: { orchestratorControl:
|
|
1018
|
-
meta:
|
|
1019
|
-
delivery: "interrupt",
|
|
1020
|
-
priority: "urgent",
|
|
1021
|
-
},
|
|
1042
|
+
payload: { orchestratorControl: control },
|
|
1043
|
+
meta: orchestratorControlMeta(control),
|
|
1022
1044
|
});
|
|
1023
1045
|
emitNewMessage(msg);
|
|
1024
1046
|
auditEvent({
|
|
@@ -1104,8 +1126,16 @@ const postOrchestrator: Handler = async (req) => {
|
|
|
1104
1126
|
}
|
|
1105
1127
|
}
|
|
1106
1128
|
const envKeys = cleanStringArray(parsed.body.envKeys, "envKeys");
|
|
1129
|
+
const version = cleanString(parsed.body.version, "version", { max: 80 });
|
|
1130
|
+
const gitSha = cleanString(parsed.body.gitSha, "gitSha", { max: 80 });
|
|
1131
|
+
const protocolVersionRaw = parsed.body.protocolVersion;
|
|
1132
|
+
const protocolVersion = protocolVersionRaw === undefined
|
|
1133
|
+
? undefined
|
|
1134
|
+
: typeof protocolVersionRaw === "number" && Number.isInteger(protocolVersionRaw) && protocolVersionRaw > 0
|
|
1135
|
+
? protocolVersionRaw
|
|
1136
|
+
: (() => { throw new ValidationError("protocolVersion must be a positive integer"); })();
|
|
1107
1137
|
const meta = cleanMeta(parsed.body.meta);
|
|
1108
|
-
const orch = upsertOrchestrator({ id, hostname, providers: providers ?? ["claude", "codex"], baseDir, envKeys, meta });
|
|
1138
|
+
const orch = upsertOrchestrator({ id, hostname, providers: providers ?? ["claude", "codex"], baseDir, envKeys, version, protocolVersion, gitSha, meta });
|
|
1109
1139
|
auditEvent({
|
|
1110
1140
|
clientId: "server-orchestrator-register-" + id + "-" + Date.now(),
|
|
1111
1141
|
kind: "state",
|
|
@@ -1192,6 +1222,16 @@ const postOrchestratorSpawn: Handler = async (req, params) => {
|
|
|
1192
1222
|
const prompt = cleanString(parsed.body.prompt, "prompt", { max: 4000 });
|
|
1193
1223
|
|
|
1194
1224
|
// Send control message to orchestrator's agent inbox
|
|
1225
|
+
const control = {
|
|
1226
|
+
action: "spawn",
|
|
1227
|
+
provider,
|
|
1228
|
+
cwd: cwd || orch.baseDir,
|
|
1229
|
+
label,
|
|
1230
|
+
approvalMode,
|
|
1231
|
+
prompt,
|
|
1232
|
+
requestedBy: "dashboard",
|
|
1233
|
+
requestedAt: Date.now(),
|
|
1234
|
+
};
|
|
1195
1235
|
const msg = sendMessage({
|
|
1196
1236
|
from: "system",
|
|
1197
1237
|
to: orch.agentId,
|
|
@@ -1199,21 +1239,9 @@ const postOrchestratorSpawn: Handler = async (req, params) => {
|
|
|
1199
1239
|
subject: "Spawn agent",
|
|
1200
1240
|
body: `Spawn ${provider} agent${label ? ` (${label})` : ""}`,
|
|
1201
1241
|
payload: {
|
|
1202
|
-
orchestratorControl:
|
|
1203
|
-
action: "spawn",
|
|
1204
|
-
provider,
|
|
1205
|
-
cwd: cwd || orch.baseDir,
|
|
1206
|
-
label,
|
|
1207
|
-
approvalMode,
|
|
1208
|
-
prompt,
|
|
1209
|
-
requestedBy: "dashboard",
|
|
1210
|
-
requestedAt: Date.now(),
|
|
1211
|
-
},
|
|
1212
|
-
},
|
|
1213
|
-
meta: {
|
|
1214
|
-
delivery: "interrupt",
|
|
1215
|
-
priority: "urgent",
|
|
1242
|
+
orchestratorControl: control,
|
|
1216
1243
|
},
|
|
1244
|
+
meta: orchestratorControlMeta(control),
|
|
1217
1245
|
});
|
|
1218
1246
|
emitNewMessage(msg);
|
|
1219
1247
|
auditEvent({
|
|
@@ -1245,6 +1273,12 @@ const postOrchestratorAction: Handler = async (req, params) => {
|
|
|
1245
1273
|
if (!action) return error("action required");
|
|
1246
1274
|
const agentId = cleanString(parsed.body.agentId, "agentId", { max: 240 });
|
|
1247
1275
|
|
|
1276
|
+
const control = {
|
|
1277
|
+
action,
|
|
1278
|
+
agentId,
|
|
1279
|
+
requestedBy: "dashboard",
|
|
1280
|
+
requestedAt: Date.now(),
|
|
1281
|
+
};
|
|
1248
1282
|
const msg = sendMessage({
|
|
1249
1283
|
from: "system",
|
|
1250
1284
|
to: orch.agentId,
|
|
@@ -1252,17 +1286,9 @@ const postOrchestratorAction: Handler = async (req, params) => {
|
|
|
1252
1286
|
subject: action === "restart" ? "Restart agent" : "Shutdown agent",
|
|
1253
1287
|
body: agentId ? `${action} ${agentId}` : `${action} all managed agents`,
|
|
1254
1288
|
payload: {
|
|
1255
|
-
orchestratorControl:
|
|
1256
|
-
action,
|
|
1257
|
-
agentId,
|
|
1258
|
-
requestedBy: "dashboard",
|
|
1259
|
-
requestedAt: Date.now(),
|
|
1260
|
-
},
|
|
1261
|
-
},
|
|
1262
|
-
meta: {
|
|
1263
|
-
delivery: "interrupt",
|
|
1264
|
-
priority: "urgent",
|
|
1289
|
+
orchestratorControl: control,
|
|
1265
1290
|
},
|
|
1291
|
+
meta: orchestratorControlMeta(control),
|
|
1266
1292
|
});
|
|
1267
1293
|
emitNewMessage(msg);
|
|
1268
1294
|
auditEvent({
|
package/src/types.ts
CHANGED
|
@@ -411,12 +411,25 @@ export interface Orchestrator {
|
|
|
411
411
|
providers: SpawnProvider[];
|
|
412
412
|
baseDir: string;
|
|
413
413
|
envKeys: string[]; // names only, never values
|
|
414
|
+
version?: string;
|
|
415
|
+
protocolVersion?: number;
|
|
416
|
+
gitSha?: string;
|
|
417
|
+
health?: OrchestratorHealth;
|
|
414
418
|
meta: Record<string, unknown>;
|
|
415
419
|
managedAgents: ManagedAgent[];
|
|
416
420
|
lastSeen: number;
|
|
417
421
|
createdAt: number;
|
|
418
422
|
}
|
|
419
423
|
|
|
424
|
+
export interface OrchestratorHealth {
|
|
425
|
+
status: "ok" | "warn" | "error";
|
|
426
|
+
restartRequired: boolean;
|
|
427
|
+
issues: Array<{
|
|
428
|
+
code: "missing-version" | "outdated" | "protocol-mismatch" | "restart-required";
|
|
429
|
+
detail: string;
|
|
430
|
+
}>;
|
|
431
|
+
}
|
|
432
|
+
|
|
420
433
|
export interface ManagedAgent {
|
|
421
434
|
agentId: string;
|
|
422
435
|
provider: SpawnProvider;
|
|
@@ -434,6 +447,9 @@ export interface RegisterOrchestratorInput {
|
|
|
434
447
|
providers: SpawnProvider[];
|
|
435
448
|
baseDir: string;
|
|
436
449
|
envKeys?: string[];
|
|
450
|
+
version?: string;
|
|
451
|
+
protocolVersion?: number;
|
|
452
|
+
gitSha?: string;
|
|
437
453
|
meta?: Record<string, unknown>;
|
|
438
454
|
}
|
|
439
455
|
|
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 ? "
|
|
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("'", "'\\''")}'`;
|