@amalgm/automations 0.2.3 → 0.2.5
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/AXIOMS.md +10 -3
- package/PURPOSE.md +8 -5
- package/README.md +2 -0
- package/dist/src/automations.js +2 -9
- package/dist/src/client.js +4 -3
- package/dist/src/errors.d.ts +3 -2
- package/dist/src/errors.js +3 -1
- package/dist/src/executor.js +2 -1
- package/dist/src/input-template.d.ts +3 -0
- package/dist/src/input-template.js +19 -0
- package/dist/src/machine-client.js +3 -1
- package/dist/src/mcp.js +15 -1
- package/package.json +1 -1
- package/skills/automations/SKILL.md +13 -0
package/AXIOMS.md
CHANGED
|
@@ -24,9 +24,11 @@
|
|
|
24
24
|
9. Supabase is authoritative for automations, triggers, workflow source, and
|
|
25
25
|
permanent run history. A definition edit or deletion never rewrites prior
|
|
26
26
|
runs.
|
|
27
|
-
10. A webhook URL is a rotatable bearer capability for exactly one trigger.
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
10. A webhook URL is a rotatable bearer capability for exactly one trigger.
|
|
28
|
+
Possession plus any configured provider proof admits that trigger; source
|
|
29
|
+
and event labels never form a second routing gate. Its random locator is not
|
|
30
|
+
sufficient without the hosted service's HMAC, and caller input never
|
|
31
|
+
supplies an owner or target.
|
|
30
32
|
11. Optional provider signing secrets are persisted but write-only: normal
|
|
31
33
|
reads reveal only that a secret is configured.
|
|
32
34
|
12. Run history is read-only in the control plane and always scoped to its
|
|
@@ -76,3 +78,8 @@
|
|
|
76
78
|
30. Run Now is triggerless manual admission. It atomically snapshots the
|
|
77
79
|
current enabled automation and compiled workflow into the pending ledger;
|
|
78
80
|
it never executes inline or changes a trigger's schedule state.
|
|
81
|
+
31. A step input is an immutable JSON template. The exact singleton
|
|
82
|
+
`{ "$runInput": true }` explicitly resolves to the run's immutable input;
|
|
83
|
+
no trigger payload is ambient and no action rediscovers it.
|
|
84
|
+
32. An adapter preserves the owning service's error code and status. Transport
|
|
85
|
+
errors may add presentation, but never relabel authorization as validation.
|
package/PURPOSE.md
CHANGED
|
@@ -27,13 +27,15 @@ The product has two composable halves over that one state:
|
|
|
27
27
|
needs to know whether a machine is online: an unclaimed run is the complete
|
|
28
28
|
offline queue.
|
|
29
29
|
|
|
30
|
-
One run is one immutable plan plus one durable ordered step journal. The
|
|
30
|
+
One run is one immutable plan, one immutable trigger input, plus one durable ordered step journal. The
|
|
31
31
|
machine records a step as running before invoking its action and records its
|
|
32
32
|
output before advancing. Reconnect resumes at the first step that is not
|
|
33
33
|
already complete. A transient network failure releases the same run back to
|
|
34
34
|
the queue with a bounded future retry time; a configuration, authorization, or
|
|
35
35
|
action error fails it. Stable per-step idempotency keys make an uncertain
|
|
36
|
-
network acknowledgement safe to repeat.
|
|
36
|
+
network acknowledgement safe to repeat. A workflow can place that run input in
|
|
37
|
+
an action payload only through the explicit `{ "$runInput": true }` template
|
|
38
|
+
value, so static configuration and occurrence data never blur together.
|
|
37
39
|
|
|
38
40
|
Automations is its own hosted service and Fly machine. Its API and scheduler
|
|
39
41
|
share the same SDK and Supabase authority; neither is composed into, proxied by,
|
|
@@ -47,9 +49,10 @@ trigger owns one opaque URL under `automations.amalgm.ai`; the URL resolves the
|
|
|
47
49
|
trigger, owner, and target, so a caller never supplies routing identity. The
|
|
48
50
|
adapter bounds and parses the body, optionally verifies the provider's signing
|
|
49
51
|
secret, deduplicates explicit provider delivery ids, and returns only after the
|
|
50
|
-
run has been stored in Supabase.
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
run has been stored in Supabase. Provider source and event labels describe the
|
|
53
|
+
occurrence; they are not a second routing gate after the URL has selected the
|
|
54
|
+
trigger. The signed URL can be rotated without changing the automation, and its
|
|
55
|
+
usable bearer token cannot be reconstructed from the database alone.
|
|
53
56
|
|
|
54
57
|
Amalgm supplies a resolved authenticated principal from its user session or
|
|
55
58
|
HMAC-refresh flow; future API keys resolve to the same principal capability.
|
package/README.md
CHANGED
|
@@ -99,6 +99,8 @@ Public webhook admission uses the URL returned on each webhook trigger:
|
|
|
99
99
|
POST https://automations.amalgm.ai/e/<opaque-capability>
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
That URL selects the exact trigger. Provider source and event labels are
|
|
103
|
+
recorded as occurrence metadata, not used as another routing condition.
|
|
102
104
|
`Idempotency-Key` and common provider delivery-id headers deduplicate retries
|
|
103
105
|
against the same trigger. The caller never supplies a user or target id.
|
|
104
106
|
|
package/dist/src/automations.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { nextCronAt } from './schedule.js';
|
|
2
|
-
import { eventReferences,
|
|
2
|
+
import { eventReferences, verifyEventSecret } from './webhook.js';
|
|
3
3
|
const noop = () => { };
|
|
4
4
|
export class EventRejectedError extends Error {
|
|
5
5
|
constructor() {
|
|
@@ -33,14 +33,7 @@ export class Automations {
|
|
|
33
33
|
const references = input.source && input.event
|
|
34
34
|
? { primary: { source: input.source, event: input.event } }
|
|
35
35
|
: eventReferences(input.headers, input.payload);
|
|
36
|
-
|
|
37
|
-
let matches = matchesEvent(trigger, reference.source, reference.event);
|
|
38
|
-
if (!matches && references.fallback) {
|
|
39
|
-
reference = references.fallback;
|
|
40
|
-
matches = matchesEvent(trigger, reference.source, reference.event);
|
|
41
|
-
}
|
|
42
|
-
if (!matches)
|
|
43
|
-
return { run: null, ...reference };
|
|
36
|
+
const reference = references.primary;
|
|
44
37
|
const now = input.now || new Date();
|
|
45
38
|
const runInput = { kind: 'event', ...reference, payload: input.payload };
|
|
46
39
|
const run = await this.store.enqueueEvent(trigger, runInput, now, input.deliveryKey);
|
package/dist/src/client.js
CHANGED
|
@@ -61,9 +61,10 @@ function createRequester(baseUrl, authorization, additionalHeaders, fetch, reque
|
|
|
61
61
|
if (response.status === 404 && nullable)
|
|
62
62
|
return null;
|
|
63
63
|
if (!response.ok) {
|
|
64
|
-
|
|
65
|
-
?
|
|
66
|
-
|
|
64
|
+
const code = payload.code?.trim() || (response.status === 403 ? 'forbidden'
|
|
65
|
+
: response.status >= 500 ? 'internal'
|
|
66
|
+
: 'validation');
|
|
67
|
+
throw new AutomationError(code, payload.error || `Automations API returned ${response.status}`, response.status);
|
|
67
68
|
}
|
|
68
69
|
return payload;
|
|
69
70
|
};
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export type AutomationErrorCode = 'conflict' | 'forbidden' | 'internal' | 'not_found' | 'validation';
|
|
2
2
|
export declare class AutomationError extends Error {
|
|
3
|
-
readonly code: AutomationErrorCode;
|
|
4
|
-
|
|
3
|
+
readonly code: AutomationErrorCode | string;
|
|
4
|
+
readonly status?: number | undefined;
|
|
5
|
+
constructor(code: AutomationErrorCode | string, message: string, status?: number | undefined);
|
|
5
6
|
}
|
|
6
7
|
export declare class ValidationError extends AutomationError {
|
|
7
8
|
constructor(message: string);
|
package/dist/src/errors.js
CHANGED
package/dist/src/executor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveActionInput } from './input-template.js';
|
|
1
2
|
import { automationPlan } from './plan.js';
|
|
2
3
|
import { completed, emptyJournal, journalJson, putStep, runJournal, stepAttempt, } from './run-journal.js';
|
|
3
4
|
export { automationPlan } from './plan.js';
|
|
@@ -138,7 +139,7 @@ export class AutomationRunExecutor {
|
|
|
138
139
|
try {
|
|
139
140
|
const output = await this.actions.call({
|
|
140
141
|
actionId: step.actionId,
|
|
141
|
-
payload: step.input,
|
|
142
|
+
payload: resolveActionInput(step.input, run.input),
|
|
142
143
|
idempotencyKey: `${run.id}:${step.id}`,
|
|
143
144
|
signal: controller.signal,
|
|
144
145
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const RUN_INPUT_REFERENCE = '$runInput';
|
|
2
|
+
/** Resolve the one explicit dynamic value permitted in an immutable action input. */
|
|
3
|
+
export function resolveActionInput(template, runInput) {
|
|
4
|
+
if (Array.isArray(template))
|
|
5
|
+
return template.map((value) => resolveActionInput(value, runInput));
|
|
6
|
+
if (!template || typeof template !== 'object')
|
|
7
|
+
return template;
|
|
8
|
+
const entries = Object.entries(template);
|
|
9
|
+
if (entries.length === 1 && entries[0]?.[0] === RUN_INPUT_REFERENCE) {
|
|
10
|
+
if (entries[0][1] !== true) {
|
|
11
|
+
throw new Error(`Workflow ${RUN_INPUT_REFERENCE} reference must equal true`);
|
|
12
|
+
}
|
|
13
|
+
return runInput;
|
|
14
|
+
}
|
|
15
|
+
return Object.fromEntries(entries.map(([key, value]) => [
|
|
16
|
+
key,
|
|
17
|
+
resolveActionInput(value, runInput),
|
|
18
|
+
]));
|
|
19
|
+
}
|
|
@@ -17,7 +17,9 @@ export function createMachineRunsClient(options) {
|
|
|
17
17
|
});
|
|
18
18
|
const payload = await response.json().catch(() => ({}));
|
|
19
19
|
if (!response.ok)
|
|
20
|
-
throw new AutomationError(response.status === 403 ? 'forbidden'
|
|
20
|
+
throw new AutomationError(payload.code?.trim() || (response.status === 403 ? 'forbidden'
|
|
21
|
+
: response.status >= 500 ? 'internal'
|
|
22
|
+
: 'validation'), payload.error ?? `Automations API returned ${response.status}`, response.status);
|
|
21
23
|
return payload;
|
|
22
24
|
};
|
|
23
25
|
return Object.freeze({
|
package/dist/src/mcp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod/v3';
|
|
3
|
+
import { AutomationError } from './errors.js';
|
|
3
4
|
import { createAutomationSchema, createScheduleTriggerSchema, createWebhookTriggerSchema, createWorkflowSchema, identifierSchema, listAutomationsSchema, listRunsSchema, runAutomationNowSchema, updateAutomationSchema, updateScheduleTriggerSchema, updateWebhookTriggerSchema, updateWorkflowSchema, } from './schema.js';
|
|
4
5
|
import { createAutomationToolSurface } from './tool-surface.js';
|
|
5
6
|
export { createAutomationToolSurface };
|
|
@@ -83,9 +84,22 @@ function toolSuccess(result) {
|
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
86
|
function toolFailure(error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
const code = error instanceof AutomationError ? error.code : 'internal';
|
|
89
|
+
const status = error instanceof AutomationError ? error.status : undefined;
|
|
86
90
|
return {
|
|
87
91
|
isError: true,
|
|
88
|
-
content: [{
|
|
92
|
+
content: [{
|
|
93
|
+
type: 'text',
|
|
94
|
+
text: `${code}: ${message}${status === undefined ? '' : ` (HTTP ${status})`}`,
|
|
95
|
+
}],
|
|
96
|
+
structuredContent: {
|
|
97
|
+
error: {
|
|
98
|
+
code,
|
|
99
|
+
message,
|
|
100
|
+
...(status === undefined ? {} : { status }),
|
|
101
|
+
},
|
|
102
|
+
},
|
|
89
103
|
};
|
|
90
104
|
}
|
|
91
105
|
const read = () => ({
|
package/package.json
CHANGED
|
@@ -54,6 +54,12 @@ user asks for the outcome.
|
|
|
54
54
|
|
|
55
55
|
Action discovery belongs to the Tools product, not Automations.
|
|
56
56
|
|
|
57
|
+
For webhook-driven work, pass the admitted provider payload into an action by
|
|
58
|
+
placing the exact value `{ "$runInput": true }` at the desired point in that
|
|
59
|
+
step's `input`. For example, an agent-review action can use a static `message`
|
|
60
|
+
and set `context` to `{ "$runInput": true }`. Never paste an example delivery
|
|
61
|
+
into the workflow: every admitted run must use its own durable input.
|
|
62
|
+
|
|
57
63
|
`pending` means a run is durable and waiting for its selected machine. `sent`
|
|
58
64
|
or `running` means that machine holds a lease. `completed` and `failed` are
|
|
59
65
|
terminal. Do not infer delivery from the schedule alone; inspect runs when the
|
|
@@ -63,3 +69,10 @@ Each webhook trigger returns one copyable `webhookUrl`; use that URL as the
|
|
|
63
69
|
provider destination. A separate provider signing secret is optional and
|
|
64
70
|
write-only. Never echo, log, or place either credential in a workflow. Rotate
|
|
65
71
|
the URL when its bearer capability may have been exposed.
|
|
72
|
+
|
|
73
|
+
For GitHub, register the returned URL as a repository webhook with JSON content
|
|
74
|
+
type and select only the requested events (normally `push`). If a provider
|
|
75
|
+
secret was configured on the trigger, use the same value as GitHub's webhook
|
|
76
|
+
secret. Creating the Amalgm trigger does not silently mutate GitHub; finish the
|
|
77
|
+
provider registration in the authenticated GitHub surface available to the
|
|
78
|
+
agent, then report both sides as configured.
|