aegis-arun 0.0.0-stage → 0.2.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/README.md +61 -3
- package/bin/aegis.mjs +42 -0
- package/package.json +18 -6
- package/relay/README.md +177 -0
- package/relay/local/README.md +47 -0
- package/relay/local/compose.yaml +132 -0
- package/relay/local/local-remote.ps1 +364 -0
- package/relay/local/make-nats-certs.ps1 +46 -0
- package/relay/local/nats-server.conf +44 -0
- package/scripts/install.mjs +54 -0
- package/scripts/platform.mjs +27 -0
- package/scripts/whatsapp-cli.mjs +71 -0
- package/scripts/windows-host-install.mjs +60 -0
- package/scripts/windows-host.mjs +246 -0
- package/scripts/windows-host.ps1 +230 -0
- package/vendor/x86_64-pc-windows-msvc/aegis-relay.exe +0 -0
- package/vendor/x86_64-pc-windows-msvc/arun.exe +0 -0
package/README.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Aegis — Terminal Agent Runtime
|
|
2
|
+
|
|
3
|
+
Aegis (`aegis`, also `arun`) is a Rust terminal agent runtime. It connects to supported model providers, runs tools through permission-filtered capabilities, and records task progress and evidence in a durable SQLite event log. You can follow work in the terminal, steer an active task, and inspect its results without handing control to a provider CLI.
|
|
4
|
+
|
|
5
|
+
## Get started
|
|
6
|
+
|
|
7
|
+
Launch `aegis` or `arun` with no arguments to open the interactive terminal. Choose a provider, sign in or configure a compatible custom endpoint, select a model, review workspace access, and describe the task. Aegis creates and follows the task for you; you do not need to enter run or attach commands.
|
|
8
|
+
|
|
9
|
+
ChatGPT and Grok use Aegis-owned direct HTTP flows. Browser approval is the recommended desktop sign-in flow; a device-code option is available for remote or headless terminals. Custom endpoints use an OpenAI-compatible interface. Claude subscription sign-in is pending. See [provider status and evidence](docs/direct-providers.md) and [browser sign-in boundaries](docs/browser-signin.md).
|
|
10
|
+
|
|
11
|
+
For a source build and advanced CLI use:
|
|
12
|
+
|
|
13
|
+
```powershell
|
|
14
|
+
cargo build --release
|
|
15
|
+
target/release/arun login chatgpt
|
|
16
|
+
target/release/arun run "Inspect this repository" --provider chatgpt
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The npm package `aegis-arun@0.2.0` is published on npm. Install it globally and launch either command:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install --global aegis-arun
|
|
23
|
+
aegis
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The package supports Windows x64 and Linux x64/arm64; macOS is unsupported. Published packages download a platform-specific runtime. Local native builds require Node 20+ and Rust. See [npm publishing](docs/npm-publishing.md) for package and release details.
|
|
27
|
+
|
|
28
|
+
## Working in the terminal
|
|
29
|
+
|
|
30
|
+
The interactive interface keeps ordinary terminal scrollback rather than taking over an alternate screen. It shows readable tool activity and results, supports steering an active task from the composer, and provides searchable menus and keyboard controls. Use `/` for the command catalog; F1 explains keyboard controls. Set `NO_COLOR=1` to disable colors or `AEGIS_REDUCED_MOTION=1` to reduce animation. See [terminal behavior and verification](docs/terminal-ui.md).
|
|
31
|
+
|
|
32
|
+
Aegis asks when intent or a decision is unclear. An unanswered question blocks dependent work, while independent work can continue. Ctrl+C interrupts active work; Ctrl+D detaches from a task. F3 opens saved tasks and recovery choices, and F5 starts a fresh conversation without deleting task history. Advanced users can inspect tasks with commands such as `arun status <run-id>`, `arun replay <run-id>`, `arun artifacts <run-id>`, and `arun metrics <run-id>`.
|
|
33
|
+
|
|
34
|
+
## Permissions and evidence
|
|
35
|
+
|
|
36
|
+
Workspace reads and search are available by default. Writes require permission; command execution is disabled unless a specific program and locally available container image are granted. Process workers use a network-disabled container with a read-only root filesystem and resource limits. Images are not pulled implicitly during task execution. Trusted-host MCP servers are an explicit exception and are not sandboxed. See [runtime contracts](docs/contracts.md) for details.
|
|
37
|
+
|
|
38
|
+
Task completion is not established by a model's claim alone. Successful operations produce evidence artifacts; explicit user requirements are tracked separately and need current-revision evidence. Interrupted operations with uncertain external effects are paused for reconciliation rather than blindly replayed. Independent completion checks can be configured, but their availability and scope should be reviewed for each project. See [task controls and proof boundaries](docs/control-surface.md).
|
|
39
|
+
|
|
40
|
+
Aegis records provider-reported usage when available and labels missing or estimated measurements rather than treating them as exact. Deadlines and resource limits still apply; there is no task-level model-turn or token cap. Metrics are measurements, not a guarantee of provider billing accuracy or task efficiency.
|
|
41
|
+
|
|
42
|
+
## Build and validate
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
cargo test
|
|
46
|
+
cargo test --test docker -- --ignored
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The ignored Docker test requires a running Docker daemon and the local `node:22-alpine` image. The npm package smoke test is run after building the native binary with `npm run build:native`, then `npm run test:package`. These checks do not establish live inference, hosted sign-in, or interactive UI quality. Review [verification notes](docs/verification.md) for dated observations and known gaps.
|
|
50
|
+
|
|
51
|
+
## Further documentation
|
|
52
|
+
|
|
53
|
+
- [Current provider evidence and remaining work](docs/direct-providers.md)
|
|
54
|
+
- [Terminal UI behavior](docs/terminal-ui.md)
|
|
55
|
+
- [Task controls and evidence boundaries](docs/control-surface.md)
|
|
56
|
+
- [Runtime contracts](docs/contracts.md)
|
|
57
|
+
- [Saved-chat continuation](docs/saved-chat-continuation.md)
|
|
58
|
+
- [npm release process](docs/npm-publishing.md)
|
|
59
|
+
- [Windows local setup](relay/local/README.md)
|
|
60
|
+
|
|
61
|
+
Aegis is under active development. Provider compatibility, account access, packaging, and verification status can change; consult the linked evidence notes rather than assuming every documented path has been independently validated.
|
package/bin/aegis.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { ensureNative } from '../scripts/install.mjs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { whatsappCommand } from '../scripts/whatsapp-cli.mjs';
|
|
6
|
+
|
|
7
|
+
async function run(executable, args) {
|
|
8
|
+
const child = spawn(executable, args, { stdio: 'inherit', windowsHide: true });
|
|
9
|
+
const handlers = new Map(['SIGINT', 'SIGTERM'].map(signal => [signal, () => { if (!child.killed) child.kill(signal); }]));
|
|
10
|
+
for (const [signal, handler] of handlers) process.on(signal, handler);
|
|
11
|
+
try {
|
|
12
|
+
return await new Promise((resolve, reject) => {
|
|
13
|
+
child.once('error', reject);
|
|
14
|
+
child.once('close', (code, signal) => resolve(signal ? 1 : code ?? 1));
|
|
15
|
+
});
|
|
16
|
+
} finally {
|
|
17
|
+
for (const [signal, handler] of handlers) process.removeListener(signal, handler);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const executable = process.env.ARUN_BINARY || await ensureNative();
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
if (args[0] === 'whatsapp') {
|
|
25
|
+
process.exitCode = await whatsappCommand(args.slice(1), executable);
|
|
26
|
+
} else if (args[0] === 'pc') {
|
|
27
|
+
if (!args[1] || ['help', '--help', '-h'].includes(args[1])) {
|
|
28
|
+
console.log('aegis pc enable --trusted-host [--workspace <directory>]\nGrants files, PowerShell and desktop tools to new tasks in that workspace.\naegis pc status');
|
|
29
|
+
} else if (args[1] === 'status' && args.length === 2) {
|
|
30
|
+
process.exitCode = await run(executable, ['mcp', 'list']);
|
|
31
|
+
} else if (args[1] === 'enable') {
|
|
32
|
+
process.exitCode = await run(process.execPath, [fileURLToPath(new URL('../scripts/windows-host-install.mjs', import.meta.url)), '--aegis', executable, ...args.slice(2)]);
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error('Usage: aegis pc enable --trusted-host [--workspace <directory>] | status');
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
process.exitCode = await run(executable, args);
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('aegis: ' + error.message);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "aegis-arun",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "aegis-arun",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A durable terminal agent with direct account sign-in and custom endpoints",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"engines": { "node": ">=20" },
|
|
8
|
+
"bin": { "aegis": "bin/aegis.mjs", "arun": "bin/aegis.mjs" },
|
|
9
|
+
"files": ["bin", "scripts/install.mjs", "scripts/platform.mjs", "scripts/windows-host.mjs", "scripts/windows-host.ps1", "scripts/windows-host-install.mjs", "scripts/whatsapp-cli.mjs", "relay/local", "vendor", "README.md"],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"postinstall": "node scripts/install.mjs",
|
|
12
|
+
"build:native": "node scripts/build-native.mjs",
|
|
13
|
+
"test:package": "node scripts/package-smoke.mjs",
|
|
14
|
+
"test": "node --test tests/npm/*.test.mjs"
|
|
15
|
+
},
|
|
16
|
+
"repository": { "type": "git", "url": "https://github.com/aleksandarkolev-st/aegis.git" },
|
|
17
|
+
"keywords": ["agent", "terminal", "runtime", "codex", "claude", "grok"]
|
|
18
|
+
}
|
package/relay/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Aegis cloud relay
|
|
2
|
+
|
|
3
|
+
This standalone Rust service connects messaging channels to Aegis through a channel-neutral `MessagingChannel` contract. The relay core owns identity, pairing, routing, notification delivery, and receipts. Evolution is the only live adapter today and implements that contract for WhatsApp. The metadata schema also reserves an iMessage channel identity, but no iMessage adapter is implemented or tested here; it remains an optional macOS-only integration.
|
|
4
|
+
|
|
5
|
+
The relay does not run code or make tool decisions. A channel adapter normalizes inbound text into a small typed command on a per-installation JetStream subject. Aegis connects outbound to NATS over TLS, consumes only its own command subject, and publishes its allowlisted remote events. The active channel adapter renders those events for destinations paired to the same actor.
|
|
6
|
+
|
|
7
|
+
## Stored data
|
|
8
|
+
|
|
9
|
+
PostgreSQL contains eight metadata tables: users, installations, channel_bindings, pairing_tokens, delivery_receipts, notification_preferences, task_conversations, and outbound_echoes. Conversation rows bind a group to one owner's task. Echo rows retain digests only: a short body reservation covers send/webhook races, and a returned provider message ID digest prevents delayed self-account echoes for 30 days. Channel identity is stored independently of provider names.
|
|
10
|
+
|
|
11
|
+
- Pairing codes are random 256-bit values. PostgreSQL stores their SHA-256 hashes and 5-minute expiry; redemption is atomic and one time.
|
|
12
|
+
- Each installation belongs to one relay user and can register multiple actor IDs. Pairing metadata keeps one current token row per actor; actor IDs are unique across installations and cannot be moved to another installation.
|
|
13
|
+
- Inbound webhook receipts keep only channel, sender ID, provider message ID, target installation/binding, disposition, and status for 30 days. They prevent provider retries from publishing commands twice or retrying a consumed pairing code.
|
|
14
|
+
- Outbound receipts keep channel, event/binding IDs, status, attempt count, provider message ID, and timestamps for 90 days.
|
|
15
|
+
- Missing notification preferences enable state changes (task_started, approval_required, blocked, completed, failed, and reply) and suppress routine progress. Progress can be explicitly enabled per channel with the admin preference endpoint.
|
|
16
|
+
- No message text, repository data, model context, provider credential, shell output, or event transcript is written to PostgreSQL or application logs.
|
|
17
|
+
- JetStream command and event streams use file storage, WorkQueue retention, and a 64 MiB cap with an explicit discard-oldest policy. Commands have a 15-minute maximum age; events have a 24-hour maximum age. Acknowledged items are removed from the work queue. Pending payloads, including normalized command text and reply text, remain only in this bounded NATS queue until acknowledgement or expiry; they are not copied into PostgreSQL or application logs. The relay updates existing stream configuration at startup, so an existing event stream receives the 24-hour policy too. An event can be retained for up to one day while the relay is offline if NATS remains available and the stream stays below 64 MiB; reaching the byte cap can evict older events sooner.
|
|
18
|
+
|
|
19
|
+
The AegisEvent schema accepts only task_started, progress, approval_required, blocked, completed, failed, and reply. Every event must include a valid actor_id. Unknown fields such as shell output or repository context are rejected. Approval events include a display_detail of at most 512 bytes with only the capability and bounded target, plus the challenge expiry; these are never persisted or logged. Expired approval notifications are acknowledged and dropped. reply_text is accepted only for a reply event and is capped at 4 KiB.
|
|
20
|
+
|
|
21
|
+
## Remote commands
|
|
22
|
+
|
|
23
|
+
The default adapter accepts direct incoming text and paired-owner messages in known task groups. Explicit self-account mode accepts only the linked owner's own self-chat and own messages in those groups. The local Windows setup remains inert until the linked owner is confirmed. Unrelated groups, other contacts in self-account mode, outbound reply echoes, status events, media-only messages, malformed sender IDs, and unauthenticated webhooks are ignored or rejected. Message text is line-ending normalized and capped at 8 KiB. It is never logged.
|
|
24
|
+
|
|
25
|
+
An unpaired number can redeem a one-time code with either exact form: `/pair <code>` or `AEGIS <code>`. The command name is case-insensitive for the `AEGIS` form; the code must be one exact 43-character token with no trailing text. Pairing codes are consumed once. These prefixes are reserved for pairing and are not forwarded as ordinary messages.
|
|
26
|
+
|
|
27
|
+
| WhatsApp text | Typed command |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| /help, /goal, /models, /settings and the terminal catalog | slash (executed locally by authenticated Aegis) |
|
|
30
|
+
| Ordinary text or /message <text> | message |
|
|
31
|
+
| /tasks or /list_tasks | list_tasks |
|
|
32
|
+
| /status | status |
|
|
33
|
+
| /result [task-id] | result |
|
|
34
|
+
| /evidence [task-id] | evidence |
|
|
35
|
+
| /details [task-id] | details |
|
|
36
|
+
| /pause | pause |
|
|
37
|
+
| /resume | resume |
|
|
38
|
+
| /cancel [task-id] | cancel |
|
|
39
|
+
| /select_task <task-id> or /use <task-id> | select_task |
|
|
40
|
+
| /approve_once <challenge-id> | approve_once |
|
|
41
|
+
| /deny <challenge-id> | deny |
|
|
42
|
+
|
|
43
|
+
`/result [alias]` is available only after a task reaches a terminal state. It returns the saved final summary, bounded to 2 KiB and marked as verified, unverified, or not verified based on the recorded task state. `/evidence [alias]` returns at most eight receipts that are referenced by verified explicit obligations and belong to the current workspace revision. Each row contains only a safe capability name, a 12-character hash prefix, and byte count; artifact contents, paths, command arguments, stdout, and previews are never returned. Conversational answers have no evidence receipts.
|
|
44
|
+
|
|
45
|
+
Approval commands carry the challenge ID. There is no /approve alias. IDs are restricted to 128 ASCII letters, digits, underscores, hyphens, or periods.
|
|
46
|
+
|
|
47
|
+
Each command envelope includes installation_id, actor_id, stable envelope_id and request_id, issued/expiry timestamps, source channel/sender/message IDs, and the typed command. Commands expire after five minutes. Retries reuse stable IDs and set JetStream's Nats-Msg-Id. Event envelopes include installation_id, actor_id, challenge_id, display_detail, and reply_text; nullable event-specific fields are null when unused. The relay restricts fan-out to the matching paired destination.
|
|
48
|
+
|
|
49
|
+
Relay-to-Aegis authentication uses TLS and separate NATS users with subject permissions. The relay authenticates as `aegis-relay`; each device authenticates as `aegis-<canonical lowercase installation UUID>` with its own password. Aegis validates the installation, expiry, typed command, and envelope actor_id against its local actor-to-run mapping before dispatch. No actor HMAC key is sent to or shared with the relay. Each Aegis event carries actor_id from the local actor-to-run scope; before delivery, the relay selects only active bindings for the same installation, actor, and active channel adapter. A phone paired to another actor on the same installation cannot receive that event.
|
|
50
|
+
|
|
51
|
+
The relay's `relay-whatsapp-delivery` durable pull consumer is supervised independently from HTTP serving. A JetStream pull error or end-of-stream drops the current pull stream, waits with exponential backoff from 1 to 30 seconds, then gets or creates the same durable consumer and resumes pending events. The backoff resets after a consumer stays connected for at least a minute. Unacknowledged events remain eligible for redelivery until acknowledged or removed by the event stream's one-day/64 MiB limits.
|
|
52
|
+
|
|
53
|
+
## NATS principal provisioning
|
|
54
|
+
|
|
55
|
+
Provision NATS users separately from HTTP channel pairing. The admin pairing route does not issue NATS credentials. Use a current supported NATS server with the named, single-filter consumer create API (introduced in 2.9). Replace any server-wide `authorization.token` configuration with an explicit user list. Generate a distinct random password for the relay and for every installation, and store production server passwords as bcrypt hashes or use an equivalent external provisioning system. Retain TLS. Rotate or revoke an installation's NATS user independently of channel pairing when its device credential is compromised.
|
|
56
|
+
|
|
57
|
+
The legacy names `NATS_AUTH_TOKEN` and `--nats-token-env` now identify **user passwords**, not NATS token authentication. The relay reads its password from `NATS_AUTH_TOKEN`. Aegis stores only the environment variable name in `remote.json`; its daemon reads the installation password from that variable. Neither client accepts a username override or falls back to shared token authentication.
|
|
58
|
+
|
|
59
|
+
For installation `I`, substitute its canonical lowercase hyphenated UUID and set `C` to `aegis-I`. Its NATS user is `C`. Give it precisely these publish permissions:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
aegis.events.I
|
|
63
|
+
$JS.API.CONSUMER.INFO.AEGIS_COMMANDS.C
|
|
64
|
+
$JS.API.CONSUMER.CREATE.AEGIS_COMMANDS.C.aegis.commands.I
|
|
65
|
+
$JS.API.CONSUMER.MSG.NEXT.AEGIS_COMMANDS.C
|
|
66
|
+
$JS.ACK.AEGIS_COMMANDS.C.*.*.*.*.*
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Give it only `_INBOX.aegis.device.I.>` as a subscribe permission. Aegis sets `_INBOX.aegis.device.I` as its custom inbox prefix. Pull messages and API/publish replies arrive through those inboxes; no direct subscription to the command subject or stream-info permission is needed. Before pulling, the daemon checks the returned consumer's stream, name, durable name, exact command filter, pull mode, and explicit acknowledgement policy. An existing consumer with different authority fails closed.
|
|
70
|
+
|
|
71
|
+
Give `aegis-relay` precisely these publish permissions:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
aegis.commands.*
|
|
75
|
+
$JS.API.STREAM.CREATE.AEGIS_COMMANDS
|
|
76
|
+
$JS.API.STREAM.UPDATE.AEGIS_COMMANDS
|
|
77
|
+
$JS.API.STREAM.CREATE.AEGIS_EVENTS
|
|
78
|
+
$JS.API.STREAM.UPDATE.AEGIS_EVENTS
|
|
79
|
+
$JS.API.STREAM.INFO.AEGIS_EVENTS
|
|
80
|
+
$JS.API.CONSUMER.INFO.AEGIS_EVENTS.relay-whatsapp-delivery
|
|
81
|
+
$JS.API.CONSUMER.CREATE.AEGIS_EVENTS.relay-whatsapp-delivery.aegis.events.*
|
|
82
|
+
$JS.API.CONSUMER.MSG.NEXT.AEGIS_EVENTS.relay-whatsapp-delivery
|
|
83
|
+
$JS.ACK.AEGIS_EVENTS.relay-whatsapp-delivery.*.*.*.*.*
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Give the relay only `_INBOX.aegis.relay.>` as a subscribe permission; its custom inbox prefix is `_INBOX.aegis.relay`. The relay also checks the returned durable consumer's scope and acknowledgement policy before pulling. Allow lists deny subjects outside the list. Do not add unrestricted users, broad `$JS.API.>` or `$JS.ACK.>` grants, shared `_INBOX.>` subscriptions, `allow_responses`, unfiltered `CONSUMER.CREATE` endpoints, or legacy `CONSUMER.DURABLE.CREATE` endpoints. In particular, a device grant for a consumer name without the exact filter suffix would let it request a different filter in the payload. The filtered endpoint checks the payload against the filter in its subject and rejects multiple filters. See the [NATS consumer API implementation](https://github.com/nats-io/nats-server/blob/v2.12.0/server/jetstream_api.go#L4286) and [authentication documentation](https://docs.nats.io/learn/security/authentication-basics).
|
|
87
|
+
|
|
88
|
+
These ACK grants cover the standalone server configuration here. If deploying JetStream domains or cross-account routing that changes ACK subjects, scope the additional emitted ACK format to the same stream and consumer, and configure the matching API prefix as required by that topology. Do not broaden an ACK grant to compensate for a topology mismatch. Administrative inspection, cleanup, and credential provisioning should use a separate trusted operator principal; devices and the relay are not granted consumer-delete permissions.
|
|
89
|
+
|
|
90
|
+
The shared `AEGIS_COMMANDS` and `AEGIS_EVENTS` streams isolate message access through these permissions, but **do not isolate availability or storage quotas**. One device's event flood can fill the shared 64 MiB event stream and evict another device's pending events. Deploy per-installation streams with exact subjects and separate limits, or per-installation JetStream accounts, when that boundary is required; those deployments also need corresponding relay consumer lifecycle changes.
|
|
91
|
+
|
|
92
|
+
## WhatsApp delivery guarantee
|
|
93
|
+
|
|
94
|
+
Outbound WhatsApp delivery is **at-least-once**, not exactly-once. Aegis event IDs and per-destination PostgreSQL receipts suppress retries after the relay commits a successful delivery receipt. The current Evolution `sendText` DTO has no provider idempotency-key field ([controller](https://github.com/evolution-foundation/evolution-api/blob/main/src/api/controllers/sendMessage.controller.ts), [DTO](https://github.com/evolution-foundation/evolution-api/blob/main/src/api/dto/sendMessage.dto.ts)), so a send that Evolution accepts before the relay receives its response or commits the receipt is ambiguous. Retrying that event can send a duplicate WhatsApp message. The relay passes stable event IDs to the provider interface for adapters that support idempotency, but Evolution cannot use them for provider-side deduplication.
|
|
95
|
+
|
|
96
|
+
## HTTP routes
|
|
97
|
+
|
|
98
|
+
- GET /healthz returns ok.
|
|
99
|
+
- POST /admin/v1/installations requires Authorization: Bearer RELAY_ADMIN_TOKEN. JSON body:
|
|
100
|
+
|
|
101
|
+
{"actor_id":"local-actor-id","installation_id":"<optional canonical Aegis UUID>"}
|
|
102
|
+
|
|
103
|
+
The relay creates the user and installation if needed, then returns a 5-minute pairing code once. Under the trusted admin bearer principal, an existing installation UUID identifies that installation's user; additional actor IDs can be provisioned under the same installation. Re-provisioning the same installation/actor pair rotates its one-time code and keeps the user, installation, and actor binding scope. An actor ID already owned by another installation is rejected with 409. The relay admin bearer is the trusted bootstrap authority for this single deployment; it is not an end-user credential.
|
|
104
|
+
- POST /admin/v1/installations/{installation_id}/notifications/{event_kind} requires the admin bearer token and JSON {"enabled":false} (or true); this compatibility route changes WhatsApp preferences.
|
|
105
|
+
- POST /admin/v1/installations/{installation_id}/channels/{channel}/notifications/{event_kind} changes preferences for `whatsapp` or the reserved `imessage` identity.
|
|
106
|
+
- POST /admin/v1/installations/{installation_id}/bindings/revoke requires the admin bearer token and JSON {"actor_id":"local-actor-id","sender_id":"+4915112345678"}. It deactivates only the WhatsApp binding matching those values and consumes any unused pairing code for that actor on the installation.
|
|
107
|
+
- POST /admin/v1/installations/{installation_id}/channels/{channel}/bindings/revoke applies the same sender-specific operation to one channel.
|
|
108
|
+
- POST /admin/v1/installations/{installation_id}/actors/{actor_id}/bindings/revoke requires the admin bearer token and no request body. It idempotently consumes any unused pairing code and deactivates every channel binding for exactly that actor and installation. It returns 204 even if nothing is currently paired, so the local daemon can use it as best-effort cloud cleanup after disabling the actor locally.
|
|
109
|
+
- POST /v1/webhooks/whatsapp/evolution authenticates before interpreting the message JSON. It accepts either a dedicated x-aegis-webhook-token header, or an HMAC signature in x-aegis-timestamp and x-aegis-signature. The signature is v1= plus base64url-no-pad HMAC-SHA256 over timestamp, a period, and the raw body; it expires after five minutes. Use a dedicated webhook secret separate from the Evolution API key.
|
|
110
|
+
|
|
111
|
+
Configure Evolution's webhook to send x-aegis-webhook-token. If it cannot set custom webhook headers, place a trusted HTTPS ingress adapter in front that validates provider authentication and adds this dedicated header. Do not expose the webhook route without authentication.
|
|
112
|
+
|
|
113
|
+
## Environment
|
|
114
|
+
|
|
115
|
+
Required variables:
|
|
116
|
+
|
|
117
|
+
| Variable | Purpose |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| DATABASE_URL | PostgreSQL URL. Production should use sslmode=verify-full. |
|
|
120
|
+
| NATS_URL | tls://host:port; plaintext NATS is rejected. |
|
|
121
|
+
| NATS_AUTH_TOKEN | Password for the fixed `aegis-relay` NATS user, at least 32 characters. Keep it in a secret manager. |
|
|
122
|
+
| NATS_TLS_ROOT_CERT | Optional PEM CA path for a private NATS certificate authority. |
|
|
123
|
+
| RELAY_ADMIN_TOKEN | Admin API bearer secret, at least 32 characters. |
|
|
124
|
+
| EVOLUTION_BASE_URL | HTTPS Evolution API base URL. HTTP is allowed only for localhost development. |
|
|
125
|
+
| EVOLUTION_INSTANCE | Evolution instance name. |
|
|
126
|
+
| EVOLUTION_API_KEY | Evolution outbound API credential; held in process configuration only. |
|
|
127
|
+
| EVOLUTION_WEBHOOK_SECRET | Dedicated webhook token/HMAC secret, at least 32 characters. |
|
|
128
|
+
| RELAY_BIND_ADDR | Defaults to 127.0.0.1:8787. |
|
|
129
|
+
|
|
130
|
+
In production, bind the process to a private interface and expose the webhook through an HTTPS reverse proxy with request limits and authentication. Do not expose a plaintext or unauthenticated NATS listener. Database initialization creates the eight metadata tables above.
|
|
131
|
+
|
|
132
|
+
## Local services and smoke test
|
|
133
|
+
|
|
134
|
+
Local Compose binds PostgreSQL and NATS to loopback only. NATS uses TLS, the relay user, and two devices with exact installation permissions; its test certificate is generated locally and is not committed. PostgreSQL plaintext is allowed only with RELAY_ALLOW_INSECURE_LOCAL_DATABASE=true and a localhost or Compose-service database host.
|
|
135
|
+
|
|
136
|
+
Compose requires `NATS_AUTH_TOKEN` for the relay password and the following complete values for each device. Use prefix `AEGIS_NATS_DEVICE_` for the first device and `AEGIS_NATS_DEVICE2_` for the second; substitute `I` and `C` as described above. NATS config variables do not interpolate UUIDs inside subject strings, so provide each full subject rather than a UUID placeholder. There are no shared-token or wildcard-device defaults.
|
|
137
|
+
|
|
138
|
+
| Variable suffix | Value |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| USERNAME | `aegis-I` |
|
|
141
|
+
| PASSWORD | Unique device password, separate from `NATS_AUTH_TOKEN` |
|
|
142
|
+
| EVENT_SUBJECT | `aegis.events.I` |
|
|
143
|
+
| CONSUMER_INFO | `$JS.API.CONSUMER.INFO.AEGIS_COMMANDS.C` |
|
|
144
|
+
| CONSUMER_CREATE | `$JS.API.CONSUMER.CREATE.AEGIS_COMMANDS.C.aegis.commands.I` |
|
|
145
|
+
| CONSUMER_NEXT | `$JS.API.CONSUMER.MSG.NEXT.AEGIS_COMMANDS.C` |
|
|
146
|
+
| ACK | `$JS.ACK.AEGIS_COMMANDS.C.*.*.*.*.*` |
|
|
147
|
+
| INBOX | `_INBOX.aegis.device.I.>` |
|
|
148
|
+
|
|
149
|
+
Derive these values from the installation identity before starting NATS. Supply the first device's password to its daemon under the environment variable named by `--nats-token-env`. Treat the inputs as operator configuration: setting a wildcard where an exact subject is specified would weaken the boundary.
|
|
150
|
+
|
|
151
|
+
For the four `$JS...` values, include double quote characters around the full value in the environment variable (for example, `"$JS.API.CONSUMER.INFO.AEGIS_COMMANDS.C"`). NATS parses environment variable values as config text; these quotes keep `$JS` literal, and are removed before the subject permission is applied.
|
|
152
|
+
|
|
153
|
+
From the repository root in PowerShell, run:
|
|
154
|
+
|
|
155
|
+
.\relay\dev\start-local.ps1
|
|
156
|
+
|
|
157
|
+
It starts Postgres and NATS, exports development-only settings, and runs the relay. The local webhook listens only on 127.0.0.1:8787; external webhook traffic still needs HTTPS.
|
|
158
|
+
|
|
159
|
+
The fake-adapter smoke tests need no provider account, database, or NATS. They exercise the channel-neutral relay core and local Aegis authority with fixtures; they do not represent a live iMessage adapter:
|
|
160
|
+
|
|
161
|
+
cargo test --manifest-path relay/Cargo.toml --test local_smoke
|
|
162
|
+
|
|
163
|
+
Or run .\relay\scripts\smoke.ps1. Provider parsing and webhook authentication have fixture-based unit tests.
|
|
164
|
+
|
|
165
|
+
### Pair an Aegis installation
|
|
166
|
+
|
|
167
|
+
After the relay is running and Aegis has a locally saved provider/model profile, set the local development credentials in a separate PowerShell session and pair the installation:
|
|
168
|
+
|
|
169
|
+
$env:AEGIS_RELAY_ADMIN_TOKEN = "local-development-admin-token-not-for-production"
|
|
170
|
+
$env:AEGIS_NATS_TOKEN = $env:AEGIS_NATS_DEVICE_PASSWORD
|
|
171
|
+
aegis remote pair --relay-admin-url http://127.0.0.1:8787 --admin-token-env AEGIS_RELAY_ADMIN_TOKEN --nats-url tls://localhost:4422 --nats-token-env AEGIS_NATS_TOKEN --nats-root-cert .\relay\dev\certs\nats.crt
|
|
172
|
+
|
|
173
|
+
Send the one-time `/pair <code>` command printed by Aegis from the WhatsApp number to bind. Then check `aegis remote status` and keep `aegis remote run` running while you use the relay. Pairing stores no credential values; Aegis reads them from the named environment variables. Supply the device password in this session before running the daemon. These sample credentials are for local development only. Configure Evolution with a valid account, HTTPS endpoint, API key, and webhook authentication before expecting WhatsApp messages to reach the local relay.
|
|
174
|
+
|
|
175
|
+
To exercise PostgreSQL pairing rotation, actor-scoped binding lookup, and durable inbound dedupe against the local database, run .\relay\scripts\db-smoke.ps1. It starts only the loopback-bound Postgres service and runs the optional integration test.
|
|
176
|
+
|
|
177
|
+
For the isolated cross-process end-to-end check, run .\relay\scripts\e2e-smoke.ps1. It builds the real Aegis binary, starts a temporary PostgreSQL and TLS JetStream Compose project on loopback ports, exercises the Aegis pairing CLI and remote daemon through the relay HTTP router, and tears down only that uniquely named test project and its volumes. The fake WhatsApp adapter avoids provider credentials; this verifies the local relay path, not delivery through a live Evolution account.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Local WhatsApp setup on Windows
|
|
2
|
+
|
|
3
|
+
This persistent stack uses Evolution API **2.3.7**, Redis, separate PostgreSQL databases for Evolution and relay metadata, and TLS NATS. Evolution runs in Docker Desktop; the real relay and installed Aegis run on Windows. The development smoke fixtures are separate.
|
|
4
|
+
|
|
5
|
+
From the Aegis workspace, run:
|
|
6
|
+
|
|
7
|
+
```powershell
|
|
8
|
+
aegis whatsapp init
|
|
9
|
+
aegis whatsapp start
|
|
10
|
+
aegis whatsapp create-instance
|
|
11
|
+
aegis whatsapp qr
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Open the printed local `whatsapp-qr.html` path. On the phone hosting the relay account, open **WhatsApp → Settings → Linked devices → Link a device** and scan. No account is linked by the setup commands alone.
|
|
15
|
+
|
|
16
|
+
For your existing single WhatsApp account, enable `self-account` after linking. It reads the linked owner from Evolution and enables only your own self-DM and own messages in known task groups. It refuses to activate before the account reports a connected owner. Then run `pair` to generate the five-minute Aegis pairing code and send it in **Message yourself**:
|
|
17
|
+
|
|
18
|
+
```powershell
|
|
19
|
+
aegis whatsapp self-account
|
|
20
|
+
aegis whatsapp pair
|
|
21
|
+
aegis whatsapp daemon
|
|
22
|
+
aegis whatsapp status
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
After sending the code to yourself, run `daemon`. Replies are protected against being mistaken for new self-account commands. Task groups accept only their paired owner's messages and route to the exact task; unrelated groups are ignored. The default adapter mode remains available for deployments with a separate relay account.
|
|
26
|
+
|
|
27
|
+
Send `/help` in WhatsApp for the terminal command catalog, `/goal <task>` to start work, and `/new` in self-chat before a separate task. Interactive terminal pickers become text choices and explicit reviews in WhatsApp. Group creation with only the linked owner is still unverified; a failed request leaves that task controllable in self-chat.
|
|
28
|
+
|
|
29
|
+
To grant files, native PowerShell and desktop applications to new workspace tasks, run `aegis pc enable --trusted-host`. It registers the included Windows MCP server and exact tool grants. Choose your model and reasoning in local settings, or use `/model gpt-6.1-sol` and `/reasoning high` for future remote tasks. Remote host operations still use Aegis's exact approval gates.
|
|
30
|
+
|
|
31
|
+
`webhook` re-registers the authenticated inbound route. `stop` stops owned Windows processes and Docker services while retaining sessions and volumes. `start` resumes the infrastructure and relay; run `daemon` again if paired. A Windows restart requires these start commands. Aegis still applies its local permissions and approval gates to remote requests.
|
|
32
|
+
|
|
33
|
+
Use `-Workspace <directory>` for another workspace, `-AegisBinary <installed arun.exe>` and `-RelayBinary <aegis-relay.exe>` if native executable discovery is unavailable. Bundled relay executables are used before the repository Cargo fallback. Initial setup reads the installed executable's real workspace identity. Re-running `init` preserves credentials; deleting state or Docker volumes is not an upgrade procedure.
|
|
34
|
+
|
|
35
|
+
## Local data and network
|
|
36
|
+
|
|
37
|
+
Random credentials, certificate/private key, process identity records, private logs and QR artifacts live in the Git-ignored `.arun/local-remote` directory. Its Windows ACL grants only the current user and SYSTEM, including inherited files. Compose container environment values remain visible to Docker administrators.
|
|
38
|
+
|
|
39
|
+
Published ports bind to `127.0.0.1`. Redis and Evolution's PostgreSQL have no host ports. Evolution uses `host.docker.internal` to reach the loopback Windows relay; webhook setup verifies this route before registration. NATS permits only the relay principal and the exact local device's command consumer, event subject and private inbox. The local relay PostgreSQL connection uses the explicit loopback plaintext exception; NATS always uses TLS.
|
|
40
|
+
|
|
41
|
+
Evolution keeps its authentication/session data in persistent Docker volumes. Message/contact/chat/history database persistence and verbose webhook logs are disabled. This does not establish a live delivery guarantee: phone linking and an actual incoming/outgoing WhatsApp exchange must be verified after scanning.
|
|
42
|
+
|
|
43
|
+
## Version and API references
|
|
44
|
+
|
|
45
|
+
The image tag follows Evolution's [2.3.7 official deployment configuration](https://github.com/evolution-foundation/evolution-api/blob/2.3.7/Docker/swarm/evolution_api_v2.yaml). Storage/log switches follow its [versioned environment example](https://github.com/evolution-foundation/evolution-api/blob/2.3.7/.env.example). Instance creation and QR retrieval use the [versioned instance routes](https://github.com/evolution-foundation/evolution-api/blob/2.3.7/src/api/routes/instance.router.ts). The webhook uses `MESSAGES_UPSERT`, `byEvents=false`, and a custom `x-aegis-webhook-token` header, supported by the [versioned webhook controller](https://github.com/evolution-foundation/evolution-api/blob/2.3.7/src/api/integrations/event/webhook/webhook.controller.ts).
|
|
46
|
+
|
|
47
|
+
Task group creation uses `POST /group/create/{instance}` with the deterministic task subject and your phone as the participant. Evolution's [2.3.7 group schema](https://github.com/evolution-foundation/evolution-api/blob/2.3.7/src/validate/group.schema.ts) requires at least one participant; an empty array is invalid. The [Baileys adapter](https://github.com/evolution-foundation/evolution-api/blob/2.3.7/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts) passes the resolved participants to WhatsApp group creation. Creating a group with the linked owner must still be verified against the actual linked account; a failed or ambiguous creation is reported in the task's DM rather than silently claiming a group exists.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
services:
|
|
2
|
+
relay-postgres:
|
|
3
|
+
image: postgres:17.6-alpine
|
|
4
|
+
restart: unless-stopped
|
|
5
|
+
environment:
|
|
6
|
+
POSTGRES_DB: aegis_relay
|
|
7
|
+
POSTGRES_USER: relay
|
|
8
|
+
POSTGRES_PASSWORD: ${RELAY_DATABASE_PASSWORD:?Run local-remote.ps1 init}
|
|
9
|
+
POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256
|
|
10
|
+
ports:
|
|
11
|
+
- "127.0.0.1:${RELAY_DATABASE_PORT}:5432"
|
|
12
|
+
volumes:
|
|
13
|
+
- relay-postgres:/var/lib/postgresql/data
|
|
14
|
+
healthcheck:
|
|
15
|
+
test: [CMD-SHELL, "pg_isready -U relay -d aegis_relay"]
|
|
16
|
+
interval: 3s
|
|
17
|
+
timeout: 3s
|
|
18
|
+
retries: 30
|
|
19
|
+
|
|
20
|
+
evolution-postgres:
|
|
21
|
+
image: postgres:17.6-alpine
|
|
22
|
+
restart: unless-stopped
|
|
23
|
+
environment:
|
|
24
|
+
POSTGRES_DB: evolution
|
|
25
|
+
POSTGRES_USER: evolution
|
|
26
|
+
POSTGRES_PASSWORD: ${EVOLUTION_DATABASE_PASSWORD:?Run local-remote.ps1 init}
|
|
27
|
+
POSTGRES_INITDB_ARGS: --auth-host=scram-sha-256
|
|
28
|
+
volumes:
|
|
29
|
+
- evolution-postgres:/var/lib/postgresql/data
|
|
30
|
+
healthcheck:
|
|
31
|
+
test: [CMD-SHELL, "pg_isready -U evolution -d evolution"]
|
|
32
|
+
interval: 3s
|
|
33
|
+
timeout: 3s
|
|
34
|
+
retries: 30
|
|
35
|
+
|
|
36
|
+
redis:
|
|
37
|
+
image: redis:7.4.5-alpine
|
|
38
|
+
restart: unless-stopped
|
|
39
|
+
command: [redis-server, --appendonly, "yes"]
|
|
40
|
+
volumes:
|
|
41
|
+
- redis:/data
|
|
42
|
+
healthcheck:
|
|
43
|
+
test: [CMD, redis-cli, ping]
|
|
44
|
+
interval: 3s
|
|
45
|
+
timeout: 3s
|
|
46
|
+
retries: 30
|
|
47
|
+
|
|
48
|
+
nats:
|
|
49
|
+
image: nats:2.12.0-alpine
|
|
50
|
+
restart: unless-stopped
|
|
51
|
+
command: [-c, /etc/nats/nats-server.conf]
|
|
52
|
+
environment:
|
|
53
|
+
NATS_AUTH_TOKEN: ${NATS_AUTH_TOKEN}
|
|
54
|
+
AEGIS_NATS_DEVICE_USERNAME: ${AEGIS_NATS_DEVICE_USERNAME}
|
|
55
|
+
AEGIS_NATS_DEVICE_PASSWORD: ${AEGIS_NATS_DEVICE_PASSWORD}
|
|
56
|
+
AEGIS_NATS_DEVICE_EVENT_SUBJECT: ${AEGIS_NATS_DEVICE_EVENT_SUBJECT}
|
|
57
|
+
AEGIS_NATS_DEVICE_CONSUMER_INFO: ${AEGIS_NATS_DEVICE_CONSUMER_INFO}
|
|
58
|
+
AEGIS_NATS_DEVICE_CONSUMER_CREATE: ${AEGIS_NATS_DEVICE_CONSUMER_CREATE}
|
|
59
|
+
AEGIS_NATS_DEVICE_CONSUMER_NEXT: ${AEGIS_NATS_DEVICE_CONSUMER_NEXT}
|
|
60
|
+
AEGIS_NATS_DEVICE_ACK: ${AEGIS_NATS_DEVICE_ACK}
|
|
61
|
+
AEGIS_NATS_DEVICE_INBOX: ${AEGIS_NATS_DEVICE_INBOX}
|
|
62
|
+
ports:
|
|
63
|
+
- "127.0.0.1:${NATS_PORT}:4222"
|
|
64
|
+
volumes:
|
|
65
|
+
- ./nats-server.conf:/etc/nats/nats-server.conf:ro
|
|
66
|
+
- ${LOCAL_REMOTE_DIR}/nats-certs:/run/certs:ro
|
|
67
|
+
- jetstream:/data/jetstream
|
|
68
|
+
healthcheck:
|
|
69
|
+
test: [CMD-SHELL, "nc -z 127.0.0.1 4222"]
|
|
70
|
+
interval: 3s
|
|
71
|
+
timeout: 3s
|
|
72
|
+
retries: 30
|
|
73
|
+
|
|
74
|
+
evolution:
|
|
75
|
+
image: evoapicloud/evolution-api:v2.3.7
|
|
76
|
+
restart: unless-stopped
|
|
77
|
+
depends_on:
|
|
78
|
+
evolution-postgres:
|
|
79
|
+
condition: service_healthy
|
|
80
|
+
redis:
|
|
81
|
+
condition: service_healthy
|
|
82
|
+
ports:
|
|
83
|
+
- "127.0.0.1:${EVOLUTION_PORT}:8080"
|
|
84
|
+
volumes:
|
|
85
|
+
- evolution-instances:/evolution/instances
|
|
86
|
+
environment:
|
|
87
|
+
SERVER_URL: http://localhost:${EVOLUTION_PORT}
|
|
88
|
+
AUTHENTICATION_API_KEY: ${EVOLUTION_API_KEY}
|
|
89
|
+
AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES: "false"
|
|
90
|
+
DATABASE_PROVIDER: postgresql
|
|
91
|
+
DATABASE_CONNECTION_URI: postgresql://evolution:${EVOLUTION_DATABASE_PASSWORD}@evolution-postgres:5432/evolution?schema=public
|
|
92
|
+
DATABASE_CONNECTION_CLIENT_NAME: aegis_local
|
|
93
|
+
DATABASE_SAVE_DATA_INSTANCE: "true"
|
|
94
|
+
DATABASE_SAVE_DATA_NEW_MESSAGE: "false"
|
|
95
|
+
DATABASE_SAVE_MESSAGE_UPDATE: "false"
|
|
96
|
+
DATABASE_SAVE_DATA_CONTACTS: "false"
|
|
97
|
+
DATABASE_SAVE_DATA_CHATS: "false"
|
|
98
|
+
DATABASE_SAVE_DATA_LABELS: "false"
|
|
99
|
+
DATABASE_SAVE_DATA_HISTORIC: "false"
|
|
100
|
+
DATABASE_SAVE_IS_ON_WHATSAPP: "false"
|
|
101
|
+
CACHE_REDIS_ENABLED: "true"
|
|
102
|
+
CACHE_REDIS_URI: redis://redis:6379/0
|
|
103
|
+
CACHE_REDIS_PREFIX_KEY: aegis_local
|
|
104
|
+
CACHE_REDIS_SAVE_INSTANCES: "false"
|
|
105
|
+
CACHE_LOCAL_ENABLED: "false"
|
|
106
|
+
TELEMETRY_ENABLED: "false"
|
|
107
|
+
LOG_LEVEL: ERROR,WARN
|
|
108
|
+
LOG_BAILEYS: error
|
|
109
|
+
WEBHOOK_GLOBAL_ENABLED: "false"
|
|
110
|
+
CONFIG_SESSION_PHONE_CLIENT: Aegis
|
|
111
|
+
CONFIG_SESSION_PHONE_NAME: Chrome
|
|
112
|
+
DEL_INSTANCE: "false"
|
|
113
|
+
OPENAI_ENABLED: "false"
|
|
114
|
+
CHATWOOT_ENABLED: "false"
|
|
115
|
+
TYPEBOT_ENABLED: "false"
|
|
116
|
+
N8N_ENABLED: "false"
|
|
117
|
+
DIFY_ENABLED: "false"
|
|
118
|
+
EVOAI_ENABLED: "false"
|
|
119
|
+
S3_ENABLED: "false"
|
|
120
|
+
healthcheck:
|
|
121
|
+
test: [CMD, node, -e, "fetch('http://127.0.0.1:8080/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
|
122
|
+
interval: 5s
|
|
123
|
+
timeout: 4s
|
|
124
|
+
start_period: 30s
|
|
125
|
+
retries: 30
|
|
126
|
+
|
|
127
|
+
volumes:
|
|
128
|
+
relay-postgres:
|
|
129
|
+
evolution-postgres:
|
|
130
|
+
redis:
|
|
131
|
+
jetstream:
|
|
132
|
+
evolution-instances:
|