amalgm 0.1.162 → 0.1.163-canary.20260722143753
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/cli.js +4 -1
- package/lib/paths.js +2 -1
- package/lib/tunnel-chat.js +6 -2
- package/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/config.js +15 -3
- package/runtime/scripts/amalgm-mcp/index.js +1 -1
- package/runtime/scripts/amalgm-mcp/state/docs.js +2 -0
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +1 -0
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +37 -10
- package/runtime/scripts/chat-core/contract.js +1 -1
- package/runtime/scripts/chat-core/server.js +2 -2
- package/runtime/scripts/chat-server/config.js +1 -0
- package/runtime/scripts/chat-server.js +2 -1
- package/runtime/scripts/proxy-token-store.js +13 -1
package/lib/cli.js
CHANGED
|
@@ -1094,7 +1094,10 @@ async function status() {
|
|
|
1094
1094
|
if (record?.webhook_url) console.log(`Events: ${record.webhook_url}`);
|
|
1095
1095
|
if (record?.tunnel_url) console.log(`Tunnel: ${record.tunnel_url}`);
|
|
1096
1096
|
if (record?.tunnel_token) {
|
|
1097
|
-
|
|
1097
|
+
const defaultChatTunnel = AMALGM_RUNTIME_LABEL === 'main'
|
|
1098
|
+
? 'wss://amalgm-chat-gateway.fly.dev/wire'
|
|
1099
|
+
: 'wss://chat.preview.amalgm.ai/wire';
|
|
1100
|
+
console.log(`Chat tunnel: ${record.chat_tunnel_url || defaultChatTunnel}`);
|
|
1098
1101
|
}
|
|
1099
1102
|
console.log(`HMAC proxy: ${proxyTokenFromRecord(record) ? 'configured' : 'not configured'}`);
|
|
1100
1103
|
|
package/lib/paths.js
CHANGED
|
@@ -49,7 +49,8 @@ const SERVICE_PID_FILE = path.join(AMALGM_RUNTIME_STATE_DIR, 'amalgm-service.pid
|
|
|
49
49
|
const SERVICE_STATE_FILE = path.join(AMALGM_RUNTIME_STATE_DIR, 'service-state.json');
|
|
50
50
|
const SERVICE_STOP_FILE = path.join(AMALGM_RUNTIME_STATE_DIR, 'amalgm-service.stop');
|
|
51
51
|
const SHUTDOWN_REASON_FILE = path.join(AMALGM_RUNTIME_STATE_DIR, 'shutdown-reason.json');
|
|
52
|
-
const DEFAULT_APP_URL = process.env.AMALGM_APP_URL
|
|
52
|
+
const DEFAULT_APP_URL = process.env.AMALGM_APP_URL
|
|
53
|
+
|| (AMALGM_RUNTIME_LABEL === 'main' ? 'https://amalgm.ai' : 'https://preview.amalgm.ai');
|
|
53
54
|
|
|
54
55
|
module.exports = {
|
|
55
56
|
AMALGM_BRANCH,
|
package/lib/tunnel-chat.js
CHANGED
|
@@ -16,7 +16,11 @@ const DEFAULT_PORT_MONITOR_PORT = runtimeServiceByName('port-monitor').defaultPo
|
|
|
16
16
|
const DEFAULT_MCP_PORT = runtimeServiceByName('amalgm-mcp').defaultPort;
|
|
17
17
|
const DEFAULT_CHAT_SERVER_PORT = runtimeServiceByName('chat-server').defaultPort;
|
|
18
18
|
const DEFAULT_TARGET_PORT = DEFAULT_CHAT_SERVER_PORT;
|
|
19
|
-
|
|
19
|
+
function defaultChatTunnelUrl() {
|
|
20
|
+
return AMALGM_RUNTIME_LABEL === 'main'
|
|
21
|
+
? 'wss://amalgm-chat-gateway.fly.dev/wire'
|
|
22
|
+
: 'wss://chat.preview.amalgm.ai/wire';
|
|
23
|
+
}
|
|
20
24
|
const CONNECT_TIMEOUT_MS = 20_000;
|
|
21
25
|
const WATCHDOG_INTERVAL_MS = 15_000;
|
|
22
26
|
const HEARTBEAT_TIMEOUT_MS = 75_000;
|
|
@@ -73,7 +77,7 @@ const HOP_BY_HOP_HEADERS = new Set([
|
|
|
73
77
|
]);
|
|
74
78
|
|
|
75
79
|
function tunnelUrl(rawUrl) {
|
|
76
|
-
const url = new URL(rawUrl ||
|
|
80
|
+
const url = new URL(rawUrl || defaultChatTunnelUrl());
|
|
77
81
|
url.searchParams.delete('token');
|
|
78
82
|
return url.toString();
|
|
79
83
|
}
|
package/package.json
CHANGED
|
@@ -58,9 +58,21 @@ const DEFAULT_CWD = process.env.AMALGM_DEFAULT_CWD || os.homedir();
|
|
|
58
58
|
const AMALGM_USER_ID = process.env.AMALGM_USER_ID || '';
|
|
59
59
|
const AMALGM_COMPUTER_ID = process.env.AMALGM_COMPUTER_ID || process.env.AMALGM_CONTAINER_ID || '';
|
|
60
60
|
|
|
61
|
+
function usesPreviewInfraDefaults() {
|
|
62
|
+
const raw = String(
|
|
63
|
+
process.env.AMALGM_RUNTIME_LABEL
|
|
64
|
+
|| process.env.AMALGM_CHANNEL
|
|
65
|
+
|| process.env.AMALGM_BRANCH
|
|
66
|
+
|| '',
|
|
67
|
+
).trim().toLowerCase();
|
|
68
|
+
return ['preview', 'canary', 'native-chat', 'staging', 'local', 'dev', 'development'].includes(raw);
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
const PROXY_TOKEN = process.env.AMALGM_PROXY_TOKEN
|
|
62
72
|
|| (process.env.AMALGM_RUNTIME_SOURCE === 'npm' ? '' : process.env.ANTHROPIC_API_KEY || '');
|
|
63
|
-
const DEFAULT_PROXY_BASE_URL =
|
|
73
|
+
const DEFAULT_PROXY_BASE_URL = usesPreviewInfraDefaults()
|
|
74
|
+
? 'https://api.preview.amalgm.ai'
|
|
75
|
+
: 'https://amalgm-api-proxy-v2.fly.dev';
|
|
64
76
|
const PROXY_BASE_URL = process.env.AMALGM_PROXY_URL || (PROXY_TOKEN ? DEFAULT_PROXY_BASE_URL : '');
|
|
65
77
|
const SUPABASE_PROXY_URL = PROXY_BASE_URL ? `${PROXY_BASE_URL}/supabase` : '';
|
|
66
78
|
|
|
@@ -85,14 +97,14 @@ const APPS_DOMAIN = (
|
|
|
85
97
|
|| process.env.NEXT_PUBLIC_APPS_DOMAIN
|
|
86
98
|
|| process.env.AMALGM_ARTIFACTS_DOMAIN
|
|
87
99
|
|| process.env.NEXT_PUBLIC_ARTIFACTS_DOMAIN
|
|
88
|
-
|| 'apps.amalgm.ai'
|
|
100
|
+
|| (usesPreviewInfraDefaults() ? 'apps.preview.amalgm.ai' : 'apps.amalgm.ai')
|
|
89
101
|
);
|
|
90
102
|
const APP_PORT_MIN = parseInt(process.env.AMALGM_APP_PORT_MIN || process.env.AMALGM_ARTIFACT_PORT_MIN || '9100', 10);
|
|
91
103
|
const APP_PORT_MAX = parseInt(process.env.AMALGM_APP_PORT_MAX || process.env.AMALGM_ARTIFACT_PORT_MAX || '9199', 10);
|
|
92
104
|
const GATEWAY_BASE_URL = (
|
|
93
105
|
process.env.AMALGM_GATEWAY_INTERNAL_URL
|
|
94
106
|
|| process.env.AMALGM_GATEWAY_URL
|
|
95
|
-
|| 'https://wire.events.amalgm.ai'
|
|
107
|
+
|| (usesPreviewInfraDefaults() ? 'https://wire.events.preview.amalgm.ai' : 'https://wire.events.amalgm.ai')
|
|
96
108
|
).replace(/\/$/, '');
|
|
97
109
|
const GATEWAY_INTERNAL_SECRET =
|
|
98
110
|
process.env.AMALGM_GATEWAY_INTERNAL_SECRET
|
|
@@ -56,7 +56,7 @@ async function ensureProxyToken() {
|
|
|
56
56
|
const adminSecret = process.env.PROXY_ADMIN_SECRET || process.env.ADMIN_SECRET;
|
|
57
57
|
if (!adminSecret) return;
|
|
58
58
|
|
|
59
|
-
const proxyUrl = process.env.AMALGM_PROXY_URL ||
|
|
59
|
+
const proxyUrl = process.env.AMALGM_PROXY_URL || DEFAULT_PROXY_BASE_URL;
|
|
60
60
|
|
|
61
61
|
try {
|
|
62
62
|
const res = await fetch(`${proxyUrl}/internal/token`, {
|
|
@@ -853,9 +853,11 @@ function loadDocEntry(pathInput) {
|
|
|
853
853
|
function openDoc(pathInput) {
|
|
854
854
|
const Y = loadYjs();
|
|
855
855
|
const entry = loadDocEntry(pathInput);
|
|
856
|
+
const replica = require('./replicas').getReplicaByLocalResource(entry.resource);
|
|
856
857
|
return {
|
|
857
858
|
resource: entry.resource,
|
|
858
859
|
nodeId: entry.nodeId,
|
|
860
|
+
cloudResourceId: replica?.resourceId || entry.nodeId || null,
|
|
859
861
|
path: entry.path,
|
|
860
862
|
state: Buffer.from(Y.encodeStateAsUpdate(entry.doc)).toString('base64'),
|
|
861
863
|
// The versioned-write handshake: hash of the exact current text; a writer
|
|
@@ -113,6 +113,7 @@ test('promotion is durable, idempotent, and installs the runtime-owned cloud bin
|
|
|
113
113
|
assert.equal(replica.localPath, target);
|
|
114
114
|
assert.equal(replica.appliedVersion, 0);
|
|
115
115
|
assert.equal(replica.contract, 'text-yjs@1');
|
|
116
|
+
assert.equal(docs.openDoc(target).cloudResourceId, resourceId);
|
|
116
117
|
});
|
|
117
118
|
|
|
118
119
|
test('batched surface edits retain separate IDs/versions and recipient file precedes UI event', () => {
|
|
@@ -96,9 +96,17 @@ function readWall() {
|
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Write this machine's line — and only this machine's line — into the
|
|
99
|
-
* wall. Other machines' lines are preserved byte-for-byte in
|
|
100
|
-
* the resulting doc edit is exactly one line's splice and
|
|
101
|
-
* publishes from other machines never overlap it.
|
|
99
|
+
* wall. Other machines' parseable lines are preserved byte-for-byte in
|
|
100
|
+
* place, so the resulting doc edit is exactly one line's splice and
|
|
101
|
+
* concurrent publishes from other machines never overlap it.
|
|
102
|
+
*
|
|
103
|
+
* Lines that do not parse as wall entries are deleted, not preserved: a
|
|
104
|
+
* garbled line is nobody's line (every writer replaces its own on the
|
|
105
|
+
* next publish), and keeping it would make corruption permanent — no
|
|
106
|
+
* writer would ever own the bytes. Deleting it is safe under concurrent
|
|
107
|
+
* publishes because valid lines are disjoint per machine and the doc
|
|
108
|
+
* rail merges a delete of garbage with any machine's splice of its own
|
|
109
|
+
* line.
|
|
102
110
|
*/
|
|
103
111
|
function publishWall() {
|
|
104
112
|
const replica = wallReplica();
|
|
@@ -116,21 +124,40 @@ function publishWall() {
|
|
|
116
124
|
cards: listRepoCards(),
|
|
117
125
|
});
|
|
118
126
|
|
|
119
|
-
const lines =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
const lines = [];
|
|
128
|
+
let previous = null;
|
|
129
|
+
let dropped = 0;
|
|
130
|
+
for (const line of current.split('\n')) {
|
|
131
|
+
if (!line) continue;
|
|
132
|
+
const entry = parseWallLine(line);
|
|
133
|
+
if (!entry) {
|
|
134
|
+
dropped += 1;
|
|
135
|
+
} else if (entry.machine === machineId()) {
|
|
136
|
+
// Duplicate own lines (a healed fork) collapse into the first slot.
|
|
137
|
+
if (previous === null) {
|
|
138
|
+
previous = line;
|
|
139
|
+
lines.push(mine);
|
|
140
|
+
} else {
|
|
141
|
+
dropped += 1;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
lines.push(line);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (previous === null) lines.push(mine);
|
|
148
|
+
|
|
149
|
+
const unchanged = dropped === 0
|
|
150
|
+
&& previous !== null
|
|
151
|
+
&& withoutPublishedAt(previous) === withoutPublishedAt(mine);
|
|
123
152
|
if (unchanged) {
|
|
124
153
|
service.counters.publishSkips += 1;
|
|
125
154
|
return { published: false, reason: 'unchanged' };
|
|
126
155
|
}
|
|
127
|
-
if (index >= 0) lines[index] = mine;
|
|
128
|
-
else lines.push(mine);
|
|
129
156
|
|
|
130
157
|
const result = docs.writeDocText(WALL_FILE, `${lines.join('\n')}\n`, { baseHash: sha256(current) });
|
|
131
158
|
service.counters.publishes += 1;
|
|
132
159
|
service.lastPublishedAt = new Date().toISOString();
|
|
133
|
-
return { published: true, merge: result.merge, cards: listRepoCards().length };
|
|
160
|
+
return { published: true, merge: result.merge, cards: listRepoCards().length, dropped };
|
|
134
161
|
}
|
|
135
162
|
|
|
136
163
|
function withoutPublishedAt(line) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const { AMALGM_DIR, DEFAULT_CWD, PROXY_BASE_URL, PROXY_TOKEN } = require('../chat-server/config');
|
|
6
|
+
const { AMALGM_DIR, DEFAULT_CWD, DEFAULT_PROXY_BASE_URL, PROXY_BASE_URL, PROXY_TOKEN } = require('../chat-server/config');
|
|
7
7
|
const { canonicalProjectPath } = require('../lib/project-paths');
|
|
8
8
|
const { authEnvelope, coerceAuth, fingerprint } = require('./auth');
|
|
9
9
|
const { runtimePort } = require('../../lib/runtime-manifest');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const http = require('http');
|
|
4
|
-
const { PORT, PROXY_BASE_URL, ensureFreshProxyToken } = require('../chat-server/config');
|
|
4
|
+
const { PORT, DEFAULT_PROXY_BASE_URL, PROXY_BASE_URL, ensureFreshProxyToken } = require('../chat-server/config');
|
|
5
5
|
const db = require('../chat-server/db');
|
|
6
6
|
const { CodexAdapter } = require('./adapters/codex');
|
|
7
7
|
const { ClaudeAdapter } = require('./adapters/claude');
|
|
@@ -111,7 +111,7 @@ function createServer(core = createCore()) {
|
|
|
111
111
|
res,
|
|
112
112
|
contract,
|
|
113
113
|
turn: core.turns.active(route.sessionId),
|
|
114
|
-
proxyBaseUrl: PROXY_BASE_URL ||
|
|
114
|
+
proxyBaseUrl: PROXY_BASE_URL || DEFAULT_PROXY_BASE_URL,
|
|
115
115
|
proxyToken,
|
|
116
116
|
upstreamPath: `${route.upstreamPath}${route.search || ''}`,
|
|
117
117
|
});
|
|
@@ -31,7 +31,8 @@ async function boot() {
|
|
|
31
31
|
}
|
|
32
32
|
const adminSecret = process.env.PROXY_ADMIN_SECRET || process.env.ADMIN_SECRET;
|
|
33
33
|
if (adminSecret) {
|
|
34
|
-
const
|
|
34
|
+
const { DEFAULT_PROXY_BASE_URL } = require('./proxy-token-store');
|
|
35
|
+
const proxyUrl = process.env.AMALGM_PROXY_URL || DEFAULT_PROXY_BASE_URL;
|
|
35
36
|
try {
|
|
36
37
|
const userId = process.env.AMALGM_USER_ID || process.env.USER_ID || process.env.NEXT_PUBLIC_AMALGM_USER_ID || '';
|
|
37
38
|
const containerId = process.env.AMALGM_CONTAINER_ID || 'standalone';
|
|
@@ -4,7 +4,19 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { amalgmDir, runtimeStateDir } = require('./lib/runtime-paths');
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
function usesPreviewInfraDefaults() {
|
|
8
|
+
const raw = String(
|
|
9
|
+
process.env.AMALGM_RUNTIME_LABEL
|
|
10
|
+
|| process.env.AMALGM_CHANNEL
|
|
11
|
+
|| process.env.AMALGM_BRANCH
|
|
12
|
+
|| '',
|
|
13
|
+
).trim().toLowerCase();
|
|
14
|
+
return ['preview', 'canary', 'native-chat', 'staging', 'local', 'dev', 'development'].includes(raw);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DEFAULT_PROXY_BASE_URL = usesPreviewInfraDefaults()
|
|
18
|
+
? 'https://api.preview.amalgm.ai'
|
|
19
|
+
: 'https://amalgm-api-proxy-v2.fly.dev';
|
|
8
20
|
const REFRESH_BUFFER_MS = 10 * 60 * 1000;
|
|
9
21
|
|
|
10
22
|
const AMALGM_DIR = amalgmDir();
|