@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.
Files changed (44) hide show
  1. package/AXIOMS.md +17 -1
  2. package/PURPOSE.md +11 -1
  3. package/README.md +5 -2
  4. package/dist/host/main.js +15 -4
  5. package/dist/host/server.d.ts +2 -0
  6. package/dist/host/server.js +25 -7
  7. package/dist/src/automations.d.ts +4 -7
  8. package/dist/src/automations.js +13 -65
  9. package/dist/src/client.d.ts +1 -0
  10. package/dist/src/client.js +3 -2
  11. package/dist/src/contract.d.ts +2 -29
  12. package/dist/src/events-http.js +4 -0
  13. package/dist/src/executor.d.ts +12 -3
  14. package/dist/src/executor.js +163 -44
  15. package/dist/src/http.js +4 -0
  16. package/dist/src/index.d.ts +4 -2
  17. package/dist/src/index.js +4 -2
  18. package/dist/src/machine-client.d.ts +1 -0
  19. package/dist/src/machine-client.js +2 -0
  20. package/dist/src/machine.d.ts +2 -1
  21. package/dist/src/machine.js +6 -1
  22. package/dist/src/mcp.d.ts +3 -0
  23. package/dist/src/mcp.js +53 -50
  24. package/dist/src/plan.d.ts +3 -0
  25. package/dist/src/plan.js +48 -0
  26. package/dist/src/run-contract.d.ts +46 -0
  27. package/dist/src/run-contract.js +1 -0
  28. package/dist/src/run-journal.d.ts +8 -0
  29. package/dist/src/run-journal.js +56 -0
  30. package/dist/src/runner.d.ts +14 -0
  31. package/dist/src/runner.js +70 -0
  32. package/dist/src/schema.d.ts +6 -6
  33. package/dist/src/schema.js +17 -2
  34. package/dist/src/supabase-crud/mappers.js +2 -0
  35. package/dist/src/supabase-crud/rows.d.ts +2 -0
  36. package/dist/src/supabase-machine.js +2 -0
  37. package/dist/src/supabase-store.d.ts +1 -4
  38. package/dist/src/supabase-store.js +0 -24
  39. package/dist/src/tool-surface.d.ts +46 -0
  40. package/dist/src/tool-surface.js +125 -0
  41. package/dist/src/types.d.ts +1 -16
  42. package/package.json +3 -3
  43. package/skills/automations/SKILL.md +20 -15
  44. package/supabase/migrations/20260830010000_durable_step_retries.sql +332 -0
