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 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
@@ -179,8 +179,6 @@ const SAFE_DAEMON_ENV_KEYS = [
179
179
  'AMALGM_SKIP_NATIVE_INSTALL',
180
180
  'CHAT_SERVER_PORT',
181
181
  'ELECTRON_RUN_AS_NODE',
182
- 'FS_WATCHER_PORT',
183
- 'FS_WATCHER_ROOT',
184
182
  'HOME',
185
183
  'LANG',
186
184
  'LC_ALL',
@@ -1,131 +1,3 @@
1
1
  'use strict';
2
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: '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
@@ -39,7 +39,6 @@ const SERVICE_ENV_KEYS = [
39
39
  'AMALGM_WORKSPACES_DIR',
40
40
  'CHAT_SERVER_PORT',
41
41
  'ELECTRON_RUN_AS_NODE',
42
- 'FS_WATCHER_PORT',
43
42
  'LANG',
44
43
  'LC_ALL',
45
44
  'NODE_PATH',
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));
@@ -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 = 8084;
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([8083, 8084]);
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) || envPort('AMALGM_GATEWAY_PORT'),
73
- mcp: Number(parsed?.ports?.amalgm_mcp) || envPort('AMALGM_MCP_PORT'),
74
- chat: Number(parsed?.ports?.chat_server) || envPort('CHAT_SERVER_PORT'),
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: envPort('AMALGM_GATEWAY_PORT'),
79
- mcp: envPort('AMALGM_MCP_PORT'),
80
- chat: envPort('CHAT_SERVER_PORT'),
78
+ gateway: runtimePort('local-gateway'),
79
+ mcp: runtimePort('amalgm-mcp'),
80
+ chat: runtimePort('chat-server'),
81
81
  };
82
82
  }
83
83
  }
@@ -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 = 8083;
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([8083, 8084]);
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) || envPort('AMALGM_GATEWAY_PORT'),
160
- mcp: Number(parsed?.ports?.amalgm_mcp) || envPort('AMALGM_MCP_PORT'),
161
- chat: Number(parsed?.ports?.chat_server) || envPort('CHAT_SERVER_PORT'),
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: envPort('AMALGM_GATEWAY_PORT'),
166
- mcp: envPort('AMALGM_MCP_PORT'),
167
- chat: envPort('CHAT_SERVER_PORT'),
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.44",
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/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/fs-watcher.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"
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:${process.env.CHAT_SERVER_PORT || 8084}`,
92
- AMALGM_MCP_URL: `http://127.0.0.1:${process.env.AMALGM_MCP_PORT || 8083}`,
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 = parseInt(process.env.AMALGM_MCP_PORT || '8083', 10);
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:${process.env.CHAT_SERVER_PORT || 8084}`;
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
- * 8083 this server
10
- * 8084 chat-server (we delegate task/event/agent runs here)
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 (:8084) and consumes the SSE stream, accumulating
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 || process.env.CHAT_SERVER_PORT || 8084}`;
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:${process.env.CHAT_SERVER_PORT || 8084}`).replace(/\/$/, '');
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 port = process.env.AMALGM_MCP_PORT || '8083';
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 = parseInt(process.env.CHAT_SERVER_PORT || '8084', 10);
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 = Number.parseInt(process.env.AMALGM_GATEWAY_PORT || '28781', 10);
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: Number.parseInt(process.env.PORT_MONITOR_PORT || '8081', 10),
81
- fsWatcher: Number.parseInt(process.env.FS_WATCHER_PORT || '8082', 10),
82
- mcp: Number.parseInt(process.env.AMALGM_MCP_PORT || '8083', 10),
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');