amalgm 0.1.123 → 0.1.124
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/lib/supervisor.js
CHANGED
|
@@ -42,6 +42,7 @@ const {
|
|
|
42
42
|
} = require('./process-cleanup');
|
|
43
43
|
const {
|
|
44
44
|
runtimeLaunchServices,
|
|
45
|
+
runtimePortsFromState,
|
|
45
46
|
runtimePortsState,
|
|
46
47
|
runtimeServiceScripts,
|
|
47
48
|
} = require('./runtime-manifest');
|
|
@@ -52,6 +53,10 @@ const {
|
|
|
52
53
|
} = require('./updater');
|
|
53
54
|
const PACKAGE_VERSION = require('../package.json').version;
|
|
54
55
|
const PACKAGE_ROOT_DIR = path.join(__dirname, '..');
|
|
56
|
+
const PORT_ALLOCATION_LOCK_DIR = path.join(AMALGM_HOME, 'runtime-port-allocation.lock');
|
|
57
|
+
const PORT_ALLOCATION_LOCK_FILE = path.join(PORT_ALLOCATION_LOCK_DIR, 'owner.json');
|
|
58
|
+
const PORT_ALLOCATION_LOCK_TIMEOUT_MS = 45_000;
|
|
59
|
+
const PORT_ALLOCATION_LOCK_STALE_MS = 120_000;
|
|
55
60
|
|
|
56
61
|
// npm installs land in the npm prefix, which is only the tree this process runs
|
|
57
62
|
// from when the runtime was started from that prefix. When the service points at
|
|
@@ -289,6 +294,11 @@ function serviceSpecs(record, ports, options = {}) {
|
|
|
289
294
|
env: service.name === 'port-monitor' ? { ...env, PORT: process.env.PORT || '0' } : env,
|
|
290
295
|
port: ports[service.portKey],
|
|
291
296
|
healthPath: '/healthz',
|
|
297
|
+
expectedRuntimeInstanceId: AMALGM_RUNTIME_INSTANCE_ID,
|
|
298
|
+
expectedService: service.name,
|
|
299
|
+
healthHeaders: env.AMALGM_RUNTIME_TOKEN
|
|
300
|
+
? { 'x-amalgm-runtime-token': env.AMALGM_RUNTIME_TOKEN }
|
|
301
|
+
: {},
|
|
292
302
|
}));
|
|
293
303
|
}
|
|
294
304
|
|
|
@@ -352,8 +362,114 @@ async function pickPort(name, envName, preferred, used) {
|
|
|
352
362
|
throw new Error(`Could not allocate a free port for ${name}`);
|
|
353
363
|
}
|
|
354
364
|
|
|
355
|
-
|
|
365
|
+
function runtimeStateFilesUnder(dir) {
|
|
366
|
+
const files = [];
|
|
367
|
+
let entries;
|
|
368
|
+
try {
|
|
369
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
370
|
+
} catch {
|
|
371
|
+
return files;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
for (const entry of entries) {
|
|
375
|
+
const fullPath = path.join(dir, entry.name);
|
|
376
|
+
if (entry.isDirectory()) {
|
|
377
|
+
files.push(...runtimeStateFilesUnder(fullPath));
|
|
378
|
+
} else if (entry.isFile() && entry.name === 'runtime-state.json') {
|
|
379
|
+
files.push(fullPath);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return files;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function stateProcessRunning(state) {
|
|
386
|
+
const pids = [
|
|
387
|
+
state?.supervisor_pid,
|
|
388
|
+
state?.pid,
|
|
389
|
+
state?.gateway_pid,
|
|
390
|
+
...(Array.isArray(state?.services) ? state.services.map((service) => service?.pid) : []),
|
|
391
|
+
];
|
|
392
|
+
return pids.some((pid) => Number.isInteger(pid) && pid > 0 && isPidRunning(pid));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function liveDeclaredRuntimePorts() {
|
|
356
396
|
const used = new Set();
|
|
397
|
+
const stateFiles = [
|
|
398
|
+
...runtimeStateFilesUnder(path.join(AMALGM_HOME, 'users')),
|
|
399
|
+
...runtimeStateFilesUnder(path.join(AMALGM_HOME, 'runtimes')),
|
|
400
|
+
];
|
|
401
|
+
for (const stateFile of new Set(stateFiles)) {
|
|
402
|
+
if (path.resolve(stateFile) === path.resolve(RUNTIME_STATE_FILE)) continue;
|
|
403
|
+
const state = readJson(stateFile, null);
|
|
404
|
+
if (!state || !stateProcessRunning(state)) continue;
|
|
405
|
+
for (const port of Object.values(runtimePortsFromState(state))) {
|
|
406
|
+
if (Number.isInteger(port) && port > 0 && port <= 65535) used.add(port);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return used;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async function acquireRuntimePortAllocationLock(options = {}) {
|
|
413
|
+
const timeoutMs = options.timeoutMs || PORT_ALLOCATION_LOCK_TIMEOUT_MS;
|
|
414
|
+
const staleMs = options.staleMs || PORT_ALLOCATION_LOCK_STALE_MS;
|
|
415
|
+
const started = Date.now();
|
|
416
|
+
ensureDir(AMALGM_HOME);
|
|
417
|
+
|
|
418
|
+
while (true) {
|
|
419
|
+
try {
|
|
420
|
+
fs.mkdirSync(PORT_ALLOCATION_LOCK_DIR, { mode: 0o700 });
|
|
421
|
+
writeJsonSecret(PORT_ALLOCATION_LOCK_FILE, {
|
|
422
|
+
pid: process.pid,
|
|
423
|
+
runtime_instance_id: AMALGM_RUNTIME_INSTANCE_ID,
|
|
424
|
+
runtime_label: AMALGM_RUNTIME_LABEL,
|
|
425
|
+
user_scope: AMALGM_RUNTIME_USER_SCOPE,
|
|
426
|
+
acquired_at: new Date().toISOString(),
|
|
427
|
+
});
|
|
428
|
+
return () => {
|
|
429
|
+
const owner = readJson(PORT_ALLOCATION_LOCK_FILE, null);
|
|
430
|
+
if (owner?.pid === process.pid) {
|
|
431
|
+
fs.rmSync(PORT_ALLOCATION_LOCK_DIR, { recursive: true, force: true });
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
} catch (error) {
|
|
435
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
436
|
+
const owner = readJson(PORT_ALLOCATION_LOCK_FILE, null);
|
|
437
|
+
let mtimeMs = 0;
|
|
438
|
+
try {
|
|
439
|
+
mtimeMs = fs.statSync(PORT_ALLOCATION_LOCK_DIR).mtimeMs;
|
|
440
|
+
} catch {
|
|
441
|
+
mtimeMs = 0;
|
|
442
|
+
}
|
|
443
|
+
const stale = !owner?.pid
|
|
444
|
+
|| !isPidRunning(owner.pid)
|
|
445
|
+
|| (mtimeMs > 0 && Date.now() - mtimeMs > staleMs);
|
|
446
|
+
if (stale) {
|
|
447
|
+
try {
|
|
448
|
+
fs.rmSync(PORT_ALLOCATION_LOCK_DIR, { recursive: true, force: true });
|
|
449
|
+
} catch {
|
|
450
|
+
// Try again on the next loop.
|
|
451
|
+
}
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (Date.now() - started > timeoutMs) {
|
|
455
|
+
throw new Error(`Timed out waiting for Amalgm runtime port allocation lock held by pid ${owner.pid}`);
|
|
456
|
+
}
|
|
457
|
+
await sleep(100);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function withRuntimePortAllocationLock(fn) {
|
|
463
|
+
const release = await acquireRuntimePortAllocationLock();
|
|
464
|
+
try {
|
|
465
|
+
return await fn();
|
|
466
|
+
} finally {
|
|
467
|
+
release();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function resolveRuntimePorts() {
|
|
472
|
+
const used = liveDeclaredRuntimePorts();
|
|
357
473
|
const ports = {};
|
|
358
474
|
for (const service of runtimeLaunchServices()) {
|
|
359
475
|
ports[service.portKey] = await pickPort(service.name, service.envName, service.defaultPort, used);
|
|
@@ -401,6 +517,26 @@ function appendServiceState(name, update) {
|
|
|
401
517
|
updateRuntimeState({ services: next });
|
|
402
518
|
}
|
|
403
519
|
|
|
520
|
+
function removePidFileForThisSupervisor() {
|
|
521
|
+
try {
|
|
522
|
+
const pidRecord = readJson(PID_FILE, null);
|
|
523
|
+
if (pidRecord?.pid === process.pid) fs.unlinkSync(PID_FILE);
|
|
524
|
+
} catch {
|
|
525
|
+
// noop
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function removeRuntimeStateForThisSupervisor() {
|
|
530
|
+
try {
|
|
531
|
+
const stateRecord = readJson(RUNTIME_STATE_FILE, null);
|
|
532
|
+
if (stateRecord?.pid === process.pid || stateRecord?.supervisor_pid === process.pid) {
|
|
533
|
+
fs.unlinkSync(RUNTIME_STATE_FILE);
|
|
534
|
+
}
|
|
535
|
+
} catch {
|
|
536
|
+
// noop
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
404
540
|
function numberFromEnv(name, fallback, minimum = 0) {
|
|
405
541
|
const value = Number(process.env[name]);
|
|
406
542
|
if (!Number.isFinite(value) || value <= 0) return fallback;
|
|
@@ -780,10 +916,34 @@ function checkServiceHealth(spec, timeoutMs = CHILD_HEALTH_POLICY.timeoutMs) {
|
|
|
780
916
|
path: spec.healthPath || '/healthz',
|
|
781
917
|
method: 'GET',
|
|
782
918
|
timeout: timeoutMs,
|
|
919
|
+
headers: spec.healthHeaders || {},
|
|
783
920
|
},
|
|
784
921
|
(res) => {
|
|
785
|
-
|
|
786
|
-
res.on('
|
|
922
|
+
const chunks = [];
|
|
923
|
+
res.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
924
|
+
res.on('end', () => {
|
|
925
|
+
if (res.statusCode < 200 || res.statusCode >= 500) {
|
|
926
|
+
resolve(false);
|
|
927
|
+
return;
|
|
928
|
+
}
|
|
929
|
+
let body = {};
|
|
930
|
+
try {
|
|
931
|
+
body = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
|
932
|
+
} catch {
|
|
933
|
+
resolve(false);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const runtimeInstanceId = body.runtime_instance_id || body.runtimeInstanceId || '';
|
|
937
|
+
if (spec.expectedRuntimeInstanceId && runtimeInstanceId !== spec.expectedRuntimeInstanceId) {
|
|
938
|
+
resolve(false);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (spec.expectedService && body.service && body.service !== spec.expectedService) {
|
|
942
|
+
resolve(false);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
resolve(true);
|
|
946
|
+
});
|
|
787
947
|
},
|
|
788
948
|
);
|
|
789
949
|
req.on('timeout', () => {
|
|
@@ -806,6 +966,23 @@ function createManagedProcess(spec, options) {
|
|
|
806
966
|
let restartDelayMs = CHILD_RESTART_POLICY.initialDelayMs;
|
|
807
967
|
let restartHistory = [];
|
|
808
968
|
let state = 'starting';
|
|
969
|
+
let readySettled = false;
|
|
970
|
+
let readyTimer = null;
|
|
971
|
+
let resolveReady;
|
|
972
|
+
let rejectReady;
|
|
973
|
+
const ready = new Promise((resolve, reject) => {
|
|
974
|
+
resolveReady = resolve;
|
|
975
|
+
rejectReady = reject;
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
const settleReady = (error = null) => {
|
|
979
|
+
if (readySettled) return;
|
|
980
|
+
readySettled = true;
|
|
981
|
+
if (readyTimer) clearTimeout(readyTimer);
|
|
982
|
+
readyTimer = null;
|
|
983
|
+
if (error) rejectReady(error);
|
|
984
|
+
else resolveReady();
|
|
985
|
+
};
|
|
809
986
|
|
|
810
987
|
const setState = (nextState, extra = {}) => {
|
|
811
988
|
state = nextState;
|
|
@@ -823,20 +1000,24 @@ function createManagedProcess(spec, options) {
|
|
|
823
1000
|
};
|
|
824
1001
|
|
|
825
1002
|
const scheduleHealthCheck = () => {
|
|
1003
|
+
const delayMs = state === 'running' ? CHILD_HEALTH_POLICY.intervalMs : 500;
|
|
826
1004
|
clearHealthTimer();
|
|
827
1005
|
if (stopped) return;
|
|
828
1006
|
healthTimer = setTimeout(async () => {
|
|
829
1007
|
if (stopped || !child) return;
|
|
830
|
-
if (Date.now() - startedAt < CHILD_HEALTH_POLICY.graceMs) {
|
|
831
|
-
scheduleHealthCheck();
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
1008
|
const ok = await checkServiceHealth(spec);
|
|
835
1009
|
if (stopped || !child) return;
|
|
836
1010
|
if (ok) {
|
|
837
1011
|
healthFailures = 0;
|
|
838
|
-
if (state !== 'running')
|
|
1012
|
+
if (state !== 'running') {
|
|
1013
|
+
setState('running');
|
|
1014
|
+
settleReady();
|
|
1015
|
+
}
|
|
839
1016
|
} else {
|
|
1017
|
+
if (Date.now() - startedAt < CHILD_HEALTH_POLICY.graceMs) {
|
|
1018
|
+
scheduleHealthCheck();
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
840
1021
|
healthFailures += 1;
|
|
841
1022
|
writePrefixed(
|
|
842
1023
|
stream,
|
|
@@ -865,7 +1046,7 @@ function createManagedProcess(spec, options) {
|
|
|
865
1046
|
}
|
|
866
1047
|
}
|
|
867
1048
|
scheduleHealthCheck();
|
|
868
|
-
},
|
|
1049
|
+
}, delayMs);
|
|
869
1050
|
};
|
|
870
1051
|
|
|
871
1052
|
const canRestart = () => {
|
|
@@ -894,6 +1075,12 @@ function createManagedProcess(spec, options) {
|
|
|
894
1075
|
healthFailures = 0;
|
|
895
1076
|
startedAt = Date.now();
|
|
896
1077
|
setState('starting');
|
|
1078
|
+
if (!readySettled) {
|
|
1079
|
+
if (readyTimer) clearTimeout(readyTimer);
|
|
1080
|
+
readyTimer = setTimeout(() => {
|
|
1081
|
+
settleReady(new Error(`${spec.name} did not become healthy on port ${spec.port}`));
|
|
1082
|
+
}, CHILD_HEALTH_POLICY.graceMs + (CHILD_HEALTH_POLICY.intervalMs * CHILD_HEALTH_POLICY.maxFailures));
|
|
1083
|
+
}
|
|
897
1084
|
child = spawn(spec.command, spec.args, {
|
|
898
1085
|
cwd: RUNTIME_DIR,
|
|
899
1086
|
env: spec.env,
|
|
@@ -907,7 +1094,7 @@ function createManagedProcess(spec, options) {
|
|
|
907
1094
|
`started pid=${child.pid} command=${[spec.command, ...spec.args].join(' ')}\n`,
|
|
908
1095
|
options.foreground ? process.stdout : null,
|
|
909
1096
|
);
|
|
910
|
-
setState('
|
|
1097
|
+
setState('starting', { started_at: new Date(startedAt).toISOString() });
|
|
911
1098
|
scheduleHealthCheck();
|
|
912
1099
|
|
|
913
1100
|
child.stdout.on('data', (chunk) => {
|
|
@@ -926,6 +1113,9 @@ function createManagedProcess(spec, options) {
|
|
|
926
1113
|
);
|
|
927
1114
|
const runtimeMs = Date.now() - startedAt;
|
|
928
1115
|
child = null;
|
|
1116
|
+
if (!readySettled) {
|
|
1117
|
+
settleReady(new Error(`${spec.name} exited before becoming healthy`));
|
|
1118
|
+
}
|
|
929
1119
|
if (!stopped) {
|
|
930
1120
|
if (runtimeMs >= CHILD_RESTART_POLICY.stableAfterMs) {
|
|
931
1121
|
restartDelayMs = CHILD_RESTART_POLICY.initialDelayMs;
|
|
@@ -955,6 +1145,7 @@ function createManagedProcess(spec, options) {
|
|
|
955
1145
|
return {
|
|
956
1146
|
stop() {
|
|
957
1147
|
stopped = true;
|
|
1148
|
+
settleReady(new Error(`${spec.name} stopped before becoming healthy`));
|
|
958
1149
|
if (restartTimer) clearTimeout(restartTimer);
|
|
959
1150
|
clearHealthTimer();
|
|
960
1151
|
setState('stopping');
|
|
@@ -967,6 +1158,7 @@ function createManagedProcess(spec, options) {
|
|
|
967
1158
|
}
|
|
968
1159
|
stream.end();
|
|
969
1160
|
},
|
|
1161
|
+
ready,
|
|
970
1162
|
};
|
|
971
1163
|
}
|
|
972
1164
|
|
|
@@ -1022,6 +1214,7 @@ async function startSupervisor(options = {}) {
|
|
|
1022
1214
|
supervisor_pid: process.pid,
|
|
1023
1215
|
source: 'npm',
|
|
1024
1216
|
runtime_label: AMALGM_RUNTIME_LABEL,
|
|
1217
|
+
runtime_instance_id: AMALGM_RUNTIME_INSTANCE_ID,
|
|
1025
1218
|
label: AMALGM_RUNTIME_LABEL,
|
|
1026
1219
|
branch: AMALGM_BRANCH,
|
|
1027
1220
|
user_scope: AMALGM_RUNTIME_USER_SCOPE,
|
|
@@ -1038,15 +1231,27 @@ async function startSupervisor(options = {}) {
|
|
|
1038
1231
|
console.warn('No registered computer found. Run `amalgm login` to enable event/chat tunnels.');
|
|
1039
1232
|
}
|
|
1040
1233
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1234
|
+
let ports = null;
|
|
1235
|
+
let managed = [];
|
|
1236
|
+
await withRuntimePortAllocationLock(async () => {
|
|
1237
|
+
ports = await resolveRuntimePorts();
|
|
1238
|
+
Object.assign(process.env, {
|
|
1239
|
+
AMALGM_GATEWAY_PORT: String(ports.gateway),
|
|
1240
|
+
AMALGM_MCP_PORT: String(ports.amalgmMcp),
|
|
1241
|
+
CHAT_SERVER_PORT: String(ports.chatServer),
|
|
1242
|
+
PORT_MONITOR_PORT: String(ports.portMonitor),
|
|
1243
|
+
});
|
|
1244
|
+
updateRuntimeState(runtimePortsState(ports));
|
|
1245
|
+
managed = serviceSpecs(record, ports, options).map((spec) => createManagedProcess(spec, options));
|
|
1246
|
+
try {
|
|
1247
|
+
await Promise.all(managed.map((proc) => proc.ready));
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
for (const proc of managed) proc.stop();
|
|
1250
|
+
removePidFileForThisSupervisor();
|
|
1251
|
+
removeRuntimeStateForThisSupervisor();
|
|
1252
|
+
throw error;
|
|
1253
|
+
}
|
|
1047
1254
|
});
|
|
1048
|
-
updateRuntimeState(runtimePortsState(ports));
|
|
1049
|
-
const managed = serviceSpecs(record, ports, options).map((spec) => createManagedProcess(spec, options));
|
|
1050
1255
|
const tunnels = [];
|
|
1051
1256
|
|
|
1052
1257
|
if (!options.localOnly && record?.tunnel_url && record?.tunnel_token) {
|
|
@@ -1079,18 +1284,8 @@ async function startSupervisor(options = {}) {
|
|
|
1079
1284
|
writeShutdownReason(signal);
|
|
1080
1285
|
for (const tunnel of tunnels) tunnel.stop();
|
|
1081
1286
|
for (const proc of managed) proc.stop();
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
if (pidRecord?.pid === process.pid) fs.unlinkSync(PID_FILE);
|
|
1085
|
-
} catch {
|
|
1086
|
-
// noop
|
|
1087
|
-
}
|
|
1088
|
-
try {
|
|
1089
|
-
const stateRecord = readJson(RUNTIME_STATE_FILE, null);
|
|
1090
|
-
if (stateRecord?.pid === process.pid) fs.unlinkSync(RUNTIME_STATE_FILE);
|
|
1091
|
-
} catch {
|
|
1092
|
-
// noop
|
|
1093
|
-
}
|
|
1287
|
+
removePidFileForThisSupervisor();
|
|
1288
|
+
removeRuntimeStateForThisSupervisor();
|
|
1094
1289
|
resolve();
|
|
1095
1290
|
};
|
|
1096
1291
|
|
package/package.json
CHANGED
|
@@ -12,6 +12,10 @@ async function handleHealthRoutes(ctx) {
|
|
|
12
12
|
const automations = listAutomations();
|
|
13
13
|
ctx.sendJson(200, {
|
|
14
14
|
status: 'ok',
|
|
15
|
+
service: 'amalgm-mcp',
|
|
16
|
+
runtime_label: process.env.AMALGM_RUNTIME_LABEL || 'main',
|
|
17
|
+
runtime_instance_id: process.env.AMALGM_RUNTIME_INSTANCE_ID || '',
|
|
18
|
+
pid: process.pid,
|
|
15
19
|
automations: automations.length,
|
|
16
20
|
enabledAutomations: automations.filter((automation) => automation.enabled !== false).length,
|
|
17
21
|
apps: loadApps().apps.length,
|
|
@@ -72,7 +72,17 @@ function createServer(core = createCore()) {
|
|
|
72
72
|
const route = parseRoute(req.url);
|
|
73
73
|
if (!route) return sendJson(res, 404, { error: 'Not found' });
|
|
74
74
|
try {
|
|
75
|
-
if (route.handler === 'healthz')
|
|
75
|
+
if (route.handler === 'healthz') {
|
|
76
|
+
return sendJson(res, 200, {
|
|
77
|
+
status: 'ok',
|
|
78
|
+
service: 'chat-server',
|
|
79
|
+
runtime: 'chat-core',
|
|
80
|
+
runtime_label: process.env.AMALGM_RUNTIME_LABEL || 'main',
|
|
81
|
+
runtime_instance_id: process.env.AMALGM_RUNTIME_INSTANCE_ID || '',
|
|
82
|
+
pid: process.pid,
|
|
83
|
+
uptime: process.uptime(),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
76
86
|
if (route.handler === 'active') return sendJson(res, 200, core.active(route.id));
|
|
77
87
|
if (route.handler === 'runtime-idle') return sendJson(res, 200, core.idleStatus());
|
|
78
88
|
if (route.handler === 'stop') {
|
|
@@ -58,6 +58,7 @@ const LOG_DIR = runtimeLogDir();
|
|
|
58
58
|
const BIND_HOST = process.env.AMALGM_BIND_HOST || '127.0.0.1';
|
|
59
59
|
const OWNER = process.env.AMALGM_RUNTIME_SOURCE || 'local';
|
|
60
60
|
const RUNTIME_LABEL = process.env.AMALGM_RUNTIME_LABEL || 'main';
|
|
61
|
+
const RUNTIME_INSTANCE_ID = process.env.AMALGM_RUNTIME_INSTANCE_ID || '';
|
|
61
62
|
const RUNTIME_BRANCH = process.env.AMALGM_BRANCH || (RUNTIME_LABEL === 'preview' ? 'preview' : 'main');
|
|
62
63
|
const RUNTIME_USER_SCOPE = process.env.AMALGM_RUNTIME_USER_ID
|
|
63
64
|
|| process.env.AMALGM_USER_SCOPE
|
|
@@ -377,6 +378,7 @@ function writeRuntimeState(actualPort) {
|
|
|
377
378
|
owner: OWNER,
|
|
378
379
|
source: OWNER,
|
|
379
380
|
runtime_label: RUNTIME_LABEL,
|
|
381
|
+
runtime_instance_id: RUNTIME_INSTANCE_ID,
|
|
380
382
|
label: RUNTIME_LABEL,
|
|
381
383
|
branch: RUNTIME_BRANCH,
|
|
382
384
|
user_scope: RUNTIME_USER_SCOPE,
|
|
@@ -982,6 +984,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
982
984
|
service: 'local-gateway',
|
|
983
985
|
owner: OWNER,
|
|
984
986
|
runtime_label: RUNTIME_LABEL,
|
|
987
|
+
runtime_instance_id: RUNTIME_INSTANCE_ID,
|
|
985
988
|
branch: RUNTIME_BRANCH,
|
|
986
989
|
user_scope: RUNTIME_USER_SCOPE,
|
|
987
990
|
pid: process.pid,
|
|
@@ -15,6 +15,8 @@ const { runtimePort } = require('../lib/runtime-manifest');
|
|
|
15
15
|
|
|
16
16
|
const PORT = runtimePort('port-monitor');
|
|
17
17
|
const BIND_HOST = process.env.AMALGM_BIND_HOST || '0.0.0.0';
|
|
18
|
+
const RUNTIME_INSTANCE_ID = process.env.AMALGM_RUNTIME_INSTANCE_ID || '';
|
|
19
|
+
const RUNTIME_LABEL = process.env.AMALGM_RUNTIME_LABEL || 'main';
|
|
18
20
|
const AGENT_PORT = parseInt(process.env.PORT || '8080');
|
|
19
21
|
const AMALGM_MCP_PORT = runtimePort('amalgm-mcp');
|
|
20
22
|
const CHAT_SERVER_PORT = runtimePort('chat-server');
|
|
@@ -186,6 +188,10 @@ const server = http.createServer((req, res) => {
|
|
|
186
188
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
187
189
|
res.end(JSON.stringify({
|
|
188
190
|
status: 'ok',
|
|
191
|
+
service: 'port-monitor',
|
|
192
|
+
runtime_label: RUNTIME_LABEL,
|
|
193
|
+
runtime_instance_id: RUNTIME_INSTANCE_ID,
|
|
194
|
+
pid: process.pid,
|
|
189
195
|
activePorts: [...knownPorts.entries()].map(([port, processInfo]) => ({ port, processName: publicProcessName(processInfo) })),
|
|
190
196
|
}));
|
|
191
197
|
return;
|