amalgm 0.1.69 → 0.1.71

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 (26) hide show
  1. package/package.json +4 -3
  2. package/runtime/lib/harnesses.js +47 -37
  3. package/runtime/scripts/amalgm-mcp/agents/store.js +1 -1
  4. package/runtime/scripts/amalgm-mcp/agents/talk.js +48 -38
  5. package/runtime/scripts/amalgm-mcp/lib/computer.js +75 -0
  6. package/runtime/scripts/amalgm-mcp/lib/prefs.js +38 -28
  7. package/runtime/scripts/amalgm-mcp/tasks/executor.js +25 -18
  8. package/runtime/scripts/chat-core/adapters/claude.js +1 -1
  9. package/runtime/scripts/chat-core/adapters/codex.js +3 -1
  10. package/runtime/scripts/chat-core/adapters/opencode.js +7 -4
  11. package/runtime/scripts/chat-core/auth.js +11 -29
  12. package/runtime/scripts/chat-core/contract.js +44 -32
  13. package/runtime/scripts/chat-core/engine.js +0 -1
  14. package/runtime/scripts/chat-core/normalizers/codex.js +29 -6
  15. package/runtime/scripts/chat-core/parts.js +2 -0
  16. package/runtime/scripts/chat-core/tests/auth.test.js +50 -44
  17. package/runtime/scripts/chat-core/tests/engine.test.js +1 -1
  18. package/runtime/scripts/chat-core/tests/native-binaries.test.js +126 -0
  19. package/runtime/scripts/chat-core/tests/native-config.test.js +87 -9
  20. package/runtime/scripts/chat-core/tool-shape.js +4 -2
  21. package/runtime/scripts/chat-core/tooling/native-binaries.js +20 -3
  22. package/runtime/scripts/chat-core/tooling/native-config.js +54 -39
  23. package/runtime/scripts/chat-core/tooling/runtime-home.js +3 -1
  24. package/runtime/scripts/chat-server/model-catalog.js +10 -5
  25. package/runtime/scripts/credential-adapter.js +1 -1
  26. package/runtime/scripts/test-claude-code-models.js +17 -12
@@ -9,7 +9,7 @@ const { resolveCredential } = require('./credentials/store');
9
9
  const AUTH_BY_HARNESS = {
10
10
  claude_code: ['amalgm', 'byok', 'provider_auth'],
11
11
  codex: ['amalgm', 'byok', 'provider_auth'],
12
- opencode: ['amalgm', 'byok'],
12
+ opencode: ['amalgm', 'byok', 'provider_auth'],
13
13
  };
14
14
 
15
15
  function shellValue(raw) {
@@ -109,35 +109,15 @@ function providerAuthIdentity(harness) {
109
109
  return `${harness || 'provider'}:provider-default`;
110
110
  }
111
111
 
112
- function deriveAuthProfileId({ method, harness, userId, tokenFingerprint }) {
113
- if (method === 'amalgm') {
114
- return `amalgm-${fingerprint(userId || process.env.AMALGM_USER_ID || 'local') || 'local'}`;
115
- }
116
- if (method === 'byok') {
117
- return `byok-${tokenFingerprint || 'default'}`;
118
- }
119
- if (method === 'provider_auth') {
120
- return `provider-${fingerprint(providerAuthIdentity(harness)) || 'default'}`;
121
- }
122
- return 'default';
123
- }
124
-
125
- function runtimeHome({ amalgmDir, harness, authProfileId }) {
126
- return path.join(amalgmDir, 'cli-homes', safePathSegment(harness), safePathSegment(authProfileId));
127
- }
128
-
129
- function pinnedRuntimeHome(amalgmDir, cliHomePath) {
130
- if (typeof cliHomePath !== 'string' || !cliHomePath.trim()) return null;
131
- const root = path.resolve(amalgmDir);
132
- const resolved = path.resolve(cliHomePath.trim());
133
- return resolved === root || resolved.startsWith(`${root}${path.sep}`) ? resolved : null;
112
+ function runtimeHome({ amalgmDir, harness }) {
113
+ return path.join(amalgmDir, 'cli-homes', safePathSegment(harness), 'home');
134
114
  }
135
115
 
136
116
  function providerAuthFingerprint(harness) {
137
117
  return fingerprint(providerAuthIdentity(harness));
138
118
  }
139
119
 
140
- function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken, proxyBaseUrl, amalgmDir, userId, credentialId, modelId, cliHomePath, authProfileId }) {
120
+ function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken, proxyBaseUrl, amalgmDir, userId, credentialId, modelId }) {
141
121
  const method = coerceAuth(harness, authMethod);
142
122
  const byok = readByokFile(amalgmDir);
143
123
  let baseUrl = null;
@@ -166,7 +146,6 @@ function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken
166
146
  } else if (method === 'provider_auth') {
167
147
  tokenFingerprint = providerAuthFingerprint(harness);
168
148
  }
