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.
- package/package.json +1 -1
- package/runtime/lib/chatInput.js +0 -4
- package/runtime/scripts/amalgm-mcp/agent-config/compiler/instructions.js +70 -0
- package/runtime/scripts/amalgm-mcp/agent-config/loadout.js +65 -0
- package/runtime/scripts/amalgm-mcp/agent-config/renderers/instructions.js +9 -49
- package/runtime/scripts/amalgm-mcp/agent-config/rest.js +10 -10
- package/runtime/scripts/amalgm-mcp/agent-config/schema.js +15 -31
- package/runtime/scripts/amalgm-mcp/agent-config/store.js +21 -5
- package/runtime/scripts/amalgm-mcp/agents/rest.js +34 -34
- package/runtime/scripts/amalgm-mcp/agents/store.js +19 -28
- package/runtime/scripts/amalgm-mcp/agents/talk.js +10 -12
- package/runtime/scripts/amalgm-mcp/agents/tools.js +1 -1
- package/runtime/scripts/amalgm-mcp/automations/runner.js +1 -6
- package/runtime/scripts/amalgm-mcp/email/inbound.js +12 -8
- package/runtime/scripts/amalgm-mcp/mcp-connections/rest.js +0 -13
- package/runtime/scripts/amalgm-mcp/server/mcp-request-context.js +5 -0
- package/runtime/scripts/amalgm-mcp/slack/inbound.js +12 -8
- package/runtime/scripts/amalgm-mcp/tests/agent-config.test.js +1 -1
- package/runtime/scripts/amalgm-mcp/tests/agents-source-of-truth.test.js +3 -5
- package/runtime/scripts/amalgm-mcp/tests/chat-payloads.test.js +1 -1
- package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +2 -12
- package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +3 -4
- package/runtime/scripts/amalgm-mcp/toolbox/loadout-context.js +57 -0
- package/runtime/scripts/amalgm-mcp/toolbox/mcp-surface.js +8 -8
- package/runtime/scripts/amalgm-mcp/toolbox/service.js +17 -13
- package/runtime/scripts/amalgm-mcp/workflows/runner.js +1 -6
- package/runtime/scripts/chat-core/chat-payload.js +165 -0
- package/runtime/scripts/chat-core/server.js +4 -2
- package/runtime/scripts/chat-core/tests/chat-payload.test.js +78 -0
- package/runtime/scripts/amalgm-mcp/toolbox/selection.js +0 -42
package/package.json
CHANGED
package/runtime/lib/chatInput.js
CHANGED
|
@@ -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 {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
89
|
-
const
|
|
88
|
+
function syncAgentFields(agentId, config) {
|
|
89
|
+
const fields = agentFieldsFromConfig(config);
|
|
90
90
|
try {
|
|
91
|
-
updateAgent(agentId,
|
|
91
|
+
updateAgent(agentId, fields, { source: 'agent-config:sync-agent-fields' });
|
|
92
92
|
} catch {
|
|
93
|
-
// Config is the source of truth;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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:
|
|
47
|
+
inheritAll: false,
|
|
44
48
|
customServers: [],
|
|
45
49
|
appIds,
|
|
46
|
-
nativeMcps: [],
|
|
47
50
|
};
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
|
93
|
-
const mcpConfig = derivedMcpTransportConfig(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
|
148
|
-
const mcpConfig = derivedMcpTransportConfig(
|
|
149
|
-
updates.
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
updates.
|
|
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
|
-
...
|
|
173
|
+
...agentFields,
|
|
174
174
|
config: savedConfig,
|
|
175
175
|
};
|
|
176
176
|
} catch (error) {
|