amalgm 0.1.51 → 0.1.52

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 (70) hide show
  1. package/lib/tunnel-events.js +48 -23
  2. package/package.json +2 -2
  3. package/runtime/lib/harnesses.js +12 -4
  4. package/runtime/scripts/amalgm-mcp/agents/store.js +5 -5
  5. package/runtime/scripts/amalgm-mcp/{artifacts → apps}/advertise.js +39 -24
  6. package/runtime/scripts/amalgm-mcp/apps/rest.js +144 -0
  7. package/runtime/scripts/amalgm-mcp/apps/store.js +171 -0
  8. package/runtime/scripts/amalgm-mcp/apps/supervisor.js +439 -0
  9. package/runtime/scripts/amalgm-mcp/apps/tools.js +176 -0
  10. package/runtime/scripts/amalgm-mcp/automations/cell-references.js +237 -0
  11. package/runtime/scripts/amalgm-mcp/automations/context.js +41 -0
  12. package/runtime/scripts/amalgm-mcp/automations/rest.js +148 -0
  13. package/runtime/scripts/amalgm-mcp/automations/runner.js +613 -0
  14. package/runtime/scripts/amalgm-mcp/automations/scheduler.js +90 -0
  15. package/runtime/scripts/amalgm-mcp/automations/store.js +1125 -0
  16. package/runtime/scripts/amalgm-mcp/automations/tool-actions.js +177 -0
  17. package/runtime/scripts/amalgm-mcp/automations/tools.js +418 -0
  18. package/runtime/scripts/amalgm-mcp/automations/validator.js +225 -0
  19. package/runtime/scripts/amalgm-mcp/browser/agent-browser.js +505 -0
  20. package/runtime/scripts/amalgm-mcp/browser/electron-bridge.js +222 -0
  21. package/runtime/scripts/amalgm-mcp/browser/page.js +13 -631
  22. package/runtime/scripts/amalgm-mcp/browser/tools.js +9 -7
  23. package/runtime/scripts/amalgm-mcp/config.js +33 -48
  24. package/runtime/scripts/amalgm-mcp/deps.js +1 -31
  25. package/runtime/scripts/amalgm-mcp/events/ingress.js +50 -42
  26. package/runtime/scripts/amalgm-mcp/events/internal-workflows.js +169 -0
  27. package/runtime/scripts/amalgm-mcp/events/matcher.js +45 -14
  28. package/runtime/scripts/amalgm-mcp/events/store.js +106 -57
  29. package/runtime/scripts/amalgm-mcp/index.js +12 -14
  30. package/runtime/scripts/amalgm-mcp/lib/prefs.js +229 -65
  31. package/runtime/scripts/amalgm-mcp/lib/tool-result.js +13 -27
  32. package/runtime/scripts/amalgm-mcp/server/core-tools.js +2 -3
  33. package/runtime/scripts/amalgm-mcp/server/http.js +106 -56
  34. package/runtime/scripts/amalgm-mcp/slack/inbound.js +1 -1
  35. package/runtime/scripts/amalgm-mcp/state/db.js +119 -0
  36. package/runtime/scripts/amalgm-mcp/state/snapshot.js +16 -3
  37. package/runtime/scripts/amalgm-mcp/tasks/executor.js +1 -1
  38. package/runtime/scripts/amalgm-mcp/tests/automations-store-runner.test.js +348 -0
  39. package/runtime/scripts/amalgm-mcp/tests/events-matcher.test.js +23 -0
  40. package/runtime/scripts/amalgm-mcp/tests/workflows-store-runner.test.js +67 -0
  41. package/runtime/scripts/amalgm-mcp/toolbox/tools.js +16 -3
  42. package/runtime/scripts/amalgm-mcp/workflows/compiler.js +222 -0
  43. package/runtime/scripts/amalgm-mcp/workflows/runner.js +593 -0
  44. package/runtime/scripts/amalgm-mcp/workflows/store.js +237 -0
  45. package/runtime/scripts/chat-core/adapters/claude.js +2 -1
  46. package/runtime/scripts/chat-core/auth.js +82 -12
  47. package/runtime/scripts/chat-core/contract.js +5 -1
  48. package/runtime/scripts/chat-core/engine.js +103 -62
  49. package/runtime/scripts/chat-core/event-schema.js +8 -0
  50. package/runtime/scripts/chat-core/events.js +5 -0
  51. package/runtime/scripts/chat-core/normalizers/codex.js +13 -1
  52. package/runtime/scripts/chat-core/parts.js +21 -6
  53. package/runtime/scripts/chat-core/sse.js +3 -0
  54. package/runtime/scripts/chat-core/tests/auth.test.js +84 -6
  55. package/runtime/scripts/chat-core/tests/engine.test.js +312 -0
  56. package/runtime/scripts/chat-core/tests/native-config.test.js +23 -0
  57. package/runtime/scripts/chat-core/tool-shape.js +4 -4
  58. package/runtime/scripts/chat-core/tooling/active-memory.js +5 -4
  59. package/runtime/scripts/chat-core/tooling/native-config.js +34 -3
  60. package/runtime/scripts/local-gateway.js +34 -27
  61. package/runtime/scripts/platform-context.txt +76 -94
  62. package/runtime/scripts/amalgm-mcp/artifacts/rest.js +0 -103
  63. package/runtime/scripts/amalgm-mcp/artifacts/store.js +0 -157
  64. package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +0 -439
  65. package/runtime/scripts/amalgm-mcp/artifacts/tools.js +0 -176
  66. package/runtime/scripts/amalgm-mcp/events/executor.js +0 -258
  67. package/runtime/scripts/amalgm-mcp/events/rest.js +0 -214
  68. package/runtime/scripts/amalgm-mcp/events/tools.js +0 -323
  69. package/runtime/scripts/amalgm-mcp/tasks/rest.js +0 -110
  70. package/runtime/scripts/amalgm-mcp/tasks/tools.js +0 -416
