amalgm 0.1.79 → 0.1.80

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 (30) hide show
  1. package/package.json +1 -1
  2. package/runtime/lib/chatInput.js +0 -4
  3. package/runtime/scripts/amalgm-mcp/agent-config/compiler/instructions.js +70 -0
  4. package/runtime/scripts/amalgm-mcp/agent-config/loadout.js +65 -0
  5. package/runtime/scripts/amalgm-mcp/agent-config/renderers/instructions.js +9 -49
  6. package/runtime/scripts/amalgm-mcp/agent-config/rest.js +10 -10
  7. package/runtime/scripts/amalgm-mcp/agent-config/schema.js +15 -31
  8. package/runtime/scripts/amalgm-mcp/agent-config/store.js +21 -5
  9. package/runtime/scripts/amalgm-mcp/agents/rest.js +34 -34
  10. package/runtime/scripts/amalgm-mcp/agents/store.js +19 -28
  11. package/runtime/scripts/amalgm-mcp/agents/talk.js +10 -12
  12. package/runtime/scripts/amalgm-mcp/agents/tools.js +1 -1
  13. package/runtime/scripts/amalgm-mcp/automations/runner.js +1 -6
  14. package/runtime/scripts/amalgm-mcp/email/inbound.js +12 -8
  15. package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +0 -13
  16. package/runtime/scripts/amalgm-mcp/server/mcp-request-context.js +5 -0
  17. package/runtime/scripts/amalgm-mcp/slack/inbound.js +12 -8
  18. package/runtime/scripts/amalgm-mcp/tests/agent-config.test.js +1 -1
  19. package/runtime/scripts/amalgm-mcp/tests/agents-source-of-truth.test.js +3 -5
  20. package/runtime/scripts/amalgm-mcp/tests/chat-payloads.test.js +1 -1
  21. package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +2 -12
  22. package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +3 -4
  23. package/runtime/scripts/amalgm-mcp/toolbox/loadout-context.js +57 -0
  24. package/runtime/scripts/amalgm-mcp/toolbox/mcp-surface.js +8 -8
  25. package/runtime/scripts/amalgm-mcp/toolbox/service.js +17 -13
  26. package/runtime/scripts/amalgm-mcp/workflows/runner.js +1 -6
  27. package/runtime/scripts/chat-core/chat-payload.js +165 -0
  28. package/runtime/scripts/chat-core/server.js +4 -2
  29. package/runtime/scripts/chat-core/tests/chat-payload.test.js +78 -0
  30. package/runtime/scripts/amalgm-mcp/toolbox/selection.js +0 -42
@@ -32,6 +32,9 @@ const {
32
32
  readUserPreferences,
33
33
  } = require('../lib/prefs');
34
34
  const credentialAdapter = require('../../credential-adapter');
35
+ const {
36
+ normalizeLoadout,
37
+ } = require('../agent-config/loadout');
35
38
 
36
39
  const DEFAULT_AGENT_STATUS = 'unknown';
37
40
  const CUSTOM_AGENT_STATUS = 'ready';
