amalgm 0.1.72 → 0.1.73

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 (64) hide show
  1. package/package.json +1 -2
  2. package/runtime/scripts/amalgm-mcp/agents/rest.js +29 -22
  3. package/runtime/scripts/amalgm-mcp/agents/store.js +90 -24
  4. package/runtime/scripts/amalgm-mcp/agents/talk.js +66 -37
  5. package/runtime/scripts/amalgm-mcp/agents/tools.js +0 -4
  6. package/runtime/scripts/amalgm-mcp/automations/runner.js +4 -5
  7. package/runtime/scripts/amalgm-mcp/automations/tool-actions.js +54 -57
  8. package/runtime/scripts/amalgm-mcp/automations/tools.js +4 -4
  9. package/runtime/scripts/amalgm-mcp/email/inbound.js +21 -0
  10. package/runtime/scripts/amalgm-mcp/events/internal-workflows.js +3 -3
  11. package/runtime/scripts/amalgm-mcp/index.js +3 -1
  12. package/runtime/scripts/amalgm-mcp/lib/prefs.js +15 -0
  13. package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +5 -3
  14. package/runtime/scripts/amalgm-mcp/runtime-worker.js +47 -0
  15. package/runtime/scripts/amalgm-mcp/server/combined-service.js +27 -0
  16. package/runtime/scripts/amalgm-mcp/server/control-plane-service.js +24 -0
  17. package/runtime/scripts/amalgm-mcp/server/core-tools.js +52 -7
  18. package/runtime/scripts/amalgm-mcp/server/http-context.js +44 -0
  19. package/runtime/scripts/amalgm-mcp/server/http-service.js +34 -0
  20. package/runtime/scripts/amalgm-mcp/server/http.js +31 -465
  21. package/runtime/scripts/amalgm-mcp/server/local-service-router.js +57 -0
  22. package/runtime/scripts/amalgm-mcp/server/mcp-adapter.js +22 -0
  23. package/runtime/scripts/amalgm-mcp/server/mcp-http-service.js +24 -0
  24. package/runtime/scripts/amalgm-mcp/server/mcp-request-context.js +32 -0
  25. package/runtime/scripts/amalgm-mcp/server/mcp.js +14 -73
  26. package/runtime/scripts/amalgm-mcp/server/routes/agents.js +29 -0
  27. package/runtime/scripts/amalgm-mcp/server/routes/apps.js +59 -0
  28. package/runtime/scripts/amalgm-mcp/server/routes/automations.js +45 -0
  29. package/runtime/scripts/amalgm-mcp/server/routes/browser.js +49 -0
  30. package/runtime/scripts/amalgm-mcp/server/routes/credentials.js +30 -0
  31. package/runtime/scripts/amalgm-mcp/server/routes/events.js +18 -0
  32. package/runtime/scripts/amalgm-mcp/server/routes/files.js +33 -0
  33. package/runtime/scripts/amalgm-mcp/server/routes/health.js +24 -0
  34. package/runtime/scripts/amalgm-mcp/server/routes/inbound.js +18 -0
  35. package/runtime/scripts/amalgm-mcp/server/routes/local.js +21 -0
  36. package/runtime/scripts/amalgm-mcp/server/routes/preferences.js +18 -0
  37. package/runtime/scripts/amalgm-mcp/server/routes/state.js +23 -0
  38. package/runtime/scripts/amalgm-mcp/server/routes/toolbox.js +59 -0
  39. package/runtime/scripts/amalgm-mcp/server/routes/workspace.js +57 -0
  40. package/runtime/scripts/amalgm-mcp/server/service-lifecycle.js +54 -0
  41. package/runtime/scripts/amalgm-mcp/services/control-plane.js +19 -0
  42. package/runtime/scripts/amalgm-mcp/services/mcp-adapter.js +12 -0
  43. package/runtime/scripts/amalgm-mcp/services/runtime-worker.js +18 -0
  44. package/runtime/scripts/amalgm-mcp/slack/inbound.js +21 -0
  45. package/runtime/scripts/amalgm-mcp/state/snapshot.js +3 -3
  46. package/runtime/scripts/amalgm-mcp/tests/agents-source-of-truth.test.js +82 -0
  47. package/runtime/scripts/amalgm-mcp/tests/automations-store-runner.test.js +5 -5
  48. package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +37 -0
  49. package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +158 -0
  50. package/runtime/scripts/amalgm-mcp/tests/server-routing.test.js +81 -0
  51. package/runtime/scripts/amalgm-mcp/tests/server-services.test.js +156 -0
  52. package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +81 -0
  53. package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +250 -0
  54. package/runtime/scripts/amalgm-mcp/toolbox/mcp-surface.js +106 -0
  55. package/runtime/scripts/amalgm-mcp/toolbox/names.js +41 -0
  56. package/runtime/scripts/amalgm-mcp/toolbox/rest.js +107 -53
  57. package/runtime/scripts/amalgm-mcp/toolbox/runner.js +9 -75
  58. package/runtime/scripts/amalgm-mcp/toolbox/selection.js +22 -0
  59. package/runtime/scripts/amalgm-mcp/toolbox/service.js +227 -0
  60. package/runtime/scripts/amalgm-mcp/toolbox/store.js +9 -78
  61. package/runtime/scripts/amalgm-mcp/toolbox/system-catalog.js +112 -0
  62. package/runtime/scripts/amalgm-mcp/toolbox/tools.js +11 -74
  63. package/runtime/scripts/amalgm-mcp/workflows/compiler.js +1 -1
  64. package/runtime/scripts/amalgm-mcp/workflows/runner.js +4 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -15,7 +15,6 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "sync-runtime": "node ../../scripts/sync-npm-package-runtime.mjs",
