amalgm 0.1.130 → 0.1.131-canary.20260625234755

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
@@ -1051,7 +1051,10 @@ async function status() {
1051
1051
  if (record?.webhook_url) console.log(`Events: ${record.webhook_url}`);
1052
1052
  if (record?.tunnel_url) console.log(`Tunnel: ${record.tunnel_url}`);
1053
1053
  if (record?.tunnel_token) {
1054
- console.log(`Chat tunnel: ${record.chat_tunnel_url || 'wss://amalgm-chat-gateway.fly.dev/wire'}`);
1054
+ const defaultChatTunnel = AMALGM_RUNTIME_LABEL === 'main'
1055
+ ? 'wss://amalgm-chat-gateway.fly.dev/wire'
1056
+ : 'wss://chat.preview.amalgm.ai/wire';
1057
+ console.log(`Chat tunnel: ${record.chat_tunnel_url || defaultChatTunnel}`);
1055
1058
  }
1056
1059
  console.log(`HMAC proxy: ${proxyTokenFromRecord(record) ? 'configured' : 'not configured'}`);
1057
1060
 
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;
@@ -54,6 +58,7 @@ const MCP_PREFIXES = [
54
58
  '/agents',
55
59
  '/agent-config',
56
60
  '/agent-bundles',
61
+ '/skills',
57
62
  '/user-preferences',
58
63
  '/browser',
59
64
  ];
@@ -72,7 +77,7 @@ const HOP_BY_HOP_HEADERS = new Set([
72
77
  ]);
73
78
 
74
79
  function tunnelUrl(rawUrl) {
75
- const url = new URL(rawUrl || DEFAULT_CHAT_TUNNEL_URL);
80
+ const url = new URL(rawUrl || defaultChatTunnelUrl());
76
81
  url.searchParams.delete('token');
77
82
  return url.toString();
78
83
  }
@@ -56,6 +56,7 @@ const MCP_PREFIXES = [
56
56
  '/agents',
57
57
  '/agent-config',
58
58
  '/agent-bundles',
59
+ '/skills',
59
60
  '/user-preferences',
60
61
  '/browser',
61
62
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.130",
3
+ "version": "0.1.131-canary.20260625234755",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -92,9 +92,12 @@ function nativeAgentBrowser(wrapperPath) {
92
92
  && (fs.existsSync('/lib/ld-musl-x86_64.so.1') || fs.existsSync('/lib/ld-musl-aarch64.so.1'));
93
93
  const name = `agent-browser-${platform}${musl ? '-musl' : ''}-${arch}${platform === 'win32' ? '.exe' : ''}`;
94
94
  const candidate = path.join(path.dirname(wrapperPath), name);
95
- if (fs.existsSync(candidate)) return candidate;
96
95
  const unpacked = asarUnpackedPath(candidate);
97
- return unpacked && fs.existsSync(unpacked) ? unpacked : null;
96
+ // Electron's asar-aware fs can report this path as "existing" even though
97
+ // the OS cannot spawn a binary through app.asar. Prefer the real unpacked
98
+ // path whenever it is available.
99
+ if (unpacked && fs.existsSync(unpacked)) return unpacked;
100
+ return fs.existsSync(candidate) ? candidate : null;
98
101
  }
99
102
 
100
103
  function asarUnpackedPath(candidate) {
@@ -52,9 +52,21 @@ const DEFAULT_CWD = process.env.AMALGM_DEFAULT_CWD || os.homedir();
52
52
  const AMALGM_USER_ID = process.env.AMALGM_USER_ID || '';
53
53
  const AMALGM_COMPUTER_ID = process.env.AMALGM_COMPUTER_ID || process.env.AMALGM_CONTAINER_ID || '';
54
54
 
55
+ function usesPreviewInfraDefaults() {
56
+ const raw = String(
57
+ process.env.AMALGM_RUNTIME_LABEL
58
+ || process.env.AMALGM_CHANNEL
59
+ || process.env.AMALGM_BRANCH
60
+ || '',
61
+ ).trim().toLowerCase();
62
+ return ['preview', 'canary', 'native-chat', 'staging', 'local', 'dev', 'development'].includes(raw);
63
+ }
64
+
55
65
  const PROXY_TOKEN = process.env.AMALGM_PROXY_TOKEN
56
66
  || (process.env.AMALGM_RUNTIME_SOURCE === 'npm' ? '' : process.env.ANTHROPIC_API_KEY || '');
57
- const DEFAULT_PROXY_BASE_URL = 'https://amalgm-api-proxy-v2.fly.dev';
67
+ const DEFAULT_PROXY_BASE_URL = usesPreviewInfraDefaults()
68
+ ? 'https://api.preview.amalgm.ai'
69
+ : 'https://amalgm-api-proxy-v2.fly.dev';
58
70
  const PROXY_BASE_URL = process.env.AMALGM_PROXY_URL || (PROXY_TOKEN ? DEFAULT_PROXY_BASE_URL : '');
59
71
  const SUPABASE_PROXY_URL = PROXY_BASE_URL ? `${PROXY_BASE_URL}/supabase` : '';
60
72
 
@@ -79,14 +91,14 @@ const APPS_DOMAIN = (
79
91
  || process.env.NEXT_PUBLIC_APPS_DOMAIN
80
92
  || process.env.AMALGM_ARTIFACTS_DOMAIN
81
93
  || process.env.NEXT_PUBLIC_ARTIFACTS_DOMAIN
82
- || 'apps.amalgm.ai'
94
+ || (usesPreviewInfraDefaults() ? 'apps.preview.amalgm.ai' : 'apps.amalgm.ai')
83
95
  );
84
96
  const APP_PORT_MIN = parseInt(process.env.AMALGM_APP_PORT_MIN || process.env.AMALGM_ARTIFACT_PORT_MIN || '9100', 10);
85
97
  const APP_PORT_MAX = parseInt(process.env.AMALGM_APP_PORT_MAX || process.env.AMALGM_ARTIFACT_PORT_MAX || '9199', 10);
86
98
  const GATEWAY_BASE_URL = (
87
99
  process.env.AMALGM_GATEWAY_INTERNAL_URL
88
100
  || process.env.AMALGM_GATEWAY_URL
89
- || 'https://wire.events.amalgm.ai'
101
+ || (usesPreviewInfraDefaults() ? 'https://wire.events.preview.amalgm.ai' : 'https://wire.events.amalgm.ai')
90
102
  ).replace(/\/$/, '');
91
103
  const GATEWAY_INTERNAL_SECRET =
92
104
  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`, {
@@ -11,19 +11,32 @@ function cleanString(value) {
11
11
  return typeof value === 'string' && value.trim() ? value.trim() : '';
12
12
  }
13
13
 
14
+ function normalizeInstallSource(value) {
15
+ const source = cleanString(value);
16
+ if (!source) return '';
17
+ if (/^https?:\/\/[^\s]+$/i.test(source)) return source;
18
+ if (/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(source)) return source;
19
+
20
+ const host = source.split('/')[0];
21
+ if (/^[A-Za-z0-9.-]+\.[A-Za-z]{2,}(?::\d+)?$/i.test(host)) {
22
+ return `https://${source}`;
23
+ }
24
+ return source;
25
+ }
26
+
14
27
  function npmCommand() {
15
28
  return process.platform === 'win32' ? 'npm.cmd' : 'npm';
16
29
  }
17
30
 
18
31
  function isSupportedInstallSource(value) {
19
- const source = cleanString(value);
32
+ const source = normalizeInstallSource(value);
20
33
  if (!source) return false;
21
34
  if (/^https?:\/\/[^\s]+$/i.test(source)) return true;
22
35
  return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(source);
23
36
  }
24
37
 
25
38
  function buildInstallSkillArgs({ installUrl, slug }) {
26
- const source = cleanString(installUrl);
39
+ const source = normalizeInstallSource(installUrl);
27
40
  const skillSlug = cleanString(slug);
28
41
  if (!isSupportedInstallSource(source)) {
29
42
  throw new Error('installUrl must be an https URL or owner/repo reference');
@@ -111,4 +124,5 @@ module.exports = {
111
124
  buildInstallSkillArgs,
112
125
  findInstalledSkillBySlug,
113
126
  installPublicSkill,
127
+ normalizeInstallSource,
114
128
  };
@@ -61,6 +61,31 @@ function walkProjectSkillRoots(selectedCwd) {
61
61
  return roots;
62
62
  }
63
63
 
64
+ function walkCodexWorktreeSkillRoots(home) {
65
+ const worktreesRoot = existingDirectory(path.join(home, '.codex', 'worktrees'));
66
+ if (!worktreesRoot) return [];
67
+
68
+ let entries = [];
69
+ try {
70
+ entries = fs.readdirSync(worktreesRoot, { withFileTypes: true });
71
+ } catch {
72
+ return [];
73
+ }
74
+
75
+ return entries
76
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
77
+ .map((entry) => existingDirectory(path.join(worktreesRoot, entry.name, '.agents', 'skills')))
78
+ .filter(Boolean);
79
+ }
80
+
81
+ function isCodexWorktreeRoot(rootPath, options = {}) {
82
+ const root = normalizePath(rootPath);
83
+ const home = normalizePath(options.homeDir) || homeDir();
84
+ if (!root || !home) return false;
85
+ const worktreesRoot = `${normalizePath(path.join(home, '.codex', 'worktrees'))}${path.sep}`;
86
+ return root.startsWith(worktreesRoot) && root.endsWith(path.normalize(path.join('.agents', 'skills')));
87
+ }
88
+
64
89
  function homeDir() {
65
90
  return normalizePath(process.env.AMALGM_NATIVE_HOME || os.homedir());
66
91
  }
@@ -75,6 +100,7 @@ function buildCanonicalSkillRoots(options = {}) {
75
100
  home ? path.join(home, '.agents', 'skills') : '',
76
101
  codeXHome ? path.join(codeXHome, 'skills') : '',
77
102
  home ? path.join(home, '.codex', 'skills') : '',
103
+ ...walkCodexWorktreeSkillRoots(home),
78
104
  claudeConfigDir ? path.join(claudeConfigDir, 'skills') : '',
79
105
  home ? path.join(home, '.claude', 'skills') : '',
80
106
  home ? path.join(home, '.config', 'claude', 'skills') : '',
@@ -127,6 +153,8 @@ module.exports = {
127
153
  homeDir,
128
154
  normalizePath,
129
155
  prefsSelectedCwd,
156
+ isCodexWorktreeRoot,
130
157
  uniquePaths,
158
+ walkCodexWorktreeSkillRoots,
131
159
  walkProjectSkillRoots,
132
160
  };
@@ -3,7 +3,12 @@
3
3
  const crypto = require('crypto');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { buildCanonicalSkillRoots, classifySkillRoot, normalizePath } = require('./roots');
6
+ const {
7
+ buildCanonicalSkillRoots,
8
+ classifySkillRoot,
9
+ isCodexWorktreeRoot,
10
+ normalizePath,
11
+ } = require('./roots');
7
12
 
8
13
  const FRONTMATTER_RE = /^---\s*\n([\s\S]*?)\n---/;
9
14
 
@@ -126,14 +131,37 @@ function scanSkillRoot(rootPath, options = {}) {
126
131
  .filter(Boolean);
127
132
  }
128
133
 
134
+ function skillDuplicateKey(skill) {
135
+ const name = cleanString(skill?.name).toLowerCase();
136
+ const contentHash = cleanString(skill?.contentHash);
137
+ if (!name || !contentHash) return '';
138
+ return `${name}::${contentHash}`;
139
+ }
140
+
129
141
  function scanInstalledSkills(options = {}) {
130
142
  const roots = buildCanonicalSkillRoots(options);
131
143
  const byPath = new Map();
144
+ const byResolvedPath = new Map();
145
+ const bySignature = new Map();
132
146
  for (const rootPath of roots) {
133
147
  for (const skill of scanSkillRoot(rootPath, options)) {
134
- const key = cleanString(skill?.source?.path);
135
- if (!key || byPath.has(key)) continue;
136
- byPath.set(key, skill);
148
+ const pathKey = cleanString(skill?.source?.path);
149
+ const resolvedKey = cleanString(skill?.source?.resolvedPath);
150
+ const signature = skillDuplicateKey(skill);
151
+
152
+ if (!pathKey || byPath.has(pathKey)) continue;
153
+ if (resolvedKey && byResolvedPath.has(resolvedKey)) continue;
154
+ if (
155
+ signature
156
+ && isCodexWorktreeRoot(skill?.source?.rootPath, options)
157
+ && bySignature.has(signature)
158
+ ) {
159
+ continue;
160
+ }
161
+
162
+ byPath.set(pathKey, skill);
163
+ if (resolvedKey) byResolvedPath.set(resolvedKey, skill);
164
+ if (signature) bySignature.set(signature, skill);
137
165
  }
138
166
  }
139
167
  return {
@@ -8,6 +8,17 @@ const path = require('node:path');
8
8
 
9
9
  const cli = require('../browser/cli');
10
10
 
11
+ function nativeBinaryName() {
12
+ const platforms = { darwin: 'darwin', linux: 'linux', win32: 'win32' };
13
+ const arches = { x64: 'x64', arm64: 'arm64' };
14
+ const platform = platforms[os.platform()];
15
+ const arch = arches[os.arch()];
16
+ if (!platform || !arch) return null;
17
+ const musl = platform === 'linux'
18
+ && (fs.existsSync('/lib/ld-musl-x86_64.so.1') || fs.existsSync('/lib/ld-musl-aarch64.so.1'));
19
+ return `agent-browser-${platform}${musl ? '-musl' : ''}-${arch}${platform === 'win32' ? '.exe' : ''}`;
20
+ }
21
+
11
22
  function withoutBinOverride(fn) {
12
23
  const saved = process.env.AMALGM_AGENT_BROWSER_BIN;
13
24
  delete process.env.AMALGM_AGENT_BROWSER_BIN;
@@ -67,7 +78,9 @@ test('agent-browser spawns the native binary directly when it exists', () => {
67
78
  test('agent-browser native path resolves from packaged app.asar to app.asar.unpacked', () => {
68
79
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-asar-browser-test-'));
69
80
  const wrapper = path.join(root, 'app.asar', 'node_modules', 'agent-browser', 'bin', 'agent-browser.js');
70
- const native = path.join(root, 'app.asar.unpacked', 'node_modules', 'agent-browser', 'bin', `agent-browser-${os.platform()}-${os.arch()}`);
81
+ const binaryName = nativeBinaryName();
82
+ if (!binaryName) return;
83
+ const native = path.join(root, 'app.asar.unpacked', 'node_modules', 'agent-browser', 'bin', binaryName);
71
84
  fs.mkdirSync(path.dirname(native), { recursive: true });
72
85
  fs.writeFileSync(native, '');
73
86
  try {
@@ -78,6 +91,29 @@ test('agent-browser native path resolves from packaged app.asar to app.asar.unpa
78
91
  }
79
92
  });
80
93
 
94
+ test('agent-browser prefers app.asar.unpacked even when the asar candidate appears to exist', () => {
95
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-asar-shim-test-'));
96
+ const wrapper = path.join(root, 'app.asar', 'node_modules', 'agent-browser', 'bin', 'agent-browser.js');
97
+ const binaryName = nativeBinaryName();
98
+ if (!binaryName) return;
99
+ const candidate = path.join(root, 'app.asar', 'node_modules', 'agent-browser', 'bin', binaryName);
100
+ const unpacked = path.join(root, 'app.asar.unpacked', 'node_modules', 'agent-browser', 'bin', binaryName);
101
+ fs.mkdirSync(path.dirname(unpacked), { recursive: true });
102
+ fs.writeFileSync(unpacked, '');
103
+ const originalExistsSync = fs.existsSync;
104
+ fs.existsSync = (value) => {
105
+ const target = String(value);
106
+ if (target === candidate || target === unpacked) return true;
107
+ return originalExistsSync(value);
108
+ };
109
+ try {
110
+ assert.equal(cli.nativeAgentBrowser(wrapper), unpacked);
111
+ } finally {
112
+ fs.existsSync = originalExistsSync;
113
+ fs.rmSync(root, { recursive: true, force: true });
114
+ }
115
+ });
116
+
81
117
  test('CLI browser sessions stay headless unless headed mode is explicit', async () => {
82
118
  await withBrowserEnv({}, () => {
83
119
  assert.equal(cli.wantsHeaded(), false);
@@ -38,6 +38,27 @@ test('scanInstalledSkills discovers canonical project/global roots and symlink m
38
38
  const globalSkillDir = path.join(home, '.codex', 'skills', 'global-review');
39
39
  writeSkill(globalSkillDir, 'Global skill');
40
40
 
41
+ const worktreeSkillDir = path.join(
42
+ home,
43
+ '.codex',
44
+ 'worktrees',
45
+ 'sandbox-check',
46
+ '.agents',
47
+ 'skills',
48
+ 'sandbox-agent',
49
+ );
50
+ writeSkill(worktreeSkillDir, 'Worktree skill');
51
+ const duplicateWorktreeSkillDir = path.join(
52
+ home,
53
+ '.codex',
54
+ 'worktrees',
55
+ 'sandbox-duplicate',
56
+ '.agents',
57
+ 'skills',
58
+ 'sandbox-agent',
59
+ );
60
+ writeSkill(duplicateWorktreeSkillDir, 'Worktree skill');
61
+
41
62
  const realProjectSkillDir = path.join(sourceRoot, 'project-portability');
42
63
  writeSkill(realProjectSkillDir, 'Project skill');
43
64
  const projectSkillRoot = path.join(project, '.agents', 'skills');
@@ -50,8 +71,16 @@ test('scanInstalledSkills discovers canonical project/global roots and symlink m
50
71
  });
51
72
 
52
73
  assert.equal(result.roots.includes(path.join(project, '.agents', 'skills')), true);
74
+ assert.equal(
75
+ result.roots.includes(path.join(home, '.codex', 'worktrees', 'sandbox-check', '.agents', 'skills')),
76
+ true,
77
+ );
78
+ assert.equal(
79
+ result.roots.includes(path.join(home, '.codex', 'worktrees', 'sandbox-duplicate', '.agents', 'skills')),
80
+ true,
81
+ );
53
82
  assert.equal(result.roots.includes(path.join(home, '.codex', 'skills')), true);
54
- assert.equal(result.skills.length, 2);
83
+ assert.equal(result.skills.length, 3);
55
84
 
56
85
  const projectSkill = result.skills.find((skill) => skill.name === 'project-portability');
57
86
  assert.equal(projectSkill?.source.scope, 'project');
@@ -61,6 +90,9 @@ test('scanInstalledSkills discovers canonical project/global roots and symlink m
61
90
  const globalSkill = result.skills.find((skill) => skill.name === 'global-review');
62
91
  assert.equal(globalSkill?.source.provider, 'codex');
63
92
  assert.equal(globalSkill?.source.scope, 'global');
93
+
94
+ const worktreeSkill = result.skills.find((skill) => skill.name === 'sandbox-agent');
95
+ assert.equal(worktreeSkill?.source.scope, 'project');
64
96
  } finally {
65
97
  fs.rmSync(home, { recursive: true, force: true });
66
98
  fs.rmSync(project, { recursive: true, force: true });
@@ -169,6 +201,30 @@ test('buildInstallSkillArgs targets the global Codex skills install flow', () =>
169
201
  );
170
202
  });
171
203
 
204
+ test('buildInstallSkillArgs normalizes bare well-known sources to https URLs', () => {
205
+ assert.deepEqual(
206
+ buildInstallSkillArgs({
207
+ installUrl: 'open.feishu.cn',
208
+ slug: 'lark-approval',
209
+ }),
210
+ [
211
+ 'exec',
212
+ '--yes',
213
+ '--package=skills',
214
+ '--',
215
+ 'skills',
216
+ 'add',
217
+ 'https://open.feishu.cn',
218
+ '--skill',
219
+ 'lark-approval',
220
+ '--agent',
221
+ 'codex',
222
+ '--global',
223
+ '--yes',
224
+ ],
225
+ );
226
+ });
227
+
172
228
  test('findInstalledSkillBySlug matches installed public skills by name or directory', () => {
173
229
  const installedSkill = {
174
230
  id: 'skill:find-skills',
@@ -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');
@@ -427,7 +427,7 @@ function createContract(payload, options = {}) {
427
427
  sessionId,
428
428
  localBaseUrl,
429
429
  proxyToken: options.proxyToken || PROXY_TOKEN,
430
- proxyBaseUrl: options.proxyBaseUrl || PROXY_BASE_URL || 'https://amalgm-api-proxy-v2.fly.dev',
430
+ proxyBaseUrl: options.proxyBaseUrl || PROXY_BASE_URL || DEFAULT_PROXY_BASE_URL,
431
431
  amalgmDir: options.amalgmDir || AMALGM_DIR || path.join(process.env.HOME || '.', '.amalgm'),
432
432
  userId,
433
433
  credentialId: payload.credentialId || payload.byokCredentialId || null,
@@ -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');
@@ -107,7 +107,7 @@ function createServer(core = createCore()) {
107
107
  res,
108
108
  contract,
109
109
  turn: core.turns.active(route.sessionId),
110
- proxyBaseUrl: PROXY_BASE_URL || 'https://amalgm-api-proxy-v2.fly.dev',
110
+ proxyBaseUrl: PROXY_BASE_URL || DEFAULT_PROXY_BASE_URL,
111
111
  proxyToken,
112
112
  upstreamPath: `${route.upstreamPath}${route.search || ''}`,
113
113
  });
@@ -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';
@@ -163,6 +163,7 @@ const MCP_PREFIXES = [
163
163
  '/agents',
164
164
  '/agent-config',
165
165
  '/agent-bundles',
166
+ '/skills',
166
167
  '/user-preferences',
167
168
  '/browser',
168
169
  ];
@@ -4,7 +4,19 @@ const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
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 = process.env.AMALGM_DIR || path.join(os.homedir(), '.amalgm');