@amalgm/automations 0.3.1 → 0.4.0
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 +11 -0
- package/PURPOSE.md +18 -0
- package/README.md +34 -0
- package/dist/host/auth.js +4 -1
- package/dist/host/main.js +9 -1
- package/dist/host/notifications.d.ts +4 -0
- package/dist/host/notifications.js +19 -0
- package/dist/host/server.js +46 -7
- package/dist/src/cli/run.d.ts +1 -1
- package/dist/src/cli/run.js +12 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/machine-client.d.ts +3 -1
- package/dist/src/machine-client.js +6 -0
- package/dist/src/machine-event-stream.d.ts +8 -0
- package/dist/src/machine-event-stream.js +58 -0
- package/dist/src/machine-http.d.ts +2 -0
- package/dist/src/machine-http.js +8 -2
- package/dist/src/machine-notifications.d.ts +15 -0
- package/dist/src/machine-notifications.js +124 -0
- package/dist/src/machine.d.ts +1 -0
- package/dist/src/runner.d.ts +3 -1
- package/dist/src/runner.js +69 -46
- package/dist/src/supabase-machine.d.ts +1 -0
- package/dist/src/supabase-machine.js +3 -0
- package/package.json +2 -1
- package/skills/amalgm-automations/SKILL.md +148 -0
- package/skills/amalgm-automations/agents/openai.yaml +4 -0
- package/skills/amalgm-automations/references/command-contract.md +173 -0
- package/skills/amalgm-automations/references/setup-and-support.md +195 -0
- package/skills/amalgm-automations/references/workflow-plans.md +152 -0
- package/supabase/migrations/20260904020000_machine_run_notifications.sql +52 -0
- package/supabase/migrations/20260904030000_machine_notification_permissions.sql +4 -0
- package/skills/automations/SKILL.md +0 -141
package/dist/src/runner.js
CHANGED
|
@@ -1,61 +1,84 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
/** Listen first, then drain. There is no timer that asks an idle machine for work. */
|
|
1
3
|
export function createAutomationMachineRunner(options) {
|
|
2
4
|
const batchSize = integer(options.batchSize ?? 8, 1, 20, 'batchSize');
|
|
3
5
|
const leaseSeconds = integer(options.leaseSeconds ?? 60, 30, 900, 'leaseSeconds');
|
|
4
|
-
const
|
|
5
|
-
const maxBackoffMs = integer(options.maxBackoffMs ??
|
|
6
|
-
let
|
|
7
|
-
let
|
|
8
|
-
let
|
|
9
|
-
let
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (
|
|
6
|
+
const reconnectDelayMs = integer(options.reconnectDelayMs ?? 1_000, 1, 60_000, 'reconnectDelayMs');
|
|
7
|
+
const maxBackoffMs = integer(options.maxBackoffMs ?? 30_000, reconnectDelayMs, 300_000, 'maxBackoffMs');
|
|
8
|
+
let lifetime = null;
|
|
9
|
+
let listening = null;
|
|
10
|
+
let draining = null;
|
|
11
|
+
let pending = false;
|
|
12
|
+
const wake = (connection, signal) => {
|
|
13
|
+
pending = true;
|
|
14
|
+
if (draining)
|
|
13
15
|
return;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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) });
|
|
16
|
+
draining = (async () => {
|
|
17
|
+
while (pending && !signal.aborted) {
|
|
18
|
+
pending = false;
|
|
19
|
+
const runs = await options.runs.claim({ limit: batchSize, leaseSeconds });
|
|
20
|
+
if (runs.length)
|
|
21
|
+
options.log?.('automations.claimed', { count: runs.length });
|
|
22
|
+
const settled = await Promise.allSettled(runs.map(options.execute));
|
|
23
|
+
for (const result of settled) {
|
|
24
|
+
if (result.status === 'rejected')
|
|
25
|
+
options.log?.('automations.run.failed', { error: safe(result.reason) });
|
|
29
26
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
active = null;
|
|
41
|
-
schedule(nextDelay);
|
|
27
|
+
// Work may arrive while execution is busy, including a partial last batch.
|
|
28
|
+
pending ||= runs.length > 0;
|
|
29
|
+
}
|
|
30
|
+
})().catch((error) => {
|
|
31
|
+
options.log?.('automations.claim.failed', { error: safe(error) });
|
|
32
|
+
connection.abort(error);
|
|
33
|
+
}).finally(() => {
|
|
34
|
+
draining = null;
|
|
35
|
+
if (pending && !signal.aborted && !connection.signal.aborted)
|
|
36
|
+
wake(connection, signal);
|
|
42
37
|
});
|
|
43
|
-
|
|
38
|
+
};
|
|
39
|
+
const listen = async (signal) => {
|
|
40
|
+
let failures = 0;
|
|
41
|
+
while (!signal.aborted) {
|
|
42
|
+
const connection = new AbortController();
|
|
43
|
+
const connectedSignal = AbortSignal.any([signal, connection.signal]);
|
|
44
|
+
const startedAt = Date.now();
|
|
45
|
+
try {
|
|
46
|
+
for await (const _ of options.notifications(connectedSignal)) {
|
|
47
|
+
if (connectedSignal.aborted)
|
|
48
|
+
break;
|
|
49
|
+
wake(connection, signal);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (!signal.aborted)
|
|
54
|
+
options.log?.('automations.notifications.disconnected', { error: safe(error) });
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
connection.abort();
|
|
58
|
+
await draining;
|
|
59
|
+
}
|
|
60
|
+
if (signal.aborted)
|
|
61
|
+
break;
|
|
62
|
+
// A flapping stream must not reset its backoff merely by sending ready.
|
|
63
|
+
failures = Date.now() - startedAt >= 30_000 ? 0 : failures + 1;
|
|
64
|
+
const backoff = Math.min(maxBackoffMs, reconnectDelayMs * 2 ** Math.min(Math.max(0, failures - 1), 10));
|
|
65
|
+
await delay(backoff, undefined, { signal, ref: false }).catch(() => { });
|
|
66
|
+
}
|
|
44
67
|
};
|
|
45
68
|
return Object.freeze({
|
|
46
69
|
start() {
|
|
47
|
-
if (
|
|
70
|
+
if (lifetime)
|
|
48
71
|
return;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
72
|
+
lifetime = new AbortController();
|
|
73
|
+
pending = false;
|
|
74
|
+
listening = listen(lifetime.signal);
|
|
52
75
|
},
|
|
53
76
|
async close() {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
77
|
+
lifetime?.abort();
|
|
78
|
+
await listening;
|
|
79
|
+
await draining;
|
|
80
|
+
lifetime = null;
|
|
81
|
+
listening = null;
|
|
59
82
|
},
|
|
60
83
|
});
|
|
61
84
|
}
|
|
@@ -11,6 +11,7 @@ export declare class SupabaseMachineRunRepository implements MachineRunRepositor
|
|
|
11
11
|
#private;
|
|
12
12
|
private readonly client;
|
|
13
13
|
constructor(client: MachineRpcClient);
|
|
14
|
+
wakeDelay(userId: string, computerId: string): Promise<number | null>;
|
|
14
15
|
claim(input: Parameters<MachineRunRepository['claim']>[0]): Promise<ClaimedAutomationRun[]>;
|
|
15
16
|
update(input: Parameters<MachineRunRepository['update']>[0]): Promise<ClaimedAutomationRun | null>;
|
|
16
17
|
}
|
|
@@ -4,6 +4,9 @@ export class SupabaseMachineRunRepository {
|
|
|
4
4
|
constructor(client) {
|
|
5
5
|
this.client = client;
|
|
6
6
|
}
|
|
7
|
+
wakeDelay(userId, computerId) {
|
|
8
|
+
return this.#call('amalgm_machine_wake_delay', { p_user_id: userId, p_target_id: computerId });
|
|
9
|
+
}
|
|
7
10
|
async claim(input) {
|
|
8
11
|
const rows = await this.#call('claim_amalgm_machine_runs', {
|
|
9
12
|
p_user_id: input.userId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amalgm/automations",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Amalgm's automation SDK: durable trigger admission and target-machine execution.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"check": "tsx scripts/check-tree.ts && tsc -p tsconfig.json --noEmit",
|
|
50
50
|
"test": "tsx --test test/*.test.ts",
|
|
51
51
|
"test:supabase": "tsx --test test/integration/postgres.test.ts",
|
|
52
|
+
"test:notifications": "tsx scripts/test-notifications.ts",
|
|
52
53
|
"verify": "npm run check && npm test && npm run build && tsx scripts/check-cli-artifact.ts",
|
|
53
54
|
"release:check": "npm run verify && npm run test:supabase",
|
|
54
55
|
"prepack": "npm run build",
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: amalgm-automations
|
|
3
|
+
description: Set up, operate, and troubleshoot Amalgm Automations through MCP or the amalgm automations CLI. Use for installation, Google sign-in, scheduled or webhook workflows, Run Now, run history, and Automations support.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Amalgm Automations
|
|
7
|
+
|
|
8
|
+
Use this skill to turn a user's requested recurring, event-driven, or manual
|
|
9
|
+
workflow into one durable Amalgm automation and verify what actually ran.
|
|
10
|
+
Automations owns configuration and permanent run history; the selected Amalgm
|
|
11
|
+
machine executes the workflow.
|
|
12
|
+
|
|
13
|
+
Amalgm Automations is free. Guide the person through Google sign-in and computer
|
|
14
|
+
approval when needed; do not introduce a payment step. A native agent or other
|
|
15
|
+
external service still uses its own account and may have its own usage costs.
|
|
16
|
+
|
|
17
|
+
## Choose the available adapter
|
|
18
|
+
|
|
19
|
+
Prefer the six `amalgm_automations_*` MCP tools when they are callable. In a
|
|
20
|
+
native local agent without that MCP server, use the matching global CLI:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
MCP CLI
|
|
24
|
+
amalgm_automations_create amalgm automations create
|
|
25
|
+
amalgm_automations_list amalgm automations list
|
|
26
|
+
amalgm_automations_get amalgm automations get
|
|
27
|
+
amalgm_automations_update amalgm automations update
|
|
28
|
+
amalgm_automations_delete amalgm automations delete
|
|
29
|
+
amalgm_automations_run_now amalgm automations run-now
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Both adapters accept the same JSON object and call the same SDK command
|
|
33
|
+
surface. Do not translate one into a different resource-level API.
|
|
34
|
+
|
|
35
|
+
Reuse a working connection. For an available MCP connection, a small `list`
|
|
36
|
+
request with `{"limit":1}` proves access; do not install or log in again. For
|
|
37
|
+
CLI use, check `amalgm automations --help` and use
|
|
38
|
+
`amalgm automations list --input '{"limit":1}'`. An empty successful list is
|
|
39
|
+
ready, not a setup failure. Respect an explicitly selected account.
|
|
40
|
+
|
|
41
|
+
If the user asks to install or sign in, or that read fails, read
|
|
42
|
+
[setup and support](references/setup-and-support.md). It covers the supported
|
|
43
|
+
install, browser approval, account selection, runtime restart, and recovery
|
|
44
|
+
commands. Carry setup through to a successful Automations read, then resume
|
|
45
|
+
the original request. Do not stop at “Shell must be running.”
|
|
46
|
+
|
|
47
|
+
Shell owns authentication. Never read, copy, or ask the user to supply a runtime
|
|
48
|
+
token, DPoP proof, device key, or machine credential. The public connection
|
|
49
|
+
commands obtain what they need; environment-variable credential setup is not
|
|
50
|
+
the user login flow.
|
|
51
|
+
|
|
52
|
+
Read [references/command-contract.md](references/command-contract.md) whenever
|
|
53
|
+
composing `create` or `update`, or when exact fields are uncertain. Read
|
|
54
|
+
[references/workflow-plans.md](references/workflow-plans.md) whenever a
|
|
55
|
+
workflow runs an Amalgm action, native executable, code script, or trigger
|
|
56
|
+
input.
|
|
57
|
+
|
|
58
|
+
## Preserve scope and intent
|
|
59
|
+
|
|
60
|
+
- List or get before changing an existing automation. Identify the exact
|
|
61
|
+
automation and preserve fields the user did not ask to change.
|
|
62
|
+
- The CLI is global. Its invocation directory never scopes the automation.
|
|
63
|
+
Execution belongs to the selected target machine.
|
|
64
|
+
- Omit `targetId` in a machine-bound session. Never guess or copy an opaque
|
|
65
|
+
target id. If the principal has multiple targets, let the service require an
|
|
66
|
+
explicit user choice.
|
|
67
|
+
- A process `cwd`, when supplied, must be absolute. If omitted, the selected
|
|
68
|
+
machine uses its documented default (normally that user's OS home).
|
|
69
|
+
- Workflow `env` is persisted ordinary configuration, not a secret store.
|
|
70
|
+
Never place Shell credentials or reusable service secrets in it.
|
|
71
|
+
- Use `--stdin` for CLI payloads containing a webhook signing secret or
|
|
72
|
+
complex source. Do not expose those values in command arguments or logs.
|
|
73
|
+
- Delete only after the exact id is established and deletion is within the
|
|
74
|
+
user's request. Deleting configuration intentionally retains run history.
|
|
75
|
+
|
|
76
|
+
## Build one complete definition
|
|
77
|
+
|
|
78
|
+
Create the automation, its schedule/webhook triggers, and its optional workflow
|
|
79
|
+
in one task-level call. The service stages multi-resource changes safely and
|
|
80
|
+
enables the definition only after setup succeeds. If setup fails, report the
|
|
81
|
+
returned disabled draft id rather than silently creating a replacement.
|
|
82
|
+
|
|
83
|
+
Use a finite `maxOccurrences` when the user's request is bounded. Do not turn
|
|
84
|
+
"ten times" into an unbounded schedule plus a future cleanup promise.
|
|
85
|
+
|
|
86
|
+
Choose workflow lanes explicitly:
|
|
87
|
+
|
|
88
|
+
- An Amalgm tool or Amalgm agent is an action step. `chat.chat_agent_run` is
|
|
89
|
+
the Amalgm-agent path and requires a configured agent installation.
|
|
90
|
+
- A user's native Codex, Claude Code, OpenCode, or other installed program is
|
|
91
|
+
a command step. Arguments are literal argv; no shell is implied.
|
|
92
|
+
- Inline shell, Node.js, or Python source is a script step. Choose this lane
|
|
93
|
+
only when code or shell semantics are intentional.
|
|
94
|
+
|
|
95
|
+
Version 1 is action-only. Use version 2 for command or script steps and when
|
|
96
|
+
mixing lanes. Discover Amalgm action ids through the Tools product and native
|
|
97
|
+
executables through the selected machine; never guess either.
|
|
98
|
+
|
|
99
|
+
## Verify Run Now to completion
|
|
100
|
+
|
|
101
|
+
`run-now` performs durable manual admission. Its initial `pending` result is
|
|
102
|
+
not evidence that work completed.
|
|
103
|
+
|
|
104
|
+
1. Supply a stable, caller-chosen `idempotency_key` whenever the request might
|
|
105
|
+
be retried.
|
|
106
|
+
2. If admission is retried, send the same automation id, input, and key. The
|
|
107
|
+
returned run id must remain the same.
|
|
108
|
+
3. Poll `get` with `include_runs: true` or a `run_query` until the admitted run
|
|
109
|
+
becomes `completed` or `failed`. Match the exact run id; do not assume the
|
|
110
|
+
newest unrelated run is yours. Space out reads and back off while state is
|
|
111
|
+
unchanged; a rate-limit response is a reason to wait, not to submit again.
|
|
112
|
+
4. Inspect every step's status and bounded output. Report failure honestly,
|
|
113
|
+
including the failing step and service error.
|
|
114
|
+
|
|
115
|
+
`pending` means durable and awaiting its machine. `sent` or `running` means the
|
|
116
|
+
machine holds a lease. Only `completed` and `failed` are terminal.
|
|
117
|
+
|
|
118
|
+
Keep the user informed while waiting. If the machine stays unavailable or the
|
|
119
|
+
current session cannot keep waiting, report the exact run id and observed
|
|
120
|
+
nonterminal state; do not claim completion, create a replacement, or promise
|
|
121
|
+
background monitoring that has not been set up.
|
|
122
|
+
|
|
123
|
+
Diagnose at the failing boundary. Examples: `process_execution_unavailable`
|
|
124
|
+
means that machine lacks the process host; an executable-not-found error is a
|
|
125
|
+
machine PATH/install problem; `Installed agent has no model` is agent
|
|
126
|
+
configuration; an action-specific HTTP error belongs to that action service.
|
|
127
|
+
Do not relabel these as Run Now failures or patch the automation around them.
|
|
128
|
+
|
|
129
|
+
## Help when something goes wrong
|
|
130
|
+
|
|
131
|
+
Use [setup and support](references/setup-and-support.md) for login/runtime
|
|
132
|
+
failures, HTTP 429 or reported rate limits, and contacting support. Preserve
|
|
133
|
+
the actual error code, distinguish the failing service, and use bounded retries.
|
|
134
|
+
Do not invent minute/hour allowances or reset times that the service has not
|
|
135
|
+
reported.
|
|
136
|
+
|
|
137
|
+
For an unresolved issue, offer **Email Aayush at aayush@amalgm.ai** and prepare
|
|
138
|
+
a concise, redacted draft when helpful. Send only with the user's explicit
|
|
139
|
+
authorization and an available email tool; otherwise give them the draft.
|
|
140
|
+
|
|
141
|
+
## Report the outcome
|
|
142
|
+
|
|
143
|
+
Return the automation id, target choice if the user selected one, trigger
|
|
144
|
+
summary, workflow lanes, and—when execution was requested—the exact run id and
|
|
145
|
+
terminal result. Mention that run history is durable. Never claim a schedule
|
|
146
|
+
fired merely because configuration creation succeeded.
|
|
147
|
+
Include automation/run links when returned by the product; do not invent a
|
|
148
|
+
deep link or use a private webhook URL as a viewing link.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Command contract
|
|
2
|
+
|
|
3
|
+
Use the field names below exactly. MCP receives the object directly. CLI
|
|
4
|
+
receives it through `--input '<json>'`, `--file <path>`, or `--stdin` and emits
|
|
5
|
+
one JSON envelope:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{"result": {}}
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Failures use stderr, a nonzero exit, and:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{"error":{"code":"...","message":"...","status":400}}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Do not scrape prose or infer success from exit code alone; parse the envelope.
|
|
18
|
+
|
|
19
|
+
## create
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"id": "optional-readable-id",
|
|
24
|
+
"targetId": "optional-explicit-target",
|
|
25
|
+
"name": "Optional name",
|
|
26
|
+
"description": "Optional description",
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"schedules": [{
|
|
29
|
+
"id": "optional-trigger-id",
|
|
30
|
+
"cron": "0 9 * * 1-5",
|
|
31
|
+
"timezone": "America/Los_Angeles",
|
|
32
|
+
"enabled": true,
|
|
33
|
+
"maxOccurrences": 10
|
|
34
|
+
}],
|
|
35
|
+
"webhooks": [{
|
|
36
|
+
"id": "optional-trigger-id",
|
|
37
|
+
"source": "github",
|
|
38
|
+
"event": "push",
|
|
39
|
+
"secret": "optional-provider-secret-at-least-16-characters",
|
|
40
|
+
"enabled": true
|
|
41
|
+
}],
|
|
42
|
+
"workflow": {
|
|
43
|
+
"id": "optional-workflow-id",
|
|
44
|
+
"name": "Optional workflow name",
|
|
45
|
+
"script": "Human-readable summary of what the workflow does.",
|
|
46
|
+
"compiled": {
|
|
47
|
+
"version": 1,
|
|
48
|
+
"steps": [{
|
|
49
|
+
"id": "notify",
|
|
50
|
+
"actionId": "channels.notify_user",
|
|
51
|
+
"input": {"title":"Reminder","message":"Call Mom"}
|
|
52
|
+
}]
|
|
53
|
+
},
|
|
54
|
+
"allowlist": null,
|
|
55
|
+
"limits": null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
At least one executable step is required when `compiled` is supplied. Omit
|
|
61
|
+
`targetId` when Shell has bound the principal to one machine. The returned
|
|
62
|
+
webhook URL is the provider destination; a configured signing secret is
|
|
63
|
+
write-only on later reads.
|
|
64
|
+
|
|
65
|
+
CLI example:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
printf '%s\n' '{"name":"Weekday check","schedules":[{"cron":"0 9 * * 1-5","timezone":"America/Los_Angeles"}],"workflow":{"script":"Run the weekday check.","compiled":{"version":2,"steps":[{"id":"check","kind":"command","command":"my-check","cwd":"/absolute/project"}]}}}' \
|
|
69
|
+
| amalgm automations create --stdin
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## list
|
|
73
|
+
|
|
74
|
+
All fields are optional:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{"targetId":"machine-id","enabled":true,"limit":20,"offset":0}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use it to find candidates, then `get` the exact definition before mutation.
|
|
81
|
+
|
|
82
|
+
## get
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"automation_id": "automation-id",
|
|
87
|
+
"include_runs": true,
|
|
88
|
+
"run_query": {"status":"completed","limit":20,"offset":0}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`include_runs` and `run_query` are optional. `run_query.status` is one of
|
|
93
|
+
`pending`, `sent`, `running`, `completed`, or `failed`.
|
|
94
|
+
|
|
95
|
+
## update
|
|
96
|
+
|
|
97
|
+
Updates are grouped so one call can change metadata and owned resources:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"automation_id": "automation-id",
|
|
102
|
+
"patch": {
|
|
103
|
+
"targetId": "optional-new-target",
|
|
104
|
+
"name": "New name or null",
|
|
105
|
+
"description": "New description or null",
|
|
106
|
+
"enabled": true
|
|
107
|
+
},
|
|
108
|
+
"schedules": {
|
|
109
|
+
"create": [{"cron":"0 9 * * *","timezone":"UTC"}],
|
|
110
|
+
"update": [{"id":"schedule-id","patch":{"enabled":false}}],
|
|
111
|
+
"delete": ["old-schedule-id"]
|
|
112
|
+
},
|
|
113
|
+
"webhooks": {
|
|
114
|
+
"create": [{"source":"github","event":"push"}],
|
|
115
|
+
"update": [{"id":"webhook-id","patch":{"rotateUrl":true}}],
|
|
116
|
+
"delete": ["old-webhook-id"]
|
|
117
|
+
},
|
|
118
|
+
"workflow": {
|
|
119
|
+
"update": {
|
|
120
|
+
"name": "Updated workflow name",
|
|
121
|
+
"script": "Updated summary",
|
|
122
|
+
"compiled": {
|
|
123
|
+
"version": 2,
|
|
124
|
+
"steps": [{
|
|
125
|
+
"id": "check",
|
|
126
|
+
"kind": "command",
|
|
127
|
+
"command": "my-check",
|
|
128
|
+
"cwd": "/absolute/project"
|
|
129
|
+
}]
|
|
130
|
+
},
|
|
131
|
+
"allowlist": null,
|
|
132
|
+
"limits": null
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Every included patch must contain a real change. Workflow change is exactly one
|
|
139
|
+
of `{ "create": ... }`, `{ "update": ... }`, or `{ "delete": true }`.
|
|
140
|
+
Schedule and webhook changes may combine create/update/delete arrays. Preserve
|
|
141
|
+
resource ids returned by `get`; ids are scoped to their automation.
|
|
142
|
+
|
|
143
|
+
## delete
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{"automation_id":"automation-id"}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
This deletes current configuration, triggers, and workflow. It does not delete
|
|
150
|
+
historical runs.
|
|
151
|
+
|
|
152
|
+
## run-now
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"automation_id": "automation-id",
|
|
157
|
+
"input": {"requestedBy":"user"},
|
|
158
|
+
"idempotency_key": "stable-key-for-this-logical-request"
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`input` may be any JSON value. Admission requires an enabled automation with an
|
|
163
|
+
executable compiled workflow. The returned run starts as `pending`; follow it
|
|
164
|
+
with `get` until terminal. Reusing the same key for the same logical request
|
|
165
|
+
returns the same run rather than creating a duplicate.
|
|
166
|
+
|
|
167
|
+
## Webhook completion
|
|
168
|
+
|
|
169
|
+
Creating an Amalgm webhook trigger does not register it at GitHub or another
|
|
170
|
+
provider. Configure the returned URL in that provider separately, use JSON
|
|
171
|
+
content type, select only the requested events, and use the same optional
|
|
172
|
+
signing secret on both sides. Rotating the URL invalidates the old bearer
|
|
173
|
+
capability.
|