18
- "prepack": "node ../../scripts/sync-npm-package-runtime.mjs",
19
18
  "pack:dry": "npm pack --dry-run",
20
19
  "check": "node --check bin/amalgm.js && node --check lib/auth-store.js && node --check lib/cli.js && node --check lib/paths.js && node --check lib/process-cleanup.js && node --check lib/runtime-identity.js && node --check lib/runtime-manifest.js && node --check lib/service.js && node --check lib/state-migration.js && node --check lib/supervisor.js && node --check lib/tunnel-chat.js && node --check lib/tunnel-events.js && node --check runtime/lib/runtime-manifest.js && node --check runtime/scripts/runtime-auth.js && node --check runtime/scripts/proxy-token-store.js && node --check runtime/scripts/local-gateway.js && node --check runtime/scripts/port-monitor.js && node --check runtime/scripts/chat-server.js && node --check runtime/scripts/chat-server/index.js && node --check runtime/scripts/chat-server/config.js && node --check runtime/scripts/chat-core/tooling/native-binaries.js && node --check runtime/scripts/chat-core/tooling/package-import.js && node --check runtime/scripts/chat-core/tooling/runtime-home.js && node --check runtime/scripts/amalgm-mcp/index.js && node --check runtime/scripts/amalgm-mcp/config.js && node --check runtime/scripts/lib/project-paths.js && node --check runtime/scripts/lib/runtime-paths.js"
21
20
  },
@@ -11,6 +11,9 @@ const {
11
11
  } = require('./store');
12
12
  const { hydrateModelPreferences } = require('../lib/prefs');
13
13
  const credentialAdapter = require('../../credential-adapter');
14
+ const {
15
+ defaultToolboxService: toolboxService,
16
+ } = require('../toolbox/service');
14
17
 
15
18
  function coerceAuthMethodForHarness(harnessId, authMethod) {
16
19
  const supported = credentialAdapter.SUPPORTED_AUTH_BY_HARNESS?.[harnessId] || ['amalgm'];
@@ -25,27 +28,28 @@ function normalizeStringList(values) {
25
28
  .filter(Boolean))];
26
29
  }
27
30
 
