@amalgm/automations 0.2.1 → 0.2.3
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 +39 -17
- package/PURPOSE.md +26 -11
- package/README.md +33 -4
- package/dist/host/config.d.ts +2 -0
- package/dist/host/config.js +8 -0
- package/dist/host/main.js +10 -5
- package/dist/host/server.d.ts +2 -0
- package/dist/host/server.js +25 -7
- package/dist/src/automations.d.ts +15 -9
- package/dist/src/automations.js +33 -76
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +5 -2
- package/dist/src/client.d.ts +1 -0
- package/dist/src/client.js +4 -2
- package/dist/src/contract.d.ts +11 -31
- package/dist/src/crud/context.d.ts +3 -1
- package/dist/src/crud/context.js +2 -1
- package/dist/src/crud/repository.d.ts +17 -6
- package/dist/src/crud/runs.d.ts +1 -1
- package/dist/src/crud/runs.js +24 -1
- package/dist/src/crud/triggers.js +18 -9
- package/dist/src/crud.d.ts +4 -2
- package/dist/src/crud.js +5 -2
- package/dist/src/events-http.d.ts +3 -5
- package/dist/src/events-http.js +22 -26
- package/dist/src/executor.d.ts +12 -3
- package/dist/src/executor.js +163 -44
- package/dist/src/http.js +8 -1
- package/dist/src/index.d.ts +6 -4
- package/dist/src/index.js +6 -4
- package/dist/src/machine-client.d.ts +1 -0
- package/dist/src/machine-client.js +2 -0
- package/dist/src/machine.d.ts +2 -1
- package/dist/src/machine.js +6 -1
- package/dist/src/mcp.d.ts +3 -0
- package/dist/src/mcp.js +61 -50
- package/dist/src/plan.d.ts +3 -0
- package/dist/src/plan.js +48 -0
- package/dist/src/run-contract.d.ts +52 -0
- package/dist/src/run-contract.js +1 -0
- package/dist/src/run-journal.d.ts +8 -0
- package/dist/src/run-journal.js +56 -0
- package/dist/src/runner.d.ts +14 -0
- package/dist/src/runner.js +70 -0
- package/dist/src/schema.d.ts +31 -15
- package/dist/src/schema.js +27 -4
- package/dist/src/supabase-crud/mappers.d.ts +3 -2
- package/dist/src/supabase-crud/mappers.js +5 -0
- package/dist/src/supabase-crud/rows.d.ts +3 -0
- package/dist/src/supabase-crud/workflow-runs.d.ts +1 -1
- package/dist/src/supabase-crud/workflow-runs.js +10 -0
- package/dist/src/supabase-machine.js +2 -0
- package/dist/src/supabase-store.d.ts +3 -6
- package/dist/src/supabase-store.js +13 -34
- package/dist/src/tool-surface.d.ts +47 -0
- package/dist/src/tool-surface.js +126 -0
- package/dist/src/types.d.ts +8 -24
- package/dist/src/webhook.d.ts +15 -1
- package/dist/src/webhook.js +86 -0
- package/package.json +3 -3
- package/skills/automations/SKILL.md +29 -16
- package/supabase/migrations/20260830010000_durable_step_retries.sql +332 -0
- package/supabase/migrations/20260831010000_public_webhook_endpoints.sql +287 -0
- package/supabase/migrations/20260831020000_manual_run_admission.sql +68 -0
package/dist/src/contract.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type Json = null | boolean | number | string | Json[] | {
|
|
2
2
|
[key: string]: Json;
|
|
3
3
|
};
|
|
4
|
+
import type { AutomationRun, ListRuns, RunAutomationNow } from './run-contract.js';
|
|
5
|
+
export type { AutomationPlan, AutomationRun, AutomationRunJournal, AutomationStepRun, AutomationStepStatus, ListRuns, RunAutomationNow, RunStatus, ToolActionStep, } from './run-contract.js';
|
|
4
6
|
export type AutomationScope = 'automations:read' | 'automations:write' | 'runs:read' | 'runs:execute' | '*';
|
|
5
7
|
/** Identity resolved by Amalgm before the SDK is bound to a caller. */
|
|
6
8
|
export interface AutomationPrincipal {
|
|
@@ -66,6 +68,8 @@ export interface WebhookTrigger extends TriggerBase {
|
|
|
66
68
|
kind: 'webhook';
|
|
67
69
|
source: string;
|
|
68
70
|
event: string;
|
|
71
|
+
/** Bearer-capability URL. Treat it as a credential and rotate it if exposed. */
|
|
72
|
+
webhookUrl: string;
|
|
69
73
|
secretConfigured: boolean;
|
|
70
74
|
}
|
|
71
75
|
export type Trigger = ScheduleTrigger | WebhookTrigger;
|
|
@@ -84,26 +88,21 @@ export interface UpdateScheduleTrigger {
|
|
|
84
88
|
/** Set a new finite budget, or null to make the schedule unbounded. */
|
|
85
89
|
maxOccurrences?: number | null;
|
|
86
90
|
}
|
|
87
|
-
export interface ToolActionStep {
|
|
88
|
-
id: string;
|
|
89
|
-
actionId: string;
|
|
90
|
-
input: Json;
|
|
91
|
-
}
|
|
92
|
-
export interface AutomationPlan {
|
|
93
|
-
version: 1;
|
|
94
|
-
steps: ToolActionStep[];
|
|
95
|
-
}
|
|
96
91
|
export interface CreateWebhookTrigger {
|
|
97
92
|
id?: string;
|
|
98
93
|
source?: string;
|
|
99
94
|
event?: string;
|
|
100
|
-
secret
|
|
95
|
+
/** Optional provider signing secret; the generated webhook URL authenticates admission. */
|
|
96
|
+
secret?: string;
|
|
101
97
|
enabled?: boolean;
|
|
102
98
|
}
|
|
103
99
|
export interface UpdateWebhookTrigger {
|
|
104
100
|
source?: string;
|
|
105
101
|
event?: string;
|
|
106
|
-
|
|
102
|
+
/** Set null to remove provider-signature verification. */
|
|
103
|
+
secret?: string | null;
|
|
104
|
+
/** Invalidates the existing URL and returns its replacement. */
|
|
105
|
+
rotateUrl?: boolean;
|
|
107
106
|
enabled?: boolean;
|
|
108
107
|
}
|
|
109
108
|
export interface Workflow {
|
|
@@ -132,25 +131,6 @@ export interface UpdateWorkflow {
|
|
|
132
131
|
allowlist?: Json | null;
|
|
133
132
|
limits?: Json | null;
|
|
134
133
|
}
|
|
135
|
-
export type RunStatus = 'pending' | 'sent' | 'running' | 'completed' | 'failed';
|
|
136
|
-
export interface AutomationRun {
|
|
137
|
-
id: string;
|
|
138
|
-
automationId: string;
|
|
139
|
-
triggerId: string;
|
|
140
|
-
workflowId: string;
|
|
141
|
-
targetId: string;
|
|
142
|
-
status: RunStatus;
|
|
143
|
-
input: Json;
|
|
144
|
-
createdAt: string;
|
|
145
|
-
sentAt?: string;
|
|
146
|
-
startedAt?: string;
|
|
147
|
-
finishedAt?: string;
|
|
148
|
-
output?: Json;
|
|
149
|
-
error?: string;
|
|
150
|
-
}
|
|
151
|
-
export interface ListRuns extends PageQuery {
|
|
152
|
-
status?: RunStatus;
|
|
153
|
-
}
|
|
154
134
|
export interface AutomationCrud {
|
|
155
135
|
readonly automations: {
|
|
156
136
|
create(input: CreateAutomation): Promise<Automation>;
|
|
@@ -183,6 +163,7 @@ export interface AutomationCrud {
|
|
|
183
163
|
delete(automationId: string): Promise<void>;
|
|
184
164
|
};
|
|
185
165
|
readonly runs: {
|
|
166
|
+
runNow(automationId: string, input?: RunAutomationNow): Promise<AutomationRun>;
|
|
186
167
|
list(automationId: string, query?: ListRuns): Promise<Page<AutomationRun>>;
|
|
187
168
|
get(automationId: string, runId: string): Promise<AutomationRun | null>;
|
|
188
169
|
};
|
|
@@ -190,4 +171,3 @@ export interface AutomationCrud {
|
|
|
190
171
|
export interface AutomationCrudService {
|
|
191
172
|
for(principal: AutomationPrincipal): AutomationCrud;
|
|
192
173
|
}
|
|
193
|
-
export {};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { AutomationPrincipal } from '../contract.js';
|
|
2
2
|
import type { AutomationCrudRepository } from './repository.js';
|
|
3
|
+
import type { WebhookEndpoints } from '../webhook.js';
|
|
3
4
|
export interface CrudContext {
|
|
4
5
|
repository: AutomationCrudRepository;
|
|
5
6
|
principal: AutomationPrincipal;
|
|
6
7
|
clock: () => Date;
|
|
8
|
+
webhooks: WebhookEndpoints;
|
|
7
9
|
read(): void;
|
|
8
10
|
write(): void;
|
|
9
11
|
runsRead(): void;
|
|
10
12
|
exists(automationId: string): Promise<void>;
|
|
11
13
|
}
|
|
12
|
-
export declare function createContext(repository: AutomationCrudRepository, principal: AutomationPrincipal, clock: () => Date): CrudContext;
|
|
14
|
+
export declare function createContext(repository: AutomationCrudRepository, principal: AutomationPrincipal, clock: () => Date, webhooks: WebhookEndpoints): CrudContext;
|
|
13
15
|
export declare function assertTarget(principal: AutomationPrincipal, targetId: string): void;
|
|
14
16
|
export declare function resolveTarget(principal: AutomationPrincipal, targetId?: string): string;
|
package/dist/src/crud/context.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ForbiddenError, NotFoundError, ValidationError } from '../errors.js';
|
|
2
2
|
import { id, requiredText } from '../validation.js';
|
|
3
|
-
export function createContext(repository, principal, clock) {
|
|
3
|
+
export function createContext(repository, principal, clock, webhooks) {
|
|
4
4
|
assertPrincipal(principal);
|
|
5
5
|
return {
|
|
6
6
|
repository,
|
|
7
7
|
principal,
|
|
8
8
|
clock,
|
|
9
|
+
webhooks,
|
|
9
10
|
read: () => assertScope(principal, 'automations:read'),
|
|
10
11
|
write: () => assertScope(principal, 'automations:write'),
|
|
11
12
|
runsRead: () => assertScope(principal, 'runs:read'),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Automation, AutomationRun, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, ListAutomations, ListRuns, Page, PageQuery, ScheduleTrigger,
|
|
1
|
+
import type { Automation, AutomationRun, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, Json, ListAutomations, ListRuns, Page, PageQuery, ScheduleTrigger, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow, WebhookTrigger, Workflow } from '../contract.js';
|
|
2
2
|
export type StoredScheduleCreate = Omit<Required<CreateScheduleTrigger>, 'maxOccurrences'> & {
|
|
3
3
|
maxOccurrences: number | null;
|
|
4
4
|
nextRunAt: string;
|
|
@@ -7,6 +7,16 @@ export type StoredScheduleUpdate = UpdateScheduleTrigger & {
|
|
|
7
7
|
nextRunAt?: string;
|
|
8
8
|
remainingOccurrences?: number | null;
|
|
9
9
|
};
|
|
10
|
+
export type WebhookTriggerRecord = Omit<WebhookTrigger, 'webhookUrl'> & {
|
|
11
|
+
endpointId: string;
|
|
12
|
+
};
|
|
13
|
+
export type StoredWebhookCreate = Omit<Required<CreateWebhookTrigger>, 'secret'> & {
|
|
14
|
+
secret: string | null;
|
|
15
|
+
endpointId: string;
|
|
16
|
+
};
|
|
17
|
+
export type StoredWebhookUpdate = Omit<UpdateWebhookTrigger, 'rotateUrl'> & {
|
|
18
|
+
endpointId?: string;
|
|
19
|
+
};
|
|
10
20
|
export type NormalizedListAutomations = Required<PageQuery> & Omit<ListAutomations, keyof PageQuery>;
|
|
11
21
|
export type NormalizedListRuns = Required<PageQuery> & Omit<ListRuns, keyof PageQuery>;
|
|
12
22
|
export interface AutomationCrudRepository {
|
|
@@ -20,16 +30,17 @@ export interface AutomationCrudRepository {
|
|
|
20
30
|
getScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<ScheduleTrigger | null>;
|
|
21
31
|
updateScheduleTrigger(userId: string, automationId: string, triggerId: string, patch: StoredScheduleUpdate): Promise<ScheduleTrigger | null>;
|
|
22
32
|
deleteScheduleTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
|
|
23
|
-
createWebhookTrigger(userId: string, automationId: string, input:
|
|
24
|
-
listWebhookTriggers(userId: string, automationId: string, query: Required<PageQuery>): Promise<Page<
|
|
25
|
-
getWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<
|
|
26
|
-
updateWebhookTrigger(userId: string, automationId: string, triggerId: string, patch:
|
|
33
|
+
createWebhookTrigger(userId: string, automationId: string, input: StoredWebhookCreate): Promise<WebhookTriggerRecord>;
|
|
34
|
+
listWebhookTriggers(userId: string, automationId: string, query: Required<PageQuery>): Promise<Page<WebhookTriggerRecord>>;
|
|
35
|
+
getWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<WebhookTriggerRecord | null>;
|
|
36
|
+
updateWebhookTrigger(userId: string, automationId: string, triggerId: string, patch: StoredWebhookUpdate): Promise<WebhookTriggerRecord | null>;
|
|
27
37
|
deleteWebhookTrigger(userId: string, automationId: string, triggerId: string): Promise<boolean>;
|
|
28
|
-
listTriggers(userId: string, automationId: string): Promise<
|
|
38
|
+
listTriggers(userId: string, automationId: string): Promise<Array<ScheduleTrigger | WebhookTriggerRecord>>;
|
|
29
39
|
createWorkflow(userId: string, automationId: string, input: Required<CreateWorkflow>): Promise<Workflow>;
|
|
30
40
|
getWorkflow(userId: string, automationId: string): Promise<Workflow | null>;
|
|
31
41
|
updateWorkflow(userId: string, automationId: string, patch: UpdateWorkflow): Promise<Workflow | null>;
|
|
32
42
|
deleteWorkflow(userId: string, automationId: string): Promise<boolean>;
|
|
33
43
|
listRuns(userId: string, automationId: string, query: NormalizedListRuns): Promise<Page<AutomationRun>>;
|
|
34
44
|
getRun(userId: string, automationId: string, runId: string): Promise<AutomationRun | null>;
|
|
45
|
+
runAutomationNow(userId: string, automationId: string, input: Json, idempotencyKey: string | null, now: Date): Promise<AutomationRun | null>;
|
|
35
46
|
}
|
package/dist/src/crud/runs.d.ts
CHANGED
package/dist/src/crud/runs.js
CHANGED
|
@@ -1,8 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AutomationError, NotFoundError, ValidationError } from '../errors.js';
|
|
2
|
+
import { compiledAutomationPlan } from '../plan.js';
|
|
3
|
+
import { parseListRuns, parseRunAutomationNow } from '../schema.js';
|
|
2
4
|
import { id, page } from '../validation.js';
|
|
5
|
+
import { assertTarget } from './context.js';
|
|
3
6
|
export function runOperations(context) {
|
|
4
7
|
const { principal, repository } = context;
|
|
5
8
|
return {
|
|
9
|
+
runNow: async (automationId, input = {}) => {
|
|
10
|
+
context.write();
|
|
11
|
+
const automationKey = id(automationId, 'Automation id');
|
|
12
|
+
const parsed = parseRunAutomationNow(input);
|
|
13
|
+
const automation = await repository.getAutomation(principal.userId, automationKey);
|
|
14
|
+
if (!automation)
|
|
15
|
+
throw new NotFoundError('Automation');
|
|
16
|
+
assertTarget(principal, automation.targetId);
|
|
17
|
+
if (!automation.enabled)
|
|
18
|
+
throw new ValidationError('Automation must be enabled before it can run');
|
|
19
|
+
const workflow = await repository.getWorkflow(principal.userId, automationKey);
|
|
20
|
+
if (!workflow?.compiled)
|
|
21
|
+
throw new ValidationError('Automation needs a compiled workflow before it can run');
|
|
22
|
+
compiledAutomationPlan(workflow.compiled);
|
|
23
|
+
const run = await repository.runAutomationNow(principal.userId, automationKey, { kind: 'manual', payload: parsed.input ?? {} }, parsed.idempotencyKey ?? null, context.clock());
|
|
24
|
+
if (!run) {
|
|
25
|
+
throw new AutomationError('conflict', 'Automation changed before the run was admitted; inspect it and try again');
|
|
26
|
+
}
|
|
27
|
+
return run;
|
|
28
|
+
},
|
|
6
29
|
list: async (automationId, query = {}) => {
|
|
7
30
|
context.runsRead();
|
|
8
31
|
const parsed = parseListRuns(query);
|
|
@@ -3,7 +3,7 @@ import { parseCreateScheduleTrigger, parseCreateWebhookTrigger, parsePageQuery,
|
|
|
3
3
|
import { nextCronAt } from '../schedule.js';
|
|
4
4
|
import { id, newId, page, schedule } from '../validation.js';
|
|
5
5
|
export function triggerOperations(context) {
|
|
6
|
-
const { principal, repository } = context;
|
|
6
|
+
const { principal, repository, webhooks } = context;
|
|
7
7
|
const unique = async (automationId, triggerId) => {
|
|
8
8
|
const exists = (await repository.listTriggers(principal.userId, automationId))
|
|
9
9
|
.some((trigger) => trigger.id === triggerId);
|
|
@@ -14,7 +14,7 @@ export function triggerOperations(context) {
|
|
|
14
14
|
list: async (automationId) => {
|
|
15
15
|
context.read();
|
|
16
16
|
await context.exists(automationId);
|
|
17
|
-
return repository.listTriggers(principal.userId, automationId);
|
|
17
|
+
return (await repository.listTriggers(principal.userId, automationId)).map((trigger) => (trigger.kind === 'webhook' ? publicWebhook(trigger, webhooks) : trigger));
|
|
18
18
|
},
|
|
19
19
|
schedule: {
|
|
20
20
|
create: async (automationId, input) => {
|
|
@@ -80,31 +80,36 @@ export function triggerOperations(context) {
|
|
|
80
80
|
const parsed = parseCreateWebhookTrigger(input);
|
|
81
81
|
if (parsed.id)
|
|
82
82
|
await unique(automationId, parsed.id);
|
|
83
|
-
return repository.createWebhookTrigger(principal.userId, automationId, {
|
|
83
|
+
return publicWebhook(await repository.createWebhookTrigger(principal.userId, automationId, {
|
|
84
84
|
id: parsed.id ? id(parsed.id, 'Webhook trigger id') : newId('webhook'),
|
|
85
85
|
source: parsed.source || '*',
|
|
86
86
|
event: parsed.event || '*',
|
|
87
|
-
secret: parsed.secret,
|
|
87
|
+
secret: parsed.secret ?? null,
|
|
88
88
|
enabled: parsed.enabled !== false,
|
|
89
|
-
|
|
89
|
+
endpointId: webhooks.createId(),
|
|
90
|
+
}), webhooks);
|
|
90
91
|
},
|
|
91
92
|
list: async (automationId, query = {}) => {
|
|
92
93
|
context.read();
|
|
93
94
|
await context.exists(automationId);
|
|
94
|
-
|
|
95
|
+
const result = await repository.listWebhookTriggers(principal.userId, automationId, page(parsePageQuery(query)));
|
|
96
|
+
return { ...result, items: result.items.map((trigger) => publicWebhook(trigger, webhooks)) };
|
|
95
97
|
},
|
|
96
98
|
get: async (automationId, triggerId) => {
|
|
97
99
|
context.read();
|
|
98
100
|
await context.exists(automationId);
|
|
99
|
-
|
|
101
|
+
const trigger = await repository.getWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'));
|
|
102
|
+
return trigger ? publicWebhook(trigger, webhooks) : null;
|
|
100
103
|
},
|
|
101
104
|
update: async (automationId, triggerId, patch) => {
|
|
102
105
|
context.write();
|
|
103
106
|
await context.exists(automationId);
|
|
104
|
-
const
|
|
107
|
+
const parsed = parseUpdateWebhookTrigger(patch);
|
|
108
|
+
const { rotateUrl, ...changes } = parsed;
|
|
109
|
+
const updated = await repository.updateWebhookTrigger(principal.userId, automationId, id(triggerId, 'Webhook trigger id'), { ...changes, ...(rotateUrl ? { endpointId: webhooks.createId() } : {}) });
|
|
105
110
|
if (!updated)
|
|
106
111
|
throw new NotFoundError('Webhook trigger');
|
|
107
|
-
return updated;
|
|
112
|
+
return publicWebhook(updated, webhooks);
|
|
108
113
|
},
|
|
109
114
|
delete: async (automationId, triggerId) => {
|
|
110
115
|
context.write();
|
|
@@ -116,3 +121,7 @@ export function triggerOperations(context) {
|
|
|
116
121
|
},
|
|
117
122
|
};
|
|
118
123
|
}
|
|
124
|
+
function publicWebhook(trigger, webhooks) {
|
|
125
|
+
const { endpointId, ...publicTrigger } = trigger;
|
|
126
|
+
return { ...publicTrigger, webhookUrl: webhooks.url(endpointId) };
|
|
127
|
+
}
|
package/dist/src/crud.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { AutomationCrud, AutomationCrudService as AutomationCrudServiceContract, AutomationPrincipal } from './contract.js';
|
|
2
2
|
import type { AutomationCrudRepository } from './crud/repository.js';
|
|
3
|
-
|
|
3
|
+
import { WebhookEndpoints } from './webhook.js';
|
|
4
|
+
export type { AutomationCrudRepository, NormalizedListAutomations, NormalizedListRuns, StoredScheduleCreate, StoredScheduleUpdate, StoredWebhookCreate, StoredWebhookUpdate, WebhookTriggerRecord, } from './crud/repository.js';
|
|
4
5
|
export declare class AutomationCrudService implements AutomationCrudServiceContract {
|
|
6
|
+
#private;
|
|
5
7
|
private readonly repository;
|
|
6
8
|
private readonly clock;
|
|
7
|
-
constructor(repository: AutomationCrudRepository, clock?: () => Date);
|
|
9
|
+
constructor(repository: AutomationCrudRepository, clock?: () => Date, webhooks?: WebhookEndpoints);
|
|
8
10
|
for(principal: AutomationPrincipal): AutomationCrud;
|
|
9
11
|
}
|
package/dist/src/crud.js
CHANGED
|
@@ -3,15 +3,18 @@ import { createContext } from './crud/context.js';
|
|
|
3
3
|
import { runOperations } from './crud/runs.js';
|
|
4
4
|
import { triggerOperations } from './crud/triggers.js';
|
|
5
5
|
import { workflowOperations } from './crud/workflow.js';
|
|
6
|
+
import { WebhookEndpoints } from './webhook.js';
|
|
6
7
|
export class AutomationCrudService {
|
|
7
8
|
repository;
|
|
8
9
|
clock;
|
|
9
|
-
|
|
10
|
+
#webhooks;
|
|
11
|
+
constructor(repository, clock = () => new Date(), webhooks) {
|
|
10
12
|
this.repository = repository;
|
|
11
13
|
this.clock = clock;
|
|
14
|
+
this.#webhooks = webhooks ?? new WebhookEndpoints('http://automations.local', Buffer.from('local-automations-webhook-key-32-bytes-minimum'));
|
|
12
15
|
}
|
|
13
16
|
for(principal) {
|
|
14
|
-
const context = createContext(this.repository, principal, this.clock);
|
|
17
|
+
const context = createContext(this.repository, principal, this.clock, this.#webhooks);
|
|
15
18
|
return {
|
|
16
19
|
automations: automationOperations(context),
|
|
17
20
|
triggers: triggerOperations(context),
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { Automations } from './automations.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type WebhookEndpoints } from './webhook.js';
|
|
3
3
|
export interface AutomationEventReceipt {
|
|
4
4
|
receivedAt: string;
|
|
5
|
-
targetId: string;
|
|
6
5
|
source: string;
|
|
7
6
|
event: string;
|
|
8
|
-
|
|
7
|
+
runId?: string;
|
|
9
8
|
status: 'pending' | 'unmatched';
|
|
10
9
|
}
|
|
11
10
|
export interface AutomationEventsApiConfig {
|
|
12
11
|
delivery: Pick<Automations, 'receiveEvent'>;
|
|
13
|
-
|
|
12
|
+
endpoints: Pick<WebhookEndpoints, 'resolve'>;
|
|
14
13
|
now?: () => Date;
|
|
15
14
|
maxBodyBytes?: number;
|
|
16
|
-
recentLimit?: number;
|
|
17
15
|
}
|
|
18
16
|
export type AutomationEventsApi = (request: Request) => Promise<Response>;
|
|
19
17
|
export declare function createAutomationEventsApi(config: AutomationEventsApiConfig): AutomationEventsApi;
|
package/dist/src/events-http.js
CHANGED
|
@@ -1,59 +1,55 @@
|
|
|
1
|
-
import { Automations, EventRejectedError } from './automations.js';
|
|
2
|
-
import {
|
|
1
|
+
import { Automations, EventEndpointNotFoundError, EventRejectedError } from './automations.js';
|
|
2
|
+
import { webhookDeliveryKey } from './webhook.js';
|
|
3
3
|
// Engine reference: amalgm-engine/runtime/scripts/amalgm-mcp/events/ingress.js.
|
|
4
|
-
// The 2 MiB transport bound
|
|
5
|
-
//
|
|
6
|
-
// in Supabase, never that an envelope was written to a local SQLite inbox.
|
|
4
|
+
// The 2 MiB transport bound is preserved. Success means the matching run is
|
|
5
|
+
// durable in Supabase, never that an envelope was written to a local inbox.
|
|
7
6
|
const DEFAULT_MAX_BODY_BYTES = 2 * 1024 * 1024;
|
|
8
|
-
const DEFAULT_RECENT_LIMIT = 200;
|
|
9
7
|
export function createAutomationEventsApi(config) {
|
|
10
|
-
const recent = [];
|
|
11
8
|
const now = config.now ?? (() => new Date());
|
|
12
9
|
const maxBodyBytes = positiveInteger(config.maxBodyBytes, DEFAULT_MAX_BODY_BYTES);
|
|
13
|
-
const recentLimit = positiveInteger(config.recentLimit, DEFAULT_RECENT_LIMIT);
|
|
14
10
|
return async (request) => {
|
|
15
11
|
const pathname = new URL(request.url).pathname;
|
|
16
|
-
|
|
12
|
+
const token = pathname.match(/^\/e\/([^/]+)$/)?.[1];
|
|
13
|
+
if (!token)
|
|
17
14
|
return json(404, { error: 'not found' });
|
|
18
|
-
if (request.method === 'GET')
|
|
19
|
-
return json(200, { events: recent.slice(-50) });
|
|
20
15
|
if (request.method !== 'POST')
|
|
21
16
|
return json(405, { error: 'method not allowed' });
|
|
22
17
|
try {
|
|
18
|
+
const endpointId = config.endpoints.resolve(token);
|
|
19
|
+
if (!endpointId)
|
|
20
|
+
throw new EventEndpointNotFoundError();
|
|
23
21
|
const body = await readLimitedBody(request, maxBodyBytes);
|
|
24
22
|
const payload = parsePayload(body);
|
|
25
23
|
const headers = Object.fromEntries(request.headers.entries());
|
|
26
|
-
const target = await config.target(request);
|
|
27
24
|
const receivedAt = now();
|
|
28
|
-
const
|
|
29
|
-
|
|
25
|
+
const admission = await config.delivery.receiveEvent({
|
|
26
|
+
endpointId,
|
|
30
27
|
headers,
|
|
31
28
|
body,
|
|
32
29
|
payload,
|
|
30
|
+
deliveryKey: webhookDeliveryKey(headers, payload),
|
|
33
31
|
now: receivedAt,
|
|
34
32
|
});
|
|
35
|
-
const firstInput = runs[0]?.input;
|
|
36
|
-
const reference = firstInput?.kind === 'event'
|
|
37
|
-
? { source: firstInput.source, event: firstInput.event }
|
|
38
|
-
: eventReference(headers, payload);
|
|
39
33
|
const receipt = {
|
|
40
34
|
receivedAt: receivedAt.toISOString(),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
status: runs.length ? 'pending' : 'unmatched',
|
|
35
|
+
source: admission.source,
|
|
36
|
+
event: admission.event,
|
|
37
|
+
...(admission.run ? { runId: admission.run.id } : {}),
|
|
38
|
+
status: admission.run ? 'pending' : 'unmatched',
|
|
46
39
|
};
|
|
47
|
-
recent.push(receipt);
|
|
48
|
-
if (recent.length > recentLimit)
|
|
49
|
-
recent.splice(0, recent.length - recentLimit);
|
|
50
40
|
return json(202, { ok: true, accepted: true, ...receipt });
|
|
51
41
|
}
|
|
52
42
|
catch (error) {
|
|
43
|
+
if (error instanceof EventEndpointNotFoundError)
|
|
44
|
+
return json(404, { error: error.message });
|
|
53
45
|
if (error instanceof EventRejectedError)
|
|
54
46
|
return json(401, { error: error.message });
|
|
55
47
|
if (error instanceof EventHttpError)
|
|
56
48
|
return json(error.status, { error: error.message });
|
|
49
|
+
const status = Number(error?.status);
|
|
50
|
+
if (Number.isInteger(status) && status >= 400 && status <= 499) {
|
|
51
|
+
return json(status, { error: error instanceof Error ? error.message : String(error) });
|
|
52
|
+
}
|
|
57
53
|
return json(500, { error: error instanceof Error ? error.message : String(error) });
|
|
58
54
|
}
|
|
59
55
|
};
|
package/dist/src/executor.d.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Json } from './contract.js';
|
|
2
2
|
import type { ClaimedAutomationRun, MachineRuns } from './machine.js';
|
|
3
|
+
export { automationPlan } from './plan.js';
|
|
3
4
|
export interface AutomationActionPort {
|
|
4
5
|
call(input: Readonly<{
|
|
5
6
|
actionId: string;
|
|
6
7
|
payload: Json;
|
|
7
8
|
idempotencyKey: string;
|
|
9
|
+
signal: AbortSignal;
|
|
8
10
|
}>): Promise<Json>;
|
|
9
11
|
}
|
|
12
|
+
export interface AutomationRunExecutorOptions {
|
|
13
|
+
readonly now?: () => Date;
|
|
14
|
+
readonly heartbeatMs?: number;
|
|
15
|
+
readonly maxAttempts?: number;
|
|
16
|
+
readonly retryDelayMs?: (attempt: number) => number;
|
|
17
|
+
}
|
|
10
18
|
export declare class AutomationRunExecutor {
|
|
19
|
+
#private;
|
|
11
20
|
private readonly runs;
|
|
12
21
|
private readonly actions;
|
|
13
|
-
constructor(runs: MachineRuns, actions: AutomationActionPort);
|
|
22
|
+
constructor(runs: MachineRuns, actions: AutomationActionPort, options?: AutomationRunExecutorOptions);
|
|
14
23
|
execute(run: ClaimedAutomationRun): Promise<void>;
|
|
15
24
|
}
|
|
16
|
-
export declare function
|
|
25
|
+
export declare function transientFailure(error: unknown): boolean;
|