@@ -0,0 +1,56 @@
1
+ export function emptyJournal() {
2
+ return { version: 1, steps: [] };
3
+ }
4
+ export function runJournal(value, plan) {
5
+ if (value === undefined)
6
+ return emptyJournal();
7
+ if (!value || typeof value !== 'object' || Array.isArray(value))
8
+ throw new Error('Run step journal is invalid');
9
+ const source = value;
10
+ if (source.version !== 1 || !Array.isArray(source.steps))
11
+ throw new Error('Run step journal is invalid');
12
+ const planIds = new Set(plan.map(({ id }) => id));
13
+ const steps = source.steps.map(runStep);
14
+ const ids = new Set();
15
+ for (const step of steps) {
16
+ if (!planIds.has(step.id))
17
+ throw new Error(`Run step is not in the immutable plan: ${step.id}`);
18
+ if (ids.has(step.id))
19
+ throw new Error(`Run step journal has a duplicate id: ${step.id}`);
20
+ ids.add(step.id);
21
+ }
22
+ return { version: 1, steps };
23
+ }
24
+ export function completed(journal, stepId) {
25
+ return journal.steps.some((step) => step.id === stepId && step.status === 'completed');
26
+ }
27
+ export function stepAttempt(journal, stepId) {
28
+ return journal.steps.find((step) => step.id === stepId)?.attempts ?? 0;
29
+ }
30
+ export function putStep(journal, step) {
31
+ const index = journal.steps.findIndex(({ id }) => id === step.id);
32
+ const steps = [...journal.steps];
33
+ if (index === -1)
34
+ steps.push(step);
35
+ else
36
+ steps[index] = step;
37
+ return { version: 1, steps };
38
+ }
39
+ /** Journals are constructed from JSON fields only; this is the persistence boundary. */
40
+ export function journalJson(journal) {
41
+ return journal;
42
+ }
43
+ function runStep(value) {
44
+ if (!value || typeof value !== 'object' || Array.isArray(value))
45
+ throw new Error('Run step is invalid');
46
+ const item = value;
47
+ if (typeof item.id !== 'string' || !item.id)
48
+ throw new Error('Run step id is invalid');
49
+ if (item.status !== 'running' && item.status !== 'completed' && item.status !== 'failed') {
50
+ throw new Error(`Run step status is invalid: ${item.id}`);
51
+ }
52
+ if (!Number.isInteger(item.attempts) || Number(item.attempts) < 1 || typeof item.startedAt !== 'string') {
53
+ throw new Error(`Run step attempt is invalid: ${item.id}`);
54
+ }
55
+ return item;
56
+ }
@@ -0,0 +1,14 @@
1
+ import type { ClaimedAutomationRun, MachineRuns } from './machine.js';
2
+ export interface AutomationMachineRunnerOptions {
3
+ readonly runs: MachineRuns;
4
+ readonly execute: (run: ClaimedAutomationRun) => Promise<void>;
5
+ readonly batchSize?: number;
6
+ readonly leaseSeconds?: number;
7
+ readonly pollIntervalMs?: number;
8
+ readonly maxBackoffMs?: number;
9
+ readonly log?: (event: string, details?: Readonly<Record<string, unknown>>) => void;
10
+ }
11
+ export declare function createAutomationMachineRunner(options: AutomationMachineRunnerOptions): Readonly<{
12
+ start(): void;
13
+ close(): Promise<void>;
14
+ }>;
@@ -0,0 +1,70 @@
1
+ export function createAutomationMachineRunner(options) {
2
+ const batchSize = integer(options.batchSize ?? 8, 1, 20, 'batchSize');
3
+ const leaseSeconds = integer(options.leaseSeconds ?? 60, 30, 900, 'leaseSeconds');
4
+ const pollIntervalMs = integer(options.pollIntervalMs ?? 1_000, 1, 60_000, 'pollIntervalMs');
5
+ const maxBackoffMs = integer(options.maxBackoffMs ?? Math.max(30_000, pollIntervalMs), pollIntervalMs, 300_000, 'maxBackoffMs');
6
+ let active = null;
7
+ let timer = null;
8
+ let stopped = true;
9
+ let failures = 0;
10
+ let nextDelay = pollIntervalMs;
11
+ const schedule = (delay) => {
12
+ if (stopped)
13
+ return;
14
+ timer = setTimeout(drain, delay);
15
+ timer.unref?.();
16
+ };
17
+ const drain = () => {
18
+ if (stopped || active)
19
+ return active;
20
+ active = options.runs.claim({ limit: batchSize, leaseSeconds })
21
+ .then(async (runs) => {
22
+ failures = 0;
23
+ if (runs.length)
24
+ options.log?.('automations.claimed', { count: runs.length });
25
+ const settled = await Promise.allSettled(runs.map(options.execute));
26
+ settled.forEach((result) => {
27
+ if (result.status === 'rejected') {
28
+ options.log?.('automations.run.failed', { error: safe(result.reason) });
29
+ }
30
+ });
31
+ nextDelay = runs.length === batchSize ? 0 : pollIntervalMs;
32
+ })
33
+ .catch((error) => {
34
+ failures += 1;
35
+ const delayMs = Math.min(maxBackoffMs, pollIntervalMs * (2 ** Math.min(failures - 1, 10)));
36
+ options.log?.('automations.poll.failed', { error: safe(error), retryInMs: delayMs });
37
+ nextDelay = delayMs;
38
+ })
39
+ .finally(() => {
40
+ active = null;
41
+ schedule(nextDelay);
42
+ });
43
+ return active;
44
+ };
45
+ return Object.freeze({
46
+ start() {
47
+ if (!stopped)
48
+ return;
49
+ stopped = false;
50
+ failures = 0;
51
+ void drain();
52
+ },
53
+ async close() {
54
+ stopped = true;
55
+ if (timer)
56
+ clearTimeout(timer);
57
+ timer = null;
58
+ await active;
59
+ },
60
+ });
61
+ }
62
+ function integer(value, minimum, maximum, name) {
63
+ if (!Number.isInteger(value) || value < minimum || value > maximum) {
64
+ throw new Error(`${name} must be an integer from ${minimum} to ${maximum}`);
65
+ }
66
+ return value;
67
+ }
68
+ function safe(error) {
69
+ return (error instanceof Error ? error.message : String(error)).replace(/[\r\n]+/g, ' ').slice(0, 500);
70
+ }
@@ -173,15 +173,15 @@ export declare const createWorkflowSchema: z.ZodObject<{
173
173
  }, "strict", z.ZodTypeAny, {
174
174
  script: string;
175
175
  name?: string | undefined;
176
- id?: string | undefined;
177
176
  compiled?: Json | undefined;
177
+ id?: string | undefined;
178
178
  allowlist?: Json | undefined;
179
179
  limits?: Json | undefined;
180
180
  }, {
181
181
  script: string;
182
182
  name?: string | undefined;
183
- id?: string | undefined;
184
183
  compiled?: Json | undefined;
184
+ id?: string | undefined;
185
185
  allowlist?: Json | undefined;
186
186
  limits?: Json | undefined;
187
187
  }>;
