amalgm 0.1.75 → 0.1.76
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/scripts/amalgm-mcp/agent-config/importers/claude.js +52 -0
- package/runtime/scripts/amalgm-mcp/agent-config/importers/codex.js +36 -0
- package/runtime/scripts/amalgm-mcp/agent-config/importers/common.js +115 -0
- package/runtime/scripts/amalgm-mcp/agent-config/importers/index.js +14 -0
- package/runtime/scripts/amalgm-mcp/agent-config/importers/opencode.js +59 -0
- package/runtime/scripts/amalgm-mcp/agent-config/renderers/hooks.js +55 -0
- package/runtime/scripts/amalgm-mcp/agent-config/renderers/instructions.js +54 -0
- package/runtime/scripts/amalgm-mcp/agent-config/rest.js +168 -0
- package/runtime/scripts/amalgm-mcp/agent-config/schema.js +248 -0
- package/runtime/scripts/amalgm-mcp/agent-config/store.js +123 -0
- package/runtime/scripts/amalgm-mcp/agents/hooks.js +2 -0
- package/runtime/scripts/amalgm-mcp/agents/rest.js +45 -2
- package/runtime/scripts/amalgm-mcp/agents/talk.js +20 -4
- package/runtime/scripts/amalgm-mcp/automations/tool-actions.js +52 -9
- package/runtime/scripts/amalgm-mcp/automations/tools.js +3 -3
- package/runtime/scripts/amalgm-mcp/events/internal-workflows.js +3 -3
- package/runtime/scripts/amalgm-mcp/lib/chat-payloads.js +101 -0
- package/runtime/scripts/amalgm-mcp/server/core-tools.js +10 -10
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/agents.js +13 -0
- package/runtime/scripts/amalgm-mcp/server/routes/chat-payloads.js +37 -0
- package/runtime/scripts/amalgm-mcp/state/db.js +11 -0
- package/runtime/scripts/amalgm-mcp/state/snapshot.js +13 -3
- package/runtime/scripts/amalgm-mcp/tests/agent-config.test.js +80 -0
- package/runtime/scripts/amalgm-mcp/tests/automations-store-runner.test.js +17 -5
- package/runtime/scripts/amalgm-mcp/tests/chat-payloads.test.js +71 -0
- package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +9 -9
- package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +29 -0
- package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +21 -16
- package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +9 -7
- package/runtime/scripts/amalgm-mcp/toolbox/selection.js +21 -1
- package/runtime/scripts/amalgm-mcp/toolbox/system-catalog.js +16 -1
- package/runtime/scripts/amalgm-mcp/workflows/compiler.js +1 -1
- package/runtime/scripts/amalgm-mcp/workflows/runner.js +8 -2
- package/runtime/scripts/chat-core/adapters/claude.js +2 -2
- package/runtime/scripts/chat-core/adapters/codex.js +82 -12
- package/runtime/scripts/chat-core/auth.js +13 -4
- package/runtime/scripts/chat-core/contract.js +18 -0
- package/runtime/scripts/chat-core/tests/auth.test.js +27 -2
- package/runtime/scripts/chat-core/tests/native-config.test.js +65 -12
- package/runtime/scripts/chat-core/tooling/native-config.js +73 -0
- package/runtime/scripts/chat-core/tooling/runtime-home.js +2 -2
- package/runtime/scripts/chat-core/tooling/system-prompt.js +3 -1
- package/runtime/scripts/local-gateway.js +1 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
const CONFIG_VERSION = 1;
|
|
6
|
+
const DEFAULT_RUNTIME_HOME_LAYOUT = 'legacy';
|
|
7
|
+
const PER_AGENT_RUNTIME_HOME_LAYOUT = 'per-agent';
|
|
8
|
+
|
|
9
|
+
function isObject(value) {
|
|
10
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function nonEmptyString(value) {
|
|
14
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
15
|
+
}
|
|
16
|
+
|
|
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
|
+
function stableId(prefix, seed) {
|
|
26
|
+
const clean = nonEmptyString(seed)
|
|
27
|
+
.toLowerCase()
|
|
28
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
29
|
+
.replace(/^-+|-+$/g, '')
|
|
30
|
+
.slice(0, 80);
|
|
31
|
+
if (clean) return `${prefix}-${clean}`;
|
|
32
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cloneJson(value) {
|
|
36
|
+
if (value == null) return value;
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(JSON.stringify(value));
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
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
|
+
function normalizeSkill(value, index = 0) {
|
|
61
|
+
if (typeof value === 'string') {
|
|
62
|
+
const name = nonEmptyString(value);
|
|
63
|
+
if (!name) return null;
|
|
64
|
+
return {
|
|
65
|
+
id: stableId('skill', name),
|
|
66
|
+
name,
|
|
67
|
+
description: '',
|
|
68
|
+
content: '',
|
|
69
|
+
enabled: true,
|
|
70
|
+
source: { kind: 'amalgm' },
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (!isObject(value)) return null;
|
|
74
|
+
const name = nonEmptyString(value.name || value.id || `Skill ${index + 1}`);
|
|
75
|
+
const content = typeof value.content === 'string' ? value.content : '';
|
|
76
|
+
if (!name && !content) return null;
|
|
77
|
+
return {
|
|
78
|
+
id: nonEmptyString(value.id) || stableId('skill', name || content.slice(0, 80)),
|
|
79
|
+
name: name || `Skill ${index + 1}`,
|
|
80
|
+
description: typeof value.description === 'string' ? value.description : '',
|
|
81
|
+
content,
|
|
82
|
+
enabled: value.enabled !== false,
|
|
83
|
+
source: isObject(value.source) ? cloneJson(value.source) : { kind: 'amalgm' },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function normalizeSkills(values) {
|
|
88
|
+
if (!Array.isArray(values)) return [];
|
|
89
|
+
const byId = new Map();
|
|
90
|
+
values.map(normalizeSkill).filter(Boolean).forEach((skill) => {
|
|
91
|
+
byId.set(skill.id, skill);
|
|
92
|
+
});
|
|
93
|
+
return [...byId.values()];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeHook(value, index = 0) {
|
|
97
|
+
if (!isObject(value)) return null;
|
|
98
|
+
const command = nonEmptyString(value.command);
|
|
99
|
+
const type = nonEmptyString(value.type) || (command ? 'command' : 'native');
|
|
100
|
+
if (type === 'command' && !command) return null;
|
|
101
|
+
const event = nonEmptyString(value.event) || nonEmptyString(value.phase) || 'UserPromptSubmit';
|
|
102
|
+
const phase = nonEmptyString(value.phase) || event;
|
|
103
|
+
const label = nonEmptyString(value.label || value.name) || (command ? command.split(/\s+/).slice(0, 3).join(' ') : event);
|
|
104
|
+
const timeout = Number(value.timeout);
|
|
105
|
+
return {
|
|
106
|
+
id: nonEmptyString(value.id) || stableId('hook', `${event}-${label}-${index}`),
|
|
107
|
+
name: label,
|
|
108
|
+
enabled: value.enabled !== false,
|
|
109
|
+
event,
|
|
110
|
+
phase,
|
|
111
|
+
matcher: typeof value.matcher === 'string' ? value.matcher : '',
|
|
112
|
+
type,
|
|
113
|
+
...(command ? { command } : {}),
|
|
114
|
+
...(Number.isFinite(timeout) && timeout > 0 ? { timeout } : {}),
|
|
115
|
+
...(typeof value.statusMessage === 'string' && value.statusMessage.trim()
|
|
116
|
+
? { statusMessage: value.statusMessage.trim() }
|
|
117
|
+
: {}),
|
|
118
|
+
source: isObject(value.source)
|
|
119
|
+
? cloneJson(value.source)
|
|
120
|
+
: {
|
|
121
|
+
kind: value.source === 'native' ? 'native-import' : 'amalgm',
|
|
122
|
+
...(nonEmptyString(value.harnessId) ? { harnessId: nonEmptyString(value.harnessId) } : {}),
|
|
123
|
+
...(nonEmptyString(value.sourcePath) ? { path: nonEmptyString(value.sourcePath) } : {}),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function normalizeHooks(values) {
|
|
129
|
+
if (!Array.isArray(values)) return [];
|
|
130
|
+
const byId = new Map();
|
|
131
|
+
values.map(normalizeHook).filter(Boolean).forEach((hook) => {
|
|
132
|
+
byId.set(hook.id, hook);
|
|
133
|
+
});
|
|
134
|
+
return [...byId.values()];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function normalizeSubagent(value, index = 0) {
|
|
138
|
+
if (typeof value === 'string') {
|
|
139
|
+
const agentId = nonEmptyString(value);
|
|
140
|
+
if (!agentId) return null;
|
|
141
|
+
return {
|
|
142
|
+
id: stableId('subagent', agentId),
|
|
143
|
+
agentId,
|
|
144
|
+
name: '',
|
|
145
|
+
description: '',
|
|
146
|
+
enabled: true,
|
|
147
|
+
source: { kind: 'amalgm' },
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (!isObject(value)) return null;
|
|
151
|
+
const agentId = nonEmptyString(value.agentId || value.agent_id || value.id);
|
|
152
|
+
if (!agentId) return null;
|
|
153
|
+
return {
|
|
154
|
+
id: nonEmptyString(value.id) || stableId('subagent', `${agentId}-${index}`),
|
|
155
|
+
agentId,
|
|
156
|
+
name: nonEmptyString(value.name),
|
|
157
|
+
description: typeof value.description === 'string' ? value.description : '',
|
|
158
|
+
enabled: value.enabled !== false,
|
|
159
|
+
source: isObject(value.source) ? cloneJson(value.source) : { kind: 'amalgm' },
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function normalizeSubagents(values) {
|
|
164
|
+
if (!Array.isArray(values)) return [];
|
|
165
|
+
const byAgent = new Map();
|
|
166
|
+
values.map(normalizeSubagent).filter(Boolean).forEach((subagent) => {
|
|
167
|
+
byAgent.set(subagent.agentId, subagent);
|
|
168
|
+
});
|
|
169
|
+
return [...byAgent.values()];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizeInstructions(value, fallback = '') {
|
|
173
|
+
if (isObject(value)) {
|
|
174
|
+
return typeof value.text === 'string' ? value.text : fallback;
|
|
175
|
+
}
|
|
176
|
+
return typeof value === 'string' ? value : fallback;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizeRuntimeHomeLayout(value, fallback = DEFAULT_RUNTIME_HOME_LAYOUT) {
|
|
180
|
+
if (value === PER_AGENT_RUNTIME_HOME_LAYOUT) return PER_AGENT_RUNTIME_HOME_LAYOUT;
|
|
181
|
+
if (value === DEFAULT_RUNTIME_HOME_LAYOUT) return DEFAULT_RUNTIME_HOME_LAYOUT;
|
|
182
|
+
return fallback === PER_AGENT_RUNTIME_HOME_LAYOUT
|
|
183
|
+
? PER_AGENT_RUNTIME_HOME_LAYOUT
|
|
184
|
+
: DEFAULT_RUNTIME_HOME_LAYOUT;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizeAgentConfig(input = {}, fallback = {}) {
|
|
188
|
+
const raw = isObject(input) ? input : {};
|
|
189
|
+
const prior = isObject(fallback) ? fallback : {};
|
|
190
|
+
const agentId = nonEmptyString(raw.agentId || raw.agent_id || prior.agentId);
|
|
191
|
+
return {
|
|
192
|
+
version: CONFIG_VERSION,
|
|
193
|
+
...(agentId ? { agentId } : {}),
|
|
194
|
+
runtimeHomeLayout: normalizeRuntimeHomeLayout(
|
|
195
|
+
raw.runtimeHomeLayout ?? raw.runtime_home_layout,
|
|
196
|
+
prior.runtimeHomeLayout,
|
|
197
|
+
),
|
|
198
|
+
instructions: normalizeInstructions(
|
|
199
|
+
raw.instructions ?? raw.systemPrompt,
|
|
200
|
+
typeof prior.instructions === 'string' ? prior.instructions : '',
|
|
201
|
+
),
|
|
202
|
+
skills: normalizeSkills(raw.skills ?? prior.skills ?? []),
|
|
203
|
+
tools: normalizeTools(raw.tools, prior.tools),
|
|
204
|
+
hooks: normalizeHooks(raw.hooks ?? prior.hooks ?? []),
|
|
205
|
+
subagents: normalizeSubagents(raw.subagents ?? prior.subagents ?? []),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function configFromAgent(agent = {}) {
|
|
210
|
+
return normalizeAgentConfig({
|
|
211
|
+
agentId: agent.id,
|
|
212
|
+
instructions: agent.systemPrompt || '',
|
|
213
|
+
skills: agent.skills || [],
|
|
214
|
+
tools: agent.tools || { mode: 'all', selectedToolIds: [] },
|
|
215
|
+
hooks: agent.hooks || [],
|
|
216
|
+
subagents: agent.subagents || [],
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function legacyFieldsFromConfig(config = {}) {
|
|
221
|
+
const normalized = normalizeAgentConfig(config);
|
|
222
|
+
return {
|
|
223
|
+
systemPrompt: normalized.instructions,
|
|
224
|
+
skills: normalized.skills
|
|
225
|
+
.filter((skill) => skill.enabled !== false)
|
|
226
|
+
.map((skill) => skill.content || skill.name)
|
|
227
|
+
.filter(Boolean),
|
|
228
|
+
tools: normalized.tools,
|
|
229
|
+
hooks: normalized.hooks,
|
|
230
|
+
subagents: normalized.subagents,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = {
|
|
235
|
+
CONFIG_VERSION,
|
|
236
|
+
DEFAULT_RUNTIME_HOME_LAYOUT,
|
|
237
|
+
PER_AGENT_RUNTIME_HOME_LAYOUT,
|
|
238
|
+
configFromAgent,
|
|
239
|
+
legacyFieldsFromConfig,
|
|
240
|
+
normalizeAgentConfig,
|
|
241
|
+
normalizeHooks,
|
|
242
|
+
normalizeRuntimeHomeLayout,
|
|
243
|
+
normalizeSkills,
|
|
244
|
+
normalizeSubagents,
|
|
245
|
+
normalizeTools,
|
|
246
|
+
stableId,
|
|
247
|
+
uniqueStrings,
|
|
248
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { openLocalDb } = require('../state/db');
|
|
4
|
+
const { insertStateEvent, publishStateEvent } = require('../state/events');
|
|
5
|
+
const {
|
|
6
|
+
configFromAgent,
|
|
7
|
+
legacyFieldsFromConfig,
|
|
8
|
+
normalizeAgentConfig,
|
|
9
|
+
} = require('./schema');
|
|
10
|
+
|
|
11
|
+
function nowIso() {
|
|
12
|
+
return new Date().toISOString();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readRow(db, agentId) {
|
|
16
|
+
return db.prepare('SELECT * FROM agent_configs WHERE agent_id = ?').get(agentId);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function rowToConfig(row) {
|
|
20
|
+
if (!row) return null;
|
|
21
|
+
try {
|
|
22
|
+
return normalizeAgentConfig(JSON.parse(row.config_json || '{}'));
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function defaultConfigForAgent(agentOrId) {
|
|
29
|
+
if (agentOrId && typeof agentOrId === 'object') {
|
|
30
|
+
return configFromAgent(agentOrId);
|
|
31
|
+
}
|
|
32
|
+
const { resolveAgent } = require('../agents/store');
|
|
33
|
+
const agent = resolveAgent(agentOrId);
|
|
34
|
+
return agent ? configFromAgent(agent) : normalizeAgentConfig({ agentId: agentOrId });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getAgentConfig(agentOrId, db = openLocalDb()) {
|
|
38
|
+
const agentId = typeof agentOrId === 'object' ? agentOrId?.id : agentOrId;
|
|
39
|
+
if (!agentId) return normalizeAgentConfig({});
|
|
40
|
+
return rowToConfig(readRow(db, agentId)) || defaultConfigForAgent(agentOrId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function upsertAgentConfig(agentId, configPatch, options = {}) {
|
|
44
|
+
if (!agentId) throw new Error('agent_id is required');
|
|
45
|
+
const db = openLocalDb();
|
|
46
|
+
const existing = getAgentConfig(agentId, db);
|
|
47
|
+
const hadRow = Boolean(readRow(db, agentId));
|
|
48
|
+
const config = normalizeAgentConfig({
|
|
49
|
+
...existing,
|
|
50
|
+
...(configPatch || {}),
|
|
51
|
+
agentId,
|
|
52
|
+
}, existing);
|
|
53
|
+
const updatedAt = nowIso();
|
|
54
|
+
const event = db.transaction(() => {
|
|
55
|
+
db.prepare(`
|
|
56
|
+
INSERT INTO agent_configs (agent_id, version, updated_at, config_json)
|
|
57
|
+
VALUES (@agent_id, @version, @updated_at, @config_json)
|
|
58
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
59
|
+
version = excluded.version,
|
|
60
|
+
updated_at = excluded.updated_at,
|
|
61
|
+
config_json = excluded.config_json
|
|
62
|
+
`).run({
|
|
63
|
+
agent_id: agentId,
|
|
64
|
+
version: config.version,
|
|
65
|
+
updated_at: updatedAt,
|
|
66
|
+
config_json: JSON.stringify(config),
|
|
67
|
+
});
|
|
68
|
+
return insertStateEvent(db, {
|
|
69
|
+
resource: 'agent_configs',
|
|
70
|
+
op: hadRow ? 'update' : 'insert',
|
|
71
|
+
id: agentId,
|
|
72
|
+
value: config,
|
|
73
|
+
clientMutationId: options.clientMutationId,
|
|
74
|
+
source: options.source || 'agent-config:update',
|
|
75
|
+
});
|
|
76
|
+
})();
|
|
77
|
+
publishStateEvent(event);
|
|
78
|
+
return config;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function deleteAgentConfig(agentId, options = {}) {
|
|
82
|
+
if (!agentId) throw new Error('agent_id is required');
|
|
83
|
+
const db = openLocalDb();
|
|
84
|
+
const existing = rowToConfig(readRow(db, agentId));
|
|
85
|
+
if (!existing) return null;
|
|
86
|
+
const event = db.transaction(() => {
|
|
87
|
+
db.prepare('DELETE FROM agent_configs WHERE agent_id = ?').run(agentId);
|
|
88
|
+
return insertStateEvent(db, {
|
|
89
|
+
resource: 'agent_configs',
|
|
90
|
+
op: 'delete',
|
|
91
|
+
id: agentId,
|
|
92
|
+
source: options.source || 'agent-config:delete',
|
|
93
|
+
});
|
|
94
|
+
})();
|
|
95
|
+
publishStateEvent(event);
|
|
96
|
+
return existing;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function listAgentConfigs(db = openLocalDb()) {
|
|
100
|
+
return db.prepare('SELECT * FROM agent_configs ORDER BY updated_at DESC, agent_id ASC')
|
|
101
|
+
.all()
|
|
102
|
+
.map(rowToConfig)
|
|
103
|
+
.filter(Boolean);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function agentWithConfig(agent) {
|
|
107
|
+
if (!agent) return null;
|
|
108
|
+
const config = getAgentConfig(agent);
|
|
109
|
+
return {
|
|
110
|
+
...agent,
|
|
111
|
+
...legacyFieldsFromConfig(config),
|
|
112
|
+
config,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
agentWithConfig,
|
|
118
|
+
deleteAgentConfig,
|
|
119
|
+
getAgentConfig,
|
|
120
|
+
legacyFieldsFromConfig,
|
|
121
|
+
listAgentConfigs,
|
|
122
|
+
upsertAgentConfig,
|
|
123
|
+
};
|
|
@@ -11,6 +11,12 @@ const {
|
|
|
11
11
|
} = require('./store');
|
|
12
12
|
const { hydrateModelPreferences } = require('../lib/prefs');
|
|
13
13
|
const credentialAdapter = require('../../credential-adapter');
|
|
14
|
+
const {
|
|
15
|
+
agentWithConfig,
|
|
16
|
+
deleteAgentConfig,
|
|
17
|
+
legacyFieldsFromConfig,
|
|
18
|
+
upsertAgentConfig,
|
|
19
|
+
} = require('../agent-config/store');
|
|
14
20
|
const {
|
|
15
21
|
defaultToolboxService: toolboxService,
|
|
16
22
|
} = require('../toolbox/service');
|
|
@@ -56,14 +62,14 @@ function normalizeToolConfig(tools) {
|
|
|
56
62
|
|
|
57
63
|
async function handleList(sendJson) {
|
|
58
64
|
await hydrateModelPreferences();
|
|
59
|
-
sendJson(200, { agents: getAllAgentsWithBuiltins() });
|
|
65
|
+
sendJson(200, { agents: getAllAgentsWithBuiltins().map(agentWithConfig) });
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
async function handleGet(body, sendJson) {
|
|
63
69
|
const { agent_id } = body;
|
|
64
70
|
const agent = resolveAgent(agent_id);
|
|
65
71
|
if (!agent) return sendJson(404, { error: `Agent not found: ${agent_id}` });
|
|
66
|
-
sendJson(200, { agent });
|
|
72
|
+
sendJson(200, { agent: agentWithConfig(agent) });
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
async function handleCreate(body, sendJson) {
|
|
@@ -78,6 +84,7 @@ async function handleCreate(body, sendJson) {
|
|
|
78
84
|
skills,
|
|
79
85
|
tools,
|
|
80
86
|
authMethod,
|
|
87
|
+
config,
|
|
81
88
|
} = body;
|
|
82
89
|
if (!name || !name.trim()) return sendJson(400, { error: 'name is required' });
|
|
83
90
|
if (!baseHarnessId) return sendJson(400, { error: 'baseHarnessId is required' });
|
|
@@ -86,6 +93,13 @@ async function handleCreate(body, sendJson) {
|
|
|
86
93
|
const mcpConfig = derivedMcpTransportConfig(toolConfig);
|
|
87
94
|
let agent;
|
|
88
95
|
try {
|
|
96
|
+
const configPatch = config && typeof config === 'object' && !Array.isArray(config)
|
|
97
|
+
? config
|
|
98
|
+
: {
|
|
99
|
+
instructions: systemPrompt || '',
|
|
100
|
+
skills: normalizeStringList(skills),
|
|
101
|
+
tools: toolConfig,
|
|
102
|
+
};
|
|
89
103
|
agent = createAgent({
|
|
90
104
|
name: name.trim(),
|
|
91
105
|
description: description || '',
|
|
@@ -101,6 +115,15 @@ async function handleCreate(body, sendJson) {
|
|
|
101
115
|
mcp: mcpConfig,
|
|
102
116
|
authMethod: coerceAuthMethodForHarness(baseHarnessId, authMethod),
|
|
103
117
|
});
|
|
118
|
+
const savedConfig = upsertAgentConfig(agent.id, {
|
|
119
|
+
...configPatch,
|
|
120
|
+
runtimeHomeLayout: 'per-agent',
|
|
121
|
+
}, { source: 'agents:create' });
|
|
122
|
+
agent = {
|
|
123
|
+
...agent,
|
|
124
|
+
...legacyFieldsFromConfig(savedConfig),
|
|
125
|
+
config: savedConfig,
|
|
126
|
+
};
|
|
104
127
|
} catch (error) {
|
|
105
128
|
return sendJson(400, { error: error.message || 'Failed to create agent' });
|
|
106
129
|
}
|
|
@@ -130,7 +153,26 @@ async function handleUpdate(body, sendJson) {
|
|
|
130
153
|
|
|
131
154
|
let agent;
|
|
132
155
|
try {
|
|
156
|
+
const configPatch = updates.config && typeof updates.config === 'object' && !Array.isArray(updates.config)
|
|
157
|
+
? updates.config
|
|
158
|
+
: {
|
|
159
|
+
instructions: updates.systemPrompt ?? existing.systemPrompt ?? '',
|
|
160
|
+
skills: normalizeStringList(updates.skills ?? existing.skills),
|
|
161
|
+
tools: toolConfig,
|
|
162
|
+
hooks: Array.isArray(updates.hooks) ? updates.hooks : existing.config?.hooks,
|
|
163
|
+
subagents: Array.isArray(updates.subagents) ? updates.subagents : existing.config?.subagents,
|
|
164
|
+
};
|
|
165
|
+
delete updates.config;
|
|
166
|
+
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;
|
|
133
170
|
agent = updateAgent(agent_id, updates);
|
|
171
|
+
agent = {
|
|
172
|
+
...agent,
|
|
173
|
+
...legacyFieldsFromConfig(savedConfig),
|
|
174
|
+
config: savedConfig,
|
|
175
|
+
};
|
|
134
176
|
} catch (error) {
|
|
135
177
|
const message = error.message || 'Failed to update agent';
|
|
136
178
|
return sendJson(message.includes('not found') ? 404 : 400, { error: message });
|
|
@@ -144,6 +186,7 @@ async function handleDelete(body, sendJson) {
|
|
|
144
186
|
if (!agent_id) return sendJson(400, { error: 'agent_id is required' });
|
|
145
187
|
let deleted;
|
|
146
188
|
try {
|
|
189
|
+
deleteAgentConfig(agent_id, { source: 'agents:delete' });
|
|
147
190
|
deleted = deleteAgent(agent_id);
|
|
148
191
|
} catch (error) {
|
|
149
192
|
const message = error.message || 'Failed to delete agent';
|
|
@@ -38,6 +38,8 @@ const {
|
|
|
38
38
|
getChatInputText,
|
|
39
39
|
normalizeChatInput,
|
|
40
40
|
} = require('../../../lib/chatInput');
|
|
41
|
+
const { getAgentConfig, legacyFieldsFromConfig } = require('../agent-config/store');
|
|
42
|
+
const { renderAgentInstructions } = require('../agent-config/renderers/instructions');
|
|
41
43
|
const credentialAdapter = require('../../credential-adapter');
|
|
42
44
|
const { buildLocalMcpServerConfigs } = require('../lib/mcp-resolver');
|
|
43
45
|
const {
|
|
@@ -128,6 +130,12 @@ function buildAgentInstructions(systemPrompt, files, skills) {
|
|
|
128
130
|
.join('\n\n');
|
|
129
131
|
}
|
|
130
132
|
|
|
133
|
+
function buildAgentFilesBlock(files) {
|
|
134
|
+
const normalizedFiles = normalizeStringList(files);
|
|
135
|
+
if (normalizedFiles.length === 0) return '';
|
|
136
|
+
return `<agent-resources>\nFiles available to this agent:\n${normalizedFiles.map((file) => `- ${file}`).join('\n')}\n</agent-resources>`;
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
function buildReturnChannelInstructions(context = {}) {
|
|
132
140
|
const callerConversationId = normalizeConversationId(context.callerSessionId);
|
|
133
141
|
if (!callerConversationId || !hasSupabase()) return '';
|
|
@@ -590,15 +598,20 @@ async function handleTalkToAgent(args, context = {}) {
|
|
|
590
598
|
agent.baseHarnessId,
|
|
591
599
|
agent.authMethod || preferredAuthMethod,
|
|
592
600
|
);
|
|
601
|
+
const agentConfig = getAgentConfig(agent);
|
|
602
|
+
const configLegacy = legacyFieldsFromConfig(agentConfig);
|
|
593
603
|
const agentFiles = normalizeStringList(agent.files);
|
|
594
|
-
const agentSkills = normalizeStringList(
|
|
595
|
-
const agentTools = agent.tools || { mode: 'all', selectedToolIds: [] };
|
|
604
|
+
const agentSkills = normalizeStringList(configLegacy.skills);
|
|
605
|
+
const agentTools = configLegacy.tools || agent.tools || { mode: 'all', selectedToolIds: [] };
|
|
596
606
|
const agentMcpAppIds = toolboxService.externalMcpToolIdsForMode(
|
|
597
607
|
agentTools.mode,
|
|
598
608
|
agentTools.selectedToolIds,
|
|
599
609
|
);
|
|
600
|
-
const baseAgentSystemPrompt =
|
|
601
|
-
|
|
610
|
+
const baseAgentSystemPrompt = [
|
|
611
|
+
renderAgentInstructions(agentConfig, { includeAgentToAgentPrompt: true }),
|
|
612
|
+
buildAgentFilesBlock(agentFiles),
|
|
613
|
+
].filter(Boolean).join('\n\n') || buildAgentInstructions(
|
|
614
|
+
configLegacy.systemPrompt || agent.systemPrompt,
|
|
602
615
|
agentFiles,
|
|
603
616
|
agentSkills,
|
|
604
617
|
);
|
|
@@ -771,6 +784,7 @@ async function handleTalkToAgent(args, context = {}) {
|
|
|
771
784
|
skills: agentSkills,
|
|
772
785
|
mcpAppIds: agentMcpAppIds,
|
|
773
786
|
tools: agentTools,
|
|
787
|
+
agentConfig,
|
|
774
788
|
...(parentOrigin || {}),
|
|
775
789
|
},
|
|
776
790
|
});
|
|
@@ -826,6 +840,8 @@ async function handleTalkToAgent(args, context = {}) {
|
|
|
826
840
|
cwd: legacyChatFields.cwd || DEFAULT_CWD,
|
|
827
841
|
authMethod: legacyChatFields.authMethod || defaultAuthMethod,
|
|
828
842
|
mcpServers,
|
|
843
|
+
agentConfig,
|
|
844
|
+
agentConfigId: agent.id,
|
|
829
845
|
...(legacyChatFields.systemPrompt ? { systemPrompt: legacyChatFields.systemPrompt } : {}),
|
|
830
846
|
...(longMessageContext ? { longMessageContext } : {}),
|
|
831
847
|
...(!isNewConversation ? { resumeSessionId: sessionId } : {}),
|
|
@@ -8,17 +8,17 @@ const {
|
|
|
8
8
|
function firstPartyToolGroups() {
|
|
9
9
|
return [
|
|
10
10
|
{
|
|
11
|
-
id: '
|
|
11
|
+
id: 'notifications',
|
|
12
12
|
kind: 'first_party',
|
|
13
13
|
tools: require('../notify'),
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
|
-
id: '
|
|
16
|
+
id: 'agents',
|
|
17
17
|
kind: 'first_party',
|
|
18
18
|
tools: require('../agents/tools'),
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
|
-
id: '
|
|
21
|
+
id: 'apps',
|
|
22
22
|
kind: 'first_party',
|
|
23
23
|
tools: require('../apps/tools'),
|
|
24
24
|
},
|
|
@@ -33,16 +33,58 @@ function normalizeRef(value) {
|
|
|
33
33
|
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function
|
|
37
|
-
const normalizedToolId = String(toolId || '').trim();
|
|
36
|
+
function findFirstPartyAction(group, actionName) {
|
|
38
37
|
const normalizedActionName = String(actionName || '').trim();
|
|
39
|
-
|
|
40
|
-
if (!group) return null;
|
|
41
|
-
const tool = group.tools.find((candidate) => (
|
|
38
|
+
return group.tools.find((candidate) => (
|
|
42
39
|
candidate.name === normalizedActionName
|
|
43
40
|
|| candidate.name === normalizedActionName.replace(/\./g, '_')
|
|
44
|
-
));
|
|
41
|
+
)) || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function canonicalFirstPartyRef(toolId, actionName) {
|
|
45
|
+
let normalizedToolId = String(toolId || '').trim();
|
|
46
|
+
if (normalizedToolId.startsWith('amalgm.')) {
|
|
47
|
+
normalizedToolId = normalizedToolId.slice('amalgm.'.length);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (normalizedToolId === 'amalgm') {
|
|
51
|
+
for (const group of firstPartyToolGroups()) {
|
|
52
|
+
const tool = findFirstPartyAction(group, actionName);
|
|
53
|
+
if (tool) return { group, actionName: tool.name };
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const group = firstPartyToolGroups().find((candidate) => candidate.id === normalizedToolId);
|
|
59
|
+
if (!group) return null;
|
|
60
|
+
const tool = findFirstPartyAction(group, actionName);
|
|
45
61
|
if (!tool) return null;
|
|
62
|
+
return { group, actionName: tool.name };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function workflowActionKeys(toolId, actionName) {
|
|
66
|
+
const raw = actionKey(toolId, actionName);
|
|
67
|
+
const canonical = canonicalFirstPartyRef(toolId, actionName);
|
|
68
|
+
if (!canonical) return [raw];
|
|
69
|
+
const keys = new Set([
|
|
70
|
+
raw,
|
|
71
|
+
actionKey(canonical.group.id, canonical.actionName),
|
|
72
|
+
actionKey(`amalgm.${canonical.group.id}`, canonical.actionName),
|
|
73
|
+
]);
|
|
74
|
+
if (canonical.group.id === 'notifications' && canonical.actionName === 'notify_user') {
|
|
75
|
+
keys.add('amalgm.notify_user');
|
|
76
|
+
}
|
|
77
|
+
if (canonical.group.id === 'agents' && canonical.actionName === 'talk_to_agent') {
|
|
78
|
+
keys.add('amalgm.talk_to_agent');
|
|
79
|
+
}
|
|
80
|
+
return Array.from(keys);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveFirstPartyToolAction(toolId, actionName) {
|
|
84
|
+
const canonical = canonicalFirstPartyRef(toolId, actionName);
|
|
85
|
+
if (!canonical) return null;
|
|
86
|
+
const { group, actionName: canonicalActionName } = canonical;
|
|
87
|
+
const tool = findFirstPartyAction(group, canonicalActionName);
|
|
46
88
|
return {
|
|
47
89
|
kind: 'first_party',
|
|
48
90
|
ref: actionKey(group.id, tool.name),
|
|
@@ -170,5 +212,6 @@ module.exports = {
|
|
|
170
212
|
resolveWorkflowToolAction,
|
|
171
213
|
suggestWorkflowToolActions,
|
|
172
214
|
validateWorkflowToolActions,
|
|
215
|
+
workflowActionKeys,
|
|
173
216
|
workflowActionSummaries,
|
|
174
217
|
};
|
|
@@ -24,10 +24,10 @@ const WORKFLOW_DESCRIPTION = [
|
|
|
24
24
|
'The script is the workflow. A trigger decides when it runs; cells decide what happens.',
|
|
25
25
|
'Workflow `trigger` refs are `source.event` strings such as `github.push`; `github.*` means any GitHub event; `*.push` means a push from any source; `*.*` means any source and event.',
|
|
26
26
|
'Cells can call `code(name, fn)`, gated local `cli(name, config)`, gated `http(name, config)`, and `tool(name, "tool.action", args)`.',
|
|
27
|
-
'Agents are tools too: use `agent(name, agentIdOrName, { prompt, run_in_background: true })` or `tool(name, "
|
|
27
|
+
'Agents are tools too: use `agent(name, agentIdOrName, { prompt, run_in_background: true })` or `tool(name, "agents.talk_to_agent", args)`.',
|
|
28
28
|
'Every function receives one ctx object: `{ automation, trigger, workflow, event, payload, headers, previous, outputs, cells, secrets, stop }`.',
|
|
29
29
|
'Cell outputs are available as `ctx.cells.cellName.output`, `ctx.cells.cellName.field` for object outputs, `ctx.outputs.cellName`, and top-level `ctx.cellName`.',
|
|
30
|
-
'Example: `tool("notify", "
|
|
30
|
+
'Example: `tool("notify", "notifications.notify_user", ({ cells }) => ({ message: cells.greet.output.message }))`.',
|
|
31
31
|
].join('\n\n');
|
|
32
32
|
|
|
33
33
|
function compactAutomation(automation, includeWorkflowText = true) {
|
|
@@ -180,7 +180,7 @@ module.exports = [
|
|
|
180
180
|
{
|
|
181
181
|
name: 'automations_list_actions',
|
|
182
182
|
description:
|
|
183
|
-
'List workflow-callable tool actions for automation scripts. Use these refs in workflow cells like `tool("name", "tool.ref", args)`. This includes built-in `http.fetch`, first-party Toolbox actions such as `
|
|
183
|
+
'List workflow-callable tool actions for automation scripts. Use these refs in workflow cells like `tool("name", "tool.ref", args)`. This includes built-in `http.fetch`, first-party Toolbox actions such as `notifications.notify_user`, and runnable Toolbox CLI/API actions.',
|
|
184
184
|
inputSchema: {
|
|
185
185
|
type: 'object',
|
|
186
186
|
properties: {
|
|
@@ -28,7 +28,7 @@ function engineNpmPublishWorkflow(repoPath) {
|
|
|
28
28
|
allowlist: {
|
|
29
29
|
localCompute: true,
|
|
30
30
|
secrets: ["NODE_AUTH_TOKEN"],
|
|
31
|
-
actions: ["
|
|
31
|
+
actions: ["notifications.notify_user"]
|
|
32
32
|
},
|
|
33
33
|
|
|
34
34
|
limits: {
|
|
@@ -116,7 +116,7 @@ function engineNpmPublishWorkflow(repoPath) {
|
|
|
116
116
|
timeoutMs: 600000
|
|
117
117
|
}),
|
|
118
118
|
|
|
119
|
-
tool("notify_user", "
|
|
119
|
+
tool("notify_user", "notifications.notify_user", {
|
|
120
120
|
subject: "amalgm npm publish complete",
|
|
121
121
|
level: "success",
|
|
122
122
|
message: ({ outputs }) => {
|
|
@@ -153,7 +153,7 @@ function internalWorkflowSeeds() {
|
|
|
153
153
|
allowlist: {
|
|
154
154
|
localCompute: true,
|
|
155
155
|
secrets: ['NODE_AUTH_TOKEN'],
|
|
156
|
-
actions: ['
|
|
156
|
+
actions: ['notifications.notify_user'],
|
|
157
157
|
},
|
|
158
158
|
limits: {
|
|
159
159
|
maxConcurrentRuns: 1,
|