169
- const profileId = authProfileId || deriveAuthProfileId({ method, harness, userId, tokenFingerprint });
170
149
  return {
171
150
  method,
172
151
  baseUrl,
@@ -176,10 +155,7 @@ function authEnvelope({ harness, authMethod, sessionId, localBaseUrl, proxyToken
176
155
  forwardBaseUrl,
177
156
  tokenRef,
178
157
  tokenFingerprint,
179
- authProfileId: profileId,
180
- runtimeHome:
181
- pinnedRuntimeHome(amalgmDir, cliHomePath)
182
- || runtimeHome({ amalgmDir, harness, authProfileId: profileId }),
158
+ runtimeHome: runtimeHome({ amalgmDir, harness }),
183
159
  };
184
160
  }
185
161
 
@@ -203,6 +179,12 @@ function runtimeEnv(contract, baseEnv = process.env) {
203
179
  env.CLAUDE_CONFIG_DIR = contract.auth.runtimeHome;
204
180
  env.OPENCODE_HOME = contract.auth.runtimeHome;
205
181
  env.OPENCODE_CONFIG_DIR = contract.auth.runtimeHome;
182
+ if (contract.harness === 'opencode') {
183
+ env.XDG_CONFIG_HOME = path.join(contract.auth.runtimeHome, '.config');
184
+ env.XDG_DATA_HOME = path.join(contract.auth.runtimeHome, '.local', 'share');
185
+ env.XDG_STATE_HOME = path.join(contract.auth.runtimeHome, '.local', 'state');
186
+ env.XDG_CACHE_HOME = path.join(contract.auth.runtimeHome, '.cache');
187
+ }
206
188
  if (contract.harness === 'claude_code' && contract.authMethod === 'provider_auth') {
207
189
  delete env.CLAUDE_CONFIG_DIR;
208
190
  }
@@ -28,26 +28,31 @@ function canonicalModel(modelId, harness) {
28
28
  'haiku': 'anthropic/claude-haiku-4.5',
29
29
  'sonnet': 'anthropic/claude-sonnet-4.6',
30
30
  'sonnet[1m]': 'anthropic/claude-sonnet-4.6',
31
- 'opus': 'anthropic/claude-opus-4.7',
32
- 'opus1m': 'anthropic/claude-opus-4.7-1m',
33
- 'opus[1m]': 'anthropic/claude-opus-4.7-1m',
31
+ 'opus': 'anthropic/claude-opus-4.8',
32
+ 'opus1m': 'anthropic/claude-opus-4.8-1m',
33
+ 'opus[1m]': 'anthropic/claude-opus-4.8-1m',
34
34
  'claude-code-haiku': 'anthropic/claude-haiku-4.5',
35
35
  'claude-code-sonnet': 'anthropic/claude-sonnet-4.6',
36
36
  'claude-code-sonnet-45': 'anthropic/claude-sonnet-4.6',
37
- 'claude-code-opus': 'anthropic/claude-opus-4.7',
38
- 'claude-code-opus-45': 'anthropic/claude-opus-4.7',
39
- 'claude-code-opus-46': 'anthropic/claude-opus-4.7',
40
- 'claude-code-opus-47': 'anthropic/claude-opus-4.7',
41
- 'claude-code-opus-4.5': 'anthropic/claude-opus-4.7',
42
- 'claude-code-opus-4-5': 'anthropic/claude-opus-4.7',
43
- 'claude-code-opus-4.6': 'anthropic/claude-opus-4.7',
44
- 'claude-code-opus-4-6': 'anthropic/claude-opus-4.7',
45
- 'claude-code-opus-4.7': 'anthropic/claude-opus-4.7',
46
- 'claude-code-opus-4-7': 'anthropic/claude-opus-4.7',
47
- 'claude-code-opus1m': 'anthropic/claude-opus-4.7-1m',
48
- 'claude-code-opus-1m': 'anthropic/claude-opus-4.7-1m',
49
- 'claude-code-opus-4.7-1m': 'anthropic/claude-opus-4.7-1m',
50
- 'claude-code-opus-4-7-1m': 'anthropic/claude-opus-4.7-1m',
37
+ 'claude-code-opus': 'anthropic/claude-opus-4.8',
38
+ 'claude-code-opus-45': 'anthropic/claude-opus-4.8',
39
+ 'claude-code-opus-46': 'anthropic/claude-opus-4.8',
40
+ 'claude-code-opus-47': 'anthropic/claude-opus-4.8',
41
+ 'claude-code-opus-48': 'anthropic/claude-opus-4.8',
42
+ 'claude-code-opus-4.5': 'anthropic/claude-opus-4.8',
43
+ 'claude-code-opus-4-5': 'anthropic/claude-opus-4.8',
44
+ 'claude-code-opus-4.6': 'anthropic/claude-opus-4.8',
45
+ 'claude-code-opus-4-6': 'anthropic/claude-opus-4.8',
46
+ 'claude-code-opus-4.7': 'anthropic/claude-opus-4.8',
47
+ 'claude-code-opus-4-7': 'anthropic/claude-opus-4.8',
48
+ 'claude-code-opus-4.8': 'anthropic/claude-opus-4.8',
49
+ 'claude-code-opus-4-8': 'anthropic/claude-opus-4.8',
50
+ 'claude-code-opus1m': 'anthropic/claude-opus-4.8-1m',
51
+ 'claude-code-opus-1m': 'anthropic/claude-opus-4.8-1m',
52
+ 'claude-code-opus-4.8-1m': 'anthropic/claude-opus-4.8-1m',
53
+ 'claude-code-opus-4-8-1m': 'anthropic/claude-opus-4.8-1m',
54
+ 'claude-code-opus-4.7-1m': 'anthropic/claude-opus-4.8-1m',
55
+ 'claude-code-opus-4-7-1m': 'anthropic/claude-opus-4.8-1m',
51
56
  'claude-code-sonnet-4.5': 'anthropic/claude-sonnet-4.6',
52
57
  'claude-code-sonnet-4-5': 'anthropic/claude-sonnet-4.6',
53
58
  'claude-code-sonnet-4.6': 'anthropic/claude-sonnet-4.6',
@@ -57,25 +62,32 @@ function canonicalModel(modelId, harness) {
57
62
  'claude-haiku-4-5': 'anthropic/claude-haiku-4.5',
58
63
  'claude-sonnet-4-5': 'anthropic/claude-sonnet-4.6',
59
64
  'claude-sonnet-4-6': 'anthropic/claude-sonnet-4.6',
60
- 'claude-opus-4-5': 'anthropic/claude-opus-4.7',
61
- 'claude-opus-4-6': 'anthropic/claude-opus-4.7',
62
- 'claude-opus-4-7': 'anthropic/claude-opus-4.7',
63
- 'claude-opus-4-7-1m': 'anthropic/claude-opus-4.7-1m',
64
- 'claude-opus-4.7-1m': 'anthropic/claude-opus-4.7-1m',
65
+ 'claude-opus-4-5': 'anthropic/claude-opus-4.8',
66
+ 'claude-opus-4-6': 'anthropic/claude-opus-4.8',
67
+ 'claude-opus-4-7': 'anthropic/claude-opus-4.8',
68
+ 'claude-opus-4-8': 'anthropic/claude-opus-4.8',
69
+ 'claude-opus-4-8-1m': 'anthropic/claude-opus-4.8-1m',
70
+ 'claude-opus-4.8-1m': 'anthropic/claude-opus-4.8-1m',
71
+ 'claude-opus-4-7-1m': 'anthropic/claude-opus-4.8-1m',
72
+ 'claude-opus-4.7-1m': 'anthropic/claude-opus-4.8-1m',
65
73
  'anthropic/claude-haiku-4-5': 'anthropic/claude-haiku-4.5',
66
74
  'anthropic/claude-haiku-4.5': 'anthropic/claude-haiku-4.5',
67
75
  'anthropic/claude-sonnet-4-5': 'anthropic/claude-sonnet-4.6',
68
76
  'anthropic/claude-sonnet-4-6': 'anthropic/claude-sonnet-4.6',
69
77
  'anthropic/claude-sonnet-4.5': 'anthropic/claude-sonnet-4.6',
70
78
  'anthropic/claude-sonnet-4.6': 'anthropic/claude-sonnet-4.6',
71
- 'anthropic/claude-opus-4-5': 'anthropic/claude-opus-4.7',
72
- 'anthropic/claude-opus-4-6': 'anthropic/claude-opus-4.7',
73
- 'anthropic/claude-opus-4-7': 'anthropic/claude-opus-4.7',
74
- 'anthropic/claude-opus-4-7-1m': 'anthropic/claude-opus-4.7-1m',
75
- 'anthropic/claude-opus-4.5': 'anthropic/claude-opus-4.7',
76
- 'anthropic/claude-opus-4.6': 'anthropic/claude-opus-4.7',
77
- 'anthropic/claude-opus-4.7': 'anthropic/claude-opus-4.7',
78
- 'anthropic/claude-opus-4.7-1m': 'anthropic/claude-opus-4.7-1m',
79
+ 'anthropic/claude-opus-4-5': 'anthropic/claude-opus-4.8',
80
+ 'anthropic/claude-opus-4-6': 'anthropic/claude-opus-4.8',
81
+ 'anthropic/claude-opus-4-7': 'anthropic/claude-opus-4.8',
82
+ 'anthropic/claude-opus-4-8': 'anthropic/claude-opus-4.8',
83
+ 'anthropic/claude-opus-4-8-1m': 'anthropic/claude-opus-4.8-1m',
84
+ 'anthropic/claude-opus-4-7-1m': 'anthropic/claude-opus-4.8-1m',
85
+ 'anthropic/claude-opus-4.5': 'anthropic/claude-opus-4.8',
86
+ 'anthropic/claude-opus-4.6': 'anthropic/claude-opus-4.8',
87
+ 'anthropic/claude-opus-4.7': 'anthropic/claude-opus-4.8',
88
+ 'anthropic/claude-opus-4.8': 'anthropic/claude-opus-4.8',
89
+ 'anthropic/claude-opus-4.8-1m': 'anthropic/claude-opus-4.8-1m',
90
+ 'anthropic/claude-opus-4.7-1m': 'anthropic/claude-opus-4.8-1m',
79
91
  };
80
92
  if (aliases[cleaned]) return aliases[cleaned];
81
93
  if (cleaned.startsWith('anthropic/')) return cleaned.replace(/-(\d+)-(\d+)(?=$|-)/, '-$1.$2');
@@ -98,6 +110,8 @@ function cliModelFor({ harness, modelId, cliModel, reasoningEffort }) {
98
110
  const aliases = {
99
111
  'anthropic/claude-haiku-4.5': 'haiku',
100
112
  'anthropic/claude-sonnet-4.6': 'sonnet',
113
+ 'anthropic/claude-opus-4.8': 'opus',
114
+ 'anthropic/claude-opus-4.8-1m': 'opus[1m]',
101
115
  'anthropic/claude-opus-4.7': 'opus',
102
116
  'anthropic/claude-opus-4.7-1m': 'opus[1m]',
103
117
  'haiku': 'haiku',
@@ -347,8 +361,6 @@ function createContract(payload, options = {}) {
347
361
  userId,
348
362
  credentialId: payload.credentialId || payload.byokCredentialId || null,
349
363
  modelId,
350
- cliHomePath: payload.cliHomePath || payload.cli_home_path || null,
351
- authProfileId: payload.authProfileId || payload.auth_profile_id || null,
352
364
  });
353
365
  const contract = {
354
366
  sessionId,
@@ -179,7 +179,6 @@ class ChatCore {
179
179
  auth: {
180
180
  baseUrl: contract.auth.baseUrl,
181
181
  tokenFingerprint: contract.auth.tokenFingerprint,
182
- authProfileId: contract.auth.authProfileId,
183
182
  runtimeHome: contract.auth.runtimeHome,
184
183
  },
185
184
  },
@@ -7,8 +7,12 @@ const { normalizeUsage } = require('../usage');
7
7
 
8
8
  const NON_TOOL_ITEM_TYPES = new Set(['agentMessage', 'contextCompaction', 'reasoning', 'userMessage']);
9
9
 
10
+ function codexToolCallId(item = {}) {
11
+ return item?.id || item?.callId || item?.call_id || item?.toolCallId || item?.tool_call_id;
12
+ }
13
+
10
14
  function isCodexToolItem(item = {}) {
11
- return Boolean(item?.id && item?.type && !NON_TOOL_ITEM_TYPES.has(item.type));
15
+ return Boolean(codexToolCallId(item) && item?.type && !NON_TOOL_ITEM_TYPES.has(item.type));
12
16
  }
13
17
 
14
18
  function codexToolName(item = {}) {
@@ -37,7 +41,10 @@ function codexToolInput(item = {}) {
37
41
  if (item[key] !== undefined) input[key] = item[key];
38
42
  }
39
43
  if (item.input && typeof item.input === 'object') Object.assign(input, item.input);
40
- if (item.arguments && typeof item.arguments === 'object') Object.assign(input, item.arguments);
44
+ const args = parseObjectish(item.arguments);
45
+ if (args) Object.assign(input, args);
46
+ const rawInput = parseObjectish(item.rawInput);
47
+ if (rawInput) Object.assign(input, rawInput);
41
48
  if (item.action && typeof item.action === 'object') {
42
49
  if (useful(item.action.query) || !useful(input.query)) input.query = item.action.query;
43
50
  if (useful(item.action.url) || !useful(input.url)) input.url = item.action.url;
@@ -52,6 +59,18 @@ function useful(value) {
52
59
  return value !== undefined && value !== null && value !== '';
53
60
  }
54
61
 
62
+ function parseObjectish(value) {
63
+ if (!value) return null;
64
+ if (typeof value === 'object' && !Array.isArray(value)) return value;
65
+ if (typeof value !== 'string') return null;
66
+ try {
67
+ const parsed = JSON.parse(value);
68
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
55
74
  function codexToolOutput(item = {}) {
56
75
  if (item.status === 'failed' && item.error) return '';
57
76
  const direct = item.aggregatedOutput ?? item.output ?? item.result ?? item.diff ?? item.error ?? item.message ?? item.text ?? item.finalText;
@@ -122,7 +141,7 @@ function codexDelta(msg, p = {}) {
122
141
  }
123
142
 
124
143
  function codexItemId(p = {}) {
125
- return p.itemId || p.item?.id || p.id || p.callId || p.toolCallId;
144
+ return p.itemId || codexToolCallId(p.item) || p.id || p.callId || p.call_id || p.toolCallId || p.tool_call_id;
126
145
  }
127
146
 
128
147
  function codexTokenTotal(value = {}) {
@@ -248,11 +267,12 @@ function normalizeCodexNotification(msg, providerSessionId, state) {
248
267
  })];
249
268
  }
250
269
  if (method === 'item/started' && isCodexToolItem(p.item)) {
270
+ const toolCallId = codexToolCallId(p.item);
251
271
  const toolName = codexToolName(p.item);
252
272
  const input = codexToolInput(p.item);
253
273
  const detail = codexToolDetail(p.item);
254
274
  const metadata = codexToolMetadata(p.item);
255
- return [toolStarted(p.item.id, {
275
+ return [toolStarted(toolCallId, {
256
276
  providerSessionId,
257
277
  toolName,
258
278
  title: codexToolTitle(p.item),
@@ -265,10 +285,11 @@ function normalizeCodexNotification(msg, providerSessionId, state) {
265
285
  })];
266
286
  }
267
287
  if (method === 'item/updated' && isCodexToolItem(p.item)) {
288
+ const toolCallId = codexToolCallId(p.item);
268
289
  const input = codexToolInput(p.item);
269
290
  const toolName = codexToolName(p.item);
270
291
  const metadata = codexToolMetadata(p.item);
271
- return [toolUpdated(p.item.id, {
292
+ return [toolUpdated(toolCallId, {
272
293
  providerSessionId,
273
294
  toolName,
274
295
  title: codexToolTitle(p.item),
@@ -282,13 +303,14 @@ function normalizeCodexNotification(msg, providerSessionId, state) {
282
303
  })];
283
304
  }
284
305
  if (method === 'item/completed' && isCodexToolItem(p.item)) {
306
+ const toolCallId = codexToolCallId(p.item);
285
307
  const toolName = codexToolName(p.item);
286
308
  const input = codexToolInput(p.item);
287
309
  const output = codexToolOutput(p.item);
288
310
  const error = codexToolError(p.item);
289
311
  const metadata = codexToolMetadata(p.item);
290
312
  const detail = codexToolDetail(p.item);
291
- return [toolCompleted(p.item.id, {
313
+ return [toolCompleted(toolCallId, {
292
314
  providerSessionId,
293
315
  toolName,
294
316
  title: codexToolTitle(p.item),
@@ -384,6 +406,7 @@ module.exports = {
384
406
  codexToolInput,
385
407
  codexToolDetail,
386
408
  codexToolError,
409
+ codexToolCallId,
387
410
  codexToolName,
388
411
  codexToolOutput,
389
412
  codexToolTitle,
@@ -24,6 +24,8 @@ function toToolPartType(name) {
24
24
  agent: 'task',
25
25
  task: 'task',
26
26
  askuserquestion: 'question',
27
+ toolsearch: 'toolSearch',
28
+ toolsearchtool: 'toolSearch',
27
29
  };
28
30
  return `tool-${aliases[clean] || clean}`;
29
31
  }
@@ -1,37 +1,54 @@
1
1
  'use strict';
2
2
 
3
3
  const assert = require('node:assert/strict');
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
4
7
  const test = require('node:test');
5
8
  const { authEnvelope, runtimeEnv } = require('../auth');
6
9
 
7
- test('amalgm auth uses a pinned CLI home across sessions and proxy token refreshes', () => {
8
- const first = authEnvelope({
9
- harness: 'codex',
10
- authMethod: 'amalgm',
11
- sessionId: 'session-one',
12
- proxyToken: 'proxy-token-one',
13
- localBaseUrl: 'http://127.0.0.1:8084',
14
- proxyBaseUrl: 'https://proxy.example.test',
15
- amalgmDir: '/tmp/amalgm-test',
16
- userId: 'user-123',
17
- });
18
- const second = authEnvelope({
19
- harness: 'codex',
20
- authMethod: 'amalgm',
21
- sessionId: 'session-two',
22
- proxyToken: 'proxy-token-two',
23
- localBaseUrl: 'http://127.0.0.1:8084',
24
- proxyBaseUrl: 'https://proxy.example.test',
25
- amalgmDir: '/tmp/amalgm-test',
26
- userId: 'user-123',
27
- });
10
+ test('codex always uses one canonical CLI home across auth methods and token refreshes', () => {
11
+ const amalgmDir = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-auth-test-'));
12
+ fs.writeFileSync(path.join(amalgmDir, '.amalgm-credentials'), 'OPENAI_API_KEY=sk-test\n');
13
+ try {
14
+ const first = authEnvelope({
15
+ harness: 'codex',
16
+ authMethod: 'amalgm',
17
+ sessionId: 'session-one',
18
+ proxyToken: 'proxy-token-one',
19
+ localBaseUrl: 'http://127.0.0.1:8084',
20
+ proxyBaseUrl: 'https://proxy.example.test',
21
+ amalgmDir,
22
+ userId: 'user-123',
23
+ });
24
+ const second = authEnvelope({
25
+ harness: 'codex',
26
+ authMethod: 'provider_auth',
27
+ sessionId: 'session-two',
28
+ proxyToken: 'proxy-token-two',
29
+ localBaseUrl: 'http://127.0.0.1:8084',
30
+ proxyBaseUrl: 'https://proxy.example.test',
31
+ amalgmDir,
32
+ userId: 'user-123',
33
+ });
34
+ const third = authEnvelope({
35
+ harness: 'codex',
36
+ authMethod: 'byok',
37
+ sessionId: 'session-three',
38
+ amalgmDir,
39
+ userId: 'user-123',
40
+ });
28
41
 
29
- assert.notEqual(first.tokenFingerprint, second.tokenFingerprint);
30
- assert.equal(first.runtimeHome, second.runtimeHome);
31
- assert.match(first.runtimeHome, /\/tmp\/amalgm-test\/cli-homes\/codex\/amalgm-[0-9a-f]{16}$/);
42
+ assert.notEqual(first.tokenFingerprint, second.tokenFingerprint);
43
+ assert.equal(first.runtimeHome, second.runtimeHome);
44
+ assert.equal(second.runtimeHome, third.runtimeHome);
45
+ assert.equal(first.runtimeHome, path.join(amalgmDir, 'cli-homes', 'codex', 'home'));
46
+ } finally {
47
+ fs.rmSync(amalgmDir, { recursive: true, force: true });
48
+ }
32
49
  });
33
50
 
34
- test('opencode amalgm auth uses a pinned CLI home across sessions and proxy token refreshes', () => {
51
+ test('opencode always uses one canonical CLI home across auth methods and token refreshes', () => {
35
52
  const first = authEnvelope({
36
53
  harness: 'opencode',
37
54
  authMethod: 'amalgm',
@@ -44,7 +61,7 @@ test('opencode amalgm auth uses a pinned CLI home across sessions and proxy toke
44
61
  });
45
62
  const second = authEnvelope({
46
63
  harness: 'opencode',
47
- authMethod: 'amalgm',
64
+ authMethod: 'provider_auth',
48
65
  sessionId: 'session-two',
49
66
  proxyToken: 'proxy-token-two',
50
67
  localBaseUrl: 'http://127.0.0.1:8084',
@@ -56,13 +73,15 @@ test('opencode amalgm auth uses a pinned CLI home across sessions and proxy toke
56
73
 
57
74
  assert.notEqual(first.tokenFingerprint, second.tokenFingerprint);
58
75
  assert.equal(first.runtimeHome, second.runtimeHome);
59
- assert.match(first.runtimeHome, /\/tmp\/amalgm-test\/cli-homes\/opencode\/amalgm-[0-9a-f]{16}$/);
76
+ assert.equal(first.runtimeHome, '/tmp/amalgm-test/cli-homes/opencode/home');
60
77
  assert.equal(env.HOME, first.runtimeHome);
61
78
  assert.equal(env.OPENCODE_HOME, first.runtimeHome);
62
79
  assert.equal(env.OPENCODE_CONFIG_DIR, first.runtimeHome);
80
+ assert.equal(env.XDG_CONFIG_HOME, '/tmp/amalgm-test/cli-homes/opencode/home/.config');
81
+ assert.equal(env.XDG_DATA_HOME, '/tmp/amalgm-test/cli-homes/opencode/home/.local/share');
63
82
  });
64
83
 
65
- test('claude provider auth uses a pinned CLI home with native config copied separately', () => {
84
+ test('claude always uses one canonical CLI home', () => {
66
85
  const envelope = authEnvelope({
67
86
  harness: 'claude_code',
68
87
  authMethod: 'provider_auth',
@@ -70,7 +89,7 @@ test('claude provider auth uses a pinned CLI home with native config copied sepa
70
89
  amalgmDir: '/tmp/amalgm-test',
71
90
  });
72
91
 
73
- assert.match(envelope.runtimeHome, /\/tmp\/amalgm-test\/cli-homes\/claude_code\/provider-[0-9a-f]{16}$/);
92
+ assert.equal(envelope.runtimeHome, '/tmp/amalgm-test/cli-homes/claude_code/home');
74
93
 
75
94
  const env = runtimeEnv({
76
95
  harness: 'claude_code',
@@ -88,19 +107,7 @@ test('claude provider auth uses a pinned CLI home with native config copied sepa
88
107
  assert.equal(env.ANTHROPIC_API_KEY, undefined);
89
108
  });
90
109
 
91
- test('codex provider auth uses a pinned provider home', () => {
92
- const envelope = authEnvelope({
93
- harness: 'codex',
94
- authMethod: 'provider_auth',
95
- sessionId: 'session-test',
96
- amalgmDir: '/tmp/amalgm-test',
97
- });
98
-
99
- assert.match(envelope.runtimeHome, /\/tmp\/amalgm-test\/cli-homes\/codex\/provider-[0-9a-f]{16}$/);
100
- assert.equal(envelope.runtimeHome.includes('/runtime/session-test/'), false);
101
- });
102
-
103
- test('stored CLI home path wins over derived path', () => {
110
+ test('provided CLI home path cannot move an agent off its canonical home', () => {
104
111
  const envelope = authEnvelope({
105
112
  harness: 'codex',
106
113
  authMethod: 'amalgm',
@@ -114,6 +121,5 @@ test('stored CLI home path wins over derived path', () => {
114
121
  authProfileId: 'manual-profile',
115
122
  });
116
123
 
117
- assert.equal(envelope.authProfileId, 'manual-profile');
118
- assert.equal(envelope.runtimeHome, '/tmp/amalgm-test/cli-homes/codex/manual-profile');
124
+ assert.equal(envelope.runtimeHome, '/tmp/amalgm-test/cli-homes/codex/home');
119
125
  });
@@ -14,7 +14,7 @@ function payload(overrides = {}) {
14
14
  userId: 'user-test',
15
15
  agentId: 'claude_code',
16
16
  authMethod: 'amalgm',
17
- modelId: 'anthropic/claude-opus-4.7',
17
+ modelId: 'anthropic/claude-opus-4.8',
18
18
  cwd: process.cwd(),
19
19
  prompt: 'hello',
20
20
  proxyToken: 'proxy-token',
@@ -0,0 +1,126 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+ const { spawnSync } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+ const test = require('node:test');
9
+ const nativeBinaries = require('../tooling/native-binaries');
10
+
11
+ function packagePath(root, packageName) {
12
+ return path.join(root, ...String(packageName).split('/'));
13
+ }
14
+
15
+ function writePackage(root, packageName) {
16
+ fs.mkdirSync(root, { recursive: true });
17
+ fs.writeFileSync(path.join(root, 'package.json'), `${JSON.stringify({ name: packageName, version: '0.0.0' }, null, 2)}\n`);
18
+ }
19
+
20
+ function writeExecutable(file) {
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ if (process.platform === 'win32') {
23
+ fs.writeFileSync(file, '@echo off\r\nexit /b 0\r\n');
24
+ } else {
25
+ fs.writeFileSync(file, '#!/bin/sh\nexit 0\n');
26
+ fs.chmodSync(file, 0o755);
27
+ }
28
+ }
29
+
30
+ function installFakePackagedAgentDeps(resourcesPath) {
31
+ const nodeModules = path.join(resourcesPath, 'app.asar.unpacked', 'node_modules');
32
+ const claudePackage = nativeBinaries.__private.claudeTargetPackage();
33
+ const codexTarget = nativeBinaries.__private.codexTarget();
34
+ const openCodePackage = nativeBinaries.__private.openCodePackageCandidates()[0];
35
+ assert.ok(claudePackage, 'current platform should have a Claude native package');
36
+ assert.ok(codexTarget, 'current platform should have a Codex native package');
37
+ assert.ok(openCodePackage, 'current platform should have an OpenCode native package');
38
+
39
+ writePackage(path.join(nodeModules, '@anthropic-ai', 'claude-agent-sdk'), '@anthropic-ai/claude-agent-sdk');
40
+ writePackage(path.join(nodeModules, '@openai', 'codex'), '@openai/codex');
41
+ writePackage(path.join(nodeModules, 'opencode-ai'), 'opencode-ai');
42
+
43
+ const claudeRoot = packagePath(
44
+ path.join(nodeModules, '@anthropic-ai', 'claude-agent-sdk', 'node_modules'),
45
+ claudePackage,
46
+ );
47
+ writePackage(claudeRoot, claudePackage);
48
+ const claude = path.join(claudeRoot, process.platform === 'win32' ? 'claude.exe' : 'claude');
49
+ writeExecutable(claude);
50
+
51
+ const codexRoot = packagePath(
52
+ path.join(nodeModules, '@openai', 'codex', 'node_modules'),
53
+ codexTarget.packageName,
54
+ );
55
+ writePackage(codexRoot, codexTarget.packageName);
56
+ const codex = path.join(codexRoot, 'vendor', codexTarget.triple, 'codex', codexTarget.binaryName);
57
+ writeExecutable(codex);
58
+
59
+ const openCodeRoot = packagePath(
60
+ path.join(nodeModules, 'opencode-ai', 'node_modules'),
61
+ openCodePackage,
62
+ );
63
+ writePackage(openCodeRoot, openCodePackage);
64
+ const opencode = path.join(openCodeRoot, 'bin', process.platform === 'win32' ? 'opencode.exe' : 'opencode');
65
+ writeExecutable(opencode);
66
+
67
+ return { claude, codex, opencode };
68
+ }
69
+
70
+ function withFakePackagedResources(fn) {
71
+ const previousResourcesPath = Object.getOwnPropertyDescriptor(process, 'resourcesPath');
72
+ const previousEnv = {
73
+ AMALGM_AGENT_BIN_DIR: process.env.AMALGM_AGENT_BIN_DIR,
74
+ AMALGM_NATIVE_NODE_MODULES: process.env.AMALGM_NATIVE_NODE_MODULES,
75
+ CLAUDE_CODE_BINARY: process.env.CLAUDE_CODE_BINARY,
76
+ PATH: process.env.PATH,
77
+ };
78
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-native-binaries-'));
79
+ const resourcesPath = path.join(root, 'resources');
80
+ const binDir = path.join(root, 'bin');
81
+ try {
82
+ Object.defineProperty(process, 'resourcesPath', {
83
+ value: resourcesPath,
84
+ configurable: true,
85
+ });
86
+ process.env.AMALGM_AGENT_BIN_DIR = binDir;
87
+ process.env.PATH = '';
88
+ delete process.env.AMALGM_NATIVE_NODE_MODULES;
89
+ delete process.env.CLAUDE_CODE_BINARY;
90
+ return fn({ root, resourcesPath, binDir });
91
+ } finally {
92
+ if (previousResourcesPath) {
93
+ Object.defineProperty(process, 'resourcesPath', previousResourcesPath);
94
+ } else {
95
+ delete process.resourcesPath;
96
+ }
97
+ for (const [key, value] of Object.entries(previousEnv)) {
98
+ if (value === undefined) delete process.env[key];
99
+ else process.env[key] = value;
100
+ }
101
+ fs.rmSync(root, { recursive: true, force: true });
102
+ }
103
+ }
104
+
105
+ test('packaged nested agent dependencies resolve to exact executable paths and spawnable shims', () => {
106
+ withFakePackagedResources(({ resourcesPath, binDir }) => {
107
+ const expected = installFakePackagedAgentDeps(resourcesPath);
108
+
109
+ assert.equal(nativeBinaries.__private.bundledClaudeBinary(), expected.claude);
110
+ assert.equal(nativeBinaries.bundledCodexBinary(), expected.codex);
111
+ assert.equal(nativeBinaries.bundledOpenCodeBinary(), expected.opencode);
112
+
113
+ const result = nativeBinaries.ensureAgentCommandShims({ quiet: true, logger: { log() {} } });
114
+ assert.equal(result.ok, true);
115
+ for (const command of ['claude', 'codex', 'opencode']) {
116
+ const shim = path.join(binDir, process.platform === 'win32' ? `${command}.cmd` : command);
117
+ assert.equal(fs.existsSync(shim), true);
118
+ const spawned = spawnSync(shim, ['--smoke'], {
119
+ stdio: 'ignore',
120
+ shell: process.platform === 'win32',
121
+ windowsHide: true,
122
+ });
123
+ assert.equal(spawned.status, 0, `${command} shim should spawn`);
124
+ }
125
+ });
126
+ });