@@ -193,26 +193,26 @@ export declare const updateWorkflowSchema: z.ZodEffects<z.ZodObject<{
193
193
  limits: z.ZodOptional<z.ZodNullable<z.ZodType<Json, z.ZodTypeDef, Json>>>;
194
194
  }, "strict", z.ZodTypeAny, {
195
195
  name?: string | null | undefined;
196
- script?: string | undefined;
197
196
  compiled?: Json | undefined;
197
+ script?: string | undefined;
198
198
  allowlist?: Json | undefined;
199
199
  limits?: Json | undefined;
200
200
  }, {
201
201
  name?: string | null | undefined;
202
- script?: string | undefined;
203
202
  compiled?: Json | undefined;
203
+ script?: string | undefined;
204
204
  allowlist?: Json | undefined;
205
205
  limits?: Json | undefined;
206
206
  }>, {
207
207
  name?: string | null | undefined;
208
- script?: string | undefined;
209
208
  compiled?: Json | undefined;
209
+ script?: string | undefined;
210
210
  allowlist?: Json | undefined;
211
211
  limits?: Json | undefined;
212
212
  }, {
213
213
  name?: string | null | undefined;
214
- script?: string | undefined;
215
214
  compiled?: Json | undefined;
215
+ script?: string | undefined;
216
216
  allowlist?: Json | undefined;
217
217
  limits?: Json | undefined;
218
218
  }>;
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod/v3';
2
2
  import { ValidationError } from './errors.js';
3
+ import { compiledAutomationPlan } from './plan.js';
3
4
  import { IDENTIFIER } from './validation.js';
4
5
  const maxPageSize = 100;
5
6
  const identifierMessage = 'Identifier is invalid';
@@ -102,10 +103,16 @@ export function parseUpdateWebhookTrigger(value) {
102
103
  return parse(updateWebhookTriggerSchema, value);
103
104
  }
104
105
  export function parseCreateWorkflow(value) {
105
- return parse(createWorkflowSchema, value);
106
+ const workflow = parse(createWorkflowSchema, value);
107
+ if (workflow.compiled !== undefined)
108
+ validateCompiledPlan(workflow.compiled);
109
+ return workflow;
106
110
  }
107
111
  export function parseUpdateWorkflow(value) {
108
- return parse(updateWorkflowSchema, value);
112
+ const workflow = parse(updateWorkflowSchema, value);
113
+ if (workflow.compiled !== undefined && workflow.compiled !== null)
114
+ validateCompiledPlan(workflow.compiled);
115
+ return workflow;
109
116
  }
