amalgm 0.1.44 → 0.1.46
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 +0 -1
- package/lib/cli.js +0 -2
- package/lib/runtime-manifest.js +1 -129
- package/lib/service.js +0 -1
- package/lib/supervisor.js +0 -3
- package/lib/tunnel-chat.js +12 -12
- package/lib/tunnel-events.js +12 -12
- package/package.json +2 -2
- package/runtime/lib/runtime-manifest.js +159 -0
- package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +3 -2
- package/runtime/scripts/amalgm-mcp/config.js +3 -2
- package/runtime/scripts/amalgm-mcp/index.js +2 -2
- package/runtime/scripts/amalgm-mcp/lib/chat-runner.js +1 -1
- package/runtime/scripts/chat-core/contract.js +2 -1
- package/runtime/scripts/chat-core/tooling/mcp-bundle.js +3 -3
- package/runtime/scripts/chat-server/config.js +2 -1
- package/runtime/scripts/local-gateway.js +17 -17
- package/runtime/scripts/port-monitor.js +7 -8
- package/runtime/scripts/fs-watcher.js +0 -923
package/README.md
CHANGED
|
@@ -37,7 +37,6 @@ amalgm run
|
|
|
37
37
|
The runtime supervisor keeps the declared local essentials alive:
|
|
38
38
|
|
|
39
39
|
- port monitor on `8081`
|
|
40
|
-
- filesystem/auth watcher on `8082`
|
|
41
40
|
- Amalgm MCP on `8083`
|
|
42
41
|
- chat server on `8084`
|
|
43
42
|
- events/previews/artifact tunnel to `wire.events.amalgm.ai`
|
package/lib/cli.js
CHANGED
package/lib/runtime-manifest.js
CHANGED
|
@@ -1,131 +1,3 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
{
|
|
5
|
-
name: 'local-gateway',
|
|
6
|
-
script: 'scripts/local-gateway.js',
|
|
7
|
-
portKey: 'gateway',
|
|
8
|
-
envName: 'AMALGM_GATEWAY_PORT',
|
|
9
|
-
stateKey: 'gateway',
|
|
10
|
-
defaultPort: 28781,
|
|
11
|
-
launchOrder: 50,
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
name: 'port-monitor',
|
|
15
|
-
script: 'scripts/port-monitor.js',
|
|
16
|
-
portKey: 'portMonitor',
|
|
17
|
-
envName: 'PORT_MONITOR_PORT',
|
|
18
|
-
stateKey: 'port_monitor',
|
|
19
|
-
defaultPort: 8081,
|
|
20
|
-
launchOrder: 10,
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
name: 'fs-watcher',
|
|
24
|
-
script: 'scripts/fs-watcher.js',
|
|
25
|
-
portKey: 'fsWatcher',
|
|
26
|
-
envName: 'FS_WATCHER_PORT',
|
|
27
|
-
stateKey: 'fs_watcher',
|
|
28
|
-
defaultPort: 8082,
|
|
29
|
-
launchOrder: 20,
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
name: 'amalgm-mcp',
|
|
33
|
-
script: 'scripts/amalgm-mcp/index.js',
|
|
34
|
-
portKey: 'amalgmMcp',
|
|
35
|
-
envName: 'AMALGM_MCP_PORT',
|
|
36
|
-
stateKey: 'amalgm_mcp',
|
|
37
|
-
defaultPort: 8083,
|
|
38
|
-
launchOrder: 40,
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: 'chat-server',
|
|
42
|
-
script: 'scripts/chat-server.js',
|
|
43
|
-
portKey: 'chatServer',
|
|
44
|
-
envName: 'CHAT_SERVER_PORT',
|
|
45
|
-
stateKey: 'chat_server',
|
|
46
|
-
defaultPort: 8084,
|
|
47
|
-
launchOrder: 30,
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
function runtimeServices() {
|
|
52
|
-
return RUNTIME_SERVICES.map((service) => ({ ...service }));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function runtimeLaunchServices() {
|
|
56
|
-
return runtimeServices().sort((left, right) => left.launchOrder - right.launchOrder);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function runtimeServiceNames() {
|
|
60
|
-
return RUNTIME_SERVICES.map((service) => service.name);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function runtimeServiceScripts() {
|
|
64
|
-
return RUNTIME_SERVICES.map((service) => service.script);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function runtimeServiceByName(name) {
|
|
68
|
-
return RUNTIME_SERVICES.find((service) => service.name === name) || null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function runtimePortsFromState(state) {
|
|
72
|
-
const statePorts = state?.ports || {};
|
|
73
|
-
const values = {};
|
|
74
|
-
for (const service of RUNTIME_SERVICES) {
|
|
75
|
-
if (service.stateKey === 'gateway' && Number.isInteger(state?.gateway_port)) {
|
|
76
|
-
values[service.name] = Number(state.gateway_port);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const port = Number(statePorts[service.stateKey] || 0);
|
|
80
|
-
if (Number.isInteger(port) && port > 0) values[service.name] = port;
|
|
81
|
-
}
|
|
82
|
-
return values;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function runtimePortsFromEnv(env = process.env) {
|
|
86
|
-
const values = {};
|
|
87
|
-
for (const service of RUNTIME_SERVICES) {
|
|
88
|
-
const port = Number(env[service.envName] || service.defaultPort);
|
|
89
|
-
if (Number.isInteger(port) && port > 0) values[service.name] = port;
|
|
90
|
-
}
|
|
91
|
-
return values;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function runtimePortsForStatus(state, env = process.env) {
|
|
95
|
-
const fromState = runtimePortsFromState(state);
|
|
96
|
-
const fromEnv = runtimePortsFromEnv(env);
|
|
97
|
-
return RUNTIME_SERVICES
|
|
98
|
-
.map((service) => [service.name, fromState[service.name] || fromEnv[service.name]])
|
|
99
|
-
.filter(([, port]) => Number.isInteger(port) && port > 0);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function runtimePortsState(ports) {
|
|
103
|
-
const statePorts = {};
|
|
104
|
-
for (const service of RUNTIME_SERVICES) {
|
|
105
|
-
const port = Number(ports?.[service.portKey] || 0);
|
|
106
|
-
if (!Number.isInteger(port) || port <= 0) continue;
|
|
107
|
-
if (service.stateKey !== 'gateway') statePorts[service.stateKey] = port;
|
|
108
|
-
}
|
|
109
|
-
return {
|
|
110
|
-
gateway_port: Number(ports?.gateway || 0) || undefined,
|
|
111
|
-
gateway_url: ports?.gateway ? `http://127.0.0.1:${ports.gateway}` : undefined,
|
|
112
|
-
ports: statePorts,
|
|
113
|
-
services: RUNTIME_SERVICES.map((service) => ({
|
|
114
|
-
name: service.name,
|
|
115
|
-
port: Number(ports?.[service.portKey] || 0) || null,
|
|
116
|
-
health_path: '/healthz',
|
|
117
|
-
})),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
module.exports = {
|
|
122
|
-
runtimeLaunchServices,
|
|
123
|
-
runtimePortsForStatus,
|
|
124
|
-
runtimePortsFromEnv,
|
|
125
|
-
runtimePortsFromState,
|
|
126
|
-
runtimePortsState,
|
|
127
|
-
runtimeServiceByName,
|
|
128
|
-
runtimeServiceNames,
|
|
129
|
-
runtimeServiceScripts,
|
|
130
|
-
runtimeServices,
|
|
131
|
-
};
|
|
3
|
+
module.exports = require('../runtime/lib/runtime-manifest');
|
package/lib/service.js
CHANGED
package/lib/supervisor.js
CHANGED
|
@@ -224,8 +224,6 @@ function baseRuntimeEnv(record, ports, options = {}) {
|
|
|
224
224
|
CHAT_SERVER_PORT: String(ports.chatServer),
|
|
225
225
|
AMALGM_MCP_PORT: String(ports.amalgmMcp),
|
|
226
226
|
PORT_MONITOR_PORT: String(ports.portMonitor),
|
|
227
|
-
FS_WATCHER_PORT: String(ports.fsWatcher),
|
|
228
|
-
FS_WATCHER_ROOT: process.env.FS_WATCHER_ROOT || defaultCwd,
|
|
229
227
|
AMALGM_RUNTIME_VERSION: require('../package.json').version,
|
|
230
228
|
};
|
|
231
229
|
|
|
@@ -628,7 +626,6 @@ async function startSupervisor(options = {}) {
|
|
|
628
626
|
AMALGM_GATEWAY_PORT: String(ports.gateway),
|
|
629
627
|
AMALGM_MCP_PORT: String(ports.amalgmMcp),
|
|
630
628
|
CHAT_SERVER_PORT: String(ports.chatServer),
|
|
631
|
-
FS_WATCHER_PORT: String(ports.fsWatcher),
|
|
632
629
|
PORT_MONITOR_PORT: String(ports.portMonitor),
|
|
633
630
|
});
|
|
634
631
|
updateRuntimeState(runtimePortsState(ports));
|
package/lib/tunnel-chat.js
CHANGED
|
@@ -5,13 +5,17 @@ const fs = require('fs');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const { WebSocket } = require('ws');
|
|
7
7
|
const { RUNTIME_STATE_FILE } = require('./paths');
|
|
8
|
+
const { runtimePort, runtimeServiceByName } = require('./runtime-manifest');
|
|
8
9
|
|
|
9
|
-
const DEFAULT_TARGET_PORT =
|
|
10
|
+
const DEFAULT_TARGET_PORT = runtimeServiceByName('chat-server').defaultPort;
|
|
10
11
|
const DEFAULT_CHAT_TUNNEL_URL = 'wss://amalgm-chat-gateway.fly.dev/wire';
|
|
11
12
|
const CONNECT_TIMEOUT_MS = 20_000;
|
|
12
13
|
const WATCHDOG_INTERVAL_MS = 15_000;
|
|
13
14
|
const HEARTBEAT_TIMEOUT_MS = 75_000;
|
|
14
|
-
const ALLOWED_TARGET_PORTS = new Set([
|
|
15
|
+
const ALLOWED_TARGET_PORTS = new Set([
|
|
16
|
+
runtimeServiceByName('amalgm-mcp').defaultPort,
|
|
17
|
+
runtimeServiceByName('chat-server').defaultPort,
|
|
18
|
+
]);
|
|
15
19
|
const HOP_BY_HOP_HEADERS = new Set([
|
|
16
20
|
'connection',
|
|
17
21
|
'keep-alive',
|
|
@@ -62,22 +66,18 @@ function responseHeaders(headers = {}) {
|
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
function readRuntimePorts() {
|
|
65
|
-
const envPort = (name) => {
|
|
66
|
-
const port = Number(process.env[name]);
|
|
67
|
-
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : undefined;
|
|
68
|
-
};
|
|
69
69
|
try {
|
|
70
70
|
const parsed = JSON.parse(fs.readFileSync(RUNTIME_STATE_FILE, 'utf8'));
|
|
71
71
|
return {
|
|
72
|
-
gateway: Number(parsed?.gateway_port || parsed?.ports?.gateway) ||
|
|
73
|
-
mcp: Number(parsed?.ports?.amalgm_mcp) ||
|
|
74
|
-
chat: Number(parsed?.ports?.chat_server) ||
|
|
72
|
+
gateway: Number(parsed?.gateway_port || parsed?.ports?.gateway) || runtimePort('local-gateway'),
|
|
73
|
+
mcp: Number(parsed?.ports?.amalgm_mcp) || runtimePort('amalgm-mcp'),
|
|
74
|
+
chat: Number(parsed?.ports?.chat_server) || runtimePort('chat-server'),
|
|
75
75
|
};
|
|
76
76
|
} catch {
|
|
77
77
|
return {
|
|
78
|
-
gateway:
|
|
79
|
-
mcp:
|
|
80
|
-
chat:
|
|
78
|
+
gateway: runtimePort('local-gateway'),
|
|
79
|
+
mcp: runtimePort('amalgm-mcp'),
|
|
80
|
+
chat: runtimePort('chat-server'),
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
}
|
package/lib/tunnel-events.js
CHANGED
|
@@ -7,12 +7,16 @@ const path = require('path');
|
|
|
7
7
|
const { WebSocket } = require('ws');
|
|
8
8
|
|
|
9
9
|
const { AMALGM_DIR, RUNTIME_STATE_FILE } = require('./paths');
|
|
10
|
+
const { runtimePort, runtimeServiceByName } = require('./runtime-manifest');
|
|
10
11
|
|
|
11
|
-
const DEFAULT_TARGET_PORT =
|
|
12
|
+
const DEFAULT_TARGET_PORT = runtimeServiceByName('amalgm-mcp').defaultPort;
|
|
12
13
|
const CONNECT_TIMEOUT_MS = 20_000;
|
|
13
14
|
const WATCHDOG_INTERVAL_MS = 15_000;
|
|
14
15
|
const HEARTBEAT_TIMEOUT_MS = 75_000;
|
|
15
|
-
const CORE_TARGET_PORTS = new Set([
|
|
16
|
+
const CORE_TARGET_PORTS = new Set([
|
|
17
|
+
runtimeServiceByName('amalgm-mcp').defaultPort,
|
|
18
|
+
runtimeServiceByName('chat-server').defaultPort,
|
|
19
|
+
]);
|
|
16
20
|
const HOP_BY_HOP_HEADERS = new Set([
|
|
17
21
|
'connection',
|
|
18
22
|
'keep-alive',
|
|
@@ -149,22 +153,18 @@ function readArtifactRoutes() {
|
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
function readRuntimePorts() {
|
|
152
|
-
const envPort = (name) => {
|
|
153
|
-
const port = Number(process.env[name]);
|
|
154
|
-
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : undefined;
|
|
155
|
-
};
|
|
156
156
|
try {
|
|
157
157
|
const parsed = JSON.parse(fs.readFileSync(RUNTIME_STATE_FILE, 'utf8'));
|
|
158
158
|
return {
|
|
159
|
-
gateway: Number(parsed?.gateway_port || parsed?.ports?.gateway) ||
|
|
160
|
-
mcp: Number(parsed?.ports?.amalgm_mcp) ||
|
|
161
|
-
chat: Number(parsed?.ports?.chat_server) ||
|
|
159
|
+
gateway: Number(parsed?.gateway_port || parsed?.ports?.gateway) || runtimePort('local-gateway'),
|
|
160
|
+
mcp: Number(parsed?.ports?.amalgm_mcp) || runtimePort('amalgm-mcp'),
|
|
161
|
+
chat: Number(parsed?.ports?.chat_server) || runtimePort('chat-server'),
|
|
162
162
|
};
|
|
163
163
|
} catch {
|
|
164
164
|
return {
|
|
165
|
-
gateway:
|
|
166
|
-
mcp:
|
|
167
|
-
chat:
|
|
165
|
+
gateway: runtimePort('local-gateway'),
|
|
166
|
+
mcp: runtimePort('amalgm-mcp'),
|
|
167
|
+
chat: runtimePort('chat-server'),
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
170
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amalgm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.46",
|
|
4
4
|
"description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"sync-runtime": "node ../../scripts/sync-npm-package-runtime.mjs",
|
|
18
18
|
"prepack": "node ../../scripts/sync-npm-package-runtime.mjs",
|
|
19
19
|
"pack:dry": "npm pack --dry-run",
|
|
20
|
-
"check": "node --check bin/amalgm.js && node --check lib/auth-store.js && node --check lib/cli.js && node --check lib/paths.js && node --check lib/process-cleanup.js && node --check lib/runtime-manifest.js && node --check lib/service.js && node --check lib/supervisor.js && node --check lib/tunnel-chat.js && node --check lib/tunnel-events.js && node --check runtime/
|
|
20
|
+
"check": "node --check bin/amalgm.js && node --check lib/auth-store.js && node --check lib/cli.js && node --check lib/paths.js && node --check lib/process-cleanup.js && node --check lib/runtime-manifest.js && node --check lib/service.js && node --check lib/supervisor.js && node --check lib/tunnel-chat.js && node --check lib/tunnel-events.js && node --check runtime/lib/runtime-manifest.js && node --check runtime/scripts/runtime-auth.js && node --check runtime/scripts/proxy-token-store.js && node --check runtime/scripts/local-gateway.js && node --check runtime/scripts/port-monitor.js && node --check runtime/scripts/chat-server.js && node --check runtime/scripts/chat-server/index.js && node --check runtime/scripts/chat-server/config.js && node --check runtime/scripts/chat-core/tooling/native-binaries.js && node --check runtime/scripts/chat-core/tooling/package-import.js && node --check runtime/scripts/amalgm-mcp/index.js && node --check runtime/scripts/amalgm-mcp/config.js"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=20"
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const RUNTIME_SERVICES = [
|
|
4
|
+
{
|
|
5
|
+
name: 'local-gateway',
|
|
6
|
+
script: 'scripts/local-gateway.js',
|
|
7
|
+
portKey: 'gateway',
|
|
8
|
+
envName: 'AMALGM_GATEWAY_PORT',
|
|
9
|
+
stateKey: 'gateway',
|
|
10
|
+
defaultPort: 28781,
|
|
11
|
+
launchOrder: 50,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'port-monitor',
|
|
15
|
+
script: 'scripts/port-monitor.js',
|
|
16
|
+
portKey: 'portMonitor',
|
|
17
|
+
envName: 'PORT_MONITOR_PORT',
|
|
18
|
+
stateKey: 'port_monitor',
|
|
19
|
+
defaultPort: 8081,
|
|
20
|
+
launchOrder: 10,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'amalgm-mcp',
|
|
24
|
+
script: 'scripts/amalgm-mcp/index.js',
|
|
25
|
+
portKey: 'amalgmMcp',
|
|
26
|
+
envName: 'AMALGM_MCP_PORT',
|
|
27
|
+
stateKey: 'amalgm_mcp',
|
|
28
|
+
defaultPort: 8083,
|
|
29
|
+
launchOrder: 40,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'chat-server',
|
|
33
|
+
script: 'scripts/chat-server.js',
|
|
34
|
+
portKey: 'chatServer',
|
|
35
|
+
envName: 'CHAT_SERVER_PORT',
|
|
36
|
+
stateKey: 'chat_server',
|
|
37
|
+
defaultPort: 8084,
|
|
38
|
+
launchOrder: 30,
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function cloneService(service) {
|
|
43
|
+
return { ...service };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function serviceByName(name) {
|
|
47
|
+
return RUNTIME_SERVICES.find((service) => service.name === name) || null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parsePort(value, fallback = null) {
|
|
51
|
+
const port = Number.parseInt(String(value ?? ''), 10);
|
|
52
|
+
if (Number.isInteger(port) && port > 0 && port <= 65535) return port;
|
|
53
|
+
return fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runtimeServices() {
|
|
57
|
+
return RUNTIME_SERVICES.map(cloneService);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function runtimeLaunchServices() {
|
|
61
|
+
return runtimeServices().sort((left, right) => left.launchOrder - right.launchOrder);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function runtimeServiceNames() {
|
|
65
|
+
return RUNTIME_SERVICES.map((service) => service.name);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function runtimeServiceScripts() {
|
|
69
|
+
return RUNTIME_SERVICES.map((service) => service.script);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function runtimeServiceByName(name) {
|
|
73
|
+
const service = serviceByName(name);
|
|
74
|
+
return service ? cloneService(service) : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runtimePort(name, env = process.env) {
|
|
78
|
+
const service = serviceByName(name);
|
|
79
|
+
if (!service) return null;
|
|
80
|
+
return parsePort(env?.[service.envName], service.defaultPort);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runtimePortByKey(portKey, env = process.env) {
|
|
84
|
+
const service = RUNTIME_SERVICES.find((candidate) => candidate.portKey === portKey);
|
|
85
|
+
return service ? runtimePort(service.name, env) : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function runtimePorts(env = process.env) {
|
|
89
|
+
const values = {};
|
|
90
|
+
for (const service of RUNTIME_SERVICES) {
|
|
91
|
+
values[service.portKey] = runtimePort(service.name, env);
|
|
92
|
+
}
|
|
93
|
+
return values;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function runtimePortsFromState(state) {
|
|
97
|
+
const statePorts = state?.ports || {};
|
|
98
|
+
const values = {};
|
|
99
|
+
for (const service of RUNTIME_SERVICES) {
|
|
100
|
+
if (service.stateKey === 'gateway' && parsePort(state?.gateway_port)) {
|
|
101
|
+
values[service.name] = parsePort(state.gateway_port);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const port = parsePort(statePorts[service.stateKey]);
|
|
105
|
+
if (port) values[service.name] = port;
|
|
106
|
+
}
|
|
107
|
+
return values;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function runtimePortsFromEnv(env = process.env) {
|
|
111
|
+
const values = {};
|
|
112
|
+
for (const service of RUNTIME_SERVICES) {
|
|
113
|
+
values[service.name] = runtimePort(service.name, env);
|
|
114
|
+
}
|
|
115
|
+
return values;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function runtimePortsForStatus(state, env = process.env) {
|
|
119
|
+
const fromState = runtimePortsFromState(state);
|
|
120
|
+
const fromEnv = runtimePortsFromEnv(env);
|
|
121
|
+
return RUNTIME_SERVICES
|
|
122
|
+
.map((service) => [service.name, fromState[service.name] || fromEnv[service.name]])
|
|
123
|
+
.filter(([, port]) => Number.isInteger(port) && port > 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function runtimePortsState(ports) {
|
|
127
|
+
const statePorts = {};
|
|
128
|
+
for (const service of RUNTIME_SERVICES) {
|
|
129
|
+
const port = parsePort(ports?.[service.portKey]);
|
|
130
|
+
if (!port) continue;
|
|
131
|
+
if (service.stateKey !== 'gateway') statePorts[service.stateKey] = port;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
gateway_port: parsePort(ports?.gateway) || undefined,
|
|
135
|
+
gateway_url: ports?.gateway ? `http://127.0.0.1:${ports.gateway}` : undefined,
|
|
136
|
+
ports: statePorts,
|
|
137
|
+
services: RUNTIME_SERVICES.map((service) => ({
|
|
138
|
+
name: service.name,
|
|
139
|
+
port: parsePort(ports?.[service.portKey]),
|
|
140
|
+
health_path: '/healthz',
|
|
141
|
+
})),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
parsePort,
|
|
147
|
+
runtimeLaunchServices,
|
|
148
|
+
runtimePort,
|
|
149
|
+
runtimePortByKey,
|
|
150
|
+
runtimePorts,
|
|
151
|
+
runtimePortsForStatus,
|
|
152
|
+
runtimePortsFromEnv,
|
|
153
|
+
runtimePortsFromState,
|
|
154
|
+
runtimePortsState,
|
|
155
|
+
runtimeServiceByName,
|
|
156
|
+
runtimeServiceNames,
|
|
157
|
+
runtimeServiceScripts,
|
|
158
|
+
runtimeServices,
|
|
159
|
+
};
|
|
@@ -10,6 +10,7 @@ const path = require('path');
|
|
|
10
10
|
const { spawn } = require('child_process');
|
|
11
11
|
const { DEFAULT_CWD } = require('../config');
|
|
12
12
|
const activeMemory = require('../../chat-core/tooling/active-memory');
|
|
13
|
+
const { runtimePort } = require('../../../lib/runtime-manifest');
|
|
13
14
|
const { syncArtifactRoutesToGateway } = require('./advertise');
|
|
14
15
|
const {
|
|
15
16
|
allocatePort,
|
|
@@ -88,8 +89,8 @@ function buildEnv(artifact) {
|
|
|
88
89
|
AMALGM_ARTIFACT_ID: artifact.id,
|
|
89
90
|
AMALGM_ARTIFACT_REF: artifact.artifactRef,
|
|
90
91
|
AMALGM_ARTIFACT_URL: artifact.publicUrl,
|
|
91
|
-
AMALGM_CHAT_SERVER_URL: `http://127.0.0.1:${
|
|
92
|
-
AMALGM_MCP_URL: `http://127.0.0.1:${
|
|
92
|
+
AMALGM_CHAT_SERVER_URL: `http://127.0.0.1:${runtimePort('chat-server')}`,
|
|
93
|
+
AMALGM_MCP_URL: `http://127.0.0.1:${runtimePort('amalgm-mcp')}`,
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -14,8 +14,9 @@ const {
|
|
|
14
14
|
proxyBaseUrl,
|
|
15
15
|
readProxyToken,
|
|
16
16
|
} = require('../proxy-token-store');
|
|
17
|
+
const { runtimePort } = require('../../lib/runtime-manifest');
|
|
17
18
|
|
|
18
|
-
const PORT =
|
|
19
|
+
const PORT = runtimePort('amalgm-mcp');
|
|
19
20
|
const MCP_PROTOCOL_VERSION = '2024-11-05';
|
|
20
21
|
|
|
21
22
|
const AMALGM_DIR = process.env.AMALGM_DIR || path.join(os.homedir(), '.amalgm');
|
|
@@ -46,7 +47,7 @@ function cleanString(value) {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Chat server (local Next.js/Electron) — same process tree; no cloud hop.
|
|
49
|
-
const CHAT_SERVER_URL = `http://localhost:${
|
|
50
|
+
const CHAT_SERVER_URL = `http://localhost:${runtimePort('chat-server')}`;
|
|
50
51
|
|
|
51
52
|
const SCHEDULER_INTERVAL_MS = 30_000;
|
|
52
53
|
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* (default: ~/.amalgm/) — same pattern as scripts/credential-adapter.js.
|
|
7
7
|
*
|
|
8
8
|
* Ports:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* this server uses the amalgm-mcp runtime port
|
|
10
|
+
* chat-server uses the chat-server runtime port for task/event/agent runs
|
|
11
11
|
*
|
|
12
12
|
* Modules:
|
|
13
13
|
* config.js env + paths
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - agents/talk.js (talk_to_agent tool)
|
|
8
8
|
* - email/inbound.js (email replies/new email → agent runs)
|
|
9
9
|
*
|
|
10
|
-
* POSTs to chat-server
|
|
10
|
+
* POSTs to chat-server and consumes the SSE stream, accumulating
|
|
11
11
|
* text from agent_message_chunk events and returning the final output.
|
|
12
12
|
*/
|
|
13
13
|
|
|
@@ -5,6 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { AMALGM_DIR, DEFAULT_CWD, PROXY_BASE_URL, PROXY_TOKEN } = require('../chat-server/config');
|
|
7
7
|
const { authEnvelope, coerceAuth, fingerprint } = require('./auth');
|
|
8
|
+
const { runtimePort } = require('../../lib/runtime-manifest');
|
|
8
9
|
|
|
9
10
|
function agentToHarness(agent) {
|
|
10
11
|
const clean = String(agent || '').trim();
|
|
@@ -323,7 +324,7 @@ function createContract(payload, options = {}) {
|
|
|
323
324
|
const fastMode = payload.fastMode === true;
|
|
324
325
|
const cliModel = cliModelFor({ harness, modelId, cliModel: payload.cliModel, reasoningEffort });
|
|
325
326
|
const cwd = resolveCwd(payload.cwd, options);
|
|
326
|
-
const localBaseUrl = options.localBaseUrl || `http://127.0.0.1:${options.port ||
|
|
327
|
+
const localBaseUrl = options.localBaseUrl || `http://127.0.0.1:${options.port || runtimePort('chat-server')}`;
|
|
327
328
|
const origin = normalizeOrigin({ ...payload, cwd });
|
|
328
329
|
const auth = authEnvelope({
|
|
329
330
|
harness,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { getRuntimeToken } = require('../../runtime-auth');
|
|
4
|
+
const { runtimePort } = require('../../../lib/runtime-manifest');
|
|
4
5
|
|
|
5
6
|
function headerArrayToRecord(headers) {
|
|
6
7
|
const out = {};
|
|
@@ -49,12 +50,11 @@ function normalizeServer(server) {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
function localBaseUrl(contract) {
|
|
52
|
-
return String(contract.localBaseUrl || `http://127.0.0.1:${
|
|
53
|
+
return String(contract.localBaseUrl || `http://127.0.0.1:${runtimePort('chat-server')}`).replace(/\/$/, '');
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
function amalgmMcpServer(contract) {
|
|
56
|
-
const
|
|
57
|
-
const url = process.env.AMALGM_MCP_URL || `http://127.0.0.1:${port}/mcp`;
|
|
57
|
+
const url = process.env.AMALGM_MCP_URL || `http://127.0.0.1:${runtimePort('amalgm-mcp')}/mcp`;
|
|
58
58
|
const headers = [
|
|
59
59
|
{ name: 'x-amalgm-runtime-token', value: getRuntimeToken() },
|
|
60
60
|
{ name: 'x-amalgm-session-id', value: contract.sessionId },
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
proxyBaseUrl,
|
|
15
15
|
readProxyToken,
|
|
16
16
|
} = require('../proxy-token-store');
|
|
17
|
+
const { runtimePort } = require('../../lib/runtime-manifest');
|
|
17
18
|
|
|
18
19
|
// ── Paths ──────────────────────────────────────────────────────────────────
|
|
19
20
|
// Set by the orchestrator (entrypoint.sh or electron/main.ts).
|
|
@@ -24,7 +25,7 @@ const DEFAULT_CWD = process.env.AMALGM_DEFAULT_CWD || '/workspace';
|
|
|
24
25
|
|
|
25
26
|
// ── Server ──────────────────────────────────────────────────────────────────
|
|
26
27
|
|
|
27
|
-
const PORT =
|
|
28
|
+
const PORT = runtimePort('chat-server');
|
|
28
29
|
const BIND_HOST = process.env.AMALGM_BIND_HOST || '0.0.0.0';
|
|
29
30
|
|
|
30
31
|
// ── API keys / proxy ────────────────────────────────────────────────────────
|
|
@@ -22,6 +22,7 @@ const {
|
|
|
22
22
|
isAuthorizedRuntimeRequest,
|
|
23
23
|
runtimeAuthHeaders,
|
|
24
24
|
} = require('./runtime-auth');
|
|
25
|
+
const { runtimePort } = require('../lib/runtime-manifest');
|
|
25
26
|
|
|
26
27
|
function loadPty() {
|
|
27
28
|
const candidates = [
|
|
@@ -51,7 +52,7 @@ const BIND_HOST = process.env.AMALGM_BIND_HOST || '127.0.0.1';
|
|
|
51
52
|
const OWNER = process.env.AMALGM_RUNTIME_SOURCE || 'local';
|
|
52
53
|
const VERSION = process.env.npm_package_version || process.env.AMALGM_RUNTIME_VERSION || '';
|
|
53
54
|
const DEFAULT_CWD = process.env.AMALGM_DEFAULT_CWD || os.homedir();
|
|
54
|
-
const PORT =
|
|
55
|
+
const PORT = runtimePort('local-gateway');
|
|
55
56
|
const RUNTIME_TOKEN_HEADER = 'x-amalgm-runtime-token';
|
|
56
57
|
const DEFAULT_DIAGNOSTIC_LOG_TAIL_BYTES = 256 * 1024;
|
|
57
58
|
const MAX_DIAGNOSTIC_LOG_TAIL_BYTES = 2 * 1024 * 1024;
|
|
@@ -61,7 +62,6 @@ const DIAGNOSTIC_LOG_FILES = Object.freeze({
|
|
|
61
62
|
'chat-server': 'chat-server.log',
|
|
62
63
|
'local-gateway': 'local-gateway.log',
|
|
63
64
|
'amalgm-mcp': 'amalgm-mcp.log',
|
|
64
|
-
'fs-watcher': 'fs-watcher.log',
|
|
65
65
|
'port-monitor': 'port-monitor.log',
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -77,10 +77,9 @@ const DIAGNOSTIC_LOG_ALIASES = Object.freeze({
|
|
|
77
77
|
|
|
78
78
|
const SERVICE_PORTS = {
|
|
79
79
|
gateway: PORT,
|
|
80
|
-
portMonitor:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
chat: Number.parseInt(process.env.CHAT_SERVER_PORT || '8084', 10),
|
|
80
|
+
portMonitor: runtimePort('port-monitor'),
|
|
81
|
+
mcp: runtimePort('amalgm-mcp'),
|
|
82
|
+
chat: runtimePort('chat-server'),
|
|
84
83
|
};
|
|
85
84
|
const LOCAL_AGENT_APP_PORT = Number.parseInt(process.env.AMALGM_LOCAL_APP_PORT || '3456', 10);
|
|
86
85
|
|
|
@@ -275,6 +274,14 @@ function readJson(file, fallback = null) {
|
|
|
275
274
|
}
|
|
276
275
|
}
|
|
277
276
|
|
|
277
|
+
function runtimeStateOwnedHere(previous) {
|
|
278
|
+
if (!previous || typeof previous !== 'object' || Object.keys(previous).length === 0) return true;
|
|
279
|
+
return previous.pid === process.pid
|
|
280
|
+
|| previous.gateway_pid === process.pid
|
|
281
|
+
|| previous.pid === process.ppid
|
|
282
|
+
|| previous.supervisor_pid === process.ppid;
|
|
283
|
+
}
|
|
284
|
+
|
|
278
285
|
function ensurePtySpawnHelperExecutable() {
|
|
279
286
|
try {
|
|
280
287
|
const nodePtyRoot = path.dirname(require.resolve(`${pty.packageName}/package.json`));
|
|
@@ -294,6 +301,10 @@ function ensurePtySpawnHelperExecutable() {
|
|
|
294
301
|
function writeRuntimeState(actualPort) {
|
|
295
302
|
const computer = readJson(path.join(AMALGM_DIR, 'computer.json'), null);
|
|
296
303
|
const previous = readJson(STATE_FILE, {});
|
|
304
|
+
if (!runtimeStateOwnedHere(previous)) {
|
|
305
|
+
console.warn('[local-gateway] runtime-state belongs to another Amalgm runtime; skipping write');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
297
308
|
writeJsonSecret(STATE_FILE, {
|
|
298
309
|
...previous,
|
|
299
310
|
schema_version: 1,
|
|
@@ -312,7 +323,6 @@ function writeRuntimeState(actualPort) {
|
|
|
312
323
|
ports: {
|
|
313
324
|
gateway: actualPort,
|
|
314
325
|
port_monitor: SERVICE_PORTS.portMonitor,
|
|
315
|
-
fs_watcher: SERVICE_PORTS.fsWatcher,
|
|
316
326
|
amalgm_mcp: SERVICE_PORTS.mcp,
|
|
317
327
|
chat_server: SERVICE_PORTS.chat,
|
|
318
328
|
},
|
|
@@ -550,12 +560,6 @@ function serviceForPath(pathname) {
|
|
|
550
560
|
if (MCP_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`))) {
|
|
551
561
|
return { port: SERVICE_PORTS.mcp, stripPrefix: '' };
|
|
552
562
|
}
|
|
553
|
-
if (pathname === '/fs-watcher' || pathname.startsWith('/fs-watcher/')) {
|
|
554
|
-
return {
|
|
555
|
-
port: SERVICE_PORTS.fsWatcher,
|
|
556
|
-
stripPrefix: '/fs-watcher',
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
563
|
if (PORT_MONITOR_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`))) {
|
|
560
564
|
return {
|
|
561
565
|
port: SERVICE_PORTS.portMonitor,
|
|
@@ -971,10 +975,6 @@ server.on('upgrade', (req, socket, head) => {
|
|
|
971
975
|
bridgePtyWebSocket(req, ws, url.pathname);
|
|
972
976
|
return;
|
|
973
977
|
}
|
|
974
|
-
if (url.pathname === '/ws/fs' || url.pathname === '/fs-watcher/ws') {
|
|
975
|
-
proxyWebSocket(req, ws, { port: SERVICE_PORTS.fsWatcher, stripPrefix: '' }, '/');
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
978
|
const target = serviceForPath(url.pathname);
|
|
979
979
|
if (!target) {
|
|
980
980
|
ws.close(1008, 'Unknown local gateway route');
|