@amalgm/automations 0.2.1 → 0.2.2
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 +17 -1
- package/PURPOSE.md +11 -1
- package/README.md +5 -2
- package/dist/host/main.js +15 -4
- package/dist/host/server.d.ts +2 -0
- package/dist/host/server.js +25 -7
- package/dist/src/automations.d.ts +4 -7
- package/dist/src/automations.js +13 -65
- package/dist/src/client.d.ts +1 -0
- package/dist/src/client.js +3 -2
- package/dist/src/contract.d.ts +2 -29
- package/dist/src/events-http.js +4 -0
- package/dist/src/executor.d.ts +12 -3
- package/dist/src/executor.js +163 -44
- package/dist/src/http.js +4 -0
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +4 -2
- 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 +53 -50
- package/dist/src/plan.d.ts +3 -0
- package/dist/src/plan.js +48 -0
- package/dist/src/run-contract.d.ts +46 -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 +6 -6
- package/dist/src/schema.js +17 -2
- package/dist/src/supabase-crud/mappers.js +2 -0
- package/dist/src/supabase-crud/rows.d.ts +2 -0
- package/dist/src/supabase-machine.js +2 -0
- package/dist/src/supabase-store.d.ts +1 -4
- package/dist/src/supabase-store.js +0 -24
- package/dist/src/tool-surface.d.ts +46 -0
- package/dist/src/tool-surface.js +125 -0
- package/dist/src/types.d.ts +1 -16
- package/package.json +3 -3
- package/skills/automations/SKILL.md +20 -15
- package/supabase/migrations/20260830010000_durable_step_retries.sql +332 -0
package/AXIOMS.md
CHANGED
|
@@ -49,8 +49,24 @@
|
|
|
49
49
|
DPoP request URL behind the Fly proxy.
|
|
50
50
|
20. Machine execution consumes the immutable workflow snapshot stored on the
|
|
51
51
|
run. It never rediscovers or silently updates the automation definition.
|
|
52
|
-
21. A compiled workflow is a small declarative sequence of tool actions. The
|
|
52
|
+
21. A compiled workflow is a small declarative sequence of one or more tool actions. The
|
|
53
53
|
executor receives tool calling as a host capability and never embeds a
|
|
54
54
|
Channels, Shell, CLI, or provider special case.
|
|
55
55
|
22. Automations is a standalone hosted service. Gateway owns none of its API,
|
|
56
56
|
scheduling, claim, execution, or persistence path.
|
|
57
|
+
23. The durable run ledger is the offline queue. The platform never keeps a
|
|
58
|
+
second online-machine delivery buffer and never drops an unclaimed run.
|
|
59
|
+
24. A run advances through its immutable plan in order. Every step transition
|
|
60
|
+
commits under the run's current lease before execution advances, and a
|
|
61
|
+
completed step is never intentionally invoked again.
|
|
62
|
+
25. A machine may begin an action only after the service confirms its current
|
|
63
|
+
lease. It renews that lease while the action is running and stops advancing
|
|
64
|
+
when renewal fails.
|
|
65
|
+
26. One run-step pair has one stable idempotency key. Step ids are unique in a
|
|
66
|
+
plan, and every action host receives that key and a cancellation signal.
|
|
67
|
+
27. Only transient transport failures retry. They release the same run with a
|
|
68
|
+
bounded future retry time and retain completed step output; invalid plans,
|
|
69
|
+
missing actions, authorization failures, and other deterministic errors
|
|
70
|
+
are terminal.
|
|
71
|
+
28. The selected target executes every action effect. The hosted service owns
|
|
72
|
+
only configuration, admission, leases, the step journal, and run history.
|
package/PURPOSE.md
CHANGED
|
@@ -23,7 +23,17 @@ The product has two composable halves over that one state:
|
|
|
23
23
|
write path. When a machine is offline, new runs remain pending; when it
|
|
24
24
|
reconnects, Shell claims pending runs directly from the Automations service
|
|
25
25
|
with its machine-bound DPoP identity and executes the persisted tool-action
|
|
26
|
-
plan. Execution itself stays on the selected machine.
|
|
26
|
+
plan. Execution itself stays on the selected machine. The platform never
|
|
27
|
+
needs to know whether a machine is online: an unclaimed run is the complete
|
|
28
|
+
offline queue.
|
|
29
|
+
|
|
30
|
+
One run is one immutable plan plus one durable ordered step journal. The
|
|
31
|
+
machine records a step as running before invoking its action and records its
|
|
32
|
+
output before advancing. Reconnect resumes at the first step that is not
|
|
33
|
+
already complete. A transient network failure releases the same run back to
|
|
34
|
+
the queue with a bounded future retry time; a configuration, authorization, or
|
|
35
|
+
action error fails it. Stable per-step idempotency keys make an uncertain
|
|
36
|
+
network acknowledgement safe to repeat.
|
|
27
37
|
|
|
28
38
|
Automations is its own hosted service and Fly machine. Its API and scheduler
|
|
29
39
|
share the same SDK and Supabase authority; neither is composed into, proxied by,
|
package/README.md
CHANGED
|
@@ -51,12 +51,15 @@ or webhook triggers and zero or one workflow. Supabase owns definitions,
|
|
|
51
51
|
schedule clocks, immutable run snapshots, machine leases, and permanent run
|
|
52
52
|
history. Webhook secrets are write-only.
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
Executable workflows use one small, non-empty format:
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
57
|
type AutomationPlan = {
|
|
58
58
|
version: 1;
|
|
59
|
-
steps:
|
|
59
|
+
steps: [
|
|
60
|
+
{ id: string; actionId: string; input: Json },
|
|
61
|
+
...Array<{ id: string; actionId: string; input: Json }>,
|
|
62
|
+
];
|
|
60
63
|
};
|
|
61
64
|
```
|
|
62
65
|
|
package/dist/host/main.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
2
2
|
import { Automations } from '../src/automations.js';
|
|
3
3
|
import { AutomationCrudService } from '../src/crud.js';
|
|
4
4
|
import { createAutomationApi } from '../src/http.js';
|
|
5
|
+
import { createAutomationEventsApi } from '../src/events-http.js';
|
|
5
6
|
import { createMachineRuns } from '../src/machine.js';
|
|
6
7
|
import { createMachineRunsApi } from '../src/machine-http.js';
|
|
7
8
|
import { SupabaseAutomationCrudRepository } from '../src/supabase-crud.js';
|
|
@@ -18,10 +19,7 @@ const authentication = createAutomationsAuthenticators({
|
|
|
18
19
|
issuer: config.authorizationIssuer,
|
|
19
20
|
supabase,
|
|
20
21
|
});
|
|
21
|
-
const delivery = new Automations(new SupabaseStore(supabase),
|
|
22
|
-
isOnline: () => false,
|
|
23
|
-
send: async () => false,
|
|
24
|
-
}, (event, details) => log(event, details));
|
|
22
|
+
const delivery = new Automations(new SupabaseStore(supabase), (event, details) => log(event, details));
|
|
25
23
|
const controlApi = createAutomationApi({
|
|
26
24
|
service: new AutomationCrudService(new SupabaseAutomationCrudRepository(supabase)),
|
|
27
25
|
authenticate: authentication.control,
|
|
@@ -31,10 +29,15 @@ const machineApi = createMachineRunsApi({
|
|
|
31
29
|
authenticate: authentication.machine,
|
|
32
30
|
runsFor: (principal) => createMachineRuns(machineRepository, principal),
|
|
33
31
|
});
|
|
32
|
+
const eventsApi = createAutomationEventsApi({
|
|
33
|
+
delivery,
|
|
34
|
+
target: async (request) => eventTarget(request),
|
|
35
|
+
});
|
|
34
36
|
const host = createAutomationsHost({
|
|
35
37
|
publicOrigin: config.publicOrigin,
|
|
36
38
|
controlApi,
|
|
37
39
|
machineApi,
|
|
40
|
+
eventsApi,
|
|
38
41
|
fireSchedules: () => delivery.fireDueCrons(),
|
|
39
42
|
schedulerIntervalMs: config.schedulerIntervalMs,
|
|
40
43
|
log,
|
|
@@ -46,3 +49,11 @@ for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
|
46
49
|
function log(event, details = {}) {
|
|
47
50
|
console.log(JSON.stringify({ service: 'amalgm-automations', event, ...details }));
|
|
48
51
|
}
|
|
52
|
+
function eventTarget(request) {
|
|
53
|
+
const userId = request.headers.get('x-amalgm-user-id')?.trim() ?? '';
|
|
54
|
+
const targetId = request.headers.get('x-amalgm-target-id')?.trim() ?? '';
|
|
55
|
+
if (!/^[0-9a-f]{8}-[0-9a-f-]{27}$/i.test(userId) || !targetId) {
|
|
56
|
+
throw Object.assign(new Error('Webhook routing headers are required'), { status: 400 });
|
|
57
|
+
}
|
|
58
|
+
return { userId, targetId };
|
|
59
|
+
}
|
package/dist/host/server.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ export declare function createAutomationsHost(options: {
|
|
|
3
3
|
readonly publicOrigin: string;
|
|
4
4
|
readonly controlApi: (request: Request) => Promise<Response>;
|
|
5
5
|
readonly machineApi: (request: Request) => Promise<Response>;
|
|
6
|
+
readonly eventsApi?: (request: Request) => Promise<Response>;
|
|
6
7
|
readonly fireSchedules: () => Promise<unknown>;
|
|
7
8
|
readonly schedulerIntervalMs: number;
|
|
9
|
+
readonly maxRequestBodyBytes?: number;
|
|
8
10
|
readonly log?: (event: string, details?: Readonly<Record<string, unknown>>) => void;
|
|
9
11
|
}): {
|
|
10
12
|
server: import("node:http").Server<typeof IncomingMessage, typeof ServerResponse>;
|
package/dist/host/server.js
CHANGED
|
@@ -17,14 +17,19 @@ export function createAutomationsHost(options) {
|
|
|
17
17
|
try {
|
|
18
18
|
if (incoming.url === '/healthz')
|
|
19
19
|
return send(outgoing, Response.json({ ok: true }));
|
|
20
|
-
const request = await webRequest(incoming, options.publicOrigin);
|
|
21
|
-
const
|
|
22
|
-
|
|
20
|
+
const request = await webRequest(incoming, options.publicOrigin, options.maxRequestBodyBytes ?? 2 * 1024 * 1024);
|
|
21
|
+
const pathname = new URL(request.url).pathname;
|
|
22
|
+
const api = pathname === '/events' && options.eventsApi
|
|
23
|
+
? options.eventsApi
|
|
24
|
+
: pathname.startsWith('/v1/machine/') ? options.machineApi : options.controlApi;
|
|
23
25
|
await send(outgoing, await api(request));
|
|
24
26
|
}
|
|
25
27
|
catch (error) {
|
|
26
28
|
log('request.failed', { error: safe(error) });
|
|
27
|
-
|
|
29
|
+
const status = error instanceof HostRequestError ? error.status : 500;
|
|
30
|
+
await send(outgoing, Response.json({
|
|
31
|
+
error: status === 500 ? 'Automations service failed' : error instanceof Error ? error.message : String(error),
|
|
32
|
+
}, { status }));
|
|
28
33
|
}
|
|
29
34
|
});
|
|
30
35
|
return {
|
|
@@ -36,10 +41,16 @@ export function createAutomationsHost(options) {
|
|
|
36
41
|
},
|
|
37
42
|
};
|
|
38
43
|
}
|
|
39
|
-
async function webRequest(request, publicOrigin) {
|
|
44
|
+
async function webRequest(request, publicOrigin, maximumBodyBytes) {
|
|
40
45
|
const chunks = [];
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
let size = 0;
|
|
47
|
+
for await (const value of request) {
|
|
48
|
+
const chunk = Buffer.from(value);
|
|
49
|
+
size += chunk.length;
|
|
50
|
+
if (size > maximumBodyBytes)
|
|
51
|
+
throw new HostRequestError(413, 'Request body is too large');
|
|
52
|
+
chunks.push(chunk);
|
|
53
|
+
}
|
|
43
54
|
const body = Buffer.concat(chunks);
|
|
44
55
|
return new Request(publicResourceRequestUrl(publicOrigin, request.url ?? '/'), {
|
|
45
56
|
method: request.method ?? 'GET',
|
|
@@ -47,6 +58,13 @@ async function webRequest(request, publicOrigin) {
|
|
|
47
58
|
...(body.length ? { body } : {}),
|
|
48
59
|
});
|
|
49
60
|
}
|
|
61
|
+
class HostRequestError extends Error {
|
|
62
|
+
status;
|
|
63
|
+
constructor(status, message) {
|
|
64
|
+
super(message);
|
|
65
|
+
this.status = status;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
50
68
|
async function send(response, source) {
|
|
51
69
|
response.writeHead(source.status, Object.fromEntries(source.headers));
|
|
52
70
|
response.end(Buffer.from(await source.arrayBuffer()));
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { AutomationLog, AutomationRun, AutomationStore, AutomationTarget,
|
|
1
|
+
import type { AutomationLog, AutomationRun, AutomationStore, AutomationTarget, Json } from './types.js';
|
|
2
2
|
export declare class EventRejectedError extends Error {
|
|
3
3
|
constructor();
|
|
4
4
|
}
|
|
5
|
+
/** Admits trigger occurrences to the durable run ledger. It never delivers them. */
|
|
5
6
|
export declare class Automations {
|
|
6
7
|
#private;
|
|
7
8
|
readonly store: AutomationStore;
|
|
8
|
-
readonly transport: AutomationTransport;
|
|
9
9
|
readonly log: AutomationLog;
|
|
10
|
-
constructor(store: AutomationStore,
|
|
10
|
+
constructor(store: AutomationStore, log?: AutomationLog);
|
|
11
11
|
receiveEvent(input: {
|
|
12
12
|
target: AutomationTarget;
|
|
13
13
|
headers: Record<string, string>;
|
|
@@ -17,8 +17,5 @@ export declare class Automations {
|
|
|
17
17
|
event?: string;
|
|
18
18
|
now?: Date;
|
|
19
19
|
}): Promise<AutomationRun[]>;
|
|
20
|
-
fireDueCrons(now?: Date): Promise<AutomationRun[]>;
|
|
21
|
-
targetOnline(target: AutomationTarget): Promise<number>;
|
|
22
|
-
updateRun(target: AutomationTarget, runId: string, update: RunUpdate): Promise<AutomationRun | null>;
|
|
23
|
-
drain(target: AutomationTarget): Promise<number>;
|
|
20
|
+
fireDueCrons(now?: Date, maximumRuns?: number): Promise<AutomationRun[]>;
|
|
24
21
|
}
|
package/dist/src/automations.js
CHANGED
|
@@ -7,14 +7,12 @@ export class EventRejectedError extends Error {
|
|
|
7
7
|
this.name = 'EventRejectedError';
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
/** Admits trigger occurrences to the durable run ledger. It never delivers them. */
|
|
10
11
|
export class Automations {
|
|
11
12
|
store;
|
|
12
|
-
transport;
|
|
13
13
|
log;
|
|
14
|
-
|
|
15
|
-
constructor(store, transport, log = noop) {
|
|
14
|
+
constructor(store, log = noop) {
|
|
16
15
|
this.store = store;
|
|
17
|
-
this.transport = transport;
|
|
18
16
|
this.log = log;
|
|
19
17
|
}
|
|
20
18
|
async receiveEvent(input) {
|
|
@@ -39,15 +37,19 @@ export class Automations {
|
|
|
39
37
|
const now = input.now || new Date();
|
|
40
38
|
const runInput = { kind: 'event', ...reference, payload: input.payload };
|
|
41
39
|
const runs = (await Promise.all(triggers.map((trigger) => (this.store.enqueueEvent(trigger, runInput, now))))).filter((run) => run !== null);
|
|
42
|
-
|
|
40
|
+
this.#logPending(runs);
|
|
43
41
|
return runs;
|
|
44
42
|
}
|
|
45
|
-
async fireDueCrons(now = new Date()) {
|
|
43
|
+
async fireDueCrons(now = new Date(), maximumRuns = 1_000) {
|
|
44
|
+
if (!Number.isInteger(maximumRuns) || maximumRuns < 1)
|
|
45
|
+
throw new Error('maximumRuns must be a positive integer');
|
|
46
46
|
const created = [];
|
|
47
47
|
for (const trigger of await this.store.dueCronTriggers(now)) {
|
|
48
48
|
let scheduledFor = trigger.nextRunAt;
|
|
49
49
|
let remaining = trigger.remainingOccurrences;
|
|
50
|
-
while (
|
|
50
|
+
while (created.length < maximumRuns
|
|
51
|
+
&& new Date(scheduledFor) <= now
|
|
52
|
+
&& (remaining === undefined || remaining > 0)) {
|
|
51
53
|
const nextRunAt = nextCronAt(trigger.cron, trigger.timezone, scheduledFor);
|
|
52
54
|
const run = await this.store.enqueueCron({ ...trigger, nextRunAt: scheduledFor }, nextRunAt, now);
|
|
53
55
|
if (!run)
|
|
@@ -57,31 +59,13 @@ export class Automations {
|
|
|
57
59
|
if (remaining !== undefined)
|
|
58
60
|
remaining -= 1;
|
|
59
61
|
}
|
|
62
|
+
if (created.length === maximumRuns)
|
|
63
|
+
break;
|
|
60
64
|
}
|
|
61
|
-
|
|
65
|
+
this.#logPending(created);
|
|
62
66
|
return created;
|
|
63
67
|
}
|
|
64
|
-
|
|
65
|
-
return this.drain(target);
|
|
66
|
-
}
|
|
67
|
-
updateRun(target, runId, update) {
|
|
68
|
-
if (update.output !== undefined)
|
|
69
|
-
assertJson(update.output, 'Run output');
|
|
70
|
-
return this.store.updateRun(target, runId, update);
|
|
71
|
-
}
|
|
72
|
-
drain(target) {
|
|
73
|
-
const key = `${target.userId}:${target.targetId}`;
|
|
74
|
-
const active = this.#drains.get(key);
|
|
75
|
-
if (active)
|
|
76
|
-
return active;
|
|
77
|
-
const drain = this.#drain(target).finally(() => {
|
|
78
|
-
if (this.#drains.get(key) === drain)
|
|
79
|
-
this.#drains.delete(key);
|
|
80
|
-
});
|
|
81
|
-
this.#drains.set(key, drain);
|
|
82
|
-
return drain;
|
|
83
|
-
}
|
|
84
|
-
async #drainCreated(runs) {
|
|
68
|
+
#logPending(runs) {
|
|
85
69
|
for (const run of runs) {
|
|
86
70
|
this.log('run.pending', {
|
|
87
71
|
runId: run.id,
|
|
@@ -89,42 +73,6 @@ export class Automations {
|
|
|
89
73
|
automationId: run.automationId,
|
|
90
74
|
});
|
|
91
75
|
}
|
|
92
|
-
const targets = new Map(runs.map((run) => [
|
|
93
|
-
`${run.userId}:${run.targetId}`,
|
|
94
|
-
{ userId: run.userId, targetId: run.targetId },
|
|
95
|
-
]));
|
|
96
|
-
await Promise.all([...targets.values()].map((target) => this.drain(target)));
|
|
97
|
-
}
|
|
98
|
-
async #drain(target) {
|
|
99
|
-
if (!this.transport.isOnline(target))
|
|
100
|
-
return 0;
|
|
101
|
-
this.log('drain.started', { targetId: target.targetId });
|
|
102
|
-
let sent = 0;
|
|
103
|
-
const failed = new Set();
|
|
104
|
-
while (this.transport.isOnline(target)) {
|
|
105
|
-
const runs = (await this.store.pendingRuns(target)).filter(({ id }) => !failed.has(id));
|
|
106
|
-
if (runs.length === 0)
|
|
107
|
-
return sent;
|
|
108
|
-
for (const run of runs) {
|
|
109
|
-
if (!this.transport.isOnline(target))
|
|
110
|
-
return sent;
|
|
111
|
-
try {
|
|
112
|
-
if (!await this.transport.send(run)) {
|
|
113
|
-
this.log('run.failed', { runId: run.id, targetId: target.targetId });
|
|
114
|
-
failed.add(run.id);
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
if (await this.store.markSent(run.id, target, new Date()))
|
|
118
|
-
sent += 1;
|
|
119
|
-
this.log('run.sent', { runId: run.id, targetId: target.targetId });
|
|
120
|
-
}
|
|
121
|
-
catch {
|
|
122
|
-
this.log('run.failed', { runId: run.id, targetId: target.targetId });
|
|
123
|
-
failed.add(run.id);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return sent;
|
|
128
76
|
}
|
|
129
77
|
}
|
|
130
78
|
function assertJson(value, label) {
|
package/dist/src/client.d.ts
CHANGED
package/dist/src/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AutomationError } from './errors.js';
|
|
2
2
|
export function createAutomationClient(options) {
|
|
3
3
|
const baseUrl = options.baseUrl.replace(/\/$/, '');
|
|
4
|
-
const request = createRequester(baseUrl, options.authorization, options.headers, options.fetch || globalThis.fetch);
|
|
4
|
+
const request = createRequester(baseUrl, options.authorization, options.headers, options.fetch || globalThis.fetch, options.requestTimeoutMs ?? 15_000);
|
|
5
5
|
return {
|
|
6
6
|
automations: {
|
|
7
7
|
create: (input) => request('/v1/automations', 'POST', input),
|
|
@@ -39,7 +39,7 @@ export function createAutomationClient(options) {
|
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
function createRequester(baseUrl, authorization, additionalHeaders, fetch) {
|
|
42
|
+
function createRequester(baseUrl, authorization, additionalHeaders, fetch, requestTimeoutMs) {
|
|
43
43
|
return async (path, method, body, nullable = false) => {
|
|
44
44
|
const url = `${baseUrl}${path}`;
|
|
45
45
|
const token = await authorization?.();
|
|
@@ -52,6 +52,7 @@ function createRequester(baseUrl, authorization, additionalHeaders, fetch) {
|
|
|
52
52
|
...await additionalHeaders?.(method, url),
|
|
53
53
|
},
|
|
54
54
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
55
|
+
signal: AbortSignal.timeout(requestTimeoutMs),
|
|
55
56
|
});
|
|
56
57
|
if (response.status === 204)
|
|
57
58
|
return undefined;
|
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 } from './run-contract.js';
|
|
5
|
+
export type { AutomationPlan, AutomationRun, AutomationRunJournal, AutomationStepRun, AutomationStepStatus, ListRuns, 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 {
|
|
@@ -84,15 +86,6 @@ export interface UpdateScheduleTrigger {
|
|
|
84
86
|
/** Set a new finite budget, or null to make the schedule unbounded. */
|
|
85
87
|
maxOccurrences?: number | null;
|
|
86
88
|
}
|
|
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
89
|
export interface CreateWebhookTrigger {
|
|
97
90
|
id?: string;
|
|
98
91
|
source?: string;
|
|
@@ -132,25 +125,6 @@ export interface UpdateWorkflow {
|
|
|
132
125
|
allowlist?: Json | null;
|
|
133
126
|
limits?: Json | null;
|
|
134
127
|
}
|
|
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
128
|
export interface AutomationCrud {
|
|
155
129
|
readonly automations: {
|
|
156
130
|
create(input: CreateAutomation): Promise<Automation>;
|
|
@@ -190,4 +164,3 @@ export interface AutomationCrud {
|
|
|
190
164
|
export interface AutomationCrudService {
|
|
191
165
|
for(principal: AutomationPrincipal): AutomationCrud;
|
|
192
166
|
}
|
|
193
|
-
export {};
|
package/dist/src/events-http.js
CHANGED
|
@@ -54,6 +54,10 @@ export function createAutomationEventsApi(config) {
|
|
|
54
54
|
return json(401, { error: error.message });
|
|
55
55
|
if (error instanceof EventHttpError)
|
|
56
56
|
return json(error.status, { error: error.message });
|
|
57
|
+
const status = Number(error?.status);
|
|
58
|
+
if (Number.isInteger(status) && status >= 400 && status <= 499) {
|
|
59
|
+
return json(status, { error: error instanceof Error ? error.message : String(error) });
|
|
60
|
+
}
|
|
57
61
|
return json(500, { error: error instanceof Error ? error.message : String(error) });
|
|
58
62
|
}
|
|
59
63
|
};
|
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;
|