@openscout/scout 0.2.64 → 0.2.68
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/README.md +54 -13
- package/bin/openscout-runtime.mjs +42 -0
- package/dist/client/assets/addon-fit_hudsonkit_false-DSvcGNZx.js +1 -0
- package/dist/client/assets/addon-webgl_hudsonkit_false-1rrLEmjL.js +1 -0
- package/dist/client/assets/index-R2y6GOWl.js +184 -0
- package/dist/client/assets/index-bOmc2x1_.css +1 -0
- package/dist/client/assets/xterm_hudsonkit_false-B2TbElXJ.js +1 -0
- package/dist/client/index.html +3 -3
- package/dist/main.mjs +20295 -8189
- package/dist/pair-supervisor.mjs +25432 -21330
- package/dist/runtime/base-daemon.mjs +1626 -0
- package/dist/runtime/broker-daemon.mjs +47985 -0
- package/dist/runtime/broker-process-manager.mjs +938 -0
- package/dist/runtime/mesh-discover.mjs +964 -0
- package/dist/scout-control-plane-web.mjs +19794 -3344
- package/dist/scout-web-server.mjs +19794 -3344
- package/package.json +17 -7
- package/dist/client/assets/addon-fit-DX4qG4td.js +0 -1
- package/dist/client/assets/addon-webgl-DCtw1yLn.js +0 -64
- package/dist/client/assets/arc.es-DglF81f4.js +0 -188
- package/dist/client/assets/index-BRvY3oC-.js +0 -159
- package/dist/client/assets/index-CUNnl56m.css +0 -1
- package/dist/client/assets/index-TVkH_WDG.js +0 -1
- package/dist/client/assets/xterm-B-qIQCd3.js +0 -16
|
@@ -0,0 +1,938 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/runtime/src/broker-process-manager.ts
|
|
3
|
+
import { spawnSync } from "child_process";
|
|
4
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
5
|
+
import { homedir as homedir3 } from "os";
|
|
6
|
+
import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
// packages/runtime/src/broker-api.ts
|
|
10
|
+
import { request as httpRequest } from "http";
|
|
11
|
+
function normalizeBaseUrl(baseUrl) {
|
|
12
|
+
const trimmed = baseUrl.trim();
|
|
13
|
+
if (!trimmed) {
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return new URL(trimmed).toString();
|
|
18
|
+
} catch {
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function requestHeaders(options) {
|
|
23
|
+
const headers = {
|
|
24
|
+
accept: "application/json",
|
|
25
|
+
...options.headers ?? {}
|
|
26
|
+
};
|
|
27
|
+
if (options.body !== undefined && !("content-type" in lowercaseKeys(headers))) {
|
|
28
|
+
headers["content-type"] = "application/json";
|
|
29
|
+
}
|
|
30
|
+
return headers;
|
|
31
|
+
}
|
|
32
|
+
function lowercaseKeys(value) {
|
|
33
|
+
const result = {};
|
|
34
|
+
for (const key of Object.keys(value)) {
|
|
35
|
+
result[key.toLowerCase()] = value[key] ?? "";
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function requestBody(options) {
|
|
40
|
+
return options.body === undefined ? undefined : JSON.stringify(options.body);
|
|
41
|
+
}
|
|
42
|
+
async function requestBrokerOverHttp(baseUrl, path, options) {
|
|
43
|
+
const response = await fetch(new URL(path, normalizeBaseUrl(baseUrl)), {
|
|
44
|
+
method: options.method ?? (options.body === undefined ? "GET" : "POST"),
|
|
45
|
+
headers: requestHeaders(options),
|
|
46
|
+
body: requestBody(options),
|
|
47
|
+
signal: options.signal
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
ok: response.ok,
|
|
51
|
+
status: response.status,
|
|
52
|
+
text: await response.text()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function shouldFallbackFromUnixSocket(error) {
|
|
56
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : undefined;
|
|
57
|
+
return code === "ENOENT" || code === "ECONNREFUSED" || code === "ENOTSOCK" || code === "EACCES" || code === "FailedToOpenSocket";
|
|
58
|
+
}
|
|
59
|
+
function errorMessage(error) {
|
|
60
|
+
return error instanceof Error ? error.message : String(error);
|
|
61
|
+
}
|
|
62
|
+
function requestBrokerOverUnixSocket(socketPath, baseUrl, path, options) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
65
|
+
const url = new URL(path, normalizedBaseUrl);
|
|
66
|
+
const body = requestBody(options);
|
|
67
|
+
const headers = requestHeaders(options);
|
|
68
|
+
if (body !== undefined) {
|
|
69
|
+
headers["content-length"] = Buffer.byteLength(body).toString();
|
|
70
|
+
}
|
|
71
|
+
const request = httpRequest({
|
|
72
|
+
socketPath,
|
|
73
|
+
path: `${url.pathname}${url.search}`,
|
|
74
|
+
method: options.method ?? (body === undefined ? "GET" : "POST"),
|
|
75
|
+
headers
|
|
76
|
+
}, (response) => {
|
|
77
|
+
let text = "";
|
|
78
|
+
response.setEncoding("utf8");
|
|
79
|
+
response.on("data", (chunk) => {
|
|
80
|
+
text += String(chunk);
|
|
81
|
+
});
|
|
82
|
+
response.on("end", () => {
|
|
83
|
+
options.signal?.removeEventListener("abort", handleAbort);
|
|
84
|
+
resolve({
|
|
85
|
+
ok: Boolean(response.statusCode && response.statusCode >= 200 && response.statusCode < 300),
|
|
86
|
+
status: response.statusCode ?? 0,
|
|
87
|
+
text
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
const handleAbort = () => {
|
|
92
|
+
request.destroy(options.signal?.reason instanceof Error ? options.signal.reason : new Error("Scout broker request aborted"));
|
|
93
|
+
};
|
|
94
|
+
request.on("error", (error) => {
|
|
95
|
+
options.signal?.removeEventListener("abort", handleAbort);
|
|
96
|
+
reject(error);
|
|
97
|
+
});
|
|
98
|
+
if (options.signal) {
|
|
99
|
+
if (options.signal.aborted) {
|
|
100
|
+
handleAbort();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
options.signal.addEventListener("abort", handleAbort, { once: true });
|
|
104
|
+
}
|
|
105
|
+
if (body !== undefined) {
|
|
106
|
+
request.write(body);
|
|
107
|
+
}
|
|
108
|
+
request.end();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async function requestBrokerWire(baseUrl, path, options) {
|
|
112
|
+
const socketPath = options.socketPath?.trim();
|
|
113
|
+
if (socketPath) {
|
|
114
|
+
try {
|
|
115
|
+
return {
|
|
116
|
+
response: await requestBrokerOverUnixSocket(socketPath, baseUrl, path, options),
|
|
117
|
+
trace: {
|
|
118
|
+
transport: "unix_socket",
|
|
119
|
+
socketPath
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (!shouldFallbackFromUnixSocket(error)) {
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
response: await requestBrokerOverHttp(baseUrl, path, options),
|
|
128
|
+
trace: {
|
|
129
|
+
transport: "http",
|
|
130
|
+
socketPath,
|
|
131
|
+
socketFallbackError: `${socketPath}: ${errorMessage(error)}`
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
response: await requestBrokerOverHttp(baseUrl, path, options),
|
|
138
|
+
trace: {
|
|
139
|
+
transport: "http"
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function requestScoutBrokerJsonWithTrace(baseUrl, path, options = {}) {
|
|
144
|
+
const { response, trace } = await requestBrokerWire(baseUrl, path, options);
|
|
145
|
+
let parsed;
|
|
146
|
+
let parsedJson = false;
|
|
147
|
+
if (response.text.length > 0) {
|
|
148
|
+
try {
|
|
149
|
+
parsed = JSON.parse(response.text);
|
|
150
|
+
parsedJson = true;
|
|
151
|
+
} catch {
|
|
152
|
+
parsedJson = false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
if (parsedJson && options.acceptErrorJson?.(parsed)) {
|
|
157
|
+
return { value: parsed, trace };
|
|
158
|
+
}
|
|
159
|
+
throw new Error(`${path} returned ${response.status}: ${response.text}`);
|
|
160
|
+
}
|
|
161
|
+
if (parsedJson) {
|
|
162
|
+
return { value: parsed, trace };
|
|
163
|
+
}
|
|
164
|
+
return { value: undefined, trace };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// packages/runtime/src/support-paths.ts
|
|
168
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
169
|
+
import { homedir } from "os";
|
|
170
|
+
import { join } from "path";
|
|
171
|
+
var OPENSCOUT_RPC_CUTOVER_MARKER = "rpc-runtime-cutover-v1";
|
|
172
|
+
function resolveOpenScoutSupportPaths() {
|
|
173
|
+
const home = homedir();
|
|
174
|
+
const supportDirectory = process.env.OPENSCOUT_SUPPORT_DIRECTORY ?? join(home, "Library", "Application Support", "OpenScout");
|
|
175
|
+
const logsDirectory = join(supportDirectory, "logs");
|
|
176
|
+
const runtimeDirectory = join(supportDirectory, "runtime");
|
|
177
|
+
const catalogDirectory = join(supportDirectory, "catalog");
|
|
178
|
+
return {
|
|
179
|
+
supportDirectory,
|
|
180
|
+
logsDirectory,
|
|
181
|
+
appLogsDirectory: join(logsDirectory, "app"),
|
|
182
|
+
brokerLogsDirectory: join(logsDirectory, "broker"),
|
|
183
|
+
runtimeDirectory,
|
|
184
|
+
catalogDirectory,
|
|
185
|
+
relayAgentsDirectory: join(runtimeDirectory, "agents"),
|
|
186
|
+
settingsPath: join(supportDirectory, "settings.json"),
|
|
187
|
+
harnessCatalogPath: join(catalogDirectory, "harness-catalog.json"),
|
|
188
|
+
relayAgentsRegistryPath: join(supportDirectory, "relay-agents.json"),
|
|
189
|
+
relayHubDirectory: process.env.OPENSCOUT_RELAY_HUB ?? join(home, ".openscout", "relay"),
|
|
190
|
+
controlHome: process.env.OPENSCOUT_CONTROL_HOME ?? join(home, ".openscout", "control-plane"),
|
|
191
|
+
desktopStatusPath: join(supportDirectory, "agent-status.json"),
|
|
192
|
+
workspaceStatePath: join(supportDirectory, "workspace-state.json"),
|
|
193
|
+
cutoverMarkerPath: join(supportDirectory, OPENSCOUT_RPC_CUTOVER_MARKER)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function ensureOpenScoutCleanSlateSync() {
|
|
197
|
+
const paths = resolveOpenScoutSupportPaths();
|
|
198
|
+
if (existsSync(paths.cutoverMarkerPath)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
rmSync(paths.supportDirectory, { recursive: true, force: true });
|
|
202
|
+
rmSync(paths.controlHome, { recursive: true, force: true });
|
|
203
|
+
rmSync(paths.relayHubDirectory, { recursive: true, force: true });
|
|
204
|
+
mkdirSync(paths.supportDirectory, { recursive: true });
|
|
205
|
+
writeFileSync(paths.cutoverMarkerPath, `${Date.now()}
|
|
206
|
+
`, "utf8");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// packages/runtime/src/tool-resolution.ts
|
|
210
|
+
import { accessSync, constants, existsSync as existsSync2 } from "fs";
|
|
211
|
+
import { homedir as homedir2 } from "os";
|
|
212
|
+
import { basename, dirname, join as join2, resolve } from "path";
|
|
213
|
+
var DEFAULT_COMMON_EXECUTABLE_DIRECTORIES = [
|
|
214
|
+
join2(homedir2(), ".bun", "bin"),
|
|
215
|
+
"/opt/homebrew/bin",
|
|
216
|
+
"/usr/local/bin"
|
|
217
|
+
];
|
|
218
|
+
function expandHomePath(value, env = process.env) {
|
|
219
|
+
const home = env.HOME ?? homedir2();
|
|
220
|
+
if (value === "~") {
|
|
221
|
+
return home;
|
|
222
|
+
}
|
|
223
|
+
if (value.startsWith("~/")) {
|
|
224
|
+
return join2(home, value.slice(2));
|
|
225
|
+
}
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
function isExecutablePath(candidate) {
|
|
229
|
+
if (!candidate) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
accessSync(candidate, constants.X_OK);
|
|
234
|
+
return true;
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function splitPathEntries(env = process.env) {
|
|
240
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
241
|
+
return (env.PATH ?? "").split(separator).filter(Boolean);
|
|
242
|
+
}
|
|
243
|
+
function dedupeDirectories(values, env) {
|
|
244
|
+
const seen = new Set;
|
|
245
|
+
const resolvedEntries = [];
|
|
246
|
+
for (const value of values) {
|
|
247
|
+
const normalized = resolve(expandHomePath(value, env));
|
|
248
|
+
if (seen.has(normalized)) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
seen.add(normalized);
|
|
252
|
+
resolvedEntries.push(normalized);
|
|
253
|
+
}
|
|
254
|
+
return resolvedEntries;
|
|
255
|
+
}
|
|
256
|
+
function resolveExecutableFromSearch(options) {
|
|
257
|
+
const env = options.env ?? process.env;
|
|
258
|
+
const envKeys = options.envKeys ?? [];
|
|
259
|
+
for (const envKey of envKeys) {
|
|
260
|
+
const explicit = env[envKey]?.trim();
|
|
261
|
+
if (!explicit) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const expanded = expandHomePath(explicit, env);
|
|
265
|
+
if (isExecutablePath(expanded)) {
|
|
266
|
+
return { path: resolve(expanded), source: "env" };
|
|
267
|
+
}
|
|
268
|
+
const foundOnPath = findExecutableOnSearchPath(explicit, env);
|
|
269
|
+
if (foundOnPath) {
|
|
270
|
+
return { path: foundOnPath.path, source: "env" };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const commonDirectories = dedupeDirectories([...options.commonDirectories ?? DEFAULT_COMMON_EXECUTABLE_DIRECTORIES], env);
|
|
274
|
+
const searchDirectories = dedupeDirectories([
|
|
275
|
+
...splitPathEntries(env),
|
|
276
|
+
...options.extraDirectories ?? [],
|
|
277
|
+
...commonDirectories
|
|
278
|
+
], env);
|
|
279
|
+
for (const directory of searchDirectories) {
|
|
280
|
+
for (const name of options.names) {
|
|
281
|
+
const candidate = join2(directory, name);
|
|
282
|
+
if (isExecutablePath(candidate)) {
|
|
283
|
+
return {
|
|
284
|
+
path: candidate,
|
|
285
|
+
source: commonDirectories.includes(directory) ? "common-path" : "path"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
function resolveBunExecutable(env = process.env) {
|
|
293
|
+
return resolveExecutableFromSearch({
|
|
294
|
+
env,
|
|
295
|
+
envKeys: ["OPENSCOUT_BUN_BIN", "SCOUT_BUN_BIN", "BUN_BIN"],
|
|
296
|
+
names: ["bun"]
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function findExecutableOnSearchPath(name, env) {
|
|
300
|
+
const searchDirectories = dedupeDirectories([
|
|
301
|
+
...splitPathEntries(env),
|
|
302
|
+
...DEFAULT_COMMON_EXECUTABLE_DIRECTORIES
|
|
303
|
+
], env);
|
|
304
|
+
for (const directory of searchDirectories) {
|
|
305
|
+
const candidate = join2(directory, name);
|
|
306
|
+
if (isExecutablePath(candidate)) {
|
|
307
|
+
return { path: candidate, source: "path" };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// packages/runtime/src/broker-process-manager.ts
|
|
314
|
+
function isTmpPath(p) {
|
|
315
|
+
return /^\/(?:private\/)?tmp\//.test(p);
|
|
316
|
+
}
|
|
317
|
+
var DEFAULT_BROKER_HOST = "127.0.0.1";
|
|
318
|
+
var DEFAULT_BROKER_HOST_MESH = "0.0.0.0";
|
|
319
|
+
var DEFAULT_BROKER_PORT = 65535;
|
|
320
|
+
var DEFAULT_ADVERTISE_SCOPE = "local";
|
|
321
|
+
function resolveAdvertiseScope() {
|
|
322
|
+
const raw = (process.env.OPENSCOUT_ADVERTISE_SCOPE ?? "").trim().toLowerCase();
|
|
323
|
+
if (raw === "mesh")
|
|
324
|
+
return "mesh";
|
|
325
|
+
if (raw === "local")
|
|
326
|
+
return "local";
|
|
327
|
+
return DEFAULT_ADVERTISE_SCOPE;
|
|
328
|
+
}
|
|
329
|
+
function resolveBrokerHost(scope = resolveAdvertiseScope()) {
|
|
330
|
+
const explicit = process.env.OPENSCOUT_BROKER_HOST;
|
|
331
|
+
if (typeof explicit === "string" && explicit.trim().length > 0) {
|
|
332
|
+
return explicit;
|
|
333
|
+
}
|
|
334
|
+
return scope === "mesh" ? DEFAULT_BROKER_HOST_MESH : DEFAULT_BROKER_HOST;
|
|
335
|
+
}
|
|
336
|
+
function isLoopbackHost(host) {
|
|
337
|
+
const trimmed = host.trim();
|
|
338
|
+
return trimmed === "127.0.0.1" || trimmed === "::1" || trimmed === "localhost";
|
|
339
|
+
}
|
|
340
|
+
var BROKER_SERVICE_POLL_INTERVAL_MS = 100;
|
|
341
|
+
var DEFAULT_BROKER_START_TIMEOUT_MS = 15000;
|
|
342
|
+
function buildDefaultBrokerUrl(host = DEFAULT_BROKER_HOST, port = DEFAULT_BROKER_PORT) {
|
|
343
|
+
return `http://${host}:${port}`;
|
|
344
|
+
}
|
|
345
|
+
function buildDefaultBrokerSocketPath(runtimeDirectory) {
|
|
346
|
+
return join3(runtimeDirectory, "broker.sock");
|
|
347
|
+
}
|
|
348
|
+
var DEFAULT_BROKER_URL = buildDefaultBrokerUrl();
|
|
349
|
+
function normalizeBrokerUrl(value) {
|
|
350
|
+
try {
|
|
351
|
+
return new URL(value).toString();
|
|
352
|
+
} catch {
|
|
353
|
+
return value.trim();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function resolveBrokerSocketPathForBaseUrl(baseUrl, config = resolveBrokerServiceConfig()) {
|
|
357
|
+
return normalizeBrokerUrl(baseUrl) === normalizeBrokerUrl(config.brokerUrl) ? config.brokerSocketPath : null;
|
|
358
|
+
}
|
|
359
|
+
function runtimePackageDir() {
|
|
360
|
+
const explicit = process.env.OPENSCOUT_RUNTIME_PACKAGE_DIR?.trim();
|
|
361
|
+
if (explicit)
|
|
362
|
+
return explicit;
|
|
363
|
+
const fromBundledPackage = findBundledRuntimeDir();
|
|
364
|
+
if (fromBundledPackage)
|
|
365
|
+
return fromBundledPackage;
|
|
366
|
+
const fromCwd = findWorkspaceRuntimeDir(process.cwd());
|
|
367
|
+
if (fromCwd)
|
|
368
|
+
return fromCwd;
|
|
369
|
+
const fromGlobal = findGlobalRuntimeDir();
|
|
370
|
+
if (fromGlobal)
|
|
371
|
+
return fromGlobal;
|
|
372
|
+
const moduleDir = dirname2(fileURLToPath(import.meta.url));
|
|
373
|
+
return resolve2(moduleDir, "..");
|
|
374
|
+
}
|
|
375
|
+
function isInstalledRuntimePackageDir(candidate) {
|
|
376
|
+
return existsSync3(join3(candidate, "package.json")) && existsSync3(join3(candidate, "bin", "openscout-runtime.mjs"));
|
|
377
|
+
}
|
|
378
|
+
function findGlobalRuntimeDir() {
|
|
379
|
+
const candidates = [
|
|
380
|
+
join3(homedir3(), ".bun", "node_modules", "@openscout", "runtime"),
|
|
381
|
+
join3(homedir3(), ".bun", "install", "global", "node_modules", "@openscout", "runtime"),
|
|
382
|
+
join3(homedir3(), ".bun", "install", "global", "node_modules", "@openscout", "scout", "node_modules", "@openscout", "runtime")
|
|
383
|
+
];
|
|
384
|
+
for (const c of candidates) {
|
|
385
|
+
if (isInstalledRuntimePackageDir(c))
|
|
386
|
+
return c;
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
const result = spawnSync("which", ["scout"], { encoding: "utf8", timeout: 3000 });
|
|
390
|
+
const scoutBin = result.stdout?.trim();
|
|
391
|
+
if (scoutBin) {
|
|
392
|
+
const scoutPkg = resolve2(scoutBin, "..", "..");
|
|
393
|
+
if (isInstalledRuntimePackageDir(scoutPkg))
|
|
394
|
+
return scoutPkg;
|
|
395
|
+
const nested = join3(scoutPkg, "node_modules", "@openscout", "runtime");
|
|
396
|
+
if (isInstalledRuntimePackageDir(nested))
|
|
397
|
+
return nested;
|
|
398
|
+
const sibling = resolve2(scoutPkg, "..", "runtime");
|
|
399
|
+
if (isInstalledRuntimePackageDir(sibling))
|
|
400
|
+
return sibling;
|
|
401
|
+
}
|
|
402
|
+
} catch {}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
function findBundledRuntimeDir() {
|
|
406
|
+
const moduleDir = dirname2(fileURLToPath(import.meta.url));
|
|
407
|
+
const candidate = resolve2(moduleDir, "..");
|
|
408
|
+
return isInstalledRuntimePackageDir(candidate) ? candidate : null;
|
|
409
|
+
}
|
|
410
|
+
function findWorkspaceRuntimeDir(startDir) {
|
|
411
|
+
let current = resolve2(startDir);
|
|
412
|
+
while (true) {
|
|
413
|
+
const candidate = join3(current, "packages", "runtime");
|
|
414
|
+
if (existsSync3(join3(candidate, "package.json")) && existsSync3(join3(candidate, "src"))) {
|
|
415
|
+
return candidate;
|
|
416
|
+
}
|
|
417
|
+
const parent = dirname2(current);
|
|
418
|
+
if (parent === current)
|
|
419
|
+
return null;
|
|
420
|
+
current = parent;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function resolveBunExecutable2() {
|
|
424
|
+
const bun = resolveBunExecutable(process.env);
|
|
425
|
+
if (bun) {
|
|
426
|
+
return bun.path;
|
|
427
|
+
}
|
|
428
|
+
throw new Error("Unable to locate Bun for broker service management. Install Bun or set OPENSCOUT_BUN_BIN.");
|
|
429
|
+
}
|
|
430
|
+
function resolveBrokerServiceMode() {
|
|
431
|
+
const explicit = (process.env.OPENSCOUT_BROKER_SERVICE_MODE ?? "").trim().toLowerCase();
|
|
432
|
+
if (explicit === "prod" || explicit === "production") {
|
|
433
|
+
return "prod";
|
|
434
|
+
}
|
|
435
|
+
if (explicit === "custom") {
|
|
436
|
+
return "custom";
|
|
437
|
+
}
|
|
438
|
+
return "dev";
|
|
439
|
+
}
|
|
440
|
+
function resolveBrokerStartTimeoutMs() {
|
|
441
|
+
const explicit = Number.parseInt(process.env.OPENSCOUT_BROKER_START_TIMEOUT_MS ?? "", 10);
|
|
442
|
+
if (Number.isFinite(explicit) && explicit > 0) {
|
|
443
|
+
return Math.max(explicit, BROKER_SERVICE_POLL_INTERVAL_MS);
|
|
444
|
+
}
|
|
445
|
+
return DEFAULT_BROKER_START_TIMEOUT_MS;
|
|
446
|
+
}
|
|
447
|
+
function resolveBrokerServiceLabel(mode) {
|
|
448
|
+
const explicit = process.env.OPENSCOUT_SERVICE_LABEL?.trim() || process.env.OPENSCOUT_BROKER_SERVICE_LABEL?.trim();
|
|
449
|
+
if (explicit) {
|
|
450
|
+
return explicit;
|
|
451
|
+
}
|
|
452
|
+
switch (mode) {
|
|
453
|
+
case "prod":
|
|
454
|
+
return "com.openscout";
|
|
455
|
+
case "custom":
|
|
456
|
+
return "com.openscout.custom";
|
|
457
|
+
case "dev":
|
|
458
|
+
default:
|
|
459
|
+
return "dev.openscout";
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function legacyBrokerServiceLabel(mode) {
|
|
463
|
+
switch (mode) {
|
|
464
|
+
case "prod":
|
|
465
|
+
return "com.openscout.broker";
|
|
466
|
+
case "custom":
|
|
467
|
+
return "com.openscout.broker.custom";
|
|
468
|
+
case "dev":
|
|
469
|
+
default:
|
|
470
|
+
return "dev.openscout.broker";
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
function resolveBrokerServiceConfig() {
|
|
474
|
+
const mode = resolveBrokerServiceMode();
|
|
475
|
+
const label = resolveBrokerServiceLabel(mode);
|
|
476
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : Number.parseInt(process.env.UID ?? "0", 10);
|
|
477
|
+
const supportPaths = resolveOpenScoutSupportPaths();
|
|
478
|
+
const defaultSupportDir = join3(homedir3(), "Library", "Application Support", "OpenScout");
|
|
479
|
+
const supportDirectory = isTmpPath(supportPaths.supportDirectory) ? defaultSupportDir : supportPaths.supportDirectory;
|
|
480
|
+
const runtimeDirectory = join3(supportDirectory, "runtime");
|
|
481
|
+
const logsDirectory = join3(supportDirectory, "logs", "broker");
|
|
482
|
+
const controlHome = isTmpPath(supportPaths.controlHome) ? join3(homedir3(), ".openscout", "control-plane") : supportPaths.controlHome;
|
|
483
|
+
const advertiseScope = resolveAdvertiseScope();
|
|
484
|
+
const brokerHost = resolveBrokerHost(advertiseScope);
|
|
485
|
+
const brokerPort = Number.parseInt(process.env.OPENSCOUT_BROKER_PORT ?? String(DEFAULT_BROKER_PORT), 10);
|
|
486
|
+
const brokerUrl = process.env.OPENSCOUT_BROKER_URL ?? buildDefaultBrokerUrl(brokerHost, brokerPort);
|
|
487
|
+
const brokerSocketPath = process.env.OPENSCOUT_BROKER_SOCKET_PATH ?? buildDefaultBrokerSocketPath(runtimeDirectory);
|
|
488
|
+
const launchAgentPath = join3(homedir3(), "Library", "LaunchAgents", `${label}.plist`);
|
|
489
|
+
return {
|
|
490
|
+
label,
|
|
491
|
+
mode,
|
|
492
|
+
uid,
|
|
493
|
+
domainTarget: `gui/${uid}`,
|
|
494
|
+
serviceTarget: `gui/${uid}/${label}`,
|
|
495
|
+
launchAgentPath,
|
|
496
|
+
supportDirectory,
|
|
497
|
+
runtimeDirectory,
|
|
498
|
+
logsDirectory,
|
|
499
|
+
stdoutLogPath: join3(logsDirectory, "stdout.log"),
|
|
500
|
+
stderrLogPath: join3(logsDirectory, "stderr.log"),
|
|
501
|
+
controlHome,
|
|
502
|
+
runtimePackageDir: runtimePackageDir(),
|
|
503
|
+
bunExecutable: resolveBunExecutable2(),
|
|
504
|
+
brokerHost,
|
|
505
|
+
brokerPort,
|
|
506
|
+
brokerUrl,
|
|
507
|
+
brokerSocketPath,
|
|
508
|
+
advertiseScope,
|
|
509
|
+
coreAgents: readCoreAgentsSync()
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function renderLaunchAgentPlist(config) {
|
|
513
|
+
const launchPath = resolveLaunchAgentPATH();
|
|
514
|
+
const coreAgentsValue = (config.coreAgents ?? []).join(",");
|
|
515
|
+
const envEntries = {
|
|
516
|
+
OPENSCOUT_BROKER_HOST: config.brokerHost,
|
|
517
|
+
OPENSCOUT_BROKER_PORT: String(config.brokerPort),
|
|
518
|
+
OPENSCOUT_BROKER_URL: config.brokerUrl,
|
|
519
|
+
OPENSCOUT_BROKER_SOCKET_PATH: config.brokerSocketPath,
|
|
520
|
+
OPENSCOUT_CONTROL_HOME: config.controlHome,
|
|
521
|
+
OPENSCOUT_BROKER_SERVICE_MODE: config.mode,
|
|
522
|
+
OPENSCOUT_BROKER_SERVICE_LABEL: config.label,
|
|
523
|
+
OPENSCOUT_SERVICE_LABEL: config.label,
|
|
524
|
+
OPENSCOUT_ADVERTISE_SCOPE: config.advertiseScope,
|
|
525
|
+
HOME: homedir3(),
|
|
526
|
+
PATH: launchPath,
|
|
527
|
+
...collectOptionalEnvVars([
|
|
528
|
+
"OPENSCOUT_MESH_ID",
|
|
529
|
+
"OPENSCOUT_MESH_SEEDS",
|
|
530
|
+
"OPENSCOUT_MESH_DISCOVERY_INTERVAL_MS",
|
|
531
|
+
"OPENSCOUT_NODE_NAME",
|
|
532
|
+
"OPENSCOUT_NODE_ID",
|
|
533
|
+
"OPENSCOUT_NODE_QUALIFIER",
|
|
534
|
+
"OPENSCOUT_TAILSCALE_BIN",
|
|
535
|
+
"OPENSCOUT_TAILSCALE_STATUS_JSON",
|
|
536
|
+
"OPENSCOUT_SSE_KEEPALIVE_MS"
|
|
537
|
+
])
|
|
538
|
+
};
|
|
539
|
+
if (coreAgentsValue) {
|
|
540
|
+
envEntries["OPENSCOUT_CORE_AGENTS"] = coreAgentsValue;
|
|
541
|
+
}
|
|
542
|
+
const envBlock = Object.entries(envEntries).map(([key, value]) => `
|
|
543
|
+
<key>${xmlEscape(key)}</key>
|
|
544
|
+
<string>${xmlEscape(value)}</string>`).join("");
|
|
545
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
546
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
547
|
+
<plist version="1.0">
|
|
548
|
+
<dict>
|
|
549
|
+
<key>Label</key>
|
|
550
|
+
<string>${xmlEscape(config.label)}</string>
|
|
551
|
+
<key>ProgramArguments</key>
|
|
552
|
+
<array>
|
|
553
|
+
<string>${xmlEscape(config.bunExecutable)}</string>
|
|
554
|
+
<string>${xmlEscape(join3(config.runtimePackageDir, "bin", "openscout-runtime.mjs"))}</string>
|
|
555
|
+
<string>base</string>
|
|
556
|
+
</array>
|
|
557
|
+
<key>WorkingDirectory</key>
|
|
558
|
+
<string>${xmlEscape(config.runtimePackageDir)}</string>
|
|
559
|
+
<key>RunAtLoad</key>
|
|
560
|
+
<true/>
|
|
561
|
+
<key>KeepAlive</key>
|
|
562
|
+
<dict>
|
|
563
|
+
<key>SuccessfulExit</key>
|
|
564
|
+
<false/>
|
|
565
|
+
</dict>
|
|
566
|
+
<key>StandardOutPath</key>
|
|
567
|
+
<string>${xmlEscape(config.stdoutLogPath)}</string>
|
|
568
|
+
<key>StandardErrorPath</key>
|
|
569
|
+
<string>${xmlEscape(config.stderrLogPath)}</string>
|
|
570
|
+
<key>EnvironmentVariables</key>
|
|
571
|
+
<dict>${envBlock}
|
|
572
|
+
</dict>
|
|
573
|
+
</dict>
|
|
574
|
+
</plist>
|
|
575
|
+
`;
|
|
576
|
+
}
|
|
577
|
+
function readCoreAgentsSync() {
|
|
578
|
+
try {
|
|
579
|
+
const settingsPath = resolveOpenScoutSupportPaths().settingsPath;
|
|
580
|
+
const raw = readFileSync(settingsPath, "utf8");
|
|
581
|
+
const settings = JSON.parse(raw);
|
|
582
|
+
const raw_agents = settings?.agents?.coreAgents;
|
|
583
|
+
if (Array.isArray(raw_agents)) {
|
|
584
|
+
return raw_agents.filter((v) => typeof v === "string" && v.trim().length > 0);
|
|
585
|
+
}
|
|
586
|
+
} catch {}
|
|
587
|
+
return [];
|
|
588
|
+
}
|
|
589
|
+
function collectOptionalEnvVars(keys) {
|
|
590
|
+
const entries = {};
|
|
591
|
+
for (const key of keys) {
|
|
592
|
+
const value = process.env[key];
|
|
593
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
594
|
+
entries[key] = value;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return entries;
|
|
598
|
+
}
|
|
599
|
+
function resolveLaunchAgentPATH() {
|
|
600
|
+
const entries = [
|
|
601
|
+
join3(homedir3(), ".bun", "bin"),
|
|
602
|
+
...(process.env.PATH ?? "").split(":").filter(Boolean),
|
|
603
|
+
"/opt/homebrew/bin",
|
|
604
|
+
"/usr/local/bin",
|
|
605
|
+
"/usr/bin",
|
|
606
|
+
"/bin",
|
|
607
|
+
"/usr/sbin",
|
|
608
|
+
"/sbin"
|
|
609
|
+
];
|
|
610
|
+
return Array.from(new Set(entries)).filter((e) => !isTmpPath(e)).join(":");
|
|
611
|
+
}
|
|
612
|
+
function xmlEscape(value) {
|
|
613
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
614
|
+
}
|
|
615
|
+
function ensureParentDirectory(filePath) {
|
|
616
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
617
|
+
}
|
|
618
|
+
function ensureServiceDirectories(config) {
|
|
619
|
+
ensureOpenScoutCleanSlateSync();
|
|
620
|
+
mkdirSync2(config.supportDirectory, { recursive: true });
|
|
621
|
+
mkdirSync2(config.runtimeDirectory, { recursive: true, mode: 448 });
|
|
622
|
+
chmodSync(config.runtimeDirectory, 448);
|
|
623
|
+
mkdirSync2(config.logsDirectory, { recursive: true });
|
|
624
|
+
mkdirSync2(config.controlHome, { recursive: true });
|
|
625
|
+
ensureParentDirectory(config.launchAgentPath);
|
|
626
|
+
}
|
|
627
|
+
function writeLaunchAgentPlist(config) {
|
|
628
|
+
ensureServiceDirectories(config);
|
|
629
|
+
writeFileSync2(config.launchAgentPath, renderLaunchAgentPlist(config), "utf8");
|
|
630
|
+
}
|
|
631
|
+
function bootoutCommand(config) {
|
|
632
|
+
return `${launchctlPath()} bootout ${config.serviceTarget}`;
|
|
633
|
+
}
|
|
634
|
+
function legacyLaunchAgentPath(config) {
|
|
635
|
+
return join3(homedir3(), "Library", "LaunchAgents", `${legacyBrokerServiceLabel(config.mode)}.plist`);
|
|
636
|
+
}
|
|
637
|
+
function legacyServiceTarget(config) {
|
|
638
|
+
return `${config.domainTarget}/${legacyBrokerServiceLabel(config.mode)}`;
|
|
639
|
+
}
|
|
640
|
+
function bootoutLegacyBrokerService(config) {
|
|
641
|
+
const legacyTarget = legacyServiceTarget(config);
|
|
642
|
+
if (legacyTarget === config.serviceTarget) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
runCommand(launchctlPath(), ["bootout", legacyTarget], { allowFailure: true });
|
|
646
|
+
}
|
|
647
|
+
function runCommand(command, args, options) {
|
|
648
|
+
const result = spawnSync(command, args, {
|
|
649
|
+
env: {
|
|
650
|
+
...process.env,
|
|
651
|
+
...options?.env ?? {}
|
|
652
|
+
},
|
|
653
|
+
encoding: "utf8"
|
|
654
|
+
});
|
|
655
|
+
const stdout = (result.stdout ?? "").trim();
|
|
656
|
+
const stderr = (result.stderr ?? "").trim();
|
|
657
|
+
const exitCode = result.status ?? 1;
|
|
658
|
+
if (exitCode !== 0 && !options?.allowFailure) {
|
|
659
|
+
throw new Error(stderr || stdout || `${command} exited with status ${exitCode}`);
|
|
660
|
+
}
|
|
661
|
+
return { exitCode, stdout, stderr };
|
|
662
|
+
}
|
|
663
|
+
function launchctlPath() {
|
|
664
|
+
return "/bin/launchctl";
|
|
665
|
+
}
|
|
666
|
+
function readLogLines(path) {
|
|
667
|
+
if (!existsSync3(path)) {
|
|
668
|
+
return [];
|
|
669
|
+
}
|
|
670
|
+
return readFileSync(path, "utf8").split(`
|
|
671
|
+
`).map((line) => line.trim()).filter(Boolean);
|
|
672
|
+
}
|
|
673
|
+
function isPackageScriptBanner(line) {
|
|
674
|
+
return /^\$\s*(bun run|npm run|pnpm\b|yarn\b)/.test(line);
|
|
675
|
+
}
|
|
676
|
+
function selectLastRelevantLogLine(lines) {
|
|
677
|
+
for (let index = lines.length - 1;index >= 0; index -= 1) {
|
|
678
|
+
const line = lines[index];
|
|
679
|
+
if (!isPackageScriptBanner(line)) {
|
|
680
|
+
return line;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return lines.at(-1) ?? null;
|
|
684
|
+
}
|
|
685
|
+
function readLastLogLine(paths) {
|
|
686
|
+
let fallback = null;
|
|
687
|
+
for (const path of paths) {
|
|
688
|
+
const lines = readLogLines(path);
|
|
689
|
+
if (lines.length === 0) {
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
const relevantLine = selectLastRelevantLogLine(lines);
|
|
693
|
+
if (!relevantLine) {
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (!isPackageScriptBanner(relevantLine)) {
|
|
697
|
+
return relevantLine;
|
|
698
|
+
}
|
|
699
|
+
fallback ??= relevantLine;
|
|
700
|
+
}
|
|
701
|
+
return fallback;
|
|
702
|
+
}
|
|
703
|
+
function parseLaunchctlPrint(output) {
|
|
704
|
+
const pidMatch = output.match(/\bpid = (\d+)/);
|
|
705
|
+
const stateMatch = output.match(/\bstate = ([^\n]+)/);
|
|
706
|
+
const lastExitMatch = output.match(/\blast exit code = (-?\d+)/i) || output.match(/\blast exit status = (-?\d+)/i);
|
|
707
|
+
return {
|
|
708
|
+
pid: pidMatch ? Number.parseInt(pidMatch[1] ?? "0", 10) : null,
|
|
709
|
+
launchdState: stateMatch?.[1]?.trim() ?? null,
|
|
710
|
+
lastExitStatus: lastExitMatch ? Number.parseInt(lastExitMatch[1] ?? "0", 10) : null
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function inspectLaunchctl(config) {
|
|
714
|
+
const printResult = runCommand(launchctlPath(), ["print", config.serviceTarget], { allowFailure: true });
|
|
715
|
+
if (printResult.exitCode !== 0) {
|
|
716
|
+
return {
|
|
717
|
+
loaded: false,
|
|
718
|
+
pid: null,
|
|
719
|
+
launchdState: null,
|
|
720
|
+
lastExitStatus: null,
|
|
721
|
+
raw: printResult.stderr || printResult.stdout
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
loaded: true,
|
|
726
|
+
raw: printResult.stdout,
|
|
727
|
+
...parseLaunchctlPrint(printResult.stdout)
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
async function fetchHealthSnapshot(config) {
|
|
731
|
+
const controller = new AbortController;
|
|
732
|
+
const timeout = setTimeout(() => controller.abort(), 1000);
|
|
733
|
+
const checkedAt = Date.now();
|
|
734
|
+
try {
|
|
735
|
+
const { value: payload, trace } = await requestScoutBrokerJsonWithTrace(config.brokerUrl, "/health", {
|
|
736
|
+
signal: controller.signal,
|
|
737
|
+
socketPath: config.brokerSocketPath
|
|
738
|
+
});
|
|
739
|
+
return {
|
|
740
|
+
reachable: true,
|
|
741
|
+
ok: Boolean(payload.ok),
|
|
742
|
+
checkedAt,
|
|
743
|
+
transport: trace.transport,
|
|
744
|
+
socketPath: trace.socketPath,
|
|
745
|
+
socketFallbackError: trace.socketFallbackError,
|
|
746
|
+
nodeId: payload.nodeId,
|
|
747
|
+
meshId: payload.meshId,
|
|
748
|
+
counts: payload.counts ? {
|
|
749
|
+
nodes: payload.counts.nodes ?? 0,
|
|
750
|
+
actors: payload.counts.actors ?? 0,
|
|
751
|
+
agents: payload.counts.agents ?? 0,
|
|
752
|
+
conversations: payload.counts.conversations ?? 0,
|
|
753
|
+
messages: payload.counts.messages ?? 0,
|
|
754
|
+
flights: payload.counts.flights ?? 0,
|
|
755
|
+
collaborationRecords: payload.counts.collaborationRecords ?? 0
|
|
756
|
+
} : undefined
|
|
757
|
+
};
|
|
758
|
+
} catch (error) {
|
|
759
|
+
return {
|
|
760
|
+
reachable: false,
|
|
761
|
+
ok: false,
|
|
762
|
+
checkedAt,
|
|
763
|
+
socketPath: config.brokerSocketPath,
|
|
764
|
+
error: error instanceof Error ? error.message : String(error)
|
|
765
|
+
};
|
|
766
|
+
} finally {
|
|
767
|
+
clearTimeout(timeout);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
async function brokerServiceStatus(config = resolveBrokerServiceConfig()) {
|
|
771
|
+
ensureServiceDirectories(config);
|
|
772
|
+
const launchctl = inspectLaunchctl(config);
|
|
773
|
+
const health = await fetchHealthSnapshot(config);
|
|
774
|
+
const installed = existsSync3(config.launchAgentPath);
|
|
775
|
+
const lastLogLine = health.reachable ? readLastLogLine([config.stdoutLogPath, config.stderrLogPath]) : readLastLogLine([config.stderrLogPath, config.stdoutLogPath]);
|
|
776
|
+
return {
|
|
777
|
+
label: config.label,
|
|
778
|
+
mode: config.mode,
|
|
779
|
+
launchAgentPath: config.launchAgentPath,
|
|
780
|
+
bootoutCommand: bootoutCommand(config),
|
|
781
|
+
brokerUrl: config.brokerUrl,
|
|
782
|
+
brokerSocketPath: config.brokerSocketPath,
|
|
783
|
+
supportDirectory: config.supportDirectory,
|
|
784
|
+
runtimeDirectory: config.runtimeDirectory,
|
|
785
|
+
controlHome: config.controlHome,
|
|
786
|
+
stdoutLogPath: config.stdoutLogPath,
|
|
787
|
+
stderrLogPath: config.stderrLogPath,
|
|
788
|
+
installed,
|
|
789
|
+
loaded: launchctl.loaded,
|
|
790
|
+
pid: launchctl.pid,
|
|
791
|
+
launchdState: launchctl.launchdState,
|
|
792
|
+
lastExitStatus: launchctl.lastExitStatus,
|
|
793
|
+
usesLaunchAgent: installed || launchctl.loaded,
|
|
794
|
+
reachable: health.reachable,
|
|
795
|
+
health,
|
|
796
|
+
lastLogLine
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
async function installBrokerService(config = resolveBrokerServiceConfig()) {
|
|
800
|
+
bootoutLegacyBrokerService(config);
|
|
801
|
+
writeLaunchAgentPlist(config);
|
|
802
|
+
return brokerServiceStatus(config);
|
|
803
|
+
}
|
|
804
|
+
async function startBrokerService(config = resolveBrokerServiceConfig()) {
|
|
805
|
+
bootoutLegacyBrokerService(config);
|
|
806
|
+
writeLaunchAgentPlist(config);
|
|
807
|
+
runCommand(launchctlPath(), ["bootout", config.serviceTarget], { allowFailure: true });
|
|
808
|
+
runCommand(launchctlPath(), ["bootstrap", config.domainTarget, config.launchAgentPath], { allowFailure: true });
|
|
809
|
+
runCommand(launchctlPath(), ["kickstart", "-k", config.serviceTarget], { allowFailure: true });
|
|
810
|
+
const attempts = Math.ceil(resolveBrokerStartTimeoutMs() / BROKER_SERVICE_POLL_INTERVAL_MS);
|
|
811
|
+
for (let attempt = 0;attempt < attempts; attempt += 1) {
|
|
812
|
+
const status2 = await brokerServiceStatus(config);
|
|
813
|
+
if (status2.health.reachable) {
|
|
814
|
+
return status2;
|
|
815
|
+
}
|
|
816
|
+
await sleep(BROKER_SERVICE_POLL_INTERVAL_MS);
|
|
817
|
+
}
|
|
818
|
+
const status = await brokerServiceStatus(config);
|
|
819
|
+
throw new Error(status.lastLogLine ?? status.health.error ?? "Broker service did not become healthy.");
|
|
820
|
+
}
|
|
821
|
+
function sleep(ms) {
|
|
822
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
823
|
+
}
|
|
824
|
+
async function stopBrokerService(config = resolveBrokerServiceConfig()) {
|
|
825
|
+
runCommand(launchctlPath(), ["bootout", config.serviceTarget], { allowFailure: true });
|
|
826
|
+
for (let attempt = 0;attempt < 30; attempt += 1) {
|
|
827
|
+
const status = await brokerServiceStatus(config);
|
|
828
|
+
if (!status.health.reachable) {
|
|
829
|
+
return status;
|
|
830
|
+
}
|
|
831
|
+
await sleep(BROKER_SERVICE_POLL_INTERVAL_MS);
|
|
832
|
+
}
|
|
833
|
+
return brokerServiceStatus(config);
|
|
834
|
+
}
|
|
835
|
+
async function restartBrokerService(config = resolveBrokerServiceConfig()) {
|
|
836
|
+
await stopBrokerService(config);
|
|
837
|
+
return startBrokerService(config);
|
|
838
|
+
}
|
|
839
|
+
async function uninstallBrokerService(config = resolveBrokerServiceConfig()) {
|
|
840
|
+
await stopBrokerService(config);
|
|
841
|
+
if (existsSync3(config.launchAgentPath)) {
|
|
842
|
+
rmSync2(config.launchAgentPath, { force: true });
|
|
843
|
+
}
|
|
844
|
+
const legacyPath = legacyLaunchAgentPath(config);
|
|
845
|
+
bootoutLegacyBrokerService(config);
|
|
846
|
+
if (existsSync3(legacyPath)) {
|
|
847
|
+
rmSync2(legacyPath, { force: true });
|
|
848
|
+
}
|
|
849
|
+
return brokerServiceStatus(config);
|
|
850
|
+
}
|
|
851
|
+
async function main() {
|
|
852
|
+
const command = process.argv[2] ?? "status";
|
|
853
|
+
const json = process.argv.includes("--json");
|
|
854
|
+
const config = resolveBrokerServiceConfig();
|
|
855
|
+
let status;
|
|
856
|
+
switch (command) {
|
|
857
|
+
case "install":
|
|
858
|
+
status = await installBrokerService(config);
|
|
859
|
+
break;
|
|
860
|
+
case "start":
|
|
861
|
+
status = await startBrokerService(config);
|
|
862
|
+
break;
|
|
863
|
+
case "stop":
|
|
864
|
+
status = await stopBrokerService(config);
|
|
865
|
+
break;
|
|
866
|
+
case "restart":
|
|
867
|
+
status = await restartBrokerService(config);
|
|
868
|
+
break;
|
|
869
|
+
case "uninstall":
|
|
870
|
+
status = await uninstallBrokerService(config);
|
|
871
|
+
break;
|
|
872
|
+
case "status":
|
|
873
|
+
status = await brokerServiceStatus(config);
|
|
874
|
+
break;
|
|
875
|
+
default:
|
|
876
|
+
console.error(`Unknown broker service command: ${command}`);
|
|
877
|
+
process.exit(1);
|
|
878
|
+
}
|
|
879
|
+
if (json) {
|
|
880
|
+
console.log(JSON.stringify(status, null, 2));
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
console.log(formatBrokerServiceStatus(status));
|
|
884
|
+
}
|
|
885
|
+
function formatBrokerServiceStatus(status) {
|
|
886
|
+
const lines = [
|
|
887
|
+
`label: ${status.label}`,
|
|
888
|
+
`mode: ${status.mode}`,
|
|
889
|
+
`launch agent: ${status.installed ? status.launchAgentPath : "not installed"}`,
|
|
890
|
+
`bootout: ${status.bootoutCommand}`,
|
|
891
|
+
`loaded: ${status.loaded ? "yes" : "no"}`,
|
|
892
|
+
`pid: ${status.pid ?? "\u2014"}`,
|
|
893
|
+
`launchd state: ${status.launchdState ?? "\u2014"}`,
|
|
894
|
+
`broker url: ${status.brokerUrl}`,
|
|
895
|
+
`broker socket: ${status.brokerSocketPath}`,
|
|
896
|
+
`reachable: ${status.reachable ? "yes" : "no"}`,
|
|
897
|
+
`health: ${status.health.ok ? "ok" : status.health.error ?? "unreachable"}`,
|
|
898
|
+
`health transport: ${status.health.transport ?? "unknown"}`,
|
|
899
|
+
`logs: ${status.stdoutLogPath}`
|
|
900
|
+
];
|
|
901
|
+
if (status.health.socketFallbackError) {
|
|
902
|
+
lines.push(`socket fallback: ${status.health.socketFallbackError}`);
|
|
903
|
+
}
|
|
904
|
+
if (status.lastLogLine) {
|
|
905
|
+
lines.push(`last log: ${status.lastLogLine}`);
|
|
906
|
+
}
|
|
907
|
+
return lines.join(`
|
|
908
|
+
`);
|
|
909
|
+
}
|
|
910
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1] && !process.argv[1].endsWith("/main.mjs")) {
|
|
911
|
+
main().catch((error) => {
|
|
912
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
913
|
+
process.exit(1);
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
export {
|
|
917
|
+
uninstallBrokerService,
|
|
918
|
+
stopBrokerService,
|
|
919
|
+
startBrokerService,
|
|
920
|
+
selectLastRelevantLogLine,
|
|
921
|
+
restartBrokerService,
|
|
922
|
+
resolveBrokerSocketPathForBaseUrl,
|
|
923
|
+
resolveBrokerServiceConfig,
|
|
924
|
+
resolveBrokerHost,
|
|
925
|
+
resolveAdvertiseScope,
|
|
926
|
+
renderLaunchAgentPlist,
|
|
927
|
+
parseLaunchctlPrint,
|
|
928
|
+
isLoopbackHost,
|
|
929
|
+
installBrokerService,
|
|
930
|
+
buildDefaultBrokerUrl,
|
|
931
|
+
buildDefaultBrokerSocketPath,
|
|
932
|
+
brokerServiceStatus,
|
|
933
|
+
DEFAULT_BROKER_URL,
|
|
934
|
+
DEFAULT_BROKER_PORT,
|
|
935
|
+
DEFAULT_BROKER_HOST_MESH,
|
|
936
|
+
DEFAULT_BROKER_HOST,
|
|
937
|
+
DEFAULT_ADVERTISE_SCOPE
|
|
938
|
+
};
|