110
117
  export function parsePageQuery(value) {
111
118
  return parse(pageQuerySchema, value);
@@ -119,3 +126,11 @@ export function parse(schema, value) {
119
126
  return result.data;
120
127
  throw new ValidationError(result.error.issues[0]?.message || 'Input is invalid');
121
128
  }
129
+ function validateCompiledPlan(value) {
130
+ try {
131
+ compiledAutomationPlan(value);
132
+ }
133
+ catch (error) {
134
+ throw new ValidationError(error instanceof Error ? error.message : String(error));
135
+ }
136
+ }
@@ -58,6 +58,8 @@ export function run(row) {
58
58
  workflowId: row.workflow_id,
59
59
  targetId: row.target_id,
60
60
  status: row.status,
61
+ attempts: row.attempts,
62
+ ...(row.retry_at ? { retryAt: timestamp(row.retry_at) } : {}),
61
63
  input: row.input,
62
64
  createdAt: timestamp(row.created_at),
63
65
  ...(row.sent_at ? { sentAt: timestamp(row.sent_at) } : {}),
@@ -42,6 +42,8 @@ export type RunRow = {
42
42
  workflow_id: string;
43
43
  target_id: string;
44
44
  status: AutomationRun['status'];
45
+ attempts: number;
46
+ retry_at: Date | string | null;
45
47
  input: AutomationRun['input'];
46
48
  created_at: Date | string;
47
49
  sent_at: Date | string | null;
@@ -43,6 +43,7 @@ function run(row) {
43
43
  input: row.input,
44
44
  status: row.status,
45
45
  attempts: row.attempts,
46
+ ...(row.retry_at ? { retryAt: timestamp(row.retry_at) } : {}),
46
47
  leaseToken: row.lease_token,
47
48
  leaseExpiresAt: timestamp(row.lease_expires_at),
48
49
  createdAt: timestamp(row.created_at),
@@ -56,6 +57,7 @@ function run(row) {
56
57
  function update(value) {
57
58
  return {
58
59
  status: value.status,
60
+ ...(value.retryAt === undefined ? {} : { retryAt: value.retryAt }),
59
61
  ...(value.output === undefined ? {} : { output: value.output }),
60
62
  ...(value.error === undefined ? {} : { error: value.error }),
61
63
  };
@@ -1,4 +1,4 @@
1
- import type { AutomationStore, AutomationTarget, AutomationRun, DueCronTrigger, RunInput, RunUpdate, StoredEventTrigger } from './types.js';
1
+ import type { AutomationStore, AutomationTarget, AutomationRun, DueCronTrigger, RunInput, StoredEventTrigger } from './types.js';
2
2
  export interface SupabaseRpcClient {
3
3
  rpc(functionName: string, arguments_: Record<string, unknown>): PromiseLike<{
4
4
  data: unknown;
@@ -17,7 +17,4 @@ export declare class SupabaseStore implements AutomationStore {
17
17
  kind: 'event';
18
18
  }>, now: Date): Promise<AutomationRun | null>;
19
19
  enqueueCron(trigger: DueCronTrigger, nextRunAt: string, now: Date): Promise<AutomationRun | null>;
20
- pendingRuns(target: AutomationTarget): Promise<AutomationRun[]>;
21
- markSent(runId: string, target: AutomationTarget, now: Date): Promise<boolean>;
22
- updateRun(target: AutomationTarget, runId: string, update: RunUpdate): Promise<AutomationRun | null>;
23
20
  }
@@ -61,30 +61,6 @@ export class SupabaseStore {
61
61
  scheduledFor: trigger.nextRunAt,
62
62
  }, now, nextRunAt);
63
63
  }
64
- async pendingRuns(target) {
65
- const rows = await this.#call('list_amalgm_pending_runs', {
66
- p_user_id: target.userId,
67
- p_target_id: target.targetId,
68
- });
69
- return rows.map(automationRun);
70
- }
71
- markSent(runId, target, now) {
72
- return this.#call('mark_amalgm_run_sent', {
73
- p_run_id: runId,
74
- p_user_id: target.userId,
75
- p_target_id: target.targetId,
76
- p_now: now.toISOString(),
77
- });
78
- }
79
- async updateRun(target, runId, update) {
80
- const rows = await this.#call('update_amalgm_run', {
81
- p_run_id: runId,
82
- p_user_id: target.userId,
83
- p_target_id: target.targetId,
84
- p_update: update,
85
- });
86
- return rows[0] ? automationRun(rows[0]) : null;
87
- }
88
64
  async #enqueue(trigger, input, now, nextRunAt) {
89
65
  const rows = await this.#call('enqueue_amalgm_run', {
90
66
  p_kind: trigger.kind,