@@ -89,9 +92,8 @@ const BUILTIN_AGENTS = BUILTIN_AGENT_BLUEPRINTS.map((agent) => ({
89
92
  files: [],
90
93
  skills: [],
91
94
  mcpAppIds: [],
92
- nativeMcps: [],
93
- tools: { mode: 'all', selectedToolIds: [] },
94
- mcp: { inheritAll: true, customServers: [], appIds: [], nativeMcps: [] },
95
+ loadout: { toolIds: [] },
96
+ mcp: { inheritAll: false, customServers: [], appIds: [] },
95
97
  builtin: false,
96
98
  deletable: true,
97
99
  editable: true,
@@ -230,30 +232,23 @@ function defaultCapabilities(agent) {
230
232
  streaming: true,
231
233
  resume: true,
232
234
  cancel: true,
233
- tools: agent?.tools?.mode === 'selected' ? 'selected' : 'all',
235
+ tools: 'selected',
234
236
  };
235
237
  }
236
238
 
237
- function normalizeMcpTransportConfig(toolConfig, mcpAppIds, nativeMcps) {
239
+ function normalizeMcpTransportConfig(loadout, mcpAppIds) {
238
240
  return {
239
- inheritAll: toolConfig.mode !== 'selected',
241
+ inheritAll: false,
240
242
  customServers: [],
241
243
  appIds: normalizeStringList(mcpAppIds),
242
- nativeMcps: normalizeStringList(nativeMcps),
243
244
  };
244
245
  }
245
246
 
246
- function normalizeToolConfig(tools) {
247
- const raw = isObject(tools) ? tools : null;
248
- const selectedToolIds = normalizeStringList(
249
- raw?.selectedToolIds
250
- ?? raw?.selected
251
- ?? [],
247
+ function normalizeAgentLoadout(input, existing) {
248
+ return normalizeLoadout(
249
+ input?.loadout ?? input?.toolset ?? input?.tools,
250
+ existing?.loadout ?? existing?.tools,
252
251
  );
253
- return {
254
- mode: raw?.mode === 'selected' ? 'selected' : 'all',
255
- selectedToolIds,
256
- };
257
252
  }
258
253
 
259
254
  function normalizeAgent(input, existing) {
@@ -262,8 +257,8 @@ function normalizeAgent(input, existing) {
262
257
  const builtin = input.builtin === true;
263
258
  const baseHarnessId = nonEmptyString(input.baseHarnessId || input.base_harness_id) || id;
264
259
  const adapter = nonEmptyString(input.adapter) || baseHarnessId;
265
- const toolConfig = normalizeToolConfig(input.tools);
266
- const mcpConfig = normalizeMcpTransportConfig(toolConfig, input.mcpAppIds, input.nativeMcps);
260
+ const loadout = normalizeAgentLoadout(input, existing);
261
+ const mcpConfig = normalizeMcpTransportConfig(loadout, input.mcpAppIds);
267
262
  const ownerComputerId = nonEmptyString(input.ownerComputerId || input.owner_computer_id)
268
263
  || nonEmptyString(existing?.ownerComputerId)
269
264
  || computerId();
@@ -286,11 +281,9 @@ function normalizeAgent(input, existing) {
286
281
  files: normalizeStringList(input.files),
287
282
  skills: normalizeStringList(input.skills),
288
283
  mcpAppIds: normalizeStringList(mcpConfig.appIds),
289
- nativeMcps: normalizeStringList(mcpConfig.nativeMcps),
290
- tools: toolConfig,
284
+ loadout,
291
285
  mcp: {
292
286
  ...mcpConfig,
293
- inheritAll: toolConfig.mode !== 'selected',
294
287
  },
295
288
  authMethod: input.authMethod || existing?.authMethod || persistedAuthMethod(baseHarnessId),
296
289
  location,
@@ -300,8 +293,8 @@ function normalizeAgent(input, existing) {
300
293
  installStatus: input.installStatus || existing?.installStatus || (builtin ? 'unknown' : 'external'),
301
294
  authStatus: input.authStatus || existing?.authStatus || 'unknown',
302
295
  capabilities: isObject(input.capabilities)
303
- ? { ...defaultCapabilities({ tools: toolConfig }), ...input.capabilities }
304
- : defaultCapabilities({ tools: toolConfig }),
296
+ ? { ...defaultCapabilities({ loadout }), ...input.capabilities }
297
+ : defaultCapabilities({ loadout }),
305
298
  builtin,
306
299
  deletable: input.deletable === false ? false : true,
307
300
  editable: input.editable === false ? false : true,
@@ -469,13 +462,11 @@ function seedBuiltinAgents(options = {}) {
469
462
  files: existing?.files || [],
470
463
  skills: existing?.skills || [],
471
464
  mcpAppIds: existing?.mcpAppIds || [],
472
- nativeMcps: [],
473
- tools: existing?.tools || { mode: 'all', selectedToolIds: [] },
465
+ loadout: existing?.loadout || existing?.tools || { toolIds: [] },
474
466
  mcp: {
475
- inheritAll: existing?.tools?.mode !== 'selected',
467
+ inheritAll: false,
476
468
  customServers: [],
477
469
  appIds: existing?.mcpAppIds || [],
478
- nativeMcps: [],
479
470
  },
480
471
  builtin: false,
481
472
  deletable: true,
@@ -38,8 +38,9 @@ const {
38
38
  getChatInputText,
39
39
  normalizeChatInput,
40
40
  } = require('../../../lib/chatInput');
41
- const { getAgentConfig, legacyFieldsFromConfig } = require('../agent-config/store');
41
+ const { agentFieldsFromConfig, getAgentConfig } = require('../agent-config/store');
42
42
  const { renderAgentInstructions } = require('../agent-config/renderers/instructions');
43
+ const { loadoutToolIds } = require('../agent-config/loadout');
43
44
  const credentialAdapter = require('../../credential-adapter');
44
45
  const { buildLocalMcpServerConfigs } = require('../lib/mcp-resolver');
45
46
  const {
@@ -599,19 +600,17 @@ async function handleTalkToAgent(args, context = {}) {
599
600
  agent.authMethod || preferredAuthMethod,
600
601
  );
601
602
  const agentConfig = getAgentConfig(agent);
602
- const configLegacy = legacyFieldsFromConfig(agentConfig);
603
+ const agentFields = agentFieldsFromConfig(agentConfig);
603
604
  const agentFiles = normalizeStringList(agent.files);
604
- const agentSkills = normalizeStringList(configLegacy.skills);
605
- const agentTools = configLegacy.tools || agent.tools || { mode: 'all', selectedToolIds: [] };
606
- const agentMcpAppIds = toolboxService.externalMcpToolIdsForMode(
607
- agentTools.mode,
608
- agentTools.selectedToolIds,
609
- );
605
+ const agentSkills = normalizeStringList(agentFields.skills);
606
+ const agentLoadout = agentConfig.loadout || agent.loadout || agent.tools || { toolIds: [] };
607
+ const agentLoadoutToolIds = loadoutToolIds(agentLoadout);
608
+ const agentMcpAppIds = toolboxService.externalMcpToolIdsForLoadout(agentLoadoutToolIds);
610
609
  const baseAgentSystemPrompt = [
611
610
  renderAgentInstructions(agentConfig, { includeAgentToAgentPrompt: true }),
612
611
  buildAgentFilesBlock(agentFiles),
613
612
  ].filter(Boolean).join('\n\n') || buildAgentInstructions(
614
- configLegacy.systemPrompt || agent.systemPrompt,
613
+ agentFields.systemPrompt || agent.systemPrompt,
615
614
  agentFiles,
616
615
  agentSkills,
617
616
  );
@@ -641,8 +640,7 @@ async function handleTalkToAgent(args, context = {}) {
641
640
  },
642
641
  tools: {
643
642
  ...((isObject(chatInput) && isObject(chatInput.tools)) ? chatInput.tools : {}),
644
- mode: agentTools.mode,
645
- toolIds: normalizeStringList(agentTools.selectedToolIds),
643
+ toolIds: agentLoadoutToolIds,
646
644
  mcpAppIds: agentMcpAppIds,
647
645
  },
648
646
  execution: {
@@ -783,7 +781,7 @@ async function handleTalkToAgent(args, context = {}) {
783
781
  files: agentFiles,
784
782
  skills: agentSkills,
785
783
  mcpAppIds: agentMcpAppIds,
786
- tools: agentTools,
784
+ loadout: agentConfig.loadout,
787
785
  agentConfig,
788
786
  ...(parentOrigin || {}),
789
787
  },
@@ -64,7 +64,7 @@ module.exports = [
64
64
  getSelectedModel(agent.baseHarnessId) ||
65
65
  DEFAULT_SELECTED_MODELS[agent.baseHarnessId],
66
66
  systemPrompt: agent.systemPrompt ? '(configured)' : '(none)',
67
- tools: agent.tools || { mode: 'all', selectedToolIds: [] },
67
+ loadout: agent.loadout || { toolIds: [] },
68
68
  location: agent.location || null,
69
69
  locationName: agent.locationName || null,
70
70
  ownerComputerId: agent.ownerComputerId || null,
@@ -311,12 +311,7 @@ async function runToolCell(cell, args, allowlist) {
311
311
  }
312
312
 
313
313
  const result = await callToolboxMcpTool(toolboxMcpToolName(resolved.action), args, {
314
- sessionMetadata: {
315
- tools: {
316
- mode: 'selected',
317
- selectedToolIds: [resolved.tool.id, resolved.action.id],
318
- },
319
- },
314
+ loadout: { toolIds: [resolved.tool.id, resolved.action.id] },
320
315
  });
321
316
  return mcpResultToOutput(result, actionKey);
322
317
  }
@@ -25,6 +25,9 @@ const {
25
25
  resolveModelSelection,
26
26
  DEFAULT_SELECTED_MODELS,
27
27
  } = require('../lib/prefs');
28
+ const { getAgentConfig } = require('../agent-config/store');
29
+ const { loadoutToolIds } = require('../agent-config/loadout');
30
+ const { defaultToolboxService: toolboxService } = require('../toolbox/service');
28
31
  const { buildLocalMcpServerConfigs } = require('../lib/mcp-resolver');
29
32
  const {
30
33
  chatInputToLegacyFields,
@@ -50,12 +53,14 @@ function normalizeStringList(values) {
50
53
  }
51
54
 
52
55
  function sessionToolConfig(metadata) {
53
- const tools = metadata?.tools && typeof metadata.tools === 'object' && !Array.isArray(metadata.tools)
54
- ? metadata.tools
55
- : {};
56
+ const agentId = asString(metadata?.agentConfigId) || asString(metadata?.agentId);
57
+ const loadout = agentId
58
+ ? getAgentConfig(agentId).loadout
59
+ : metadata?.loadout || metadata?.tools || {};
60
+ const toolIds = loadoutToolIds(loadout);
56
61
  return {
57
- mode: tools.mode === 'selected' ? 'selected' : 'all',
58
- toolIds: normalizeStringList(tools.selectedToolIds),
62
+ toolIds,
63
+ mcpAppIds: toolboxService.externalMcpToolIdsForLoadout(toolIds),
59
64
  };
60
65
  }
61
66
 
@@ -154,8 +159,7 @@ async function handleEmailInbound(body, sendJson) {
154
159
  systemPrompt: asString(metadata.systemPrompt),
155
160
  },
156
161
  tools: {
157
- mcpAppIds: Array.isArray(metadata.mcpAppIds) ? metadata.mcpAppIds : [],
158
- mode: toolConfig.mode,
162
+ mcpAppIds: toolConfig.mcpAppIds,
159
163
  toolIds: toolConfig.toolIds,
160
164
  },
161
165
  execution: {
@@ -170,7 +174,7 @@ async function handleEmailInbound(body, sendJson) {
170
174
  cwd,
171
175
  computerId,
172
176
  systemPrompt: asString(metadata.systemPrompt),
173
- mcpAppIds: Array.isArray(metadata.mcpAppIds) ? metadata.mcpAppIds : [],
177
+ mcpAppIds: toolConfig.mcpAppIds,
174
178
  });
175
179
 
176
180
  const legacyChatFields = chatInputToLegacyFields(normalizedChatInput, {
@@ -4,7 +4,6 @@
4
4
  */
5
5
 
6
6
  const fs = require('fs');
7
- const os = require('os');
8
7
  const path = require('path');
9
8
  const { AMALGM_DIR } = require('../config');
10
9
  const { appendStateEvent } = require('../state/events');
@@ -13,7 +12,6 @@ const {
13
12
  } = require('../toolbox/service');
14
13
 
15
14
  const CONNECTIONS_FILE = path.join(AMALGM_DIR, 'mcp-connections.json');
16
- const CLAUDE_FILE = path.join(os.homedir(), '.claude.json');
17
15
  let mcpRegistry = null;
18
16
  let mcpRegistryLoadAttempted = false;
19
17
  let warnedRegistryLoadFailure = false;
@@ -96,21 +94,10 @@ function sanitizeConnections(connections) {
96
94
  return sanitized;
97
95
  }
98
96
 
99
- function readNativeMcps() {
100
- try {
101
- const raw = fs.readFileSync(CLAUDE_FILE, 'utf8');
102
- const parsed = JSON.parse(raw);
103
- return Array.isArray(parsed?.claudeAiMcpEverConnected) ? parsed.claudeAiMcpEverConnected : [];
104
- } catch {
105
- return [];
106
- }
107
- }
108
-
109
97
  function buildConnectionsSnapshot() {
110
98
  const file = readConnectionsFile();
111
99
  return {
112
100
  connections: sanitizeConnections(file.connections),
113
- nativeMcps: readNativeMcps(),
114
101
  };
115
102
  }
116
103
 
@@ -21,6 +21,11 @@ async function buildMcpRequestContext(extra) {
21
21
  const parent = Array.isArray(rows) ? rows[0] : null;
22
22
  if (parent) {
23
23
  ctx.sessionMetadata = parent.metadata || {};
24
+ ctx.agentConfigId =
25
+ parent.metadata?.agentConfigId
26
+ || parent.metadata?.agentId
27
+ || parent.metadata?.customAgentId
28
+ || undefined;
24
29
  ctx.originName = parent.title || undefined;
25
30
  ctx.originHarnessId = parent.harness || undefined;
26
31
  ctx.originBaseHarnessId = parent.metadata?.baseHarnessId || undefined;
@@ -23,6 +23,9 @@ const {
23
23
  resolveModelSelection,
24
24
  DEFAULT_SELECTED_MODELS,
25
25
  } = require('../lib/prefs');
26
+ const { getAgentConfig } = require('../agent-config/store');
27
+ const { loadoutToolIds } = require('../agent-config/loadout');
28
+ const { defaultToolboxService: toolboxService } = require('../toolbox/service');
26
29
  const { buildLocalMcpServerConfigs } = require('../lib/mcp-resolver');
27
30
  const {
28
31
  chatInputToLegacyFields,
@@ -48,12 +51,14 @@ function normalizeStringList(values) {
48
51
  }
49
52
 
50
53
  function sessionToolConfig(metadata) {
51
- const tools = metadata?.tools && typeof metadata.tools === 'object' && !Array.isArray(metadata.tools)
52
- ? metadata.tools
53
- : {};
54
+ const agentId = asString(metadata?.agentConfigId) || asString(metadata?.agentId);
55
+ const loadout = agentId
56
+ ? getAgentConfig(agentId).loadout
57
+ : metadata?.loadout || metadata?.tools || {};
58
+ const toolIds = loadoutToolIds(loadout);
54
59
  return {
55
- mode: tools.mode === 'selected' ? 'selected' : 'all',
56
- toolIds: normalizeStringList(tools.selectedToolIds),
60
+ toolIds,
61
+ mcpAppIds: toolboxService.externalMcpToolIdsForLoadout(toolIds),
57
62
  };
58
63
  }
59
64
 
@@ -147,8 +152,7 @@ async function handleSlackInbound(body, sendJson) {
147
152
  systemPrompt: slackSystemPrompt || null,
148
153
  },
149
154
  tools: {
150
- mcpAppIds: Array.isArray(metadata.mcpAppIds) ? metadata.mcpAppIds : [],
151
- mode: toolConfig.mode,
155
+ mcpAppIds: toolConfig.mcpAppIds,
152
156
  toolIds: toolConfig.toolIds,
153
157
  },
154
158
  execution: {
@@ -163,7 +167,7 @@ async function handleSlackInbound(body, sendJson) {
163
167
  cwd,
164
168
  computerId,
165
169
  systemPrompt: slackSystemPrompt || undefined,
166
- mcpAppIds: Array.isArray(metadata.mcpAppIds) ? metadata.mcpAppIds : [],
170
+ mcpAppIds: toolConfig.mcpAppIds,
167
171
  });
168
172
 
169
173
  const legacyChatFields = chatInputToLegacyFields(normalizedChatInput, {
@@ -65,7 +65,7 @@ test('codex native import ports command hooks without inheriting MCP or auth con
65
65
  assert.equal(config.hooks[0].timeout, 90);
66
66
  assert.equal(config.hooks[0].statusMessage, 'Searching memories...');
67
67
  assert.equal(config.hooks[0].source.provider, 'codex');
68
- assert.equal(config.tools.mode, 'all');
68
+ assert.deepEqual(config.loadout.toolIds, []);
69
69
 
70
70
  const serialized = JSON.stringify(config);
71
71
  assert.equal(serialized.includes('mcp_servers'), false);
@@ -70,13 +70,11 @@ test('custom agent auth and model settings survive normalization', () => {
70
70
  baseModelId: 'openai/gpt-5.5',
71
71
  authMethod: 'provider_auth',
72
72
  modelSettings: { effort: 'medium', fastMode: true },
73
- tools: { mode: 'selected', selectedToolIds: ['tool.search'] },
73
+ loadout: { toolIds: ['tool.search'] },
74
74
  });
75
75
 
76
76
  assert.equal(agent.authMethod, 'provider_auth');
77
77
  assert.deepEqual(agent.modelSettings, { effort: 'medium', fastMode: true });
78
- assert.deepEqual(agent.tools, {
79
- mode: 'selected',
80
- selectedToolIds: ['tool.search'],
81
- });
78
+ assert.deepEqual(agent.loadout, { toolIds: ['tool.search'] });
79
+ assert.equal(Object.prototype.hasOwnProperty.call(agent, 'tools'), false);
82
80
  });
@@ -34,7 +34,7 @@ test('chat payloads persist in local SQLite and snapshot by id', () => {
34
34
  customAgentId: null,
35
35
  systemPrompt: null,
36
36
  mcpAppIds: ['github'],
37
- tools: { mode: 'selected', selectedToolIds: ['tool.search'] },
37
+ loadout: { toolIds: ['tool.search'] },
38
38
  pendingWorktree: false,
39
39
  };
40
40
 
@@ -131,12 +131,7 @@ test('MCP surface filters static tools in selected-tool mode', async () => {
131
131
  },
132
132
  });
133
133
  const context = {
134
- sessionMetadata: {
135
- tools: {
136
- mode: 'selected',
137
- selectedToolIds: ['amalgm.test'],
138
- },
139
- },
134
+ loadout: { toolIds: ['amalgm.test'] },
140
135
  };
141
136
 
142
137
  const listed = await surface.listTools(context);
@@ -167,12 +162,7 @@ test('MCP surface accepts legacy selected ids for first-party tools', async () =
167
162
  });
168
163
 
169
164
  const listed = await surface.listTools({
170
- sessionMetadata: {
171
- tools: {
172
- mode: 'selected',
173
- selectedToolIds: ['amalgm.browser'],
174
- },
175
- },
165
+ loadout: { toolIds: ['amalgm.browser'] },
176
166
  });
177
167
 
178
168
  assert.deepEqual(listed.map((tool) => tool.name), ['toolbox__browser_browser_navigate']);
@@ -186,7 +186,7 @@ test('toolbox service resolves only runnable CLI/API actions', () => {
186
186
  assert.equal(service.resolveRunnableAction('amalgm', 'notify_user'), null);
187
187
 
188
188
  const selected = service.listRunnableActions({
189
- sessionMetadata: { tools: { mode: 'selected', selectedToolIds: ['api.weather.lookup'] } },
189
+ loadout: { toolIds: ['api.weather.lookup'] },
190
190
  });
191
191
  assert.deepEqual(selected.map(({ action }) => action.id), ['api.weather.lookup']);
192
192
  });
@@ -194,9 +194,8 @@ test('toolbox service resolves only runnable CLI/API actions', () => {
194
194
  test('toolbox service derives only external MCP ids from selected tools', () => {
195
195
  const service = createToolboxService(createRepository());
196
196
 
197
- assert.deepEqual(service.externalMcpToolIdsForMode('all'), ['remote.mcp']);
198
197
  assert.deepEqual(
199
- service.externalMcpToolIdsForSelection([
198
+ service.externalMcpToolIdsForLoadout([
200
199
  'remote.mcp',
201
200
  'browser',
202
201
  'api.weather',
@@ -209,7 +208,7 @@ test('toolbox service derives only external MCP ids from selected tools', () =>
209
208
  test('toolbox service exposes portable MCP descriptors', () => {
210
209
  const service = createToolboxService(createRepository());
211
210
  const descriptors = service.listMcpTools({
212
- sessionMetadata: { tools: { mode: 'selected', selectedToolIds: ['api.weather.lookup'] } },
211
+ loadout: { toolIds: ['api.weather.lookup'] },
213
212
  });
214
213
 
215
214
  assert.equal(descriptors.length, 1);
@@ -0,0 +1,57 @@
1
+ 'use strict';
2
+
3
+ const { loadoutToolIds } = require('../agent-config/loadout');
4
+
5
+ const FIRST_PARTY_TOOL_IDS = new Set([
6
+ 'automations',
7
+ 'notifications',
8
+ 'agents',
9
+ 'apps',
10
+ 'browser',
11
+ ]);
12
+
13
+ function agentConfigIdFromContext(context) {
14
+ const metadata = context?.sessionMetadata || {};
15
+ return (
16
+ context?.agentConfigId
17
+ || metadata.agentConfigId
18
+ || metadata.agentId
19
+ || metadata.customAgentId
20
+ || null
21
+ );
22
+ }
23
+
24
+ function loadoutSetFromContext(context) {
25
+ const direct = context?.loadout || context?.toolset || null;
26
+ if (direct) return new Set(loadoutToolIds(direct));
27
+
28
+ const agentConfigId = agentConfigIdFromContext(context);
29
+ if (!agentConfigId) return null;
30
+
31
+ const { getAgentConfig } = require('../agent-config/store');
32
+ return new Set(loadoutToolIds(getAgentConfig(agentConfigId)));
33
+ }
34
+
35
+ function loadoutHas(loadoutToolIds, id) {
36
+ if (!loadoutToolIds || !id) return false;
37
+ const value = String(id || '').trim();
38
+ if (loadoutToolIds.has(value)) return true;
39
+ const toolId = value.split('.')[0];
40
+ if (loadoutToolIds.has(toolId)) return true;
41
+ if (FIRST_PARTY_TOOL_IDS.has(toolId) && loadoutToolIds.has(`amalgm.${value}`)) return true;
42
+ if (FIRST_PARTY_TOOL_IDS.has(value) && loadoutToolIds.has(`amalgm.${value}`)) return true;
43
+ if (value === 'notifications.notify_user' && loadoutToolIds.has('amalgm.notify_user')) return true;
44
+ if (value === 'agents.talk_to_agent' && loadoutToolIds.has('amalgm.talk_to_agent')) return true;
45
+ return false;
46
+ }
47
+
48
+ function loadoutContainsToolOrAction(loadoutToolIds, toolId, actionId) {
49
+ if (!loadoutToolIds) return true;
50
+ return loadoutHas(loadoutToolIds, toolId) || loadoutHas(loadoutToolIds, actionId);
51
+ }
52
+
53
+ module.exports = {
54
+ agentConfigIdFromContext,
55
+ loadoutContainsToolOrAction,
56
+ loadoutSetFromContext,
57
+ };
@@ -13,9 +13,9 @@ const { CORE_TOOLS } = require('../server/core-tools');
13
13
  const { toolboxMcpToolName } = require('./names');
14
14
  const { callToolboxMcpTool } = require('./runner');
15
15
  const {
16
- selectedContainsToolOrAction,
17
- selectedToolIdsForContext,
18
- } = require('./selection');
16
+ loadoutContainsToolOrAction,
17
+ loadoutSetFromContext,
18
+ } = require('./loadout-context');
19
19
  const {
20
20
  defaultToolboxService: toolboxService,
21
21
  } = require('./service');
@@ -43,9 +43,9 @@ function defaultListDynamicTools(context) {
43
43
  return toolboxService.listMcpTools(context);
44
44
  }
45
45
 
46
- function isStaticToolSelected(tool, selectedToolIds) {
47
- return selectedContainsToolOrAction(
48
- selectedToolIds,
46
+ function isStaticToolInLoadout(tool, loadoutToolIds) {
47
+ return loadoutContainsToolOrAction(
48
+ loadoutToolIds,
49
49
  tool.toolboxToolId,
50
50
  tool.toolboxActionId,
51
51
  );
@@ -59,8 +59,8 @@ function createToolboxMcpSurface(options = {}) {
59
59
  const callDynamicTool = options.callDynamicTool || callToolboxMcpTool;
60
60
 
61
61
  function staticToolsForContext(context) {
62
- const selectedToolIds = selectedToolIdsForContext(context);
63
- return staticTools.filter((tool) => isStaticToolSelected(tool, selectedToolIds));
62
+ const loadoutToolIds = loadoutSetFromContext(context);
63
+ return staticTools.filter((tool) => isStaticToolInLoadout(tool, loadoutToolIds));
64
64
  }
65
65
 
66
66
  function findStaticTool(toolName, context) {
@@ -10,9 +10,9 @@
10
10
  const store = require('./store');
11
11
  const { portableInputSchema, toolboxMcpToolName } = require('./names');
12
12
  const {
13
- selectedContainsToolOrAction,
14
- selectedToolIdsForContext,
15
- } = require('./selection');
13
+ loadoutContainsToolOrAction,
14
+ loadoutSetFromContext,
15
+ } = require('./loadout-context');
16
16
  const { mergeSystemToolboxCatalog } = require('./system-catalog');
17
17
 
18
18
  function actionRef(toolId, actionName) {
@@ -132,7 +132,15 @@ function createToolboxService(repository = store) {
132
132
  };
133
133
  }
134
134
 
135
- function externalMcpToolIdsForMode(mode = 'all', toolIds = []) {
135
+ function libraryToolIds() {
136
+ const toolbox = readCatalog();
137
+ return Object.values(toolbox.tools || {})
138
+ .filter((tool) => tool?.status === 'enabled')
139
+ .map((tool) => tool.id)
140
+ .filter(Boolean);
141
+ }
142
+
143
+ function externalMcpToolIdsForLoadout(toolIds = []) {
136
144
  const selected = new Set(
137
145
  (Array.isArray(toolIds) ? toolIds : [])
138
146
  .map((value) => String(value || '').trim())
@@ -140,23 +148,19 @@ function createToolboxService(repository = store) {
140
148
  );
141
149
  const toolbox = readCatalog();
142
150
  return Object.values(toolbox.tools || {})
143
- .filter((tool) => mode !== 'selected' || selected.has(tool.id))
151
+ .filter((tool) => selected.has(tool.id))
144
152
  .filter(isExternalMcpTool)
145
153
  .map((tool) => tool.id);
146
154
  }
147
155
 
148
- function externalMcpToolIdsForSelection(toolIds = []) {
149
- return externalMcpToolIdsForMode('selected', toolIds);
150
- }
151
-
152
156
  function listRunnableActions(context) {
153
157
  const toolbox = readToolbox();
154
- const selectedToolIds = selectedToolIdsForContext(context);
158
+ const loadoutToolIds = loadoutSetFromContext(context);
155
159
  return Object.values(toolbox.toolActions || {})
156
160
  .map((action) => ({ action, tool: toolbox.tools?.[action.toolId] }))
157
161
  .filter(({ action, tool }) => {
158
162
  if (!isRunnableToolboxTool(tool) || action.status !== 'enabled') return false;
159
- return selectedContainsToolOrAction(selectedToolIds, tool.id, action.id);
163
+ return loadoutContainsToolOrAction(loadoutToolIds, tool.id, action.id);
160
164
  });
161
165
  }
162
166
 
@@ -196,11 +200,11 @@ function createToolboxService(repository = store) {
196
200
  return {
197
201
  deleteAction: repository.deleteToolAction,
198
202
  deleteTool: repository.deleteTool,
199
- externalMcpToolIdsForMode,
200
- externalMcpToolIdsForSelection,
203
+ externalMcpToolIdsForLoadout,
201
204
  getAction,
202
205
  getTool,
203
206
  listMcpTools,
207
+ libraryToolIds,
204
208
  listRunnableActions,
205
209
  listTools,
206
210
  readCatalog,
@@ -317,12 +317,7 @@ async function runToolCell(cell, args, allowlist) {
317
317
  }
318
318
 
319
319
  const result = await callToolboxMcpTool(toolboxMcpToolName(resolved.action), args, {
320
- sessionMetadata: {
321
- tools: {
322
- mode: 'selected',
323
- selectedToolIds: [resolved.tool.id, resolved.action.id],
324
- },
325
- },
320
+ loadout: { toolIds: [resolved.tool.id, resolved.action.id] },
326
321
  });
327
322
  return mcpResultToOutput(result, actionKey);
328
323
  }