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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/runtime/scripts/amalgm-mcp/agent-config/importers/claude.js +52 -0
  3. package/runtime/scripts/amalgm-mcp/agent-config/importers/codex.js +36 -0
  4. package/runtime/scripts/amalgm-mcp/agent-config/importers/common.js +115 -0
  5. package/runtime/scripts/amalgm-mcp/agent-config/importers/index.js +14 -0
  6. package/runtime/scripts/amalgm-mcp/agent-config/importers/opencode.js +59 -0
  7. package/runtime/scripts/amalgm-mcp/agent-config/renderers/hooks.js +55 -0
  8. package/runtime/scripts/amalgm-mcp/agent-config/renderers/instructions.js +54 -0
  9. package/runtime/scripts/amalgm-mcp/agent-config/rest.js +168 -0
  10. package/runtime/scripts/amalgm-mcp/agent-config/schema.js +248 -0
  11. package/runtime/scripts/amalgm-mcp/agent-config/store.js +123 -0
  12. package/runtime/scripts/amalgm-mcp/agents/hooks.js +2 -0
  13. package/runtime/scripts/amalgm-mcp/agents/rest.js +45 -2
  14. package/runtime/scripts/amalgm-mcp/agents/talk.js +20 -4
  15. package/runtime/scripts/amalgm-mcp/automations/tool-actions.js +52 -9
  16. package/runtime/scripts/amalgm-mcp/automations/tools.js +3 -3
  17. package/runtime/scripts/amalgm-mcp/events/internal-workflows.js +3 -3
  18. package/runtime/scripts/amalgm-mcp/lib/chat-payloads.js +101 -0
  19. package/runtime/scripts/amalgm-mcp/server/core-tools.js +10 -10
  20. package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
  21. package/runtime/scripts/amalgm-mcp/server/routes/agents.js +13 -0
  22. package/runtime/scripts/amalgm-mcp/server/routes/chat-payloads.js +37 -0
  23. package/runtime/scripts/amalgm-mcp/state/db.js +11 -0
  24. package/runtime/scripts/amalgm-mcp/state/snapshot.js +13 -3
  25. package/runtime/scripts/amalgm-mcp/tests/agent-config.test.js +80 -0
  26. package/runtime/scripts/amalgm-mcp/tests/automations-store-runner.test.js +17 -5
  27. package/runtime/scripts/amalgm-mcp/tests/chat-payloads.test.js +71 -0
  28. package/runtime/scripts/amalgm-mcp/tests/core-tools.test.js +9 -9
  29. package/runtime/scripts/amalgm-mcp/tests/mcp-surface.test.js +29 -0
  30. package/runtime/scripts/amalgm-mcp/tests/system-catalog.test.js +21 -16
  31. package/runtime/scripts/amalgm-mcp/tests/toolbox-service.test.js +9 -7
  32. package/runtime/scripts/amalgm-mcp/toolbox/selection.js +21 -1
  33. package/runtime/scripts/amalgm-mcp/toolbox/system-catalog.js +16 -1
  34. package/runtime/scripts/amalgm-mcp/workflows/compiler.js +1 -1
  35. package/runtime/scripts/amalgm-mcp/workflows/runner.js +8 -2
  36. package/runtime/scripts/chat-core/adapters/claude.js +2 -2
  37. package/runtime/scripts/chat-core/adapters/codex.js +82 -12
  38. package/runtime/scripts/chat-core/auth.js +13 -4
  39. package/runtime/scripts/chat-core/contract.js +18 -0
  40. package/runtime/scripts/chat-core/tests/auth.test.js +27 -2
  41. package/runtime/scripts/chat-core/tests/native-config.test.js +65 -12
  42. package/runtime/scripts/chat-core/tooling/native-config.js +73 -0
  43. package/runtime/scripts/chat-core/tooling/runtime-home.js +2 -2
  44. package/runtime/scripts/chat-core/tooling/system-prompt.js +3 -1
  45. 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
+ };
@@ -178,5 +178,7 @@ function collectNativeHooks() {
178
178
  }
179
179
 
180
180
  module.exports = {
181
+ collectClaudeHooks,
182
+ collectCodexHooks,
181
183
  collectNativeHooks,
182
184
  };
@@ -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(agent.skills);
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 = buildAgentInstructions(
601
- agent.systemPrompt,
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: 'amalgm.notifications',
11
+ id: 'notifications',
12
12
  kind: 'first_party',
13
13
  tools: require('../notify'),
14
14
  },
15
15
  {
16
- id: 'amalgm.agents',
16
+ id: 'agents',
17
17
  kind: 'first_party',
18
18
  tools: require('../agents/tools'),
19
19
  },
20
20
  {
21
- id: 'amalgm.apps',
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 resolveFirstPartyToolAction(toolId, actionName) {
37
- const normalizedToolId = String(toolId || '').trim();
36
+ function findFirstPartyAction(group, actionName) {
38
37
  const normalizedActionName = String(actionName || '').trim();
39
- const group = firstPartyToolGroups().find((candidate) => candidate.id === normalizedToolId);
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, "amalgm.agents.talk_to_agent", args)`.',
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", "amalgm.notifications.notify_user", ({ cells }) => ({ message: cells.greet.output.message }))`.',
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 `amalgm.notifications.notify_user`, and runnable Toolbox CLI/API actions.',
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: ["amalgm.notifications.notify_user"]
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", "amalgm.notifications.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: ['amalgm.notifications.notify_user'],
156
+ actions: ['notifications.notify_user'],
157
157
  },
158
158
  limits: {
159
159
  maxConcurrentRuns: 1,