@@ -0,0 +1,46 @@
1
+ import type { Automation, AutomationCrud, AutomationRun, CreateAutomation, CreateScheduleTrigger, CreateWebhookTrigger, CreateWorkflow, ListAutomations, ListRuns, Page, Trigger, UpdateAutomation, UpdateScheduleTrigger, UpdateWebhookTrigger, UpdateWorkflow, Workflow } from './contract.js';
2
+ export interface AutomationView {
3
+ automation: Automation;
4
+ triggers: Trigger[];
5
+ workflow: Workflow | null;
6
+ runs?: Page<AutomationRun>;
7
+ }
8
+ export interface CreateAutomationDefinition extends CreateAutomation {
9
+ schedules?: CreateScheduleTrigger[];
10
+ webhooks?: CreateWebhookTrigger[];
11
+ workflow?: CreateWorkflow;
12
+ }
13
+ interface Changes<Create extends {
14
+ id?: string;
15
+ }, Update> {
16
+ create?: Create[];
17
+ update?: Array<{
18
+ id: string;
19
+ patch: Update;
20
+ }>;
21
+ delete?: string[];
22
+ }
23
+ export type WorkflowChange = {
24
+ create: CreateWorkflow;
25
+ } | {
26
+ update: UpdateWorkflow;
27
+ } | {
28
+ delete: true;
29
+ };
30
+ export interface UpdateAutomationDefinition {
31
+ patch?: UpdateAutomation;
32
+ schedules?: Changes<CreateScheduleTrigger, UpdateScheduleTrigger>;
33
+ webhooks?: Changes<CreateWebhookTrigger, UpdateWebhookTrigger>;
34
+ workflow?: WorkflowChange;
35
+ }
36
+ export interface AutomationToolSurface {
37
+ create(input: CreateAutomationDefinition): Promise<AutomationView>;
38
+ list(query?: ListAutomations): Promise<Page<Automation>>;
39
+ get(automationId: string, runs?: false | ListRuns): Promise<AutomationView>;
40
+ update(automationId: string, input: UpdateAutomationDefinition): Promise<AutomationView>;
41
+ delete(automationId: string): Promise<{
42
+ deleted: string;
43
+ }>;
44
+ }
45
+ export declare function createAutomationToolSurface(sdk: AutomationCrud): AutomationToolSurface;
46
+ export {};
@@ -0,0 +1,125 @@
1
+ import { NotFoundError, ValidationError } from './errors.js';
2
+ export function createAutomationToolSurface(sdk) {
3
+ const view = async (automationId, runs = false) => {
4
+ const automation = await sdk.automations.get(automationId);
5
+ if (!automation)
6
+ throw new NotFoundError('Automation');
7
+ const [triggers, workflow, history] = await Promise.all([
8
+ sdk.triggers.list(automationId),
9
+ sdk.workflow.get(automationId),
10
+ runs === false ? undefined : sdk.runs.list(automationId, runs),
11
+ ]);
12
+ return { automation, triggers, workflow, ...(history ? { runs: history } : {}) };
13
+ };
14
+ const surface = {
15
+ async create(input) {
16
+ const { schedules = [], webhooks = [], workflow, ...automationInput } = input;
17
+ validateCreateIds(schedules, webhooks);
18
+ const staged = schedules.length > 0 || webhooks.length > 0 || workflow !== undefined;
19
+ const requestedEnabled = automationInput.enabled !== false;
20
+ const automation = await sdk.automations.create({
21
+ ...automationInput,
22
+ ...(staged ? { enabled: false } : {}),
23
+ });
24
+ try {
25
+ if (workflow)
26
+ await sdk.workflow.create(automation.id, workflow);
27
+ for (const schedule of schedules)
28
+ await sdk.triggers.schedule.create(automation.id, schedule);
29
+ for (const webhook of webhooks)
30
+ await sdk.triggers.webhook.create(automation.id, webhook);
31
+ if (staged && requestedEnabled)
32
+ await sdk.automations.update(automation.id, { enabled: true });
33
+ }
34
+ catch (error) {
35
+ throw disabledDraftError(automation.id, error);
36
+ }
37
+ return view(automation.id);
38
+ },
39
+ list: (query = {}) => sdk.automations.list(query),
40
+ get: view,
41
+ async update(automationId, input) {
42
+ requireChanges(input);
43
+ validateChanges(input.schedules, 'schedule');
44
+ validateChanges(input.webhooks, 'webhook');
45
+ const current = await sdk.automations.get(automationId);
46
+ if (!current)
47
+ throw new NotFoundError('Automation');
48
+ const changesResources = hasChanges(input.schedules) || hasChanges(input.webhooks) || Boolean(input.workflow);
49
+ const finalEnabled = input.patch?.enabled ?? current.enabled;
50
+ const { enabled: _enabled, ...metadata } = input.patch ?? {};
51
+ if (!changesResources) {
52
+ await sdk.automations.update(automationId, input.patch);
53
+ return view(automationId);
54
+ }
55
+ if (current.enabled)
56
+ await sdk.automations.update(automationId, { enabled: false });
57
+ try {
58
+ await applyTriggerChanges(automationId, input.schedules, sdk.triggers.schedule);
59
+ await applyTriggerChanges(automationId, input.webhooks, sdk.triggers.webhook);
60
+ await applyWorkflowChange(automationId, input.workflow, sdk.workflow);
61
+ await sdk.automations.update(automationId, { ...metadata, enabled: finalEnabled });
62
+ }
63
+ catch (error) {
64
+ throw disabledDraftError(automationId, error);
65
+ }
66
+ return view(automationId);
67
+ },
68
+ async delete(automationId) {
69
+ await sdk.automations.delete(automationId);
70
+ return { deleted: automationId };
71
+ },
72
+ };
73
+ return Object.freeze(surface);
74
+ }
75
+ async function applyTriggerChanges(automationId, changes, operations) {
76
+ if (!changes)
77
+ return;
78
+ for (const triggerId of changes.delete ?? [])
79
+ await operations.delete(automationId, triggerId);
80
+ for (const item of changes.update ?? [])
81
+ await operations.update(automationId, item.id, item.patch);
82
+ for (const input of changes.create ?? [])
83
+ await operations.create(automationId, input);
84
+ }
85
+ async function applyWorkflowChange(automationId, change, workflow) {
86
+ if (!change)
87
+ return;
88
+ if ('create' in change)
89
+ await workflow.create(automationId, change.create);
90
+ else if ('update' in change)
91
+ await workflow.update(automationId, change.update);
92
+ else
93
+ await workflow.delete(automationId);
94
+ }
95
+ function requireChanges(input) {
96
+ if (!input.patch && !hasChanges(input.schedules) && !hasChanges(input.webhooks) && !input.workflow) {
97
+ throw new ValidationError('Automation update must include a change');
98
+ }
99
+ }
100
+ function hasChanges(changes) {
101
+ return Boolean(changes && [changes.create, changes.update, changes.delete]
102
+ .some((items) => items && items.length > 0));
103
+ }
104
+ function validateChanges(changes, label) {
105
+ if (!changes)
106
+ return;
107
+ const ids = [
108
+ ...changes.create?.flatMap(({ id }) => id ? [id] : []) ?? [],
109
+ ...changes.update?.map(({ id }) => id) ?? [],
110
+ ...changes.delete ?? [],
111
+ ];
112
+ if (new Set(ids).size !== ids.length) {
113
+ throw new ValidationError(`The same ${label} trigger cannot be changed twice`);
114
+ }
115
+ }
116
+ function validateCreateIds(schedules, webhooks) {
117
+ const ids = [...schedules, ...webhooks].flatMap(({ id }) => id ? [id] : []);
118
+ if (new Set(ids).size !== ids.length) {
119
+ throw new ValidationError('Trigger ids must be unique within an automation');
120
+ }
121
+ }
122
+ function disabledDraftError(automationId, error) {
123
+ const reason = error instanceof Error ? error.message : String(error);
124
+ return new Error(`Automation ${automationId} remains disabled because configuration failed: ${reason}`);
125
+ }
@@ -51,13 +51,6 @@ export interface AutomationRun {
51
51
  output?: Json;
52
52
  error?: string;
53
53
  }