@@ -0,0 +1,237 @@
1
+ 'use strict';
2
+
3
+ const CONTEXT_KEYS = new Set([
4
+ 'automation',
5
+ 'trigger',
6
+ 'workflow',
7
+ 'event',
8
+ 'payload',
9
+ 'headers',
10
+ 'previous',
11
+ 'outputs',
12
+ 'cells',
13
+ 'secrets',
14
+ 'stop',
15
+ ]);
16
+
17
+ const NON_CELL_PROPERTIES = new Set([
18
+ 'constructor',
19
+ 'hasOwnProperty',
20
+ 'isPrototypeOf',
21
+ 'propertyIsEnumerable',
22
+ 'toLocaleString',
23
+ 'toString',
24
+ 'valueOf',
25
+ ]);
26
+
27
+ function isObject(value) {
28
+ return !!value && typeof value === 'object' && !Array.isArray(value);
29
+ }
30
+
31
+ function isFunctionRef(value) {
32
+ return isObject(value) && typeof value.$fn === 'string';
33
+ }
34
+
35
+ function buildCodeMask(sourceText) {
36
+ const source = String(sourceText || '');
37
+ const mask = new Array(source.length).fill(true);
38
+ let state = 'code';
39
+ let quote = null;
40
+ let escaped = false;
41
+
42
+ for (let i = 0; i < source.length; i += 1) {
43
+ const char = source[i];
44
+ const next = source[i + 1];
45
+
46
+ if (state === 'line_comment') {
47
+ mask[i] = false;
48
+ if (char === '\n') state = 'code';
49
+ continue;
50
+ }
51
+
52
+ if (state === 'block_comment') {
53
+ mask[i] = false;
54
+ if (char === '*' && next === '/') {
55
+ mask[i + 1] = false;
56
+ i += 1;
57
+ state = 'code';
58
+ }
59
+ continue;
60
+ }
61
+
62
+ if (state === 'string') {
63
+ mask[i] = false;
64
+ if (escaped) {
65
+ escaped = false;
66
+ } else if (char === '\\') {
67
+ escaped = true;
68
+ } else if (char === quote) {
69
+ state = 'code';
70
+ quote = null;
71
+ }
72
+ continue;
73
+ }
74
+
75
+ if (state === 'template') {
76
+ mask[i] = false;
77
+ if (escaped) {
78
+ escaped = false;
79
+ } else if (char === '\\') {
80
+ escaped = true;
81
+ } else if (char === '`') {
82
+ state = 'code';
83
+ }
84
+ continue;
85
+ }
86
+
87
+ if (char === '/' && next === '/') {
88
+ mask[i] = false;
89
+ mask[i + 1] = false;
90
+ i += 1;
91
+ state = 'line_comment';
92
+ continue;
93
+ }
94
+
95
+ if (char === '/' && next === '*') {
96
+ mask[i] = false;
97
+ mask[i + 1] = false;
98
+ i += 1;
99
+ state = 'block_comment';
100
+ continue;
101
+ }
102
+
103
+ if (char === '"' || char === "'") {
104
+ mask[i] = false;
105
+ quote = char;
106
+ escaped = false;
107
+ state = 'string';
108
+ continue;
109
+ }
110
+
111
+ if (char === '`') {
112
+ mask[i] = false;
113
+ escaped = false;
114
+ state = 'template';
115
+ }
116
+ }
117
+
118
+ return mask;
119
+ }
120
+
121
+ function codeIndex(match, token) {
122
+ const relative = match[0].search(new RegExp(`\\b${token}\\b`));
123
+ return relative === -1 ? match.index : match.index + relative;
124
+ }
125
+
126
+ function pushRef(refs, source, mask, match, token, name) {
127
+ const baseIndex = codeIndex(match, token);
128
+ if (!mask[baseIndex]) return;
129
+ const cleanName = String(name || '').trim();
130
+ if (!cleanName || NON_CELL_PROPERTIES.has(cleanName)) return;
131
+ refs.push({ name: cleanName, source });
132
+ }
133
+
134
+ function extractCellReferences(sourceText) {
135
+ const source = String(sourceText || '');
136
+ const mask = buildCodeMask(source);
137
+ const refs = [];
138
+ const member = '\\s*(?:\\?\\.|\\.)\\s*';
139
+ const optionalMember = '\\s*(?:\\?\\.)?\\s*';
140
+ const patterns = [
141
+ { source: 'cells', token: 'cells', re: new RegExp(`\\b(?:ctx${member})?cells${member}([A-Za-z_$][\\w$]*)`, 'g') },
142
+ { source: 'cells', token: 'cells', re: new RegExp(`\\b(?:ctx${member})?cells${optionalMember}\\[\\s*['"]([^'"]+)['"]\\s*\\]`, 'g') },
143
+ { source: 'outputs', token: 'outputs', re: new RegExp(`\\b(?:ctx${member})?outputs${member}([A-Za-z_$][\\w$]*)`, 'g') },
144
+ { source: 'outputs', token: 'outputs', re: new RegExp(`\\b(?:ctx${member})?outputs${optionalMember}\\[\\s*['"]([^'"]+)['"]\\s*\\]`, 'g') },
145
+ ];
146
+
147
+ for (const pattern of patterns) {
148
+ let match;
149
+ while ((match = pattern.re.exec(source)) !== null) {
150
+ pushRef(refs, pattern.source, mask, match, pattern.token, match[1]);
151
+ }
152
+ }
153
+
154
+ const ctxAliasPattern = /\bctx\s*(?:\?\.)?\s*\.\s*([A-Za-z_$][\w$]*)/g;
155
+ let match;
156
+ while ((match = ctxAliasPattern.exec(source)) !== null) {
157
+ if (!mask[match.index]) continue;
158
+ const name = String(match[1] || '').trim();
159
+ if (!name || CONTEXT_KEYS.has(name) || NON_CELL_PROPERTIES.has(name)) continue;
160
+ refs.push({ name, source: 'ctx' });
161
+ }
162
+
163
+ return refs;
164
+ }
165
+
166
+ function collectFunctionSources(value, label, sources) {
167
+ if (isFunctionRef(value)) {
168
+ sources.push({ label, source: value.$fn });
169
+ return;
170
+ }
171
+ if (Array.isArray(value)) {
172
+ value.forEach((item, index) => collectFunctionSources(item, `${label}[${index}]`, sources));
173
+ return;
174
+ }
175
+ if (isObject(value)) {
176
+ for (const [key, child] of Object.entries(value)) {
177
+ collectFunctionSources(child, `${label}.${key}`, sources);
178
+ }
179
+ }
180
+ }
181
+
182
+ function functionSourcesForCell(cell) {
183
+ const sources = [];
184
+ if (typeof cell.code === 'string') sources.push({ label: 'code', source: cell.code });
185
+ collectFunctionSources(cell.if, 'if', sources);
186
+ collectFunctionSources(cell.args, 'args', sources);
187
+ collectFunctionSources(cell.config, 'config', sources);
188
+ return sources;
189
+ }
190
+
191
+ function validateCellReferences(ir) {
192
+ const cells = Array.isArray(ir?.cells) ? ir.cells : [];
193
+ const declared = cells.map((cell) => cell.name).filter(Boolean);
194
+ const declaredSet = new Set(declared);
195
+ const seen = new Set();
196
+ const errors = [];
197
+
198
+ for (const cell of cells) {
199
+ const reported = new Set();
200
+ for (const fn of functionSourcesForCell(cell)) {
201
+ for (const ref of extractCellReferences(fn.source)) {
202
+ const key = `${ref.name}:${fn.label}`;
203
+ if (reported.has(key)) continue;
204
+ reported.add(key);
205
+
206
+ if (!declaredSet.has(ref.name)) {
207
+ errors.push({
208
+ phase: 'static',
209
+ cell: cell.name,
210
+ reference: ref.name,
211
+ source: ref.source,
212
+ message: `Cell "${cell.name}" references unknown cell "${ref.name}" in ${fn.label}. Declared cells: ${declared.join(', ') || '(none)'}.`,
213
+ });
214
+ } else if (!seen.has(ref.name)) {
215
+ const self = ref.name === cell.name;
216
+ errors.push({
217
+ phase: 'static',
218
+ cell: cell.name,
219
+ reference: ref.name,
220
+ source: ref.source,
221
+ message: self
222
+ ? `Cell "${cell.name}" references its own output in ${fn.label}, but a cell output is only available after that cell finishes.`
223
+ : `Cell "${cell.name}" references cell "${ref.name}" in ${fn.label} before it has run. Move "${ref.name}" earlier in cells[].`,
224
+ });
225
+ }
226
+ }
227
+ }
228
+ seen.add(cell.name);
229
+ }
230
+
231
+ return errors;
232
+ }
233
+
234
+ module.exports = {
235
+ extractCellReferences,
236
+ validateCellReferences,
237
+ };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ function isObject(value) {
4
+ return !!value && typeof value === 'object' && !Array.isArray(value);
5
+ }
6
+
7
+ function buildCells(outputs = {}) {
8
+ const cells = {};
9
+ for (const [name, output] of Object.entries(outputs || {})) {
10
+ cells[name] = isObject(output)
11
+ ? { ...output, output }
12
+ : { output, value: output };
13
+ }
14
+ return cells;
15
+ }
16
+
17
+ function buildWorkflowInput(ctx, stop) {
18
+ const outputs = ctx.outputs || {};
19
+ const cells = buildCells(outputs);
20
+
21
+ return {
22
+ automation: ctx.automation,
23
+ event: ctx.event,
24
+ payload: ctx.event?.payload,
25
+ headers: ctx.event?.headers || {},
26
+ trigger: ctx.trigger,
27
+ workflow: ctx.workflow,
28
+ previous: ctx.previous,
29
+ outputs,
30
+ cells,
31
+ steps: cells,
32
+ secrets: ctx.secrets,
33
+ stop,
34
+ ...outputs,
35
+ };
36
+ }
37
+
38
+ module.exports = {
39
+ buildCells,
40
+ buildWorkflowInput,
41
+ };
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * /automations/* REST routes for the control pane.
5
+ */
6
+
7
+ const {
8
+ createAutomation,
9
+ deleteAutomation,
10
+ deleteAutomationByTriggerId,
11
+ getAutomation,
12
+ getAutomationByTriggerId,
13
+ listAutomationRuns,
14
+ listAutomations,
15
+ listTriggers,
16
+ listWorkflows,
17
+ updateAutomation,
18
+ updateAutomationByTriggerId,
19
+ } = require('./store');
20
+ const { executeAutomationById } = require('./runner');
21
+ const { validateWorkflowText } = require('./validator');
22
+
23
+ function sendError(sendJson, status, error) {
24
+ sendJson(status, { error: error.message || String(error) });
25
+ }
26
+
27
+ async function handleList(_req, sendJson) {
28
+ sendJson(200, {
29
+ automations: listAutomations(),
30
+ triggers: listTriggers(),
31
+ workflows: listWorkflows(),
32
+ });
33
+ }
34
+
35
+ async function handleGet(body, sendJson) {
36
+ const automationId = body?.automation_id || body?.automationId;
37
+ const triggerId = body?.trigger_id || body?.triggerId;
38
+ const automation = automationId
39
+ ? getAutomation(automationId)
40
+ : triggerId
41
+ ? getAutomationByTriggerId(triggerId)
42
+ : null;
43
+ if (!automation) return sendJson(404, { error: 'Automation not found' });
44
+ sendJson(200, {
45
+ automation,
46
+ runs: listAutomationRuns({ automationId: automation.id, limit: 50 }),
47
+ });
48
+ }
49
+
50
+ async function handleCreate(body, sendJson) {
51
+ try {
52
+ const automation = createAutomation(body || {}, { source: 'automations:rest:create' });
53
+ sendJson(200, { ok: true, automation, trigger: automation.triggers[0] || null, workflow: automation.workflow });
54
+ } catch (error) {
55
+ sendError(sendJson, 400, error);
56
+ }
57
+ }
58
+
59
+ async function handleValidate(body, sendJson) {
60
+ try {
61
+ const workflowText = body?.workflowText || body?.workflow;
62
+ if (typeof workflowText !== 'string' || !workflowText.trim()) {
63
+ return sendJson(400, { error: 'workflowText is required' });
64
+ }
65
+ const validation = await validateWorkflowText(workflowText, body || {});
66
+ sendJson(validation.ok ? 200 : 400, validation);
67
+ } catch (error) {
68
+ sendError(sendJson, 400, error);
69
+ }
70
+ }
71
+
72
+ async function handleUpdate(body, sendJson) {
73
+ try {
74
+ const { automation_id, automationId, workflow, ...updates } = body || {};
75
+ const id = automation_id || automationId;
76
+ if (!id) return sendJson(400, { error: 'automation_id is required' });
77
+ const automation = updateAutomation(id, {
78
+ ...updates,
79
+ ...(workflow !== undefined && updates.workflowText === undefined ? { workflowText: workflow } : {}),
80
+ }, { source: 'automations:rest:update' });
81
+ sendJson(200, { ok: true, automation, trigger: automation.triggers[0] || null, workflow: automation.workflow });
82
+ } catch (error) {
83
+ sendError(sendJson, 400, error);
84
+ }
85
+ }
86
+
87
+ async function handleDelete(body, sendJson) {
88
+ try {
89
+ const id = body?.automation_id || body?.automationId;
90
+ if (!id) return sendJson(400, { error: 'automation_id is required' });
91
+ const deleted = deleteAutomation(id, { source: 'automations:rest:delete' });
92
+ sendJson(200, { ok: true, deleted });
93
+ } catch (error) {
94
+ sendError(sendJson, 400, error);
95
+ }
96
+ }
97
+
98
+ async function handleRunNow(body, sendJson) {
99
+ try {
100
+ const id = body?.automation_id || body?.automationId;
101
+ if (!id) return sendJson(400, { error: 'automation_id is required' });
102
+ const run = await executeAutomationById(id, {
103
+ id: `manual:${Date.now()}`,
104
+ source: 'amalgm.manual',
105
+ event: 'run_now',
106
+ payload: body?.payload || {},
107
+ headers: {},
108
+ receivedAt: new Date().toISOString(),
109
+ }, { triggerId: body?.trigger_id || body?.triggerId });
110
+ sendJson(200, { ok: true, run });
111
+ } catch (error) {
112
+ sendError(sendJson, 400, error);
113
+ }
114
+ }
115
+
116
+ async function handleTriggerUpdate(body, sendJson) {
117
+ try {
118
+ const id = body?.trigger_id || body?.triggerId;
119
+ if (!id) return sendJson(400, { error: 'trigger_id is required' });
120
+ const automation = updateAutomationByTriggerId(id, body || {}, { source: 'automations:rest:trigger-update' });
121
+ sendJson(200, { ok: true, automation, trigger: automation.triggers.find((trigger) => trigger.id === id) || null, workflow: automation.workflow });
122
+ } catch (error) {
123
+ sendError(sendJson, 400, error);
124
+ }
125
+ }
126
+
127
+ async function handleTriggerDelete(body, sendJson) {
128
+ try {
129
+ const id = body?.trigger_id || body?.triggerId;
130
+ if (!id) return sendJson(400, { error: 'trigger_id is required' });
131
+ const deleted = deleteAutomationByTriggerId(id, { source: 'automations:rest:trigger-delete' });
132
+ sendJson(200, { ok: true, deleted });
133
+ } catch (error) {
134
+ sendError(sendJson, 400, error);
135
+ }
136
+ }
137
+
138
+ module.exports = {
139
+ handleCreate,
140
+ handleDelete,
141
+ handleGet,
142
+ handleList,
143
+ handleRunNow,
144
+ handleTriggerDelete,
145
+ handleTriggerUpdate,
146
+ handleUpdate,
147
+ handleValidate,
148
+ };