amalgm 0.0.0 → 0.0.1

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.
Files changed (107) hide show
  1. package/README.md +37 -1
  2. package/bin/amalgm.js +8 -2
  3. package/lib/auth-store.js +223 -0
  4. package/lib/cli.js +1000 -0
  5. package/lib/paths.js +30 -0
  6. package/lib/supervisor.js +467 -0
  7. package/lib/tunnel-chat.js +328 -0
  8. package/lib/tunnel-events.js +499 -0
  9. package/package.json +29 -3
  10. package/runtime/README.md +4 -0
  11. package/runtime/lib/chatInput.js +306 -0
  12. package/runtime/lib/harnesses.js +988 -0
  13. package/runtime/lib/local/amalgmStore.js +128 -0
  14. package/runtime/lib/local/credentialResolver.js +425 -0
  15. package/runtime/lib/mcpApps/registry.js +619 -0
  16. package/runtime/package.json +5 -0
  17. package/runtime/scripts/amalgm-mcp/agents/rest.js +165 -0
  18. package/runtime/scripts/amalgm-mcp/agents/store.js +153 -0
  19. package/runtime/scripts/amalgm-mcp/agents/talk.js +1156 -0
  20. package/runtime/scripts/amalgm-mcp/agents/tools.js +210 -0
  21. package/runtime/scripts/amalgm-mcp/artifacts/advertise.js +132 -0
  22. package/runtime/scripts/amalgm-mcp/artifacts/rest.js +103 -0
  23. package/runtime/scripts/amalgm-mcp/artifacts/store.js +141 -0
  24. package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +402 -0
  25. package/runtime/scripts/amalgm-mcp/artifacts/tools.js +176 -0
  26. package/runtime/scripts/amalgm-mcp/browser/page.js +637 -0
  27. package/runtime/scripts/amalgm-mcp/browser/tools.js +688 -0
  28. package/runtime/scripts/amalgm-mcp/config.js +138 -0
  29. package/runtime/scripts/amalgm-mcp/credentials/rest.js +45 -0
  30. package/runtime/scripts/amalgm-mcp/deps.js +40 -0
  31. package/runtime/scripts/amalgm-mcp/email/inbound.js +215 -0
  32. package/runtime/scripts/amalgm-mcp/events/executor.js +179 -0
  33. package/runtime/scripts/amalgm-mcp/events/ingress.js +113 -0
  34. package/runtime/scripts/amalgm-mcp/events/matcher.js +125 -0
  35. package/runtime/scripts/amalgm-mcp/events/rest.js +200 -0
  36. package/runtime/scripts/amalgm-mcp/events/ring-buffer.js +19 -0
  37. package/runtime/scripts/amalgm-mcp/events/store.js +98 -0
  38. package/runtime/scripts/amalgm-mcp/events/tools.js +306 -0
  39. package/runtime/scripts/amalgm-mcp/events/webhook-url.js +28 -0
  40. package/runtime/scripts/amalgm-mcp/fs/rest.js +293 -0
  41. package/runtime/scripts/amalgm-mcp/index.js +100 -0
  42. package/runtime/scripts/amalgm-mcp/lib/chat-runner.js +167 -0
  43. package/runtime/scripts/amalgm-mcp/lib/email-md.js +288 -0
  44. package/runtime/scripts/amalgm-mcp/lib/mcp-resolver.js +63 -0
  45. package/runtime/scripts/amalgm-mcp/lib/prefs.js +393 -0
  46. package/runtime/scripts/amalgm-mcp/lib/storage.js +92 -0
  47. package/runtime/scripts/amalgm-mcp/lib/supabase.js +118 -0
  48. package/runtime/scripts/amalgm-mcp/lib/tool-result.js +177 -0
  49. package/runtime/scripts/amalgm-mcp/local/rest.js +80 -0
  50. package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +151 -0
  51. package/runtime/scripts/amalgm-mcp/notify/index.js +107 -0
  52. package/runtime/scripts/amalgm-mcp/server/http.js +335 -0
  53. package/runtime/scripts/amalgm-mcp/server/mcp.js +116 -0
  54. package/runtime/scripts/amalgm-mcp/slack/inbound.js +200 -0
  55. package/runtime/scripts/amalgm-mcp/tasks/executor.js +225 -0
  56. package/runtime/scripts/amalgm-mcp/tasks/rest.js +110 -0
  57. package/runtime/scripts/amalgm-mcp/tasks/schedule-normalization.js +85 -0
  58. package/runtime/scripts/amalgm-mcp/tasks/scheduler.js +105 -0
  59. package/runtime/scripts/amalgm-mcp/tasks/store.js +139 -0
  60. package/runtime/scripts/amalgm-mcp/tasks/tools.js +391 -0
  61. package/runtime/scripts/amalgm-mcp/user-api-keys/rest.js +105 -0
  62. package/runtime/scripts/amalgm-mcp/workspace/rest.js +389 -0
  63. package/runtime/scripts/chat-core/adapters/claude.js +163 -0
  64. package/runtime/scripts/chat-core/adapters/codex.js +313 -0
  65. package/runtime/scripts/chat-core/adapters/opencode.js +412 -0
  66. package/runtime/scripts/chat-core/auth.js +177 -0
  67. package/runtime/scripts/chat-core/contract.js +326 -0
  68. package/runtime/scripts/chat-core/credentials/store.js +212 -0
  69. package/runtime/scripts/chat-core/egress.js +87 -0
  70. package/runtime/scripts/chat-core/engine.js +195 -0
  71. package/runtime/scripts/chat-core/event-schema.js +231 -0
  72. package/runtime/scripts/chat-core/events.js +190 -0
  73. package/runtime/scripts/chat-core/index.js +11 -0
  74. package/runtime/scripts/chat-core/input.js +50 -0
  75. package/runtime/scripts/chat-core/normalizers/claude.js +450 -0
  76. package/runtime/scripts/chat-core/normalizers/codex.js +380 -0
  77. package/runtime/scripts/chat-core/normalizers/normalizer_spec.md +259 -0
  78. package/runtime/scripts/chat-core/normalizers/opencode.js +552 -0
  79. package/runtime/scripts/chat-core/normalizers/tool_contract.md +123 -0
  80. package/runtime/scripts/chat-core/normalizers/usage_contract.md +304 -0
  81. package/runtime/scripts/chat-core/parts.js +253 -0
  82. package/runtime/scripts/chat-core/recorder.js +65 -0
  83. package/runtime/scripts/chat-core/runtime.js +86 -0
  84. package/runtime/scripts/chat-core/server.js +163 -0
  85. package/runtime/scripts/chat-core/sse.js +196 -0
  86. package/runtime/scripts/chat-core/stores.js +100 -0
  87. package/runtime/scripts/chat-core/tool-display.js +149 -0
  88. package/runtime/scripts/chat-core/tool-shape.js +143 -0
  89. package/runtime/scripts/chat-core/tooling/mcp-bundle.js +161 -0
  90. package/runtime/scripts/chat-core/tooling/mcp-relay.js +97 -0
  91. package/runtime/scripts/chat-core/tooling/native-binaries.js +608 -0
  92. package/runtime/scripts/chat-core/tooling/system-prompt.js +20 -0
  93. package/runtime/scripts/chat-core/usage.js +343 -0
  94. package/runtime/scripts/chat-server/config.js +110 -0
  95. package/runtime/scripts/chat-server/db.js +529 -0
  96. package/runtime/scripts/chat-server/index.js +33 -0
  97. package/runtime/scripts/chat-server/model-catalog.js +327 -0
  98. package/runtime/scripts/chat-server.js +75 -0
  99. package/runtime/scripts/credential-adapter.js +129 -0
  100. package/runtime/scripts/fs-watcher.js +888 -0
  101. package/runtime/scripts/local-gateway.js +852 -0
  102. package/runtime/scripts/platform-context.txt +246 -0
  103. package/runtime/scripts/port-monitor.js +175 -0
  104. package/runtime/scripts/proxy-token-store.js +162 -0
  105. package/runtime/scripts/runtime-auth.js +163 -0
  106. package/runtime/scripts/test-claude-code-models.js +87 -0
  107. package/runtime/tsconfig.json +15 -0
