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 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
- console.log(`Chat tunnel: ${record.chat_tunnel_url || 'wss://amalgm-chat-gateway.fly.dev/wire'}`);
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 || 'https://amalgm.ai';
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,
@@ -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
- const DEFAULT_CHAT_TUNNEL_URL = 'wss://amalgm-chat-gateway.fly.dev/wire';
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 || DEFAULT_CHAT_TUNNEL_URL);
80
+ const url = new URL(rawUrl || defaultChatTunnelUrl());
77
81
  url.searchParams.delete('token');
78
82
  return url.toString();
79
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.162",
3
+ "version": "0.1.163-canary.20260722143753",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -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 = 'https://amalgm-api-proxy-v2.fly.dev';
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 || 'https://amalgm-api-proxy-v2.fly.dev';
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 place, so
100
- * the resulting doc edit is exactly one line's splice and concurrent
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 = current.split('\n').filter((line) => line.length > 0);
120
- const index = lines.findIndex((line) => parseWallLine(line)?.machine === machineId());
121
- const unchanged = index >= 0
122
- && withoutPublishedAt(lines[index]) === withoutPublishedAt(mine);
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 || 'https://amalgm-api-proxy-v2.fly.dev',
114
+ proxyBaseUrl: PROXY_BASE_URL || DEFAULT_PROXY_BASE_URL,
115
115
  proxyToken,
116
116
  upstreamPath: `${route.upstreamPath}${route.search || ''}`,
117
117
  });
@@ -100,6 +100,7 @@ module.exports = {
100
100
  PROXY_TOKEN,
101
101
  ensureFreshProxyToken,
102
102
  readProxyToken,
103
+ DEFAULT_PROXY_BASE_URL,
103
104
  PROXY_BASE_URL,
104
105
  OPENAI_PROXY_URL,
105
106
  AI_GATEWAY_PROXY_URL,
@@ -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 proxyUrl = process.env.AMALGM_PROXY_URL || 'https://amalgm-api-proxy-v2.fly.dev';
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
- const DEFAULT_PROXY_BASE_URL = 'https://amalgm-api-proxy-v2.fly.dev';
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();