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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amalgm",
3
- "version": "0.1.79",
3
+ "version": "0.1.80",
4
4
  "description": "Amalgm local computer runtime: login, MCP, chat, events, previews, and tunnels.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -58,7 +58,6 @@ const ChatInputSchema = z.object({
58
58
  }).default({}),
59
59
  tools: z.object({
60
60
  mcpAppIds: z.array(z.string()).default([]),
61
- mode: z.enum(['all', 'selected']).default('all'),
62
61
  toolIds: z.array(z.string()).default([]),
63
62
  }).default({}),
64
63
  execution: z.object({
@@ -224,12 +223,9 @@ function normalizeChatInput(chatInput, legacy = {}) {
224
223
  },
225
224
  tools: {
226
225
  mcpAppIds: uniqueStrings(rawTools.mcpAppIds || legacy.mcpAppIds),
227
- mode: rawTools.mode === 'selected' || legacy.toolMode === 'selected' ? 'selected' : 'all',
228
226
  toolIds: uniqueStrings(
229
227
  rawTools.toolIds
230
- || rawTools.selectedToolIds
231
228
  || legacy.toolIds
232
- || legacy.selectedToolIds,
233
229
  ),
234
230
  },
235
231
  execution: {
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const { normalizeAgentConfig } = require('../schema');
4
+
5
+ function sourcePath(source) {
6
+ if (!source || typeof source !== 'object' || Array.isArray(source)) return '';
7
+ return typeof source.path === 'string' ? source.path.trim() : '';
8
+ }
9
+
10
+ function markdownListItem(title, details = []) {
11
+ const cleanTitle = String(title || '').trim();
12
+ if (!cleanTitle) return '';
13
+ const lines = [`- ${cleanTitle}`];
14
+ for (const detail of details.map((value) => String(value || '').trim()).filter(Boolean)) {
15
+ lines.push(` ${detail}`);
16
+ }
17
+ return lines.join('\n');
18
+ }
19
+
20
+ function compileSkillsSection(skills) {
21
+ const enabled = (Array.isArray(skills) ? skills : []).filter((skill) => skill.enabled !== false);
22
+ if (enabled.length === 0) return '';
23
+
24
+ const lines = ['## Skills you should use'];
25
+ for (const skill of enabled) {
26
+ const title = skill.description
27
+ ? `${skill.name || skill.id}: ${skill.description}`
28
+ : `${skill.name || skill.id}`;
29
+ const path = sourcePath(skill.source);
30
+ const details = [
31
+ path ? `Path: ${path}` : '',
32
+ skill.content && path ? 'Read the skill file when the task matches this skill.' : '',
33
+ skill.content && !path ? skill.content.trim() : '',
34
+ ];
35
+ lines.push(markdownListItem(title, details));
36
+ }
37
+ return lines.join('\n');
38
+ }
39
+
40
+ function compileSubagentsSection(subagents) {
41
+ const enabled = (Array.isArray(subagents) ? subagents : []).filter((subagent) => subagent.enabled !== false);
42
+ if (enabled.length === 0) return '';
43
+
44
+ const lines = ['## Agents you should use'];
45
+ for (const subagent of enabled) {
46
+ const name = subagent.name || subagent.agentId;
47
+ const title = subagent.description
48
+ ? `${name}: ${subagent.description}`
49
+ : name;
50
+ lines.push(markdownListItem(title, [`Agent id: ${subagent.agentId}`]));
51
+ }
52
+ return lines.join('\n');
53
+ }
54
+
55
+ function compileAgentInstructions(config, options = {}) {
56
+ const normalized = normalizeAgentConfig(config);
57
+ const chunks = [
58
+ normalized.instructions,
59
+ compileSkillsSection(normalized.skills),
60
+ compileSubagentsSection(normalized.subagents),
61
+ options.extraMarkdown,
62
+ ];
63
+ return chunks.map((chunk) => String(chunk || '').trim()).filter(Boolean).join('\n\n');
64
+ }
65
+
66
+ module.exports = {
67
+ compileAgentInstructions,
68
+ compileSkillsSection,
69
+ compileSubagentsSection,
70
+ };
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ function isObject(value) {
4
+ return !!value && typeof value === 'object' && !Array.isArray(value);
5
+ }
6
+
7
+ function uniqueStrings(values) {
8
+ if (!Array.isArray(values)) return [];
9
+ return [...new Set(values
10
+ .filter((value) => typeof value === 'string')
11
+ .map((value) => value.trim())
12
+ .filter(Boolean))];
13
+ }
14
+
15
+ function rawToolIds(value) {
16
+ const raw = isObject(value) ? value : {};
17
+ return uniqueStrings(
18
+ raw.toolIds
19
+ || raw.selectedToolIds
20
+ || raw.selected
21
+ || raw.tools
22
+ || [],
23
+ );
24
+ }
25
+
26
+ function hasExplicitToolIds(value) {
27
+ if (!isObject(value)) return false;
28
+ return Object.prototype.hasOwnProperty.call(value, 'toolIds')
29
+ || Object.prototype.hasOwnProperty.call(value, 'selectedToolIds')
30
+ || Object.prototype.hasOwnProperty.call(value, 'selected')
31
+ || Object.prototype.hasOwnProperty.call(value, 'tools');
32
+ }
33
+
34
+ function normalizeLoadout(value, fallback = {}, options = {}) {
35
+ const raw = isObject(value) ? value : {};
36
+ const prior = isObject(fallback) ? fallback : {};
37
+ const explicit = rawToolIds(raw);
38
+ const previous = rawToolIds(prior);
39
+ const defaultToolIds = uniqueStrings(options.defaultToolIds || []);
40
+
41
+ if (hasExplicitToolIds(raw)) return { toolIds: explicit };
42
+ if (explicit.length > 0) return { toolIds: explicit };
43
+ if (hasExplicitToolIds(prior)) return { toolIds: previous };
44
+ if (previous.length > 0) return { toolIds: previous };
45
+
46
+ const isLegacyAll =
47
+ (raw.mode && raw.mode !== 'selected')
48
+ || (prior.mode && prior.mode !== 'selected');
49
+ if (isLegacyAll && defaultToolIds.length > 0) return { toolIds: defaultToolIds };
50
+
51
+ return { toolIds: [] };
52
+ }
53
+
54
+ function loadoutToolIds(configOrLoadout) {
55
+ if (isObject(configOrLoadout?.loadout)) {
56
+ return normalizeLoadout(configOrLoadout.loadout).toolIds;
57
+ }
58
+ return normalizeLoadout(configOrLoadout).toolIds;
59
+ }
60
+
61
+ module.exports = {
62
+ loadoutToolIds,
63
+ normalizeLoadout,
64
+ uniqueStrings,
65
+ };
@@ -1,54 +1,14 @@
1
1
  'use strict';
2
2
 
3
- const { normalizeAgentConfig } = require('../schema');
4
-
5
- const AGENT_TO_AGENT_PROMPT = [
6
- 'You are an Amalgm agent. Other first-class Amalgm agents may be available as peers.',
7
- 'When a task is better handled by a configured peer, use the Amalgm agent-to-agent tool instead of inventing a hidden subagent.',
8
- ].join(' ');
9
-
10
- function renderSkills(skills) {
11
- const enabled = (Array.isArray(skills) ? skills : []).filter((skill) => skill.enabled !== false);
12
- if (enabled.length === 0) return '';
13
- const lines = ['Skills available to this agent:'];
14
- for (const skill of enabled) {
15
- if (skill.content) {
16
- lines.push(`- ${skill.name || skill.id}: ${skill.description || 'see skill content below'}`);
17
- lines.push('');
18
- lines.push(`<skill id="${skill.id}" name="${skill.name || skill.id}">`);
19
- lines.push(skill.content.trim());
20
- lines.push('</skill>');
21
- } else {
22
- lines.push(`- ${skill.name || skill.id}${skill.description ? `: ${skill.description}` : ''}`);
23
- }
24
- }
25
- return `<agent-skills>\n${lines.join('\n')}\n</agent-skills>`;
26
- }
27
-
28
- function renderSubagents(subagents) {
29
- const enabled = (Array.isArray(subagents) ? subagents : []).filter((subagent) => subagent.enabled !== false);
30
- if (enabled.length === 0) return '';
31
- const lines = [
32
- 'Peer agents available through the Amalgm agent-to-agent tool:',
33
- ...enabled.map((subagent) => (
34
- `- ${subagent.name || subagent.agentId} (${subagent.agentId})${subagent.description ? `: ${subagent.description}` : ''}`
35
- )),
36
- ];
37
- return `<agent-peers>\n${lines.join('\n')}\n</agent-peers>`;
38
- }
39
-
40
- function renderAgentInstructions(config, options = {}) {
41
- const normalized = normalizeAgentConfig(config);
42
- const chunks = [
43
- normalized.instructions,
44
- options.includeAgentToAgentPrompt === false ? '' : AGENT_TO_AGENT_PROMPT,
45
- renderSkills(normalized.skills),
46
- renderSubagents(normalized.subagents),
47
- ];
48
- return chunks.map((chunk) => String(chunk || '').trim()).filter(Boolean).join('\n\n');
49
- }
3
+ const {
4
+ compileAgentInstructions,
5
+ compileSkillsSection,
6
+ compileSubagentsSection,
7
+ } = require('../compiler/instructions');
50
8
 
51
9
  module.exports = {
52
- AGENT_TO_AGENT_PROMPT,
53
- renderAgentInstructions,
10
+ compileAgentInstructions,
11
+ compileSkillsSection,
12
+ compileSubagentsSection,
13
+ renderAgentInstructions: compileAgentInstructions,
54
14
  };
@@ -8,8 +8,8 @@ const {
8
8
  } = require('../agents/store');
9
9
  const { importNativeConfigForHarness } = require('./importers');
10
10
  const {
11
+ agentFieldsFromConfig,
11
12
  getAgentConfig,
12
- legacyFieldsFromConfig,
13
13
  upsertAgentConfig,
14
14
  } = require('./store');
15
15
  const {
@@ -59,7 +59,7 @@ function ensureImportedAgent(parentAgent, nativeAgent) {
59
59
  if (existing) {
60
60
  const config = upsertAgentConfig(existing.id, {
61
61
  instructions: nativeAgent.instructions || existing.systemPrompt || '',
62
- tools: existing.tools || { mode: 'all', selectedToolIds: [] },
62
+ loadout: existing.loadout || existing.tools || { toolIds: [] },
63
63
  skills: existing.skills || [],
64
64
  }, { source: 'agent-config:import-native-subagent' });
65
65
  return { agent: { ...existing, config }, created: false };
@@ -73,24 +73,24 @@ function ensureImportedAgent(parentAgent, nativeAgent) {
73
73
  modelSettings: parentAgent.modelSettings,
74
74
  systemPrompt: nativeAgent.instructions || '',
75
75
  skills: [],
76
- tools: { mode: 'all', selectedToolIds: [] },
76
+ loadout: { toolIds: [] },
77
77
  authMethod: parentAgent.authMethod,
78
78
  }, { source: 'agent-config:import-native-subagent' });
79
79
  const config = upsertAgentConfig(agent.id, {
80
80
  runtimeHomeLayout: 'per-agent',
81
81
  instructions: nativeAgent.instructions || '',
82
82
  skills: [],
83
- tools: { mode: 'all', selectedToolIds: [] },
83
+ loadout: { toolIds: [] },
84
84
  }, { source: 'agent-config:import-native-subagent' });
85
85
  return { agent: { ...agent, config }, created: true };
86
86
  }
87
87
 
88
- function syncLegacyAgentFields(agentId, config) {
89
- const legacy = legacyFieldsFromConfig(config);
88
+ function syncAgentFields(agentId, config) {
89
+ const fields = agentFieldsFromConfig(config);
90
90
  try {
91
- updateAgent(agentId, legacy, { source: 'agent-config:sync-legacy-fields' });
91
+ updateAgent(agentId, fields, { source: 'agent-config:sync-agent-fields' });
92
92
  } catch {
93
- // Config is the source of truth; legacy sync is best-effort for older UI paths.
93
+ // Config is the source of truth; row projection is best-effort for list views.
94
94
  }
95
95
  }
96
96
 
@@ -110,7 +110,7 @@ async function handleUpdate(body, sendJson) {
110
110
  let saved;
111
111
  try {
112
112
  saved = upsertAgentConfig(agent_id, config || {}, { source: 'agent-config:update-rest' });
113
- syncLegacyAgentFields(agent_id, saved);
113
+ syncAgentFields(agent_id, saved);
114
114
  } catch (error) {
115
115
  return sendJson(400, { error: error.message || 'Failed to update agent config' });
116
116
  }
@@ -148,7 +148,7 @@ async function handleImportNative(body, sendJson) {
148
148
  const existing = getAgentConfig(agent);
149
149
  const merged = mergeImportedConfig(existing, imported.config || {}, subagentRefs, replace === true);
150
150
  const saved = upsertAgentConfig(agent.id, merged, { source: 'agent-config:import-native' });
151
- syncLegacyAgentFields(agent.id, saved);
151
+ syncAgentFields(agent.id, saved);
152
152
 
153
153
  return sendJson(200, {
154
154
  ok: true,
@@ -5,6 +5,10 @@ const crypto = require('crypto');
5
5
  const CONFIG_VERSION = 1;
6
6
  const DEFAULT_RUNTIME_HOME_LAYOUT = 'legacy';
7
7
  const PER_AGENT_RUNTIME_HOME_LAYOUT = 'per-agent';
8
+ const {
9
+ normalizeLoadout,
10
+ uniqueStrings,
11
+ } = require('./loadout');
8
12
 
9
13
  function isObject(value) {
10
14
  return !!value && typeof value === 'object' && !Array.isArray(value);
@@ -14,14 +18,6 @@ function nonEmptyString(value) {
14
18
  return typeof value === 'string' && value.trim() ? value.trim() : '';
15
19
  }
16
20
 
17
- function uniqueStrings(values) {
18
- if (!Array.isArray(values)) return [];
19
- return [...new Set(values
20
- .filter((value) => typeof value === 'string')
21
- .map((value) => value.trim())
22
- .filter(Boolean))];
23
- }
24
-
25
21
  function stableId(prefix, seed) {
26
22
  const clean = nonEmptyString(seed)
27
23
  .toLowerCase()
@@ -41,22 +37,6 @@ function cloneJson(value) {
41
37
  }
42
38
  }
43
39
 
44
- function normalizeTools(value, fallback = {}) {
45
- const raw = isObject(value) ? value : {};
46
- const prior = isObject(fallback) ? fallback : {};
47
- const selectedToolIds = uniqueStrings(
48
- raw.selectedToolIds
49
- || raw.toolIds
50
- || raw.selected
51
- || prior.selectedToolIds
52
- || [],
53
- );
54
- return {
55
- mode: raw.mode === 'selected' || prior.mode === 'selected' ? 'selected' : 'all',
56
- selectedToolIds,
57
- };
58
- }
59
-
60
40
  function normalizeSkill(value, index = 0) {
61
41
  if (typeof value === 'string') {
62
42
  const name = nonEmptyString(value);
@@ -184,10 +164,12 @@ function normalizeRuntimeHomeLayout(value, fallback = DEFAULT_RUNTIME_HOME_LAYOU
184
164
  : DEFAULT_RUNTIME_HOME_LAYOUT;
185
165
  }
186
166
 
187
- function normalizeAgentConfig(input = {}, fallback = {}) {
167
+ function normalizeAgentConfig(input = {}, fallback = {}, options = {}) {
188
168
  const raw = isObject(input) ? input : {};
189
169
  const prior = isObject(fallback) ? fallback : {};
190
170
  const agentId = nonEmptyString(raw.agentId || raw.agent_id || prior.agentId);
171
+ const rawLoadout = raw.loadout ?? raw.toolset ?? raw.tools;
172
+ const priorLoadout = prior.loadout ?? prior.toolset ?? prior.tools;
191
173
  return {
192
174
  version: CONFIG_VERSION,
193
175
  ...(agentId ? { agentId } : {}),
@@ -200,7 +182,9 @@ function normalizeAgentConfig(input = {}, fallback = {}) {
200
182
  typeof prior.instructions === 'string' ? prior.instructions : '',
201
183
  ),
202
184
  skills: normalizeSkills(raw.skills ?? prior.skills ?? []),
203
- tools: normalizeTools(raw.tools, prior.tools),
185
+ loadout: normalizeLoadout(rawLoadout, priorLoadout, {
186
+ defaultToolIds: options.defaultLoadoutToolIds,
187
+ }),
204
188
  hooks: normalizeHooks(raw.hooks ?? prior.hooks ?? []),
205
189
  subagents: normalizeSubagents(raw.subagents ?? prior.subagents ?? []),
206
190
  };
@@ -211,13 +195,13 @@ function configFromAgent(agent = {}) {
211
195
  agentId: agent.id,
212
196
  instructions: agent.systemPrompt || '',
213
197
  skills: agent.skills || [],
214
- tools: agent.tools || { mode: 'all', selectedToolIds: [] },
198
+ loadout: agent.loadout || agent.toolset || agent.tools || { toolIds: [] },
215
199
  hooks: agent.hooks || [],
216
200
  subagents: agent.subagents || [],
217
201
  });
218
202
  }
219
203
 
220
- function legacyFieldsFromConfig(config = {}) {
204
+ function agentFieldsFromConfig(config = {}) {
221
205
  const normalized = normalizeAgentConfig(config);
222
206
  return {
223
207
  systemPrompt: normalized.instructions,
@@ -225,7 +209,7 @@ function legacyFieldsFromConfig(config = {}) {
225
209
  .filter((skill) => skill.enabled !== false)
226
210
  .map((skill) => skill.content || skill.name)
227
211
  .filter(Boolean),
228
- tools: normalized.tools,
212
+ loadout: normalized.loadout,
229
213
  hooks: normalized.hooks,
230
214
  subagents: normalized.subagents,
231
215
  };
@@ -235,14 +219,14 @@ module.exports = {
235
219
  CONFIG_VERSION,
236
220
  DEFAULT_RUNTIME_HOME_LAYOUT,
237
221
  PER_AGENT_RUNTIME_HOME_LAYOUT,
222
+ agentFieldsFromConfig,
238
223
  configFromAgent,
239
- legacyFieldsFromConfig,
240
224
  normalizeAgentConfig,
241
225
  normalizeHooks,
226
+ normalizeLoadout,
242
227
  normalizeRuntimeHomeLayout,
243
228
  normalizeSkills,
244
229
  normalizeSubagents,
245
- normalizeTools,
246
230
  stableId,
247
231
  uniqueStrings,
248
232
  };
@@ -3,8 +3,8 @@
3
3
  const { openLocalDb } = require('../state/db');
4
4
  const { insertStateEvent, publishStateEvent } = require('../state/events');
5
5
  const {
6
+ agentFieldsFromConfig,
6
7
  configFromAgent,
7
- legacyFieldsFromConfig,
8
8
  normalizeAgentConfig,
9
9
  } = require('./schema');
10
10
 
@@ -16,10 +16,24 @@ function readRow(db, agentId) {
16
16
  return db.prepare('SELECT * FROM agent_configs WHERE agent_id = ?').get(agentId);
17
17
  }
18
18
 
19
+ function defaultLoadoutToolIds() {
20
+ try {
21
+ const { defaultToolboxService } = require('../toolbox/service');
22
+ return Object.values(defaultToolboxService.readCatalog().tools || {})
23
+ .filter((tool) => tool?.status === 'enabled')
24
+ .map((tool) => tool.id)
25
+ .filter(Boolean);
26
+ } catch {
27
+ return [];
28
+ }
29
+ }
30
+
19
31
  function rowToConfig(row) {
20
32
  if (!row) return null;
21
33
  try {
22
- return normalizeAgentConfig(JSON.parse(row.config_json || '{}'));
34
+ return normalizeAgentConfig(JSON.parse(row.config_json || '{}'), {}, {
35
+ defaultLoadoutToolIds: defaultLoadoutToolIds(),
36
+ });
23
37
  } catch {
24
38
  return null;
25
39
  }
@@ -49,7 +63,9 @@ function upsertAgentConfig(agentId, configPatch, options = {}) {
49
63
  ...existing,
50
64
  ...(configPatch || {}),
51
65
  agentId,
52
- }, existing);
66
+ }, existing, {
67
+ defaultLoadoutToolIds: defaultLoadoutToolIds(),
68
+ });
53
69
  const updatedAt = nowIso();
54
70
  const event = db.transaction(() => {
55
71
  db.prepare(`
@@ -108,16 +124,16 @@ function agentWithConfig(agent) {
108
124
  const config = getAgentConfig(agent);
109
125
  return {
110
126
  ...agent,
111
- ...legacyFieldsFromConfig(config),
127
+ ...agentFieldsFromConfig(config),
112
128
  config,
113
129
  };
114
130
  }
115
131
 
116
132
  module.exports = {
117
133
  agentWithConfig,
134
+ agentFieldsFromConfig,
118
135
  deleteAgentConfig,
119
136
  getAgentConfig,
120
- legacyFieldsFromConfig,
121
137
  listAgentConfigs,
122
138
  upsertAgentConfig,
123
139
  };
@@ -12,14 +12,17 @@ const {
12
12
  const { hydrateModelPreferences } = require('../lib/prefs');
13
13
  const credentialAdapter = require('../../credential-adapter');
14
14
  const {
15
+ agentFieldsFromConfig,
15
16
  agentWithConfig,
16
17
  deleteAgentConfig,
17
- legacyFieldsFromConfig,
18
18
  upsertAgentConfig,
19
19
  } = require('../agent-config/store');
20
20
  const {
21
21
  defaultToolboxService: toolboxService,
22
22
  } = require('../toolbox/service');
23
+ const {
24
+ normalizeLoadout,
25
+ } = require('../agent-config/loadout');
23
26
 
24
27
  function coerceAuthMethodForHarness(harnessId, authMethod) {
25
28
  const supported = credentialAdapter.SUPPORTED_AUTH_BY_HARNESS?.[harnessId] || ['amalgm'];
@@ -34,30 +37,25 @@ function normalizeStringList(values) {
34
37
  .filter(Boolean))];
35
38
  }
36
39
 
37
- function derivedMcpTransportConfig(toolConfig) {
38
- const appIds = toolboxService.externalMcpToolIdsForMode(
39
- toolConfig.mode,
40
- toolConfig.selectedToolIds,
41
- );
40
+ function defaultLoadout() {
41
+ return { toolIds: toolboxService.libraryToolIds() };
42
+ }
43
+
44
+ function derivedMcpTransportConfig(loadout) {
45
+ const appIds = toolboxService.externalMcpToolIdsForLoadout(loadout.toolIds);
42
46
  return {
43
- inheritAll: toolConfig.mode !== 'selected',
47
+ inheritAll: false,
44
48
  customServers: [],
45
49
  appIds,
46
- nativeMcps: [],
47
50
  };
48
51
  }
49
52
 
50
- function normalizeToolConfig(tools) {
51
- const raw = tools && typeof tools === 'object' && !Array.isArray(tools) ? tools : null;
52
- const selectedToolIds = normalizeStringList(
53
- raw?.selectedToolIds
54
- ?? raw?.selected
55
- ?? [],
53
+ function normalizeRequestLoadout(input, fallback = null) {
54
+ return normalizeLoadout(
55
+ input?.loadout ?? input?.toolset ?? input?.tools,
56
+ fallback?.loadout ?? fallback?.tools,
57
+ { defaultToolIds: toolboxService.libraryToolIds() },
56
58
  );
57
- return {
58
- mode: raw?.mode === 'selected' ? 'selected' : 'all',
59
- selectedToolIds,
60
- };
61
59
  }
62
60
 
63
61
  async function handleList(sendJson) {
@@ -82,15 +80,15 @@ async function handleCreate(body, sendJson) {
82
80
  systemPrompt,
83
81
  files,
84
82
  skills,
85
- tools,
83
+ loadout,
86
84
  authMethod,
87
85
  config,
88
86
  } = body;
89
87
  if (!name || !name.trim()) return sendJson(400, { error: 'name is required' });
90
88
  if (!baseHarnessId) return sendJson(400, { error: 'baseHarnessId is required' });
91
89
 
92
- const toolConfig = normalizeToolConfig(tools);
93
- const mcpConfig = derivedMcpTransportConfig(toolConfig);
90
+ const agentLoadout = normalizeRequestLoadout({ loadout }, { loadout: defaultLoadout() });
91
+ const mcpConfig = derivedMcpTransportConfig(agentLoadout);
94
92
  let agent;
95
93
  try {
96
94
  const configPatch = config && typeof config === 'object' && !Array.isArray(config)
@@ -98,7 +96,7 @@ async function handleCreate(body, sendJson) {
98
96
  : {
99
97
  instructions: systemPrompt || '',
100
98
  skills: normalizeStringList(skills),
101
- tools: toolConfig,
99
+ loadout: agentLoadout,
102
100
  };
103
101
  agent = createAgent({
104
102
  name: name.trim(),
@@ -110,8 +108,7 @@ async function handleCreate(body, sendJson) {
110
108
  files: normalizeStringList(files),
111
109
  skills: normalizeStringList(skills),
112
110
  mcpAppIds: mcpConfig.appIds,
113
- nativeMcps: mcpConfig.nativeMcps,
114
- tools: toolConfig,
111
+ loadout: agentLoadout,
115
112
  mcp: mcpConfig,
116
113
  authMethod: coerceAuthMethodForHarness(baseHarnessId, authMethod),
117
114
  });
@@ -121,7 +118,7 @@ async function handleCreate(body, sendJson) {
121
118
  }, { source: 'agents:create' });
122
119
  agent = {
123
120
  ...agent,
124
- ...legacyFieldsFromConfig(savedConfig),
121
+ ...agentFieldsFromConfig(savedConfig),
125
122
  config: savedConfig,
126
123
  };
127
124
  } catch (error) {
@@ -144,11 +141,10 @@ async function handleUpdate(body, sendJson) {
144
141
  updates.authMethod = coerceAuthMethodForHarness(targetHarnessId, existing.authMethod);
145
142
  }
146
143
 
147
- const toolConfig = normalizeToolConfig(updates.tools ?? existing.tools);
148
- const mcpConfig = derivedMcpTransportConfig(toolConfig);
149
- updates.tools = toolConfig;
144
+ const agentLoadout = normalizeRequestLoadout(updates, existing);
145
+ const mcpConfig = derivedMcpTransportConfig(agentLoadout);
146
+ updates.loadout = agentLoadout;
150
147
  updates.mcpAppIds = mcpConfig.appIds;
151
- updates.nativeMcps = mcpConfig.nativeMcps;
152
148
  updates.mcp = mcpConfig;
153
149
 
154
150
  let agent;
@@ -158,19 +154,23 @@ async function handleUpdate(body, sendJson) {
158
154
  : {
159
155
  instructions: updates.systemPrompt ?? existing.systemPrompt ?? '',
160
156
  skills: normalizeStringList(updates.skills ?? existing.skills),
161
- tools: toolConfig,
157
+ loadout: agentLoadout,
162
158
  hooks: Array.isArray(updates.hooks) ? updates.hooks : existing.config?.hooks,
163
159
  subagents: Array.isArray(updates.subagents) ? updates.subagents : existing.config?.subagents,
164
160
  };
165
161
  delete updates.config;
166
162
  const savedConfig = upsertAgentConfig(agent_id, configPatch, { source: 'agents:update' });
167
- updates.systemPrompt = legacyFieldsFromConfig(savedConfig).systemPrompt;
168
- updates.skills = legacyFieldsFromConfig(savedConfig).skills;
169
- updates.tools = savedConfig.tools;
163
+ const agentFields = agentFieldsFromConfig(savedConfig);
164
+ const savedMcpConfig = derivedMcpTransportConfig(savedConfig.loadout);
165
+ updates.systemPrompt = agentFields.systemPrompt;
166
+ updates.skills = agentFields.skills;
167
+ updates.loadout = savedConfig.loadout;
168
+ updates.mcpAppIds = savedMcpConfig.appIds;
169
+ updates.mcp = savedMcpConfig;
170
170
  agent = updateAgent(agent_id, updates);
171
171
  agent = {
172
172
  ...agent,
173
- ...legacyFieldsFromConfig(savedConfig),
173
+ ...agentFields,
174
174
  config: savedConfig,
175
175
  };
176
176
  } catch (error) {