@@ -0,0 +1,328 @@
1
+ 'use strict';
2
+
3
+ const http = require('http');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const { WebSocket } = require('ws');
7
+ const { RUNTIME_STATE_FILE } = require('./paths');
8
+
9
+ const DEFAULT_TARGET_PORT = 8084;
10
+ const DEFAULT_CHAT_TUNNEL_URL = 'wss://amalgm-chat-gateway.fly.dev/wire';
11
+ const CONNECT_TIMEOUT_MS = 20_000;
12
+ const WATCHDOG_INTERVAL_MS = 15_000;
13
+ const HEARTBEAT_TIMEOUT_MS = 75_000;
14
+ const ALLOWED_TARGET_PORTS = new Set([8083, 8084]);
15
+ const HOP_BY_HOP_HEADERS = new Set([
16
+ 'connection',
17
+ 'keep-alive',
18
+ 'proxy-authenticate',
19
+ 'proxy-authorization',
20
+ 'te',
21
+ 'trailer',
22
+ 'trailers',
23
+ 'transfer-encoding',
24
+ 'upgrade',
25
+ 'host',
26
+ 'content-length',
27
+ ]);
28
+
29
+ function tunnelUrl(rawUrl) {
30
+ const url = new URL(rawUrl || DEFAULT_CHAT_TUNNEL_URL);
31
+ url.searchParams.delete('token');
32
+ return url.toString();
33
+ }
34
+
35
+ function tunnelHeaders(token) {
36
+ return {
37
+ authorization: `Bearer ${token}`,
38
+ 'user-agent': `amalgm-npm/${require('../package.json').version} (${os.platform()}; ${os.arch()})`,
39
+ };
40
+ }
41
+
42
+ function normalizeHeaders(headers = {}) {
43
+ const out = {};
44
+ for (const [key, value] of Object.entries(headers || {})) {
45
+ const lower = key.toLowerCase();
46
+ if (HOP_BY_HOP_HEADERS.has(lower)) continue;
47
+ if (Array.isArray(value)) out[key] = value.join(', ');
48
+ else if (typeof value === 'string') out[key] = value;
49
+ else if (value != null) out[key] = String(value);
50
+ }
51
+ return out;
52
+ }
53
+
54
+ function responseHeaders(headers = {}) {
55
+ const out = {};
56
+ for (const [key, value] of Object.entries(headers)) {
57
+ if (Array.isArray(value)) out[key] = value.join(', ');
58
+ else if (typeof value === 'string') out[key] = value;
59
+ else if (value != null) out[key] = String(value);
60
+ }
61
+ return out;
62
+ }
63
+
64
+ 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
+ try {
70
+ const parsed = JSON.parse(fs.readFileSync(RUNTIME_STATE_FILE, 'utf8'));
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'),
75
+ };
76
+ } catch {
77
+ return {
78
+ gateway: envPort('AMALGM_GATEWAY_PORT'),
79
+ mcp: envPort('AMALGM_MCP_PORT'),
80
+ chat: envPort('CHAT_SERVER_PORT'),
81
+ };
82
+ }
83
+ }
84
+
85
+ function allowedTargetPorts() {
86
+ const ports = new Set(ALLOWED_TARGET_PORTS);
87
+ const runtimePorts = readRuntimePorts();
88
+ for (const port of [runtimePorts.gateway, runtimePorts.mcp, runtimePorts.chat]) {
89
+ if (Number.isInteger(port) && port > 0 && port <= 65535) ports.add(port);
90
+ }
91
+ return ports;
92
+ }
93
+
94
+ function defaultTargetPort() {
95
+ const port = readRuntimePorts().chat;
96
+ return Number.isInteger(port) && port > 0 && port <= 65535 ? port : DEFAULT_TARGET_PORT;
97
+ }
98
+
99
+ function runtimeGatewayPort() {
100
+ const port = readRuntimePorts().gateway;
101
+ return Number.isInteger(port) && port > 0 && port <= 65535 ? port : null;
102
+ }
103
+
104
+ function createChatTunnel({ record, foreground = false }) {
105
+ let stopped = false;
106
+ let ws = null;
107
+ let reconnectTimer = null;
108
+ let watchdogTimer = null;
109
+ let backoffMs = 1000;
110
+ let connectStartedAt = 0;
111
+ let lastGatewayFrameAt = 0;
112
+
113
+ function log(message) {
114
+ if (foreground) console.log(`[chat-tunnel] ${message}`);
115
+ }
116
+
117
+ function warn(message) {
118
+ if (foreground) console.warn(`[chat-tunnel] ${message}`);
119
+ }
120
+
121
+ function send(frame) {
122
+ if (!ws || ws.readyState !== WebSocket.OPEN) return false;
123
+ ws.send(JSON.stringify(frame));
124
+ return true;
125
+ }
126
+
127
+ function terminateSocket(reason) {
128
+ if (!ws) return;
129
+ warn(`${reason}; reconnecting`);
130
+ try {
131
+ ws.terminate();
132
+ } catch {
133
+ try {
134
+ ws.close();
135
+ } catch {
136
+ // noop
137
+ }
138
+ }
139
+ }
140
+
141
+ function startWatchdog() {
142
+ if (watchdogTimer) return;
143
+
144
+ watchdogTimer = setInterval(() => {
145
+ if (stopped || !ws) return;
146
+ const now = Date.now();
147
+
148
+ if (ws.readyState === WebSocket.CONNECTING) {
149
+ if (connectStartedAt && now - connectStartedAt > CONNECT_TIMEOUT_MS) {
150
+ terminateSocket('connect timeout');
151
+ }
152
+ return;
153
+ }
154
+
155
+ if (ws.readyState !== WebSocket.OPEN) return;
156
+
157
+ send({
158
+ type: 'hello',
159
+ app_version: require('../package.json').version,
160
+ runtime_gateway_port: runtimeGatewayPort(),
161
+ });
162
+
163
+ try {
164
+ ws.ping();
165
+ } catch {
166
+ terminateSocket('ping failed');
167
+ return;
168
+ }
169
+
170
+ if (lastGatewayFrameAt && now - lastGatewayFrameAt > HEARTBEAT_TIMEOUT_MS) {
171
+ terminateSocket('heartbeat timeout');
172
+ }
173
+ }, WATCHDOG_INTERVAL_MS);
174
+
175
+ if (typeof watchdogTimer.unref === 'function') watchdogTimer.unref();
176
+ }
177
+
178
+ function stopWatchdog() {
179
+ if (!watchdogTimer) return;
180
+ clearInterval(watchdogTimer);
181
+ watchdogTimer = null;
182
+ }
183
+
184
+ function localHeaders(headers) {
185
+ const out = normalizeHeaders(headers);
186
+ const runtimeToken = record?.runtime_token || process.env.AMALGM_RUNTIME_TOKEN || '';
187
+ if (runtimeToken) out['x-amalgm-runtime-token'] = runtimeToken;
188
+ return out;
189
+ }
190
+
191
+ function resolveTargetPort(frame) {
192
+ const candidate = frame.target_port == null ? defaultTargetPort() : Number(frame.target_port);
193
+ if (!Number.isInteger(candidate) || !allowedTargetPorts().has(candidate)) return null;
194
+ return candidate;
195
+ }
196
+
197
+ function scheduleReconnect() {
198
+ if (stopped || reconnectTimer) return;
199
+ const delay = backoffMs;
200
+ backoffMs = Math.min(backoffMs * 1.5, 15000);
201
+ reconnectTimer = setTimeout(() => {
202
+ reconnectTimer = null;
203
+ connect();
204
+ }, delay);
205
+ }
206
+
207
+ function forwardStream(frame) {
208
+ const targetPort = resolveTargetPort(frame);
209
+ if (!targetPort) {
210
+ send({
211
+ type: 'stream_res_error',
212
+ req_id: frame.req_id,
213
+ status: 403,
214
+ message: 'Target port is not allowed',
215
+ });
216
+ return;
217
+ }
218
+ const body = frame.body_b64 ? Buffer.from(frame.body_b64, 'base64') : Buffer.alloc(0);
219
+ const req = http.request(
220
+ {
221
+ hostname: '127.0.0.1',
222
+ port: targetPort,
223
+ method: frame.method || 'POST',
224
+ path: frame.path || '/api/agent-stream',
225
+ headers: localHeaders(frame.headers),
226
+ },
227
+ (res) => {
228
+ send({
229
+ type: 'stream_res_start',
230
+ req_id: frame.req_id,
231
+ status: res.statusCode || 200,
232
+ headers: responseHeaders(res.headers),
233
+ });
234
+ res.on('data', (chunk) => {
235
+ send({
236
+ type: 'stream_res_data',
237
+ req_id: frame.req_id,
238
+ chunk_b64: Buffer.from(chunk).toString('base64'),
239
+ });
240
+ });
241
+ res.on('end', () => {
242
+ send({ type: 'stream_res_end', req_id: frame.req_id });
243
+ });
244
+ },
245
+ );
246
+
247
+ req.on('error', (error) => {
248
+ send({
249
+ type: 'stream_res_error',
250
+ req_id: frame.req_id,
251
+ status: 502,
252
+ message: error.message || 'Local chat request failed',
253
+ });
254
+ });
255
+
256
+ req.end(body);
257
+ }
258
+
259
+ function handleFrame(frame) {
260
+ if (!frame || typeof frame.type !== 'string') return;
261
+ if (frame.type === 'ping') {
262
+ send({ type: 'pong' });
263
+ return;
264
+ }
265
+ if (frame.type === 'stream_req') {
266
+ forwardStream(frame);
267
+ }
268
+ }
269
+
270
+ function connect() {
271
+ if (stopped) return;
272
+ const url = tunnelUrl(record.chat_tunnel_url);
273
+ ws = new WebSocket(url, {
274
+ headers: tunnelHeaders(record.tunnel_token),
275
+ });
276
+ connectStartedAt = Date.now();
277
+ startWatchdog();
278
+ ws.on('open', () => {
279
+ backoffMs = 1000;
280
+ connectStartedAt = 0;
281
+ lastGatewayFrameAt = Date.now();
282
+ log(`connected ${record.computer_id}`);
283
+ send({
284
+ type: 'hello',
285
+ app_version: require('../package.json').version,
286
+ runtime_gateway_port: runtimeGatewayPort(),
287
+ });
288
+ });
289
+ ws.on('pong', () => {
290
+ lastGatewayFrameAt = Date.now();
291
+ });
292
+ ws.on('message', (data) => {
293
+ lastGatewayFrameAt = Date.now();
294
+ try {
295
+ handleFrame(JSON.parse(data.toString()));
296
+ } catch {
297
+ // Ignore malformed gateway frames.
298
+ }
299
+ });
300
+ ws.on('close', (code, reason) => {
301
+ connectStartedAt = 0;
302
+ lastGatewayFrameAt = 0;
303
+ warn(`closed code=${code} reason=${reason ? reason.toString() : ''}`);
304
+ scheduleReconnect();
305
+ });
306
+ ws.on('error', (error) => {
307
+ warn(error.message);
308
+ });
309
+ }
310
+
311
+ return {
312
+ start: connect,
313
+ stop() {
314
+ stopped = true;
315
+ if (reconnectTimer) clearTimeout(reconnectTimer);
316
+ stopWatchdog();
317
+ if (ws) {
318
+ try {
319
+ ws.close();
320
+ } catch {
321
+ // noop
322
+ }
323
+ }
324
+ },
325
+ };
326
+ }
327
+
328
+ module.exports = { createChatTunnel };