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.
- package/lib/tunnel-events.js +48 -23
- package/package.json +2 -2
- package/runtime/lib/harnesses.js +12 -4
- package/runtime/scripts/amalgm-mcp/agents/store.js +5 -5
- package/runtime/scripts/amalgm-mcp/{artifacts → apps}/advertise.js +39 -24
- package/runtime/scripts/amalgm-mcp/apps/rest.js +144 -0
- package/runtime/scripts/amalgm-mcp/apps/store.js +171 -0
- package/runtime/scripts/amalgm-mcp/apps/supervisor.js +439 -0
- package/runtime/scripts/amalgm-mcp/apps/tools.js +176 -0
- package/runtime/scripts/amalgm-mcp/automations/cell-references.js +237 -0
- package/runtime/scripts/amalgm-mcp/automations/context.js +41 -0
- package/runtime/scripts/amalgm-mcp/automations/rest.js +148 -0
- package/runtime/scripts/amalgm-mcp/automations/runner.js +613 -0
- package/runtime/scripts/amalgm-mcp/automations/scheduler.js +90 -0
- package/runtime/scripts/amalgm-mcp/automations/store.js +1125 -0
- package/runtime/scripts/amalgm-mcp/automations/tool-actions.js +177 -0
- package/runtime/scripts/amalgm-mcp/automations/tools.js +418 -0
- package/runtime/scripts/amalgm-mcp/automations/validator.js +225 -0
- package/runtime/scripts/amalgm-mcp/browser/agent-browser.js +505 -0
- package/runtime/scripts/amalgm-mcp/browser/electron-bridge.js +222 -0
- package/runtime/scripts/amalgm-mcp/browser/page.js +13 -631
- package/runtime/scripts/amalgm-mcp/browser/tools.js +9 -7
- package/runtime/scripts/amalgm-mcp/config.js +33 -48
- package/runtime/scripts/amalgm-mcp/deps.js +1 -31
- package/runtime/scripts/amalgm-mcp/events/ingress.js +50 -42
- package/runtime/scripts/amalgm-mcp/events/internal-workflows.js +169 -0
- package/runtime/scripts/amalgm-mcp/events/matcher.js +45 -14
- package/runtime/scripts/amalgm-mcp/events/store.js +106 -57
- package/runtime/scripts/amalgm-mcp/index.js +12 -14
- package/runtime/scripts/amalgm-mcp/lib/prefs.js +229 -65
- package/runtime/scripts/amalgm-mcp/lib/tool-result.js +13 -27
- package/runtime/scripts/amalgm-mcp/server/core-tools.js +2 -3
- package/runtime/scripts/amalgm-mcp/server/http.js +106 -56
- package/runtime/scripts/amalgm-mcp/slack/inbound.js +1 -1
- package/runtime/scripts/amalgm-mcp/state/db.js +119 -0
- package/runtime/scripts/amalgm-mcp/state/snapshot.js +16 -3
- package/runtime/scripts/amalgm-mcp/tasks/executor.js +1 -1
- package/runtime/scripts/amalgm-mcp/tests/automations-store-runner.test.js +348 -0
- package/runtime/scripts/amalgm-mcp/tests/events-matcher.test.js +23 -0
- package/runtime/scripts/amalgm-mcp/tests/workflows-store-runner.test.js +67 -0
- package/runtime/scripts/amalgm-mcp/toolbox/tools.js +16 -3
- package/runtime/scripts/amalgm-mcp/workflows/compiler.js +222 -0
- package/runtime/scripts/amalgm-mcp/workflows/runner.js +593 -0
- package/runtime/scripts/amalgm-mcp/workflows/store.js +237 -0
- package/runtime/scripts/chat-core/adapters/claude.js +2 -1
- package/runtime/scripts/chat-core/auth.js +82 -12
- package/runtime/scripts/chat-core/contract.js +5 -1
- package/runtime/scripts/chat-core/engine.js +103 -62
- package/runtime/scripts/chat-core/event-schema.js +8 -0
- package/runtime/scripts/chat-core/events.js +5 -0
- package/runtime/scripts/chat-core/normalizers/codex.js +13 -1
- package/runtime/scripts/chat-core/parts.js +21 -6
- package/runtime/scripts/chat-core/sse.js +3 -0
- package/runtime/scripts/chat-core/tests/auth.test.js +84 -6
- package/runtime/scripts/chat-core/tests/engine.test.js +312 -0
- package/runtime/scripts/chat-core/tests/native-config.test.js +23 -0
- package/runtime/scripts/chat-core/tool-shape.js +4 -4
- package/runtime/scripts/chat-core/tooling/active-memory.js +5 -4
- package/runtime/scripts/chat-core/tooling/native-config.js +34 -3
- package/runtime/scripts/local-gateway.js +34 -27
- package/runtime/scripts/platform-context.txt +76 -94
- package/runtime/scripts/amalgm-mcp/artifacts/rest.js +0 -103
- package/runtime/scripts/amalgm-mcp/artifacts/store.js +0 -157
- package/runtime/scripts/amalgm-mcp/artifacts/supervisor.js +0 -439
- package/runtime/scripts/amalgm-mcp/artifacts/tools.js +0 -176
- package/runtime/scripts/amalgm-mcp/events/executor.js +0 -258
- package/runtime/scripts/amalgm-mcp/events/rest.js +0 -214
- package/runtime/scripts/amalgm-mcp/events/tools.js +0 -323
- package/runtime/scripts/amalgm-mcp/tasks/rest.js +0 -110
- package/runtime/scripts/amalgm-mcp/tasks/tools.js +0 -416
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { readToolbox } = require('../toolbox/store');
|
|
4
|
+
const { toolboxMcpToolName } = require('../toolbox/runner');
|
|
5
|
+
|
|
6
|
+
function coreTools() {
|
|
7
|
+
return [
|
|
8
|
+
...require('../notify'),
|
|
9
|
+
...require('../agents/tools'),
|
|
10
|
+
...require('../apps/tools'),
|
|
11
|
+
];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function actionKey(toolId, actionName) {
|
|
15
|
+
return `${toolId}.${actionName}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeRef(value) {
|
|
19
|
+
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isRunnableToolboxTool(tool) {
|
|
23
|
+
return !!tool
|
|
24
|
+
&& tool.status === 'enabled'
|
|
25
|
+
&& tool.origin !== 'system'
|
|
26
|
+
&& (tool.type === 'cli' || tool.type === 'api');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveAmalgmCoreTool(actionName) {
|
|
30
|
+
const normalized = String(actionName || '').trim();
|
|
31
|
+
return coreTools().find((tool) => (
|
|
32
|
+
tool.name === normalized
|
|
33
|
+
|| tool.name === normalized.replace(/\./g, '_')
|
|
34
|
+
)) || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveToolboxAction(toolId, actionName) {
|
|
38
|
+
const normalizedToolId = String(toolId || '').trim();
|
|
39
|
+
const normalizedActionName = String(actionName || '').trim();
|
|
40
|
+
const toolbox = readToolbox();
|
|
41
|
+
const action = Object.values(toolbox.toolActions || {}).find((candidate) => {
|
|
42
|
+
if (candidate.toolId !== normalizedToolId) return false;
|
|
43
|
+
return candidate.name === normalizedActionName
|
|
44
|
+
|| candidate.id === normalizedActionName
|
|
45
|
+
|| candidate.id === `${normalizedToolId}.${normalizedActionName}`
|
|
46
|
+
|| candidate.id === `${normalizedToolId}:${normalizedActionName}`;
|
|
47
|
+
});
|
|
48
|
+
if (!action || action.status !== 'enabled') return null;
|
|
49
|
+
const tool = toolbox.tools?.[action.toolId];
|
|
50
|
+
return isRunnableToolboxTool(tool) ? { tool, action } : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveWorkflowToolAction(toolId, actionName) {
|
|
54
|
+
const normalizedToolId = String(toolId || '').trim();
|
|
55
|
+
const normalizedActionName = String(actionName || '').trim();
|
|
56
|
+
if (normalizedToolId === 'http' && normalizedActionName === 'fetch') {
|
|
57
|
+
return {
|
|
58
|
+
kind: 'builtin',
|
|
59
|
+
ref: 'http.fetch',
|
|
60
|
+
toolId: 'http',
|
|
61
|
+
actionName: 'fetch',
|
|
62
|
+
description: 'Fetch a URL from a workflow; requires allowlist.network=true at runtime.',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (normalizedToolId === 'amalgm') {
|
|
66
|
+
const tool = resolveAmalgmCoreTool(normalizedActionName);
|
|
67
|
+
if (!tool) return null;
|
|
68
|
+
return {
|
|
69
|
+
kind: 'amalgm',
|
|
70
|
+
ref: actionKey('amalgm', tool.name),
|
|
71
|
+
toolId: 'amalgm',
|
|
72
|
+
actionName: tool.name,
|
|
73
|
+
tool,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const resolved = resolveToolboxAction(normalizedToolId, normalizedActionName);
|
|
77
|
+
if (!resolved) return null;
|
|
78
|
+
return {
|
|
79
|
+
kind: 'toolbox',
|
|
80
|
+
ref: actionKey(resolved.tool.id, resolved.action.name),
|
|
81
|
+
toolId: resolved.tool.id,
|
|
82
|
+
actionName: resolved.action.name,
|
|
83
|
+
tool: resolved.tool,
|
|
84
|
+
action: resolved.action,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function workflowActionSummaries() {
|
|
89
|
+
const toolbox = readToolbox();
|
|
90
|
+
const actions = [{
|
|
91
|
+
ref: 'http.fetch',
|
|
92
|
+
id: 'http.fetch',
|
|
93
|
+
toolId: 'http',
|
|
94
|
+
actionName: 'fetch',
|
|
95
|
+
kind: 'builtin',
|
|
96
|
+
description: 'Fetch a URL; requires allowlist.network=true.',
|
|
97
|
+
}];
|
|
98
|
+
|
|
99
|
+
for (const tool of coreTools()) {
|
|
100
|
+
actions.push({
|
|
101
|
+
ref: actionKey('amalgm', tool.name),
|
|
102
|
+
id: actionKey('amalgm', tool.name),
|
|
103
|
+
toolId: 'amalgm',
|
|
104
|
+
actionName: tool.name,
|
|
105
|
+
kind: 'amalgm',
|
|
106
|
+
description: tool.description || '',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const action of Object.values(toolbox.toolActions || {})) {
|
|
111
|
+
const tool = toolbox.tools?.[action.toolId];
|
|
112
|
+
if (!isRunnableToolboxTool(tool) || action.status !== 'enabled') continue;
|
|
113
|
+
actions.push({
|
|
114
|
+
ref: actionKey(tool.id, action.name),
|
|
115
|
+
id: action.id,
|
|
116
|
+
toolId: tool.id,
|
|
117
|
+
actionName: action.name,
|
|
118
|
+
kind: 'toolbox',
|
|
119
|
+
toolName: tool.name,
|
|
120
|
+
toolType: tool.type,
|
|
121
|
+
description: action.description || '',
|
|
122
|
+
mcpToolName: toolboxMcpToolName(action),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return actions.sort((a, b) => a.ref.localeCompare(b.ref));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function suggestWorkflowToolActions(toolId, actionName, limit = 5) {
|
|
130
|
+
const needle = normalizeRef(actionKey(toolId, actionName));
|
|
131
|
+
const actionNameNeedle = normalizeRef(actionName);
|
|
132
|
+
const scored = workflowActionSummaries().map((action) => {
|
|
133
|
+
const ref = normalizeRef(action.ref);
|
|
134
|
+
const id = normalizeRef(action.id);
|
|
135
|
+
const mcpName = normalizeRef(action.mcpToolName);
|
|
136
|
+
let score = 0;
|
|
137
|
+
if (ref === needle || id === needle || mcpName === needle) score += 100;
|
|
138
|
+
if (ref.includes(needle) || id.includes(needle) || mcpName.includes(needle)) score += 40;
|
|
139
|
+
if (needle.includes(ref) || needle.includes(id) || (mcpName && needle.includes(mcpName))) score += 30;
|
|
140
|
+
if (actionNameNeedle && ref.includes(actionNameNeedle)) score += 10;
|
|
141
|
+
return { action, score };
|
|
142
|
+
}).filter((item) => item.score > 0);
|
|
143
|
+
const list = scored.length > 0
|
|
144
|
+
? scored.sort((a, b) => b.score - a.score)
|
|
145
|
+
: workflowActionSummaries().map((action) => ({ action, score: 0 }));
|
|
146
|
+
return list.slice(0, limit).map((item) => item.action.ref);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function validateWorkflowToolActions(ir) {
|
|
150
|
+
const errors = [];
|
|
151
|
+
for (const cell of Array.isArray(ir?.cells) ? ir.cells : []) {
|
|
152
|
+
if (cell.kind !== 'tool') continue;
|
|
153
|
+
if (resolveWorkflowToolAction(cell.toolId, cell.actionName)) continue;
|
|
154
|
+
const ref = actionKey(cell.toolId, cell.actionName);
|
|
155
|
+
const suggestions = suggestWorkflowToolActions(cell.toolId, cell.actionName);
|
|
156
|
+
errors.push({
|
|
157
|
+
phase: 'static',
|
|
158
|
+
cell: cell.name,
|
|
159
|
+
toolId: cell.toolId,
|
|
160
|
+
actionName: cell.actionName,
|
|
161
|
+
message: [
|
|
162
|
+
`Tool action "${ref}" was not found.`,
|
|
163
|
+
suggestions.length > 0 ? `Did you mean: ${suggestions.join(', ')}?` : 'Use automations_list_actions to inspect callable workflow actions.',
|
|
164
|
+
].join(' '),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return errors;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
module.exports = {
|
|
171
|
+
resolveAmalgmCoreTool,
|
|
172
|
+
resolveToolboxAction,
|
|
173
|
+
resolveWorkflowToolAction,
|
|
174
|
+
suggestWorkflowToolActions,
|
|
175
|
+
validateWorkflowToolActions,
|
|
176
|
+
workflowActionSummaries,
|
|
177
|
+
};
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { textResult, errorResult } = require('../lib/tool-result');
|
|
4
|
+
const {
|
|
5
|
+
createAutomation,
|
|
6
|
+
deleteAutomation,
|
|
7
|
+
getAutomation,
|
|
8
|
+
listAutomationRuns,
|
|
9
|
+
listAutomations,
|
|
10
|
+
updateAutomation,
|
|
11
|
+
} = require('./store');
|
|
12
|
+
const { executeAutomationById } = require('./runner');
|
|
13
|
+
const { validateWorkflowText } = require('./validator');
|
|
14
|
+
const { workflowActionSummaries } = require('./tool-actions');
|
|
15
|
+
|
|
16
|
+
const TRIGGER_DESCRIPTION = [
|
|
17
|
+
'Automation triggers. `kind="event"` creates a webhook trigger with source/event matching and returns a webhook URL + secret. Event refs are `source.event`, for example `github.push`; empty source/event becomes `*`.',
|
|
18
|
+
'`kind="scheduled"` creates a local scheduled trigger with a `schedule` object: `{ kind: "cron", expr, tz }`, `{ kind: "once", at }`, or `{ kind: "interval", ms }`.',
|
|
19
|
+
'For a manual-only automation, still create one trigger with `enabled: false`; `automations_run_now` can run the workflow without waiting for that trigger to fire.',
|
|
20
|
+
].join('\n\n');
|
|
21
|
+
|
|
22
|
+
const WORKFLOW_DESCRIPTION = [
|
|
23
|
+
'Source-of-truth workflow script. Use `export default workflow({ trigger, allowlist, limits, cells })`.',
|
|
24
|
+
'The script is the workflow. A trigger decides when it runs; cells decide what happens.',
|
|
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
|
+
'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.talk_to_agent", args)`.',
|
|
28
|
+
'Every function receives one ctx object: `{ automation, trigger, workflow, event, payload, headers, previous, outputs, cells, secrets, stop }`.',
|
|
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.notify_user", ({ cells }) => ({ message: cells.greet.output.message }))`.',
|
|
31
|
+
].join('\n\n');
|
|
32
|
+
|
|
33
|
+
function compactAutomation(automation, includeWorkflowText = true) {
|
|
34
|
+
return {
|
|
35
|
+
id: automation.id,
|
|
36
|
+
name: automation.name,
|
|
37
|
+
description: automation.description,
|
|
38
|
+
enabled: automation.enabled,
|
|
39
|
+
status: automation.status,
|
|
40
|
+
projectPath: automation.projectPath,
|
|
41
|
+
triggerIds: automation.triggerIds,
|
|
42
|
+
workflowIds: automation.workflowIds,
|
|
43
|
+
triggers: automation.triggers,
|
|
44
|
+
workflows: automation.workflows.map((workflow) => ({
|
|
45
|
+
...workflow,
|
|
46
|
+
...(includeWorkflowText ? {} : { workflowText: undefined }),
|
|
47
|
+
})),
|
|
48
|
+
createdAt: automation.createdAt,
|
|
49
|
+
updatedAt: automation.updatedAt,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function summarizeTrigger(trigger) {
|
|
54
|
+
return {
|
|
55
|
+
id: trigger.id,
|
|
56
|
+
kind: trigger.kind,
|
|
57
|
+
name: trigger.name,
|
|
58
|
+
enabled: trigger.enabled,
|
|
59
|
+
status: trigger.status,
|
|
60
|
+
...(trigger.kind === 'scheduled' ? {
|
|
61
|
+
schedule: trigger.schedule,
|
|
62
|
+
nextRunAt: trigger.nextRunAt,
|
|
63
|
+
} : {
|
|
64
|
+
source: trigger.source,
|
|
65
|
+
event: trigger.event,
|
|
66
|
+
ref: `${trigger.source || '*'}.${trigger.event || '*'}`,
|
|
67
|
+
webhookUrl: trigger.webhookUrl,
|
|
68
|
+
secret: trigger.secret,
|
|
69
|
+
}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function summarizeWorkflow(workflow) {
|
|
74
|
+
if (!workflow) return null;
|
|
75
|
+
return {
|
|
76
|
+
id: workflow.id,
|
|
77
|
+
name: workflow.name,
|
|
78
|
+
enabled: workflow.enabled,
|
|
79
|
+
status: workflow.status,
|
|
80
|
+
...(workflow.compilerErrors?.length ? { compilerErrors: workflow.compilerErrors } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function summarizeAutomation(automation) {
|
|
85
|
+
return {
|
|
86
|
+
id: automation.id,
|
|
87
|
+
name: automation.name,
|
|
88
|
+
enabled: automation.enabled,
|
|
89
|
+
status: automation.status,
|
|
90
|
+
triggerIds: automation.triggerIds,
|
|
91
|
+
workflowIds: automation.workflowIds,
|
|
92
|
+
triggers: automation.triggers.map(summarizeTrigger),
|
|
93
|
+
workflow: summarizeWorkflow(automation.workflow || automation.workflows?.[0]),
|
|
94
|
+
updatedAt: automation.updatedAt,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function automationResponse(automation, { verbose = true, includeWorkflowText = true } = {}) {
|
|
99
|
+
return verbose
|
|
100
|
+
? compactAutomation(automation, includeWorkflowText)
|
|
101
|
+
: summarizeAutomation(automation);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseBoolean(value, fallback) {
|
|
105
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isObject(value) {
|
|
109
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function summarizeRun(run) {
|
|
113
|
+
const cellsByName = new Map();
|
|
114
|
+
for (const event of Array.isArray(run?.events) ? run.events : []) {
|
|
115
|
+
if (!event.cell?.name) continue;
|
|
116
|
+
cellsByName.set(event.cell.name, {
|
|
117
|
+
name: event.cell.name,
|
|
118
|
+
kind: event.cell.kind,
|
|
119
|
+
status: event.cell.status,
|
|
120
|
+
durationMs: event.cell.durationMs,
|
|
121
|
+
error: event.cell.error || undefined,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
id: run.id,
|
|
126
|
+
runId: run.runId || run.id,
|
|
127
|
+
automationId: run.automationId,
|
|
128
|
+
automationName: run.automationName,
|
|
129
|
+
triggerId: run.triggerId,
|
|
130
|
+
triggerName: run.triggerName,
|
|
131
|
+
workflowId: run.workflowId,
|
|
132
|
+
workflowName: run.workflowName,
|
|
133
|
+
status: run.status,
|
|
134
|
+
startedAt: run.startedAt,
|
|
135
|
+
finishedAt: run.finishedAt,
|
|
136
|
+
durationMs: run.durationMs,
|
|
137
|
+
error: run.error,
|
|
138
|
+
cellSummaries: Array.from(cellsByName.values()),
|
|
139
|
+
outputKeys: isObject(run.output) ? Object.keys(run.output) : [],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = [
|
|
144
|
+
{
|
|
145
|
+
name: 'automations_validate',
|
|
146
|
+
description:
|
|
147
|
+
'Validate a workflow script before saving it. Compiles the single workflow script and, by default, does a dry run that executes code cells but does not call external tools, HTTP, or CLI commands. Use this before automations_create/update when authoring non-trivial workflows.',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
workflowText: { type: 'string', description: WORKFLOW_DESCRIPTION },
|
|
152
|
+
workflow: { type: 'string', description: 'Alias for workflowText.' },
|
|
153
|
+
dry_run: { type: 'boolean', description: 'If false, only compile. Defaults to true.' },
|
|
154
|
+
execute_code: { type: 'boolean', description: 'If false, code cells are not executed during dry run. Defaults to true.' },
|
|
155
|
+
event: {
|
|
156
|
+
type: 'object',
|
|
157
|
+
description: 'Optional fake event for dry run.',
|
|
158
|
+
properties: {
|
|
159
|
+
source: { type: 'string' },
|
|
160
|
+
event: { type: 'string' },
|
|
161
|
+
payload: { type: 'object' },
|
|
162
|
+
headers: { type: 'object' },
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
mock_outputs: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
description: 'Optional per-cell mock outputs, keyed by cell name. Use this when a downstream cell consumes output from a dry-run-stubbed tool/http/cli cell.',
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
async handler(input = {}) {
|
|
172
|
+
try {
|
|
173
|
+
const validation = await validateWorkflowText(input.workflowText || input.workflow, input);
|
|
174
|
+
return textResult(JSON.stringify(validation, null, 2));
|
|
175
|
+
} catch (error) {
|
|
176
|
+
return errorResult(error.message || String(error));
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'automations_list_actions',
|
|
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`, Amalgm actions such as `amalgm.notify_user`, and runnable Toolbox CLI/API actions.',
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: 'object',
|
|
186
|
+
properties: {
|
|
187
|
+
query: { type: 'string', description: 'Optional case-insensitive filter across ref, id, name, and description.' },
|
|
188
|
+
kind: { type: 'string', enum: ['builtin', 'amalgm', 'toolbox'], description: 'Optional action kind filter.' },
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
async handler({ query, kind } = {}) {
|
|
192
|
+
try {
|
|
193
|
+
const cleanQuery = typeof query === 'string' ? query.trim().toLowerCase() : '';
|
|
194
|
+
const actions = workflowActionSummaries()
|
|
195
|
+
.filter((action) => !kind || action.kind === kind)
|
|
196
|
+
.filter((action) => {
|
|
197
|
+
if (!cleanQuery) return true;
|
|
198
|
+
return [
|
|
199
|
+
action.ref,
|
|
200
|
+
action.id,
|
|
201
|
+
action.toolId,
|
|
202
|
+
action.actionName,
|
|
203
|
+
action.toolName,
|
|
204
|
+
action.description,
|
|
205
|
+
action.mcpToolName,
|
|
206
|
+
].some((value) => String(value || '').toLowerCase().includes(cleanQuery));
|
|
207
|
+
});
|
|
208
|
+
return textResult(JSON.stringify({ actions }, null, 2));
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return errorResult(error.message || String(error));
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'automations_create',
|
|
216
|
+
description:
|
|
217
|
+
'Create a local Automation. An automation is one or more triggers plus one workflow. It is stored on the user computer in the Local Live SQLite store and runs locally. Scheduled/event triggers do not wake an agent unless the workflow explicitly calls an agent/tool.',
|
|
218
|
+
inputSchema: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: {
|
|
221
|
+
id: { type: 'string', description: 'Optional stable automation id. Usually omit this.' },
|
|
222
|
+
name: { type: 'string', description: 'Human-readable automation name.' },
|
|
223
|
+
description: { type: 'string', description: 'Optional markdown description.' },
|
|
224
|
+
enabled: { type: 'boolean', description: 'Whether the automation starts active.' },
|
|
225
|
+
projectPath: { type: 'string', description: 'Default project/workspace path used by workflow cells.' },
|
|
226
|
+
triggers: {
|
|
227
|
+
type: 'array',
|
|
228
|
+
description: TRIGGER_DESCRIPTION,
|
|
229
|
+
items: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
kind: { type: 'string', enum: ['event', 'webhook', 'scheduled'] },
|
|
233
|
+
name: { type: 'string' },
|
|
234
|
+
description: { type: 'string' },
|
|
235
|
+
enabled: { type: 'boolean' },
|
|
236
|
+
source: { type: 'string', description: 'Event/webhook source label, e.g. github. Empty means any source.' },
|
|
237
|
+
event: { type: 'string', description: 'Event/webhook event label, e.g. push. Empty means any event.' },
|
|
238
|
+
sourceUrl: { type: 'string', description: 'Optional display URL for source identity/favicon.' },
|
|
239
|
+
schedule: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
description: 'Scheduled trigger config for kind=scheduled.',
|
|
242
|
+
properties: {
|
|
243
|
+
kind: { type: 'string', enum: ['cron', 'once', 'interval'] },
|
|
244
|
+
expr: { type: 'string' },
|
|
245
|
+
tz: { type: 'string' },
|
|
246
|
+
at: { type: 'string' },
|
|
247
|
+
ms: { type: 'number' },
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
workflowText: { type: 'string', description: WORKFLOW_DESCRIPTION },
|
|
254
|
+
workflow: { type: 'string', description: 'Alias for workflowText.' },
|
|
255
|
+
allowlist: { type: 'object', description: 'Workflow permissions: localCompute, network, secrets, actions.' },
|
|
256
|
+
limits: { type: 'object', description: 'Workflow limits: maxConcurrentRuns, queueLimit, cellTimeoutMs, codeTimeoutMs.' },
|
|
257
|
+
verbose: { type: 'boolean', description: 'Set false for a concise response with ids, status, triggers, webhook URL/secret, and workflow summary. Defaults to true.' },
|
|
258
|
+
},
|
|
259
|
+
required: ['name', 'triggers'],
|
|
260
|
+
},
|
|
261
|
+
async handler(input = {}) {
|
|
262
|
+
try {
|
|
263
|
+
const workflowText = input.workflowText || input.workflow;
|
|
264
|
+
if (typeof workflowText !== 'string' || !workflowText.trim()) {
|
|
265
|
+
return errorResult('workflowText or workflow is required.');
|
|
266
|
+
}
|
|
267
|
+
const automation = createAutomation({
|
|
268
|
+
...input,
|
|
269
|
+
workflowText,
|
|
270
|
+
}, { source: 'automations_create' });
|
|
271
|
+
return textResult(`Automation created.\n\n${JSON.stringify(automationResponse(automation, {
|
|
272
|
+
verbose: input.verbose !== false,
|
|
273
|
+
}), null, 2)}`);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
return errorResult(error.message || String(error));
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: 'automations_list',
|
|
281
|
+
description: 'List local automations with their triggers, workflow summary, webhook URLs, secrets, schedules, and status.',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
enabled_only: { type: 'boolean', description: 'If true, only return enabled automations.' },
|
|
286
|
+
include_workflow_text: { type: 'boolean', description: 'If false, omit workflow source text from the listing.' },
|
|
287
|
+
verbose: { type: 'boolean', description: 'Set false for concise automation summaries. Defaults to true.' },
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
async handler({ enabled_only, include_workflow_text, verbose } = {}) {
|
|
291
|
+
try {
|
|
292
|
+
let automations = listAutomations();
|
|
293
|
+
if (enabled_only) automations = automations.filter((automation) => automation.enabled !== false);
|
|
294
|
+
const includeWorkflowText = parseBoolean(include_workflow_text, false);
|
|
295
|
+
if (automations.length === 0) return textResult('No automations found.');
|
|
296
|
+
return textResult(JSON.stringify({
|
|
297
|
+
automations: automations.map((automation) => automationResponse(automation, {
|
|
298
|
+
verbose: verbose !== false,
|
|
299
|
+
includeWorkflowText,
|
|
300
|
+
})),
|
|
301
|
+
}, null, 2));
|
|
302
|
+
} catch (error) {
|
|
303
|
+
return errorResult(error.message || String(error));
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'automations_get',
|
|
309
|
+
description: 'Get one local automation by id, including all triggers, workflow source, compiled IR, and recent runs.',
|
|
310
|
+
inputSchema: {
|
|
311
|
+
type: 'object',
|
|
312
|
+
properties: {
|
|
313
|
+
automation_id: { type: 'string' },
|
|
314
|
+
include_runs: { type: 'boolean', description: 'Include recent run history. Defaults to true.' },
|
|
315
|
+
verbose: { type: 'boolean', description: 'Set false for a concise automation summary. Defaults to true.' },
|
|
316
|
+
},
|
|
317
|
+
required: ['automation_id'],
|
|
318
|
+
},
|
|
319
|
+
async handler({ automation_id, include_runs, verbose } = {}) {
|
|
320
|
+
try {
|
|
321
|
+
const automation = getAutomation(automation_id);
|
|
322
|
+
if (!automation) return errorResult(`Automation not found: ${automation_id}`);
|
|
323
|
+
const runs = include_runs === false ? undefined : listAutomationRuns({ automationId: automation.id, limit: 20 });
|
|
324
|
+
return textResult(JSON.stringify({
|
|
325
|
+
automation: automationResponse(automation, { verbose: verbose !== false }),
|
|
326
|
+
...(runs ? { runs } : {}),
|
|
327
|
+
}, null, 2));
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return errorResult(error.message || String(error));
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
name: 'automations_update',
|
|
335
|
+
description:
|
|
336
|
+
'Update a local automation. You can edit metadata, enabled state, replace triggers, or update the workflow source/allowlist/limits. The next trigger firing uses the updated workflow.',
|
|
337
|
+
inputSchema: {
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {
|
|
340
|
+
automation_id: { type: 'string' },
|
|
341
|
+
name: { type: 'string' },
|
|
342
|
+
description: { type: 'string' },
|
|
343
|
+
enabled: { type: 'boolean' },
|
|
344
|
+
projectPath: { type: 'string' },
|
|
345
|
+
triggers: { type: 'array', description: TRIGGER_DESCRIPTION },
|
|
346
|
+
workflowText: { type: 'string', description: WORKFLOW_DESCRIPTION },
|
|
347
|
+
workflow: { type: 'string', description: 'Alias for workflowText.' },
|
|
348
|
+
allowlist: { type: 'object' },
|
|
349
|
+
limits: { type: 'object' },
|
|
350
|
+
verbose: { type: 'boolean', description: 'Set false for a concise response. Defaults to true.' },
|
|
351
|
+
},
|
|
352
|
+
required: ['automation_id'],
|
|
353
|
+
},
|
|
354
|
+
async handler({ automation_id, workflow, verbose, ...updates } = {}) {
|
|
355
|
+
try {
|
|
356
|
+
const automation = updateAutomation(automation_id, {
|
|
357
|
+
...updates,
|
|
358
|
+
...(workflow !== undefined && updates.workflowText === undefined ? { workflowText: workflow } : {}),
|
|
359
|
+
}, { source: 'automations_update' });
|
|
360
|
+
return textResult(`Automation updated.\n\n${JSON.stringify(automationResponse(automation, {
|
|
361
|
+
verbose: verbose !== false,
|
|
362
|
+
}), null, 2)}`);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
return errorResult(error.message || String(error));
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: 'automations_delete',
|
|
370
|
+
description: 'Delete a local automation, including its triggers and workflow if no other automation uses that workflow.',
|
|
371
|
+
inputSchema: {
|
|
372
|
+
type: 'object',
|
|
373
|
+
properties: { automation_id: { type: 'string' } },
|
|
374
|
+
required: ['automation_id'],
|
|
375
|
+
},
|
|
376
|
+
async handler({ automation_id } = {}) {
|
|
377
|
+
try {
|
|
378
|
+
const deleted = deleteAutomation(automation_id, { source: 'automations_delete' });
|
|
379
|
+
return textResult(`Automation "${deleted.name}" deleted.`);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
return errorResult(error.message || String(error));
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: 'automations_run_now',
|
|
387
|
+
description: 'Run an automation workflow immediately, outside its configured triggers. This works for manual-only automations that use a disabled trigger.',
|
|
388
|
+
inputSchema: {
|
|
389
|
+
type: 'object',
|
|
390
|
+
properties: {
|
|
391
|
+
automation_id: { type: 'string' },
|
|
392
|
+
trigger_id: { type: 'string', description: 'Optional trigger context to use for the run.' },
|
|
393
|
+
payload: { type: 'object', description: 'Optional payload exposed to workflow cells as event.payload.' },
|
|
394
|
+
verbose: { type: 'boolean', description: 'Set false for a concise run summary. Defaults to true.' },
|
|
395
|
+
},
|
|
396
|
+
required: ['automation_id'],
|
|
397
|
+
},
|
|
398
|
+
async handler({ automation_id, trigger_id, payload, verbose } = {}) {
|
|
399
|
+
try {
|
|
400
|
+
const run = await executeAutomationById(automation_id, {
|
|
401
|
+
id: `manual:${Date.now()}`,
|
|
402
|
+
source: 'amalgm.manual',
|
|
403
|
+
event: 'run_now',
|
|
404
|
+
payload: payload || {},
|
|
405
|
+
headers: {},
|
|
406
|
+
receivedAt: new Date().toISOString(),
|
|
407
|
+
}, { triggerId: trigger_id });
|
|
408
|
+
return textResult(`Automation run started.\n\n${JSON.stringify(
|
|
409
|
+
verbose === false ? summarizeRun(run) : run,
|
|
410
|
+
null,
|
|
411
|
+
2,
|
|
412
|
+
)}`);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
return errorResult(error.message || String(error));
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
];
|