54
- export interface RunUpdate {
55
- status: 'running' | 'completed' | 'failed';
56
- startedAt?: string;
57
- finishedAt?: string;
58
- output?: Json;
59
- error?: string;
60
- }
61
54
  export interface AutomationStore {
62
55
  eventTriggers(target: AutomationTarget): Promise<StoredEventTrigger[]>;
63
56
  dueCronTriggers(now: Date): Promise<DueCronTrigger[]>;
@@ -65,13 +58,5 @@ export interface AutomationStore {
65
58
  kind: 'event';
66
59
  }>, now: Date): Promise<AutomationRun | null>;
67
60
  enqueueCron(trigger: DueCronTrigger, nextRunAt: string, now: Date): Promise<AutomationRun | null>;
68
- pendingRuns(target: AutomationTarget): Promise<AutomationRun[]>;
69
- markSent(runId: string, target: AutomationTarget, now: Date): Promise<boolean>;
70
- updateRun(target: AutomationTarget, runId: string, update: RunUpdate): Promise<AutomationRun | null>;
71
- }
72
- /** The complete transport capability Automations needs from Core. */
73
- export interface AutomationTransport {
74
- isOnline(target: AutomationTarget): boolean;
75
- send(run: AutomationRun): Promise<boolean>;
76
61
  }
