amalgm 0.1.70 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.70",
3
+ "version": "0.1.71",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -24,9 +24,10 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@ai-sdk/gateway": "^3.0.107",
27
- "@anthropic-ai/claude-agent-sdk": "^0.2.122",
27
+ "@anthropic-ai/claude-agent-sdk": "^0.3.154",
28
+ "@anthropic-ai/sdk": "^0.100.0",
28
29
  "@homebridge/node-pty-prebuilt-multiarch": "^0.13.1",
29
- "@modelcontextprotocol/sdk": "^1.27.1",
30
+ "@modelcontextprotocol/sdk": "^1.29.0",
30
31
  "@openai/codex": "^0.128.0",
31
32
  "@opencode-ai/sdk": "^1.14.29",
32
33
  "agent-browser": "^0.27.0",
@@ -55,29 +55,34 @@ function buildGatewayCliModel(gatewayId, harness) {
55
55
 
56
56
  // lib/modelNaming.ts
57
57
  var DEFAULT_MODEL_IDS = {
58
- claude_code: "anthropic/claude-opus-4.7",
58
+ claude_code: "anthropic/claude-opus-4.8",
59
59
  codex: "openai/gpt-5.5",
60
60
  opencode: "opencode-deepseek-deepseek-v4-pro",
61
61
  pi: "pi-anthropic-claude-sonnet-4.6",
62
62
  amp: "amp-default",
63
63
  cursor: "cursor/composer-2-fast"
64
64
  };
65
- var CLAUDE_OPUS_4_7_1M_MODEL_ID = "anthropic/claude-opus-4.7-1m";
65
+ var CLAUDE_OPUS_4_8_1M_MODEL_ID = "anthropic/claude-opus-4.8-1m";
66
66
  var CLAUDE_LEGACY_ALIASES = {
67
- "claude-code-opus": "anthropic/claude-opus-4.7",
68
- "claude-code-opus-47": "anthropic/claude-opus-4.7",
69
- "claude-code-opus-46": "anthropic/claude-opus-4.7",
70
- "claude-code-opus-45": "anthropic/claude-opus-4.7",
71
- "claude-code-opus-4.7": "anthropic/claude-opus-4.7",
72
- "claude-code-opus-4-7": "anthropic/claude-opus-4.7",
73
- "claude-code-opus-4.6": "anthropic/claude-opus-4.7",
74
- "claude-code-opus-4-6": "anthropic/claude-opus-4.7",
75
- "claude-code-opus-4.5": "anthropic/claude-opus-4.7",
76
- "claude-code-opus-4-5": "anthropic/claude-opus-4.7",
77
- "claude-code-opus1m": CLAUDE_OPUS_4_7_1M_MODEL_ID,
78
- "claude-code-opus-1m": CLAUDE_OPUS_4_7_1M_MODEL_ID,
79
- "claude-code-opus-4.7-1m": CLAUDE_OPUS_4_7_1M_MODEL_ID,
80
- "claude-code-opus-4-7-1m": CLAUDE_OPUS_4_7_1M_MODEL_ID,
67
+ "claude-code-opus": "anthropic/claude-opus-4.8",
68
+ "claude-code-opus-48": "anthropic/claude-opus-4.8",
69
+ "claude-code-opus-47": "anthropic/claude-opus-4.8",
70
+ "claude-code-opus-46": "anthropic/claude-opus-4.8",
71
+ "claude-code-opus-45": "anthropic/claude-opus-4.8",
72
+ "claude-code-opus-4.8": "anthropic/claude-opus-4.8",
73
+ "claude-code-opus-4-8": "anthropic/claude-opus-4.8",
74
+ "claude-code-opus-4.7": "anthropic/claude-opus-4.8",
75
+ "claude-code-opus-4-7": "anthropic/claude-opus-4.8",
76
+ "claude-code-opus-4.6": "anthropic/claude-opus-4.8",
77
+ "claude-code-opus-4-6": "anthropic/claude-opus-4.8",
78
+ "claude-code-opus-4.5": "anthropic/claude-opus-4.8",
79
+ "claude-code-opus-4-5": "anthropic/claude-opus-4.8",
80
+ "claude-code-opus1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
81
+ "claude-code-opus-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
82
+ "claude-code-opus-4.8-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
83
+ "claude-code-opus-4-8-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
84
+ "claude-code-opus-4.7-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
85
+ "claude-code-opus-4-7-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
81
86
  "claude-code-sonnet": "anthropic/claude-sonnet-4.6",
82
87
  "claude-code-sonnet-45": "anthropic/claude-sonnet-4.6",
83
88
  "claude-code-sonnet-4.6": "anthropic/claude-sonnet-4.6",
@@ -87,21 +92,25 @@ var CLAUDE_LEGACY_ALIASES = {
87
92
  "claude-code-haiku": "anthropic/claude-haiku-4.5",
88
93
  "claude-code-haiku-4.5": "anthropic/claude-haiku-4.5",
89
94
  "claude-code-haiku-4-5": "anthropic/claude-haiku-4.5",
90
- "anthropic/claude-opus-4.6": "anthropic/claude-opus-4.7",
91
- "anthropic/claude-opus-4.5": "anthropic/claude-opus-4.7",
95
+ "anthropic/claude-opus-4.7": "anthropic/claude-opus-4.8",
96
+ "anthropic/claude-opus-4.6": "anthropic/claude-opus-4.8",
97
+ "anthropic/claude-opus-4.5": "anthropic/claude-opus-4.8",
92
98
  "anthropic/claude-sonnet-4.5": "anthropic/claude-sonnet-4.6",
93
- "claude-opus-4.7-1m": CLAUDE_OPUS_4_7_1M_MODEL_ID,
94
- "claude-opus-4-7-1m": CLAUDE_OPUS_4_7_1M_MODEL_ID,
95
- opus: "anthropic/claude-opus-4.7",
96
- opus1m: CLAUDE_OPUS_4_7_1M_MODEL_ID,
97
- "opus[1m]": CLAUDE_OPUS_4_7_1M_MODEL_ID,
99
+ "claude-opus-4.8-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
100
+ "claude-opus-4-8-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
101
+ "claude-opus-4.7-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
102
+ "claude-opus-4-7-1m": CLAUDE_OPUS_4_8_1M_MODEL_ID,
103
+ opus: "anthropic/claude-opus-4.8",
104
+ opus1m: CLAUDE_OPUS_4_8_1M_MODEL_ID,
105
+ "opus[1m]": CLAUDE_OPUS_4_8_1M_MODEL_ID,
98
106
  sonnet: "anthropic/claude-sonnet-4.6",
99
107
  "sonnet[1m]": "anthropic/claude-sonnet-4.6",
100
108
  haiku: "anthropic/claude-haiku-4.5"
101
109
  };
102
110
  var CLAUDE_HARNESS_MODELS = {
111
+ "anthropic/claude-opus-4.8": "opus",
112
+ [CLAUDE_OPUS_4_8_1M_MODEL_ID]: "opus[1m]",
103
113
  "anthropic/claude-opus-4.7": "opus",
104
- [CLAUDE_OPUS_4_7_1M_MODEL_ID]: "opus[1m]",
105
114
  "anthropic/claude-opus-4.6": "opus",
106
115
  "anthropic/claude-opus-4.5": "opus",
107
116
  "anthropic/claude-sonnet-4.6": "sonnet",
@@ -109,7 +118,7 @@ var CLAUDE_HARNESS_MODELS = {
109
118
  "anthropic/claude-haiku-4.5": "haiku"
110
119
  };
111
120
  var INTERNAL_TO_GATEWAY_MODEL_IDS = {
112
- [CLAUDE_OPUS_4_7_1M_MODEL_ID]: "anthropic/claude-opus-4.7"
121
+ [CLAUDE_OPUS_4_8_1M_MODEL_ID]: "anthropic/claude-opus-4.8"
113
122
  };
114
123
  var CURSOR_MODEL_ALIASES = {
115
124
  "cursor-opus-4.6-thinking": "anthropic/claude-opus-4.6-high-thinking",
@@ -148,11 +157,12 @@ function normalizeClaudeModelPart(modelPart) {
148
157
  const hasOneMillionContext = /(?:\[\s*1m\s*\]|:1m|-1m)$/i.test(modelPart);
149
158
  const base = modelPart.replace(/\[\s*1m\s*\]$/i, "").replace(/:1m$/i, "").replace(/-1m$/i, "").replace(/-\d{8}$/, "").replace(/-(\d+)-(\d+)(?=$|[-:])/, "-$1.$2");
150
159
  const currentBase = {
151
- "claude-opus-4.6": "claude-opus-4.7",
152
- "claude-opus-4.5": "claude-opus-4.7",
160
+ "claude-opus-4.7": "claude-opus-4.8",
161
+ "claude-opus-4.6": "claude-opus-4.8",
162
+ "claude-opus-4.5": "claude-opus-4.8",
153
163
  "claude-sonnet-4.5": "claude-sonnet-4.6"
154
164
  }[base] ?? base;
155
- return hasOneMillionContext && currentBase === "claude-opus-4.7" ? `${currentBase}-1m` : currentBase;
165
+ return hasOneMillionContext && currentBase === "claude-opus-4.8" ? `${currentBase}-1m` : currentBase;
156
166
  }
157
167
  function normalizeClaudeModelId(value) {
158
168
  const cleaned = cleanModelId(value);
@@ -404,20 +414,20 @@ var CLAUDE_MODELS = [
404
414
  providerGroup: "anthropic"
405
415
  },
406
416
  {
407
- id: "anthropic/claude-opus-4.7",
408
- name: "Opus 4.7",
417
+ id: "anthropic/claude-opus-4.8",
418
+ name: "Opus 4.8",
409
419
  description: "Most capable Claude Code model",
410
- gatewayModelId: "anthropic/claude-opus-4.7",
411
- cliModel: modelIdToHarnessModel("anthropic/claude-opus-4.7", "claude_code"),
420
+ gatewayModelId: "anthropic/claude-opus-4.8",
421
+ cliModel: modelIdToHarnessModel("anthropic/claude-opus-4.8", "claude_code"),
412
422
  provider: "claude",
413
423
  providerGroup: "anthropic"
414
424
  },
415
425
  {
416
- id: "anthropic/claude-opus-4.7-1m",
417
- name: "Opus 4.7 1M",
426
+ id: "anthropic/claude-opus-4.8-1m",
427
+ name: "Opus 4.8 1M",
418
428
  description: "Claude Code Opus with 1M context",
419
- gatewayModelId: "anthropic/claude-opus-4.7",
420
- cliModel: modelIdToHarnessModel("anthropic/claude-opus-4.7-1m", "claude_code"),
429
+ gatewayModelId: "anthropic/claude-opus-4.8",
430
+ cliModel: modelIdToHarnessModel("anthropic/claude-opus-4.8-1m", "claude_code"),
421
431
  provider: "claude",
422
432
  providerGroup: "anthropic"
423
433
  }
@@ -42,7 +42,7 @@ const BUILTIN_AGENT_BLUEPRINTS = [
42
42
  'Anthropic Claude agent — general-purpose coding, research, and reasoning',
43
43
  adapter: 'claude_code',
44
44
  baseHarnessId: 'claude_code',
45
- baseModelId: 'anthropic/claude-opus-4.7',
45
+ baseModelId: 'anthropic/claude-opus-4.8',
46
46
  },
47
47
  {
48
48
  id: 'codex',
@@ -22,6 +22,7 @@ const {
22
22
  supabasePatch,
23
23
  supabaseSelect,
24
24
  } = require('../lib/supabase');
25
+ const { resolveLocalComputerId } = require('../lib/computer');
25
26
  const {
26
27
  hydrateModelPreferences,
27
28
  getPreferredAuthMethod,
@@ -537,6 +538,7 @@ async function handleTalkToAgent(args, context = {}) {
537
538
  const userMessageId = crypto.randomUUID();
538
539
  const assistantMessageId = crypto.randomUUID();
539
540
  const runId = assistantMessageId;
541
+ const computerId = resolveLocalComputerId();
540
542
  const asyncMode = run_in_background === true || String(mode || '').toLowerCase() === 'async';
541
543
  const includeMetrics = includeMetricsFlag(include_metrics);
542
544
  const foregroundTimeoutMs = coerceForegroundTimeoutMs(timeout_ms);
@@ -702,44 +704,50 @@ async function handleTalkToAgent(args, context = {}) {
702
704
  }
703
705
  }
704
706
 
705
- await supabaseInsert('sessions', {
706
- id: sessionId,
707
- user_id: AMALGM_USER_ID,
708
- container_id: os.hostname() || 'agent-conversation',
709
- harness: agent.id,
710
- title: taskDescription || 'New Chat',
711
- status: 'in-progress',
712
- origin: 'agent',
713
- origin_id: callerSessionId,
714
- metadata: {
715
- agentId: agent.id,
716
- agentName: agent.name,
717
- taskDescription,
718
- baseHarnessId: agent.baseHarnessId,
719
- baseModelId: agent.builtin
720
- ? getSelectedModel(agent.baseHarnessId) || agent.baseModelId
721
- : agent.baseModelId ||
722
- getSelectedModel(agent.baseHarnessId) ||
723
- DEFAULT_SELECTED_MODELS[agent.baseHarnessId],
724
- isAgentConversation: true,
725
- builtin: !!agent.builtin,
726
- systemPrompt: baseAgentSystemPrompt || '',
727
- ...(returnChannelInstructions
728
- ? {
729
- returnChannel: {
730
- deliveryTool: 'talk_to_agent',
731
- mode: 'response_to_caller',
732
- callerConversationId: normalizeConversationId(context.callerSessionId),
733
- },
734
- }
735
- : {}),
736
- files: agentFiles,
737
- skills: agentSkills,
738
- mcpAppIds: agentMcpAppIds,
739
- tools: agentTools,
740
- ...(parentOrigin || {}),
741
- },
742
- });
707
+ if (!computerId) {
708
+ console.error('[AmalgmMCP:Agent] DB session creation skipped: missing registered computer_id');
709
+ } else {
710
+ await supabaseInsert('sessions', {
711
+ id: sessionId,
712
+ user_id: AMALGM_USER_ID,
713
+ computer_id: computerId,
714
+ container_id: os.hostname() || 'agent-conversation',
715
+ harness: agent.id,
716
+ title: taskDescription || 'New Chat',
717
+ status: 'in-progress',
718
+ origin: 'agent',
719
+ origin_id: callerSessionId,
720
+ metadata: {
721
+ agentId: agent.id,
722
+ agentName: agent.name,
723
+ taskDescription,
724
+ computerId,
725
+ baseHarnessId: agent.baseHarnessId,
726
+ baseModelId: agent.builtin
727
+ ? getSelectedModel(agent.baseHarnessId) || agent.baseModelId
728
+ : agent.baseModelId ||
729
+ getSelectedModel(agent.baseHarnessId) ||
730
+ DEFAULT_SELECTED_MODELS[agent.baseHarnessId],
731
+ isAgentConversation: true,
732
+ builtin: !!agent.builtin,
733
+ systemPrompt: baseAgentSystemPrompt || '',
734
+ ...(returnChannelInstructions
735
+ ? {
736
+ returnChannel: {
737
+ deliveryTool: 'talk_to_agent',
738
+ mode: 'response_to_caller',
739
+ callerConversationId: normalizeConversationId(context.callerSessionId),
740
+ },
741
+ }
742
+ : {}),
743
+ files: agentFiles,
744
+ skills: agentSkills,
745
+ mcpAppIds: agentMcpAppIds,
746
+ tools: agentTools,
747
+ ...(parentOrigin || {}),
748
+ },
749
+ });
750
+ }
743
751
  } catch (err) {
744
752
  console.error('[AmalgmMCP:Agent] DB session creation failed:', err.message);
745
753
  }
@@ -777,6 +785,8 @@ async function handleTalkToAgent(args, context = {}) {
777
785
  prompt: legacyChatFields.prompt,
778
786
  userId: AMALGM_USER_ID,
779
787
  codeSessionId: sessionId,
788
+ computerId,
789
+ skipPersistence: hasSupabase() && isNewConversation && !computerId,
780
790
  assistantMessageId,
781
791
  userMessageId,
782
792
  userParts: legacyChatFields.userParts,
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const {
7
+ AMALGM_DIR,
8
+ AMALGM_USER_ID,
9
+ COMPUTER_RECORD_FILE,
10
+ } = require('../config');
11
+
12
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
13
+
14
+ function nonEmptyString(value) {
15
+ return typeof value === 'string' && value.trim() ? value.trim() : '';
16
+ }
17
+
18
+ function isUuid(value) {
19
+ return UUID_RE.test(nonEmptyString(value));
20
+ }
21
+
22
+ function readJson(filePath) {
23
+ try {
24
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function runtimeLabel() {
31
+ const raw = nonEmptyString(
32
+ process.env.AMALGM_RUNTIME_LABEL
33
+ || process.env.NEXT_PUBLIC_AMALGM_RUNTIME_LABEL
34
+ || process.env.AMALGM_BRANCH,
35
+ ).toLowerCase();
36
+ if (['preview', 'canary', 'native-chat', 'staging'].includes(raw)) return 'preview';
37
+ if (['local', 'dev', 'development'].includes(raw)) return 'local';
38
+ return 'main';
39
+ }
40
+
41
+ function candidateComputerFiles() {
42
+ const home = process.env.AMALGM_HOME || path.join(os.homedir(), '.amalgm');
43
+ const label = runtimeLabel();
44
+ const files = [
45
+ COMPUTER_RECORD_FILE,
46
+ path.join(AMALGM_DIR, 'runtime-state.json'),
47
+ path.join(AMALGM_DIR, 'runtimes', label, 'runtime-state.json'),
48
+ path.join(home, 'computer.json'),
49
+ ];
50
+ if (AMALGM_USER_ID) {
51
+ files.push(
52
+ path.join(home, 'users', AMALGM_USER_ID, 'computer.json'),
53
+ path.join(home, 'users', AMALGM_USER_ID, 'runtimes', label, 'runtime-state.json'),
54
+ );
55
+ }
56
+ return [...new Set(files)];
57
+ }
58
+
59
+ function resolveLocalComputerId() {
60
+ const explicit = nonEmptyString(process.env.AMALGM_COMPUTER_ID);
61
+ if (isUuid(explicit)) return explicit;
62
+
63
+ for (const filePath of candidateComputerFiles()) {
64
+ const record = readJson(filePath);
65
+ if (isUuid(record?.computer_id)) return record.computer_id.trim();
66
+ }
67
+
68
+ const legacyContainerId = nonEmptyString(process.env.AMALGM_CONTAINER_ID);
69
+ return isUuid(legacyContainerId) ? legacyContainerId : '';
70
+ }
71
+
72
+ module.exports = {
73
+ isUuid,
74
+ resolveLocalComputerId,
75
+ };
@@ -14,7 +14,7 @@ const {
14
14
  } = require('../../lib/project-paths');
15
15
 
16
16
  const DEFAULT_SELECTED_MODELS = {
17
- claude_code: 'anthropic/claude-opus-4.7',
17
+ claude_code: 'anthropic/claude-opus-4.8',
18
18
  codex: 'openai/gpt-5.5',
19
19
  opencode: 'opencode-deepseek-deepseek-v4-pro',
20
20
  pi: 'pi-anthropic-claude-sonnet-4.6',
@@ -48,23 +48,28 @@ const GATEWAY_PROVIDER_PREFIXES = [
48
48
  'perplexity', 'xai', 'xiaomi', 'zai',
49
49
  ];
50
50
 
51
- const CLAUDE_OPUS_4_7_1M_MODEL_ID = 'anthropic/claude-opus-4.7-1m';
51
+ const CLAUDE_OPUS_4_8_1M_MODEL_ID = 'anthropic/claude-opus-4.8-1m';
52
52
 
53
53
  const CLAUDE_LEGACY_ALIASES = {
54
- 'claude-code-opus': 'anthropic/claude-opus-4.7',
55
- 'claude-code-opus-47': 'anthropic/claude-opus-4.7',
56
- 'claude-code-opus-46': 'anthropic/claude-opus-4.7',
57
- 'claude-code-opus-45': 'anthropic/claude-opus-4.7',
58
- 'claude-code-opus-4.7': 'anthropic/claude-opus-4.7',
59
- 'claude-code-opus-4-7': 'anthropic/claude-opus-4.7',
60
- 'claude-code-opus-4.6': 'anthropic/claude-opus-4.7',
61
- 'claude-code-opus-4-6': 'anthropic/claude-opus-4.7',
62
- 'claude-code-opus-4.5': 'anthropic/claude-opus-4.7',
63
- 'claude-code-opus-4-5': 'anthropic/claude-opus-4.7',
64
- 'claude-code-opus1m': CLAUDE_OPUS_4_7_1M_MODEL_ID,
65
- 'claude-code-opus-1m': CLAUDE_OPUS_4_7_1M_MODEL_ID,
66
- 'claude-code-opus-4.7-1m': CLAUDE_OPUS_4_7_1M_MODEL_ID,
67
- 'claude-code-opus-4-7-1m': CLAUDE_OPUS_4_7_1M_MODEL_ID,
54
+ 'claude-code-opus': 'anthropic/claude-opus-4.8',
55
+ 'claude-code-opus-48': 'anthropic/claude-opus-4.8',
56
+ 'claude-code-opus-47': 'anthropic/claude-opus-4.8',
57
+ 'claude-code-opus-46': 'anthropic/claude-opus-4.8',
58
+ 'claude-code-opus-45': 'anthropic/claude-opus-4.8',
59
+ 'claude-code-opus-4.8': 'anthropic/claude-opus-4.8',
60
+ 'claude-code-opus-4-8': 'anthropic/claude-opus-4.8',
61
+ 'claude-code-opus-4.7': 'anthropic/claude-opus-4.8',
62
+ 'claude-code-opus-4-7': 'anthropic/claude-opus-4.8',
63
+ 'claude-code-opus-4.6': 'anthropic/claude-opus-4.8',
64
+ 'claude-code-opus-4-6': 'anthropic/claude-opus-4.8',
65
+ 'claude-code-opus-4.5': 'anthropic/claude-opus-4.8',
66
+ 'claude-code-opus-4-5': 'anthropic/claude-opus-4.8',
67
+ 'claude-code-opus1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
68
+ 'claude-code-opus-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
69
+ 'claude-code-opus-4.8-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
70
+ 'claude-code-opus-4-8-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
71
+ 'claude-code-opus-4.7-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
72
+ 'claude-code-opus-4-7-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
68
73
  'claude-code-sonnet': 'anthropic/claude-sonnet-4.6',
69
74
  'claude-code-sonnet-45': 'anthropic/claude-sonnet-4.6',
70
75
  'claude-code-sonnet-4.6': 'anthropic/claude-sonnet-4.6',
@@ -74,22 +79,26 @@ const CLAUDE_LEGACY_ALIASES = {
74
79
  'claude-code-haiku': 'anthropic/claude-haiku-4.5',
75
80
  'claude-code-haiku-4.5': 'anthropic/claude-haiku-4.5',
76
81
  'claude-code-haiku-4-5': 'anthropic/claude-haiku-4.5',
77
- 'anthropic/claude-opus-4.6': 'anthropic/claude-opus-4.7',
78
- 'anthropic/claude-opus-4.5': 'anthropic/claude-opus-4.7',
82
+ 'anthropic/claude-opus-4.7': 'anthropic/claude-opus-4.8',
83
+ 'anthropic/claude-opus-4.6': 'anthropic/claude-opus-4.8',
84
+ 'anthropic/claude-opus-4.5': 'anthropic/claude-opus-4.8',
79
85
  'anthropic/claude-sonnet-4.5': 'anthropic/claude-sonnet-4.6',
80
- 'claude-opus-4.7-1m': CLAUDE_OPUS_4_7_1M_MODEL_ID,
81
- 'claude-opus-4-7-1m': CLAUDE_OPUS_4_7_1M_MODEL_ID,
82
- opus: 'anthropic/claude-opus-4.7',
83
- opus1m: CLAUDE_OPUS_4_7_1M_MODEL_ID,
84
- 'opus[1m]': CLAUDE_OPUS_4_7_1M_MODEL_ID,
86
+ 'claude-opus-4.8-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
87
+ 'claude-opus-4-8-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
88
+ 'claude-opus-4.7-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
89
+ 'claude-opus-4-7-1m': CLAUDE_OPUS_4_8_1M_MODEL_ID,
90
+ opus: 'anthropic/claude-opus-4.8',
91
+ opus1m: CLAUDE_OPUS_4_8_1M_MODEL_ID,
92
+ 'opus[1m]': CLAUDE_OPUS_4_8_1M_MODEL_ID,
85
93
  sonnet: 'anthropic/claude-sonnet-4.6',
86
94
  'sonnet[1m]': 'anthropic/claude-sonnet-4.6',
87
95
  haiku: 'anthropic/claude-haiku-4.5',
88
96
  };
89
97
 
90
98
  const CLAUDE_HARNESS_MODELS = {
99
+ 'anthropic/claude-opus-4.8': 'opus',
100
+ [CLAUDE_OPUS_4_8_1M_MODEL_ID]: 'opus[1m]',
91
101
  'anthropic/claude-opus-4.7': 'opus',
92
- [CLAUDE_OPUS_4_7_1M_MODEL_ID]: 'opus[1m]',
93
102
  'anthropic/claude-opus-4.6': 'opus',
94
103
  'anthropic/claude-opus-4.5': 'opus',
95
104
  'anthropic/claude-sonnet-4.6': 'sonnet',
@@ -162,11 +171,12 @@ function normalizeClaudeModelPart(modelPart) {
162
171
  .replace(/-\d{8}$/, '')
163
172
  .replace(/-(\d+)-(\d+)(?=$|[-:])/, '-$1.$2');
164
173
  const currentBase = {
165
- 'claude-opus-4.6': 'claude-opus-4.7',
166
- 'claude-opus-4.5': 'claude-opus-4.7',
174
+ 'claude-opus-4.7': 'claude-opus-4.8',
175
+ 'claude-opus-4.6': 'claude-opus-4.8',
176
+ 'claude-opus-4.5': 'claude-opus-4.8',
167
177
  'claude-sonnet-4.5': 'claude-sonnet-4.6',
168
178
  }[base] || base;
169
- return hasOneMillionContext && currentBase === 'claude-opus-4.7'
179
+ return hasOneMillionContext && currentBase === 'claude-opus-4.8'
170
180
  ? currentBase + '-1m'
171
181
  : currentBase;
172
182
  }
@@ -14,9 +14,10 @@
14
14
  const crypto = require('crypto');
15
15
  const os = require('os');
16
16
 
17
- const { AMALGM_COMPUTER_ID, AMALGM_USER_ID, DEFAULT_CWD } = require('../config');
17
+ const { AMALGM_USER_ID, DEFAULT_CWD } = require('../config');
18
18
  const { runThroughChatServer } = require('../lib/chat-runner');
19
19
  const { hasSupabase, supabaseInsert, supabasePatch } = require('../lib/supabase');
20
+ const { resolveLocalComputerId } = require('../lib/computer');
20
21
  const { appendRunLog, updateTaskMeta } = require('./store');
21
22
  const {
22
23
  chatInputToLegacyFields,
@@ -167,24 +168,29 @@ async function executeTask(task, claimedRun = null) {
167
168
  const codeSessionId = crypto.randomUUID();
168
169
  const userMessageId = crypto.randomUUID();
169
170
  const assistantMessageId = crypto.randomUUID();
171
+ const computerId = resolveLocalComputerId();
170
172
 
171
173
  if (hasSupabase()) {
172
- try {
173
- await supabaseInsert('sessions', {
174
- id: codeSessionId,
175
- user_id: AMALGM_USER_ID,
176
- computer_id: AMALGM_COMPUTER_ID,
177
- container_id: os.hostname() || 'task-execution',
178
- harness,
179
- title: 'New Chat',
180
- origin: 'task',
181
- origin_id: task.id,
182
- project_path: projectPath,
183
- status: 'in-progress',
184
- metadata: { taskRunId: runId, automated: true, originName: task.name },
185
- });
186
- } catch (err) {
187
- console.error('[AmalgmMCP:Exec] DB session creation failed:', err.message);
174
+ if (!computerId) {
175
+ console.error('[AmalgmMCP:Exec] DB session creation skipped: missing registered computer_id');
176
+ } else {
177
+ try {
178
+ await supabaseInsert('sessions', {
179
+ id: codeSessionId,
180
+ user_id: AMALGM_USER_ID,
181
+ computer_id: computerId,
182
+ container_id: os.hostname() || 'task-execution',
183
+ harness,
184
+ title: 'New Chat',
185
+ origin: 'task',
186
+ origin_id: task.id,
187
+ project_path: projectPath,
188
+ status: 'in-progress',
189
+ metadata: { taskRunId: runId, automated: true, originName: task.name, computerId },
190
+ });
191
+ } catch (err) {
192
+ console.error('[AmalgmMCP:Exec] DB session creation failed:', err.message);
193
+ }
188
194
  }
189
195
  }
190
196
 
@@ -200,7 +206,8 @@ async function executeTask(task, claimedRun = null) {
200
206
  prompt: legacy.prompt,
201
207
  userId: AMALGM_USER_ID,
202
208
  codeSessionId,
203
- computerId: AMALGM_COMPUTER_ID,
209
+ computerId,
210
+ skipPersistence: hasSupabase() && !computerId,
204
211
  assistantMessageId,
205
212
  userMessageId,
206
213
  agentId: harnessToAgent(harness),
@@ -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',
@@ -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
  }
@@ -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',
@@ -253,7 +253,7 @@ test('claude extracts native hooks without enabling full filesystem settings', (
253
253
  authMethod: 'provider_auth',
254
254
  auth: { runtimeHome },
255
255
  cwd: home,
256
- cliModel: 'anthropic/claude-opus-4.7',
256
+ cliModel: 'anthropic/claude-opus-4.8',
257
257
  mcpServers: [],
258
258
  });
259
259
 
@@ -52,11 +52,12 @@ function normalizeError(error, fallback = 'Tool failed') {
52
52
 
53
53
  function toolKind(name = '', input = {}, metadata = {}) {
54
54
  const clean = String(name || '').toLowerCase();
55
+ const compact = clean.replace(/[^a-z0-9]/g, '');
55
56
  const mcp = metadata.mcp || parseMcpName(name);
56
57
  if (metadata.subagent || clean === 'task' || clean.includes('spawnagent') || clean.includes('collabagent')) return 'subagent';
57
58
  if (clean.includes('todo')) return 'todo';
58
59
  if (clean.includes('question') || clean.includes('askuser')) return 'question';
59
- if (clean.includes('toolsearch')) return 'search';
60
+ if (compact.includes('toolsearch')) return 'search';
60
61
  if (clean.includes('bash') || clean.includes('command')) return 'bash';
61
62
  if (clean.includes('websearch') || clean === 'websearch') return 'web_search';
62
63
  if (clean.includes('browser') || mcp?.tool?.startsWith?.('browser_')) return 'browser';
@@ -101,6 +102,7 @@ function titleForTool({ name, input = {}, output, error, metadata = {}, fallback
101
102
  if (mcp) return titleFromMcp(mcp.tool, input);
102
103
 
103
104
  const clean = String(name || '').toLowerCase();
105
+ const compact = clean.replace(/[^a-z0-9]/g, '');
104
106
  if (metadata.action?.type === 'search') {
105
107
  return `Searching ${compactText(metadata.action.query || input.query || 'the web', 80)}`;
106
108
  }
@@ -114,7 +116,7 @@ function titleForTool({ name, input = {}, output, error, metadata = {}, fallback
114
116
  const host = hostLabel(url) || 'current page';
115
117
  return pattern ? `Finding "${compactText(pattern, 60)}" on ${host}` : `Finding text on ${host}`;
116
118
  }
117
- if (clean.includes('toolsearch')) {
119
+ if (compact.includes('toolsearch')) {
118
120
  const selected = String(input.query || '').replace(/^select:/i, '').trim();
119
121
  return selected ? `Selecting ${compactText(selected, 80)}` : 'Selecting tool';
120
122
  }
@@ -49,6 +49,8 @@ const MODEL_CATALOG = [
49
49
  { id: "anthropic/claude-opus-4.6-1m", name: "Claude Opus 4.6 (1M context)", provider: "anthropic", contextWindow: 1000000, maxOutput: 128000, inputPrice: 5, outputPrice: 25, cacheRead: 0.5, cacheWrite: 6.25, tags: ["tool-use", "reasoning", "vision", "file-input", "explicit-caching", "web-search"], released: "2026-02-05" },
50
50
  { id: "anthropic/claude-opus-4.7", name: "Claude Opus 4.7", provider: "anthropic", contextWindow: 200000, maxOutput: 128000, inputPrice: 5, outputPrice: 25, cacheRead: 0.5, cacheWrite: 6.25, tags: ["tool-use", "reasoning", "vision", "file-input", "explicit-caching", "web-search"], released: "2026-04-16" },
51
51
  { id: "anthropic/claude-opus-4.7-1m", name: "Claude Opus 4.7 (1M context)", provider: "anthropic", contextWindow: 1000000, maxOutput: 128000, inputPrice: 5, outputPrice: 25, cacheRead: 0.5, cacheWrite: 6.25, tags: ["tool-use", "reasoning", "vision", "file-input", "explicit-caching", "web-search"], released: "2026-04-16" },
52
+ { id: "anthropic/claude-opus-4.8", name: "Claude Opus 4.8", provider: "anthropic", contextWindow: 200000, maxOutput: 128000, inputPrice: 5, outputPrice: 25, cacheRead: 0.5, cacheWrite: 6.25, tags: ["tool-use", "reasoning", "vision", "file-input", "explicit-caching", "web-search"], released: null },
53
+ { id: "anthropic/claude-opus-4.8-1m", name: "Claude Opus 4.8 (1M context)", provider: "anthropic", contextWindow: 1000000, maxOutput: 128000, inputPrice: 5, outputPrice: 25, cacheRead: 0.5, cacheWrite: 6.25, tags: ["tool-use", "reasoning", "vision", "file-input", "explicit-caching", "web-search"], released: null },
52
54
  { id: "anthropic/claude-sonnet-4", name: "Claude Sonnet 4", provider: "anthropic", contextWindow: 1000000, maxOutput: 64000, inputPrice: 3, outputPrice: 15, cacheRead: 0.3, cacheWrite: 3.75, tags: ["file-input", "reasoning", "tool-use", "vision", "explicit-caching"], released: "2025-05-22" },
53
55
  { id: "anthropic/claude-sonnet-4.5", name: "Claude Sonnet 4.5", provider: "anthropic", contextWindow: 1000000, maxOutput: 64000, inputPrice: 3, outputPrice: 15, cacheRead: 0.3, cacheWrite: 3.75, tags: ["file-input", "reasoning", "tool-use", "vision", "explicit-caching"], released: "2025-09-29" },
54
56
  { id: "anthropic/claude-sonnet-4.6", name: "Claude Sonnet 4.6", provider: "anthropic", contextWindow: 1000000, maxOutput: 128000, inputPrice: 3, outputPrice: 15, cacheRead: 0.3, cacheWrite: 3.75, tags: ["file-input", "reasoning", "tool-use", "vision", "explicit-caching", "web-search"], released: "2026-02-17" },
@@ -236,10 +238,13 @@ const MODEL_CATALOG = [
236
238
  /** Find model by exact ID or fuzzy match (strips provider prefix, date suffixes). */
237
239
  // CLI agent internal model names → catalog IDs
238
240
  const MODEL_ALIASES = {
239
- 'claude-code-opus': 'anthropic/claude-opus-4.7',
240
- 'opus': 'anthropic/claude-opus-4.7',
241
- 'opus1m': 'anthropic/claude-opus-4.7-1m',
242
- 'opus[1m]': 'anthropic/claude-opus-4.7-1m',
241
+ 'claude-code-opus': 'anthropic/claude-opus-4.8',
242
+ 'opus': 'anthropic/claude-opus-4.8',
243
+ 'opus1m': 'anthropic/claude-opus-4.8-1m',
244
+ 'opus[1m]': 'anthropic/claude-opus-4.8-1m',
245
+ 'claude-opus-4-8': 'anthropic/claude-opus-4.8',
246
+ 'claude-opus-4.8-1m': 'anthropic/claude-opus-4.8-1m',
247
+ 'claude-opus-4.8:1m': 'anthropic/claude-opus-4.8-1m',
243
248
  'claude-opus-4-7': 'anthropic/claude-opus-4.7',
244
249
  'claude-opus-4.7-1m': 'anthropic/claude-opus-4.7-1m',
245
250
  'claude-opus-4.7:1m': 'anthropic/claude-opus-4.7-1m',
@@ -255,7 +260,7 @@ function getModel(modelId) {
255
260
  if (!modelId) return null;
256
261
  const id = modelId.toLowerCase();
257
262
 
258
- // Check aliases first (e.g. claude-code-opus -> anthropic/claude-opus-4.7)
263
+ // Check aliases first (e.g. claude-code-opus -> anthropic/claude-opus-4.8)
259
264
  const aliased = MODEL_ALIASES[id];
260
265
  if (aliased) return MODEL_CATALOG.find(e => e.id === aliased) || null;
261
266
 
@@ -15,7 +15,7 @@ const {
15
15
  resolveModelSelection,
16
16
  } = require('./amalgm-mcp/lib/prefs');
17
17
 
18
- const OPUS_1M = 'anthropic/claude-opus-4.7-1m';
18
+ const OPUS_1M = 'anthropic/claude-opus-4.8-1m';
19
19
 
20
20
  assert.deepEqual(
21
21
  getHarnessModels('claude_code').map((model) => ({
@@ -26,21 +26,26 @@ assert.deepEqual(
26
26
  [
27
27
  { id: 'anthropic/claude-haiku-4.5', name: 'Haiku 4.5', cliModel: 'haiku' },
28
28
  { id: 'anthropic/claude-sonnet-4.6', name: 'Sonnet 4.6', cliModel: 'sonnet' },
29
- { id: 'anthropic/claude-opus-4.7', name: 'Opus 4.7', cliModel: 'opus' },
30
- { id: OPUS_1M, name: 'Opus 4.7 1M', cliModel: 'opus[1m]' },
29
+ { id: 'anthropic/claude-opus-4.8', name: 'Opus 4.8', cliModel: 'opus' },
30
+ { id: OPUS_1M, name: 'Opus 4.8 1M', cliModel: 'opus[1m]' },
31
31
  ],
32
32
  );
33
33
 
34
34
  assert.equal(canonicalModel('haiku', 'claude_code'), 'anthropic/claude-haiku-4.5');
35
35
  assert.equal(canonicalModel('sonnet', 'claude_code'), 'anthropic/claude-sonnet-4.6');
36
- assert.equal(canonicalModel('opus', 'claude_code'), 'anthropic/claude-opus-4.7');
36
+ assert.equal(canonicalModel('opus', 'claude_code'), 'anthropic/claude-opus-4.8');
37
37
  assert.equal(canonicalModel('opus1m', 'claude_code'), OPUS_1M);
38
38
  assert.equal(canonicalModel('opus[1m]', 'claude_code'), OPUS_1M);
39
- assert.equal(canonicalModel('claude-code-opus-46', 'claude_code'), 'anthropic/claude-opus-4.7');
40
- assert.equal(canonicalModel('claude-code-opus-4.6', 'claude_code'), 'anthropic/claude-opus-4.7');
41
- assert.equal(canonicalModel('claude-opus-4-6', 'claude_code'), 'anthropic/claude-opus-4.7');
42
- assert.equal(canonicalModel('anthropic/claude-opus-4-6', 'claude_code'), 'anthropic/claude-opus-4.7');
43
- assert.equal(canonicalModel('anthropic/claude-opus-4.5', 'claude_code'), 'anthropic/claude-opus-4.7');
39
+ assert.equal(canonicalModel('claude-code-opus-48', 'claude_code'), 'anthropic/claude-opus-4.8');
40
+ assert.equal(canonicalModel('claude-code-opus-4.8', 'claude_code'), 'anthropic/claude-opus-4.8');
41
+ assert.equal(canonicalModel('claude-opus-4-8', 'claude_code'), 'anthropic/claude-opus-4.8');
42
+ assert.equal(canonicalModel('claude-code-opus-47', 'claude_code'), 'anthropic/claude-opus-4.8');
43
+ assert.equal(canonicalModel('claude-code-opus-46', 'claude_code'), 'anthropic/claude-opus-4.8');
44
+ assert.equal(canonicalModel('claude-code-opus-4.6', 'claude_code'), 'anthropic/claude-opus-4.8');
45
+ assert.equal(canonicalModel('claude-opus-4-6', 'claude_code'), 'anthropic/claude-opus-4.8');
46
+ assert.equal(canonicalModel('anthropic/claude-opus-4-6', 'claude_code'), 'anthropic/claude-opus-4.8');
47
+ assert.equal(canonicalModel('anthropic/claude-opus-4.7', 'claude_code'), 'anthropic/claude-opus-4.8');
48
+ assert.equal(canonicalModel('anthropic/claude-opus-4.5', 'claude_code'), 'anthropic/claude-opus-4.8');
44
49
  assert.equal(canonicalModel('claude-code-sonnet-45', 'claude_code'), 'anthropic/claude-sonnet-4.6');
45
50
  assert.equal(canonicalModel('claude-code-sonnet-4.5', 'claude_code'), 'anthropic/claude-sonnet-4.6');
46
51
  assert.equal(canonicalModel('claude-sonnet-4-5', 'claude_code'), 'anthropic/claude-sonnet-4.6');
@@ -49,14 +54,14 @@ assert.equal(canonicalModel('claude-code-haiku-4.5', 'claude_code'), 'anthropic/
49
54
 
50
55
  assert.equal(cliModelFor({ harness: 'claude_code', modelId: 'anthropic/claude-haiku-4.5' }), 'haiku');
51
56
  assert.equal(cliModelFor({ harness: 'claude_code', modelId: 'anthropic/claude-sonnet-4.6' }), 'sonnet');
52
- assert.equal(cliModelFor({ harness: 'claude_code', modelId: 'anthropic/claude-opus-4.7' }), 'opus');
57
+ assert.equal(cliModelFor({ harness: 'claude_code', modelId: 'anthropic/claude-opus-4.8' }), 'opus');
53
58
  assert.equal(cliModelFor({ harness: 'claude_code', modelId: OPUS_1M }), 'opus[1m]');
54
- assert.equal(cliModelFor({ harness: 'claude_code', modelId: 'anthropic/claude-opus-4.7', cliModel: 'opus1m' }), 'opus[1m]');
59
+ assert.equal(cliModelFor({ harness: 'claude_code', modelId: 'anthropic/claude-opus-4.8', cliModel: 'opus1m' }), 'opus[1m]');
55
60
 
56
61
  for (const [modelId, cliModel] of [
57
62
  ['anthropic/claude-haiku-4.5', 'haiku'],
58
63
  ['anthropic/claude-sonnet-4.6', 'sonnet'],
59
- ['anthropic/claude-opus-4.7', 'opus'],
64
+ ['anthropic/claude-opus-4.8', 'opus'],
60
65
  [OPUS_1M, 'opus[1m]'],
61
66
  ]) {
62
67
  const contract = createContract({