28
- function normalizeMcpConfig(mcp, mcpAppIds, nativeMcps) {
31
+ function derivedMcpTransportConfig(toolConfig) {
32
+ const appIds = toolboxService.externalMcpToolIdsForMode(
33
+ toolConfig.mode,
34
+ toolConfig.selectedToolIds,
35
+ );
29
36
  return {
30
- inheritAll: mcp?.inheritAll !== false,
31
- customServers: Array.isArray(mcp?.customServers) ? mcp.customServers : [],
32
- appIds: normalizeStringList(mcp?.appIds ?? mcpAppIds),
33
- nativeMcps: normalizeStringList(mcp?.nativeMcps ?? nativeMcps),
37
+ inheritAll: toolConfig.mode !== 'selected',
38
+ customServers: [],
39
+ appIds,
40
+ nativeMcps: [],
34
41
  };
35
42
  }
36
43
 
37
- function normalizeToolConfig(tools, legacyMcp) {
44
+ function normalizeToolConfig(tools) {
38
45
  const raw = tools && typeof tools === 'object' && !Array.isArray(tools) ? tools : null;
39
46
  const selectedToolIds = normalizeStringList(
40
47
  raw?.selectedToolIds
41
48
  ?? raw?.selected
42
- ?? [
43
- ...normalizeStringList(legacyMcp?.appIds),
44
- ...normalizeStringList(legacyMcp?.nativeMcps),
45
- ],
49
+ ?? [],
46
50
  );
47
51
  return {
48
- mode: raw?.mode === 'selected' || (!raw && legacyMcp?.inheritAll === false) ? 'selected' : 'all',
52
+ mode: raw?.mode === 'selected' ? 'selected' : 'all',
49
53
  selectedToolIds,
50
54
  };
51
55
  }
@@ -68,20 +72,18 @@ async function handleCreate(body, sendJson) {
68
72
  description,
69
73
  baseHarnessId,
70
74
  baseModelId,
75
+ modelSettings,
71
76
  systemPrompt,
72
77
  files,
73
78
  skills,
74
- mcpAppIds,
75
- nativeMcps,
76
- mcp,
77
79
  tools,
78
80
  authMethod,
79
81
  } = body;
80
82
  if (!name || !name.trim()) return sendJson(400, { error: 'name is required' });
81
83
  if (!baseHarnessId) return sendJson(400, { error: 'baseHarnessId is required' });
82
84
 
83
- const mcpConfig = normalizeMcpConfig(mcp, mcpAppIds, nativeMcps);
84
- const toolConfig = normalizeToolConfig(tools, mcpConfig);
85
+ const toolConfig = normalizeToolConfig(tools);
86
+ const mcpConfig = derivedMcpTransportConfig(toolConfig);
85
87
  let agent;
86
88
  try {
87
89
  agent = createAgent({
@@ -89,16 +91,14 @@ async function handleCreate(body, sendJson) {
89
91
  description: description || '',
90
92
  baseHarnessId,
91
93
  baseModelId: baseModelId || '',
94
+ modelSettings,
92
95
  systemPrompt: systemPrompt || '',
93
96
  files: normalizeStringList(files),
94
97
  skills: normalizeStringList(skills),
95
- mcpAppIds: normalizeStringList(mcpConfig.appIds),
96
- nativeMcps: normalizeStringList(mcpConfig.nativeMcps),
98
+ mcpAppIds: mcpConfig.appIds,
99
+ nativeMcps: mcpConfig.nativeMcps,
97
100
  tools: toolConfig,
98
- mcp: {
99
- ...mcpConfig,
100
- inheritAll: toolConfig.mode !== 'selected',
101
- },
101
+ mcp: mcpConfig,
102
102
  authMethod: coerceAuthMethodForHarness(baseHarnessId, authMethod),
103
103
  });
104
104
  } catch (error) {
@@ -121,6 +121,13 @@ async function handleUpdate(body, sendJson) {
121
121
  updates.authMethod = coerceAuthMethodForHarness(targetHarnessId, existing.authMethod);
122
122
  }
123
123
 
124
+ const toolConfig = normalizeToolConfig(updates.tools ?? existing.tools);
125
+ const mcpConfig = derivedMcpTransportConfig(toolConfig);
126
+ updates.tools = toolConfig;
127
+ updates.mcpAppIds = mcpConfig.appIds;
128
+ updates.nativeMcps = mcpConfig.nativeMcps;
129
+ updates.mcp = mcpConfig;
130
+
124
131
  let agent;
125
132
  try {
126
133
  agent = updateAgent(agent_id, updates);
@@ -25,13 +25,26 @@ const {
25
25
  } = require('../lib/storage');
26
26
  const { openLocalDb } = require('../state/db');
27
27
  const { insertStateEvent, publishStateEvent } = require('../state/events');
28
- const { getPreferredAuthMethod } = require('../lib/prefs');
28
+ const {
29
+ getModelSettingsForSelection,
30
+ getPreferredAuthMethod,
31
+ getSelectedModel,
32
+ readUserPreferences,
33
+ } = require('../lib/prefs');
29
34
  const credentialAdapter = require('../../credential-adapter');
30
35
 
31
36
  const DEFAULT_AGENT_STATUS = 'unknown';
32
37
  const CUSTOM_AGENT_STATUS = 'ready';
33
38
  const BUILTIN_AGENT_IDS = ['claude_code', 'codex', 'opencode', 'pi'];
34
39
  const DEFAULT_AGENTS_SEEDED_FILE = path.join(STORAGE_DIR, '.default-agents-seeded');
40
+ const DEFAULT_AGENT_MODEL_SETTINGS_BACKFILLED_FILE = path.join(
41
+ STORAGE_DIR,
42
+ '.default-agent-model-settings-backfilled',
43
+ );
44
+ const DEFAULT_AGENT_AUTH_BACKFILLED_FILE = path.join(
45
+ STORAGE_DIR,
46
+ '.default-agent-auth-backfilled',
47
+ );
35
48
  const AMALGM_COMPUTER_ID = process.env.AMALGM_COMPUTER_ID || '';
36
49
 
37
50
  const BUILTIN_AGENT_BLUEPRINTS = [
@@ -146,16 +159,47 @@ function persistedAuthMethod(baseHarnessId) {
146
159
  return localFallback;
147
160
  }
148
161
 
149
- function defaultAgentAuthMethod(baseHarnessId, existing) {
162
+ function defaultAgentAuthMethod(baseHarnessId, existing, allowPreferenceBackfill = false) {
150
163
  const persisted = persistedAuthMethod(baseHarnessId);
151
164
  const existingAuth = nonEmptyString(existing?.authMethod);
152
165
  if (!existingAuth) return persisted;
153
- if (existingAuth === 'amalgm' && persisted && persisted !== 'amalgm') {
166
+ if (allowPreferenceBackfill && existingAuth === 'amalgm' && persisted && persisted !== 'amalgm') {
154
167
  return persisted;
155
168
  }
156
169
  return existingAuth;
157
170
  }
158
171
 
172
+ function normalizeEffort(value) {
173
+ if (typeof value !== 'string') return null;
174
+ const normalized = value.trim().toLowerCase().replace(/_/g, '-');
175
+ if (normalized === 'low') return 'low';
176
+ if (normalized === 'medium') return 'medium';
177
+ if (normalized === 'high') return 'high';
178
+ if (normalized === 'xhigh' || normalized === 'extra-high' || normalized === 'extra high') return 'xhigh';
179
+ if (normalized === 'max') return 'max';
180
+ return null;
181
+ }
182
+
183
+ function normalizeModelSettings(value, fallback = null, harnessId = null) {
184
+ const raw = isObject(value) ? value : isObject(fallback) ? fallback : null;
185
+ if (!raw) return null;
186
+ const effort = normalizeEffort(raw.effort || raw.reasoningEffort || raw.reasoning);
187
+ const allowedEfforts =
188
+ harnessId === 'claude_code'
189
+ ? new Set(['low', 'medium', 'high', 'xhigh', 'max'])
190
+ : harnessId === 'codex'
191
+ ? new Set(['low', 'medium', 'high', 'xhigh'])
192
+ : null;
193
+ const normalized = {
194
+ ...(effort && (!allowedEfforts || allowedEfforts.has(effort)) ? { effort } : {}),
195
+ ...((harnessId === 'claude_code' || harnessId === 'codex' || !harnessId)
196
+ && (raw.fastMode === true || raw.speed === 'fast')
197
+ ? { fastMode: true }
198
+ : {}),
199
+ };
200
+ return Object.keys(normalized).length > 0 ? normalized : null;
201
+ }
202
+
159
203
  function normalizeLocation(inputLocation, fallback = {}) {
160
204
  const id = nonEmptyString(fallback.ownerComputerId) || computerId();
161
205
  const name = nonEmptyString(fallback.locationName) || computerName();
@@ -190,27 +234,24 @@ function defaultCapabilities(agent) {
190
234
  };
191
235
  }
192
236
 
193
- function normalizeMcpConfig(mcp, mcpAppIds, nativeMcps) {
237
+ function normalizeMcpTransportConfig(toolConfig, mcpAppIds, nativeMcps) {
194
238
  return {
195
- inheritAll: mcp?.inheritAll !== false,
196
- customServers: Array.isArray(mcp?.customServers) ? mcp.customServers : [],
197
- appIds: normalizeStringList(mcpAppIds !== undefined ? mcpAppIds : mcp?.appIds),
198
- nativeMcps: normalizeStringList(nativeMcps !== undefined ? nativeMcps : mcp?.nativeMcps),
239
+ inheritAll: toolConfig.mode !== 'selected',
240
+ customServers: [],
241
+ appIds: normalizeStringList(mcpAppIds),
242
+ nativeMcps: normalizeStringList(nativeMcps),
199
243
  };
200
244
  }
201
245
 
202
- function normalizeToolConfig(tools, legacyMcp) {
246
+ function normalizeToolConfig(tools) {
203
247
  const raw = isObject(tools) ? tools : null;
204
248
  const selectedToolIds = normalizeStringList(
205
249
  raw?.selectedToolIds
206
250
  ?? raw?.selected
207
- ?? [
208
- ...normalizeStringList(legacyMcp?.appIds),
209
- ...normalizeStringList(legacyMcp?.nativeMcps),
210
- ],
251
+ ?? [],
211
252
  );
212
253
  return {
213
- mode: raw?.mode === 'selected' || (!raw && legacyMcp?.inheritAll === false) ? 'selected' : 'all',
254
+ mode: raw?.mode === 'selected' ? 'selected' : 'all',
214
255
  selectedToolIds,
215
256
  };
216
257
  }
@@ -221,8 +262,8 @@ function normalizeAgent(input, existing) {
221
262
  const builtin = input.builtin === true;
222
263
  const baseHarnessId = nonEmptyString(input.baseHarnessId || input.base_harness_id) || id;
223
264
  const adapter = nonEmptyString(input.adapter) || baseHarnessId;
224
- const mcpConfig = normalizeMcpConfig(input.mcp, input.mcpAppIds, input.nativeMcps);
225
- const toolConfig = normalizeToolConfig(input.tools, mcpConfig);
265
+ const toolConfig = normalizeToolConfig(input.tools);
266
+ const mcpConfig = normalizeMcpTransportConfig(toolConfig, input.mcpAppIds, input.nativeMcps);
226
267
  const ownerComputerId = nonEmptyString(input.ownerComputerId || input.owner_computer_id)
227
268
  || nonEmptyString(existing?.ownerComputerId)
228
269
  || computerId();
@@ -240,6 +281,7 @@ function normalizeAgent(input, existing) {
240
281
  adapter,
241
282
  baseHarnessId,
242
283
  baseModelId: input.baseModelId || existing?.baseModelId || '',
284
+ modelSettings: normalizeModelSettings(input.modelSettings, existing?.modelSettings, baseHarnessId),
243
285
  systemPrompt: input.systemPrompt || '',
244
286
  files: normalizeStringList(input.files),
245
287
  skills: normalizeStringList(input.skills),
@@ -395,25 +437,46 @@ function customAgentRowCount(db = openLocalDb()) {
395
437
  function seedBuiltinAgents(options = {}) {
396
438
  const db = openLocalDb();
397
439
  const alreadySeeded = fs.existsSync(DEFAULT_AGENTS_SEEDED_FILE);
440
+ const authBackfilled = fs.existsSync(DEFAULT_AGENT_AUTH_BACKFILLED_FILE);
441
+ const modelSettingsBackfilled = fs.existsSync(DEFAULT_AGENT_MODEL_SETTINGS_BACKFILLED_FILE);
442
+ try {
443
+ readUserPreferences();
444
+ } catch {
445
+ // Best-effort preference cache fill for default agent bootstrapping.
446
+ }
398
447
  const events = db.transaction(() => {
399
448
  const inserted = [];
400
449
  for (const blueprint of BUILTIN_AGENT_BLUEPRINTS) {
401
450
  const existing = readAgentRow(db, blueprint.id);
402
451
  if (!existing && alreadySeeded) continue;
452
+ const baseModelId =
453
+ existing?.baseModelId ||
454
+ getSelectedModel(blueprint.baseHarnessId) ||
455
+ blueprint.baseModelId;
456
+ const modelSettings =
457
+ existing?.modelSettings ||
458
+ (!modelSettingsBackfilled
459
+ ? getModelSettingsForSelection(blueprint.baseHarnessId, baseModelId)
460
+ : null);
403
461
  const next = normalizeAgent({
404
462
  ...blueprint,
405
463
  name: existing?.name || blueprint.name,
406
464
  description: existing?.description ?? blueprint.description,
407
465
  adapter: existing?.adapter || blueprint.adapter,
408
466
  baseHarnessId: existing?.baseHarnessId || blueprint.baseHarnessId,
409
- baseModelId: blueprint.baseModelId,
467
+ baseModelId,
410
468
  systemPrompt: existing?.systemPrompt || '',
411
469
  files: existing?.files || [],
412
470
  skills: existing?.skills || [],
413
471
  mcpAppIds: existing?.mcpAppIds || [],
414
- nativeMcps: existing?.nativeMcps || [],
472
+ nativeMcps: [],
415
473
  tools: existing?.tools || { mode: 'all', selectedToolIds: [] },
416
- mcp: existing?.mcp || { inheritAll: true, customServers: [], appIds: [], nativeMcps: [] },
474
+ mcp: {
475
+ inheritAll: existing?.tools?.mode !== 'selected',
476
+ customServers: [],
477
+ appIds: existing?.mcpAppIds || [],
478
+ nativeMcps: [],
479
+ },
417
480
  builtin: false,
418
481
  deletable: true,
419
482
  editable: true,
@@ -423,7 +486,12 @@ function seedBuiltinAgents(options = {}) {
423
486
  authDetails: existing?.authDetails,
424
487
  configDir: existing?.configDir,
425
488
  createdAt: existing?.createdAt,
426
- authMethod: persistedAuthMethod(existing?.baseHarnessId || blueprint.baseHarnessId),
489
+ modelSettings,
490
+ authMethod: defaultAgentAuthMethod(
491
+ existing?.baseHarnessId || blueprint.baseHarnessId,
492
+ existing,
493
+ !authBackfilled,
494
+ ),
427
495
  }, existing);
428
496
  if (existing && agentsEqualForWrite(existing, next)) continue;
429
497
  upsertAgentRow(db, next);
@@ -438,6 +506,8 @@ function seedBuiltinAgents(options = {}) {
438
506
  try {
439
507
  ensureDir(STORAGE_DIR);
440
508
  fs.writeFileSync(DEFAULT_AGENTS_SEEDED_FILE, nowIso());
509
+ fs.writeFileSync(DEFAULT_AGENT_AUTH_BACKFILLED_FILE, nowIso());
510
+ fs.writeFileSync(DEFAULT_AGENT_MODEL_SETTINGS_BACKFILLED_FILE, nowIso());
441
511
  } catch {
442
512
  // Best-effort marker; default seeding still works without it.
443
513
  }
@@ -564,7 +634,6 @@ function updateAgent(agentId, updates, options = {}) {
564
634
  if (duplicate) throw new Error(`An agent named "${requestedName}" already exists`);
565
635
  }
566
636
 
567
- const hasOwn = (key) => Object.prototype.hasOwnProperty.call(updates || {}, key);
568
637
  const merged = {
569
638
  ...existing,
570
639
  ...updates,
@@ -574,9 +643,6 @@ function updateAgent(agentId, updates, options = {}) {
574
643
  editable: existing.editable,
575
644
  updatedAt: nowIso(),
576
645
  };
577
- if (!hasOwn('tools') && (hasOwn('mcp') || hasOwn('mcpAppIds') || hasOwn('nativeMcps'))) {
578
- delete merged.tools;
579
- }
580
646
 
581
647
  const next = normalizeAgent(merged, existing);
582
648
 
@@ -40,6 +40,9 @@ const {
40
40
  } = require('../../../lib/chatInput');
41
41
  const credentialAdapter = require('../../credential-adapter');
42
42
  const { buildLocalMcpServerConfigs } = require('../lib/mcp-resolver');
43
+ const {
44
+ defaultToolboxService: toolboxService,
45
+ } = require('../toolbox/service');
43
46
 
44
47
  const MAX_INLINE_MESSAGE_CHARS = Number(
45
48
  process.env.AMALGM_TALK_TO_AGENT_MAX_INLINE_CHARS
@@ -340,6 +343,39 @@ function coerceAuthMethodForHarness(harnessId, authMethod) {
340
343
  return supported.includes(authMethod) ? authMethod : supported[0] || 'amalgm';
341
344
  }
342
345
 
346
+ function normalizeEffort(value) {
347
+ if (typeof value !== 'string') return null;
348
+ const normalized = value.trim().toLowerCase().replace(/_/g, '-');
349
+ if (normalized === 'low') return 'low';
350
+ if (normalized === 'medium') return 'medium';
351
+ if (normalized === 'high') return 'high';
352
+ if (normalized === 'xhigh' || normalized === 'extra-high' || normalized === 'extra high') return 'xhigh';
353
+ if (normalized === 'max') return 'max';
354
+ return null;
355
+ }
356
+
357
+ function sanitizeAgentModelSettings(settings, harness) {
358
+ if (!settings || typeof settings !== 'object' || Array.isArray(settings)) return {};
359
+ const effort = normalizeEffort(settings.effort || settings.reasoningEffort || settings.reasoning);
360
+ const allowedEfforts =
361
+ harness === 'claude_code'
362
+ ? new Set(['low', 'medium', 'high', 'xhigh', 'max'])
363
+ : harness === 'codex'
364
+ ? new Set(['low', 'medium', 'high', 'xhigh'])
365
+ : new Set();
366
+ return {
367
+ ...(effort && allowedEfforts.has(effort) ? { effort } : {}),
368
+ ...((harness === 'claude_code' || harness === 'codex') && settings.fastMode === true ? { fastMode: true } : {}),
369
+ };
370
+ }
371
+
372
+ function modelIdForAgentSettings(modelId, harness, modelSettings) {
373
+ if (harness !== 'codex' || !modelSettings.effort || typeof modelId !== 'string') return modelId;
374
+ return modelId
375
+ .replace(/(?::thinking-|-thinking-)(low|medium|high|xhigh)$/i, '')
376
+ .replace(/\/(low|medium|high|xhigh)$/i, '') + '/' + modelSettings.effort;
377
+ }
378
+
343
379
  async function appendAsyncCompletionToCallerSession({
344
380
  callerSessionId,
345
381
  agent,
@@ -550,19 +586,17 @@ async function handleTalkToAgent(args, context = {}) {
550
586
  ? credentialAdapter.getPersistedAuthMode(agent.baseHarnessId)
551
587
  : 'amalgm';
552
588
  const preferredAuthMethod = getPreferredAuthMethod(agent.baseHarnessId, persistedAuthMethod);
553
- const isDefaultAgentClass = agent.id === agent.baseHarnessId;
554
589
  const defaultAuthMethod = coerceAuthMethodForHarness(
555
590
  agent.baseHarnessId,
556
- isDefaultAgentClass && agent.authMethod === 'amalgm' && preferredAuthMethod !== 'amalgm'
557
- ? preferredAuthMethod
558
- : agent.authMethod || preferredAuthMethod,
591
+ agent.authMethod || preferredAuthMethod,
559
592
  );
560
593
  const agentFiles = normalizeStringList(agent.files);
561
594
  const agentSkills = normalizeStringList(agent.skills);
562
595
  const agentTools = agent.tools || { mode: 'all', selectedToolIds: [] };
563
- const agentMcpAppIds = agentTools.mode === 'selected'
564
- ? normalizeStringList(agentTools.selectedToolIds)
565
- : normalizeStringList(agent.mcp?.appIds ?? agent.mcpAppIds);
596
+ const agentMcpAppIds = toolboxService.externalMcpToolIdsForMode(
597
+ agentTools.mode,
598
+ agentTools.selectedToolIds,
599
+ );
566
600
  const baseAgentSystemPrompt = buildAgentInstructions(
567
601
  agent.systemPrompt,
568
602
  agentFiles,
@@ -576,35 +610,27 @@ async function handleTalkToAgent(args, context = {}) {
576
610
  const fallbackModelId = agent.builtin
577
611
  ? agent.baseModelId
578
612
  : agent.baseModelId || DEFAULT_SELECTED_MODELS[agent.baseHarnessId] || null;
613
+ const agentModelSettings = sanitizeAgentModelSettings(agent.modelSettings, agent.baseHarnessId);
614
+ const effectiveModelId = modelIdForAgentSettings(
615
+ fallbackModelId,
616
+ agent.baseHarnessId,
617
+ agentModelSettings,
618
+ );
579
619
  const normalizedChatInput = normalizeChatInput({
580
620
  ...(isObject(chatInput) ? chatInput : {}),
581
621
  agent: {
582
622
  ...((isObject(chatInput) && isObject(chatInput.agent)) ? chatInput.agent : {}),
583
- harness:
584
- ((isObject(chatInput) && isObject(chatInput.agent) && chatInput.agent.harness) || null)
585
- || agent.baseHarnessId,
586
- model:
587
- ((isObject(chatInput) && isObject(chatInput.agent) && chatInput.agent.model) || null)
588
- || fallbackModelId,
589
- authMethod: coerceAuthMethodForHarness(
590
- agent.baseHarnessId,
591
- ((isObject(chatInput) && isObject(chatInput.agent) && chatInput.agent.authMethod) || null)
592
- || defaultAuthMethod,
593
- ),
623
+ harness: agent.baseHarnessId,
624
+ model: effectiveModelId,
625
+ authMethod: defaultAuthMethod,
594
626
  customAgentId: agent.builtin ? null : agent.id,
595
- systemPrompt:
596
- ((isObject(chatInput) && isObject(chatInput.agent) && chatInput.agent.systemPrompt) || null)
597
- || agentSystemPrompt
598
- || null,
627
+ systemPrompt: agentSystemPrompt || null,
599
628
  },
600
629
  tools: {
601
630
  ...((isObject(chatInput) && isObject(chatInput.tools)) ? chatInput.tools : {}),
602
- mcpAppIds: normalizeStringList([
603
- ...agentMcpAppIds,
604
- ...((((isObject(chatInput) && isObject(chatInput.tools) && Array.isArray(chatInput.tools.mcpAppIds))
605
- ? chatInput.tools.mcpAppIds
606
- : []))),
607
- ]),
631
+ mode: agentTools.mode,
632
+ toolIds: normalizeStringList(agentTools.selectedToolIds),
633
+ mcpAppIds: agentMcpAppIds,
608
634
  },
609
635
  execution: {
610
636
  ...((isObject(chatInput) && isObject(chatInput.execution)) ? chatInput.execution : {}),
@@ -616,7 +642,7 @@ async function handleTalkToAgent(args, context = {}) {
616
642
  }, {
617
643
  prompt: taskPrompt,
618
644
  harness: agent.baseHarnessId,
619
- modelId: fallbackModelId,
645
+ modelId: effectiveModelId,
620
646
  authMethod: defaultAuthMethod,
621
647
  cwd,
622
648
  systemPrompt: agentSystemPrompt,
@@ -624,7 +650,7 @@ async function handleTalkToAgent(args, context = {}) {
624
650
  const legacyChatFields = chatInputToLegacyFields(normalizedChatInput, {
625
651
  prompt: taskPrompt,
626
652
  harness: agent.baseHarnessId,
627
- modelId: fallbackModelId,
653
+ modelId: effectiveModelId,
628
654
  authMethod: defaultAuthMethod,
629
655
  cwd,
630
656
  systemPrompt: agentSystemPrompt,
@@ -723,11 +749,12 @@ async function handleTalkToAgent(args, context = {}) {
723
749
  taskDescription,
724
750
  computerId,
725
751
  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],
752
+ baseModelId:
753
+ agent.baseModelId ||
754
+ getSelectedModel(agent.baseHarnessId) ||
755
+ DEFAULT_SELECTED_MODELS[agent.baseHarnessId],
756
+ modelSettings: agentModelSettings,
757
+ authMethod: defaultAuthMethod,
731
758
  isAgentConversation: true,
732
759
  builtin: !!agent.builtin,
733
760
  systemPrompt: baseAgentSystemPrompt || '',
@@ -776,8 +803,9 @@ async function handleTalkToAgent(args, context = {}) {
776
803
  try {
777
804
  const { modelId: uiModelId, cliModel, reasoningEffort } = resolveModelSelection(
778
805
  agent.baseHarnessId,
779
- legacyChatFields.modelId || fallbackModelId,
806
+ legacyChatFields.modelId || effectiveModelId,
780
807
  );
808
+ const effectiveReasoningEffort = agentModelSettings.effort || reasoningEffort;
781
809
  const mcpServers = await buildLocalMcpServerConfigs(legacyChatFields.mcpAppIds || []);
782
810
 
783
811
  const chatPayload = {
@@ -793,7 +821,8 @@ async function handleTalkToAgent(args, context = {}) {
793
821
  agentId: chatAgentId,
794
822
  modelId: uiModelId,
795
823
  cliModel,
796
- ...(reasoningEffort ? { reasoningEffort } : {}),
824
+ ...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
825
+ ...(agentModelSettings.fastMode ? { fastMode: true } : {}),
797
826
  cwd: legacyChatFields.cwd || DEFAULT_CWD,
798
827
  authMethod: legacyChatFields.authMethod || defaultAuthMethod,
799
828
  mcpServers,
@@ -64,10 +64,6 @@ module.exports = [
64
64
  getSelectedModel(agent.baseHarnessId) ||
65
65
  DEFAULT_SELECTED_MODELS[agent.baseHarnessId],
66
66
  systemPrompt: agent.systemPrompt ? '(configured)' : '(none)',
67
- mcp: {
68
- inheritAll: agent.mcp?.inheritAll ?? true,
69
- customServerCount: agent.mcp?.customServers?.length ?? 0,
70
- },
71
67
  tools: agent.tools || { mode: 'all', selectedToolIds: [] },
72
68
  location: agent.location || null,
73
69
  locationName: agent.locationName || null,
@@ -9,7 +9,7 @@ const { DEFAULT_CWD } = require('../config');
9
9
  const { compileWorkflowText } = require('../workflows/compiler');
10
10
  const { callToolboxMcpTool, toolboxMcpToolName } = require('../toolbox/runner');
11
11
  const { buildWorkflowInput } = require('./context');
12
- const { resolveAmalgmCoreTool, resolveToolboxAction, suggestWorkflowToolActions } = require('./tool-actions');
12
+ const { resolveFirstPartyToolAction, resolveToolboxAction, suggestWorkflowToolActions } = require('./tool-actions');
13
13
  const {
14
14
  getAutomation,
15
15
  getAutomationByTriggerId,
@@ -290,10 +290,9 @@ async function runToolCell(cell, args, allowlist) {
290
290
  return runHttpTool(args);
291
291
  }
292
292
 
293
- if (cell.toolId === 'amalgm') {
294
- const coreTool = resolveAmalgmCoreTool(cell.actionName);
295
- if (!coreTool?.handler) throw new Error(`Amalgm action not found: ${cell.actionName}.`);
296
- const result = await coreTool.handler(args, {
293
+ const firstPartyAction = resolveFirstPartyToolAction(cell.toolId, cell.actionName);
294
+ if (firstPartyAction) {
295
+ const result = await firstPartyAction.tool.handler(args, {
297
296
  workflow: true,
298
297
  sessionMetadata: {
299
298
  origin: 'automation-workflow',