77
- export type AutomationLog = (event: 'event.rejected' | 'run.pending' | 'drain.started' | 'run.sent' | 'run.failed', details: Readonly<Record<string, string>>) => void;
62
+ export type AutomationLog = (event: 'event.rejected' | 'run.pending', details: Readonly<Record<string, string>>) => void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@amalgm/automations",
3
- "version": "0.2.1",
4
- "description": "Amalgm's cloud automation SDK: Supabase-backed configuration, trigger admission, and run delivery.",
3
+ "version": "0.2.2",
4
+ "description": "Amalgm's automation SDK: durable trigger admission and target-machine execution.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
7
7
  "type": "git",
@@ -39,7 +39,7 @@
39
39
  "files": [
40
40
  "dist",
41
41
  "skills",
42
- "supabase",
42
+ "supabase/migrations",
43
43
  "AXIOMS.md",
44
44
  "PURPOSE.md",
45
45
  "README.md"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: automations
3
- description: Create, inspect, change, or delete Amalgm automations, schedules, workflows, and run history through the Automations MCP tools.
3
+ description: Create, inspect, change, or delete complete Amalgm automation definitions and inspect their run history through the Automations MCP tools.
4
4
  ---
5
5
 
6
6
  # Amalgm Automations
@@ -11,11 +11,8 @@ the agent adapter over the same hosted SDK used by the UI.
11
11
  ## Create a scheduled notification
12
12
 
13
13
  For a request such as “remind me to call my mom every minute for the next ten
14
- minutes,” create three resources for the current machine:
15
-
16
- 1. `amalgm_automations_create` with a clear name and the current machine target.
17
- 2. `amalgm_workflow_create` with a readable script summary and this compiled
18
- plan:
14
+ minutes,” make one `amalgm_automations_create` call containing the complete
15
+ definition. Include a readable workflow summary and this compiled plan:
19
16
 
20
17
  ```json
21
18
  {
@@ -30,19 +27,27 @@ minutes,” create three resources for the current machine:
30
27
  }
31
28
  ```
32
29
 
33
- 3. `amalgm_schedule_triggers_create` with cron `* * * * *`, the user's
34
- timezone, and `maxOccurrences: 10`.
30
+ Include one schedule with cron `* * * * *`, the user's timezone, and
31
+ `maxOccurrences: 10`. Omit `targetId` in a machine-bound session; never guess
32
+ an opaque target id.
35
33
 
36
- Create the automation disabled, attach its workflow and trigger, then enable it
37
- only after both exist. If setup fails, leave it disabled and explain which
38
- resource failed. Never replace a bounded occurrence count with an unbounded
39
- schedule plus a promise to clean it up later.
34
+ The tool stages multi-resource configuration disabled and enables it only after
35
+ setup succeeds. If setup fails, report the returned disabled draft id. Never
36
+ replace a bounded occurrence count with an unbounded schedule plus a promise to
37
+ clean it up later.
40
38
 
41
39
  ## Read and change
42
40
 
43
- Use list/get before changing an existing automation. Schedule and webhook
44
- triggers are separate resources. A workflow is zero-or-one per automation. Run
45
- history is read-only and remains after configuration deletion.
41
+ Use `amalgm_automations_list` or `amalgm_automations_get` before changing an
42
+ existing automation. Use `amalgm_automations_update` for grouped metadata,
43
+ schedule, webhook, or workflow changes, and `amalgm_automations_delete` only
44
+ after identifying the exact automation. Schedule and webhook triggers remain
45
+ distinct resources inside the definition. A workflow is zero-or-one per
46
+ automation. Run history is read-only, requested through `get`, and remains
47
+ after configuration deletion.
48
+
49
+ Action discovery belongs to the Tools product, not Automations. Manual
50
+ execution is not advertised until the delivery SDK supports it.
46
51
 
47
52
  `pending` means a run is durable and waiting for its selected machine. `sent`
48
53
  or `running` means that machine holds a lease. `completed` and `failed` are