@hasna/loops 0.4.27 → 0.4.29
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/CHANGELOG.md +205 -1
- package/README.md +232 -49
- package/dist/api/index.d.ts +20 -19
- package/dist/api/index.js +7770 -1342
- package/dist/cli/index.js +2976 -966
- package/dist/cli/safe-error-context.d.ts +1 -0
- package/dist/daemon/daemon.d.ts +1 -0
- package/dist/daemon/index.js +1899 -499
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/mode.d.ts +6 -12
- package/dist/index.d.ts +3 -3
- package/dist/index.js +5319 -1096
- package/dist/lib/advancement.d.ts +52 -0
- package/dist/lib/agent-adapter.d.ts +20 -2
- package/dist/lib/auth/route-policy.d.ts +14 -0
- package/dist/lib/auth/tenant-auth.d.ts +38 -0
- package/dist/lib/cloud/mode.d.ts +2 -8
- package/dist/lib/cloud/storage.d.ts +1 -2
- package/dist/lib/cloud/transport.d.ts +4 -18
- package/dist/lib/errors.d.ts +43 -1
- package/dist/lib/executor.d.ts +9 -0
- package/dist/lib/format.d.ts +2 -2
- package/dist/lib/goal/runner.d.ts +36 -3
- package/dist/lib/health.d.ts +1 -1
- package/dist/lib/labels.d.ts +4 -0
- package/dist/lib/loop-status.d.ts +4 -0
- package/dist/lib/migration.d.ts +70 -17
- package/dist/lib/mode.d.ts +2 -5
- package/dist/lib/mode.js +28 -37
- package/dist/lib/route/fields.d.ts +8 -0
- package/dist/lib/route/index.d.ts +2 -2
- package/dist/lib/route/throttle.d.ts +7 -0
- package/dist/lib/route/types.d.ts +3 -0
- package/dist/lib/run-completion.d.ts +18 -0
- package/dist/lib/scheduler.d.ts +5 -11
- package/dist/lib/storage/contract.d.ts +15 -1
- package/dist/lib/storage/index.d.ts +1 -1
- package/dist/lib/storage/index.js +4083 -486
- package/dist/lib/storage/pg-executor.d.ts +10 -5
- package/dist/lib/storage/postgres-loop-storage.d.ts +52 -26
- package/dist/lib/storage/postgres-schema.d.ts +8 -0
- package/dist/lib/storage/postgres-schema.js +1326 -1
- package/dist/lib/storage/postgres.d.ts +3 -1
- package/dist/lib/storage/postgres.js +1356 -27
- package/dist/lib/storage/provider-credentials.d.ts +84 -0
- package/dist/lib/storage/shared-database-transfer.d.ts +136 -0
- package/dist/lib/storage/sqlite.d.ts +15 -2
- package/dist/lib/storage/sqlite.js +1379 -217
- package/dist/lib/storage/tenant-backfill-s3.d.ts +53 -0
- package/dist/lib/storage/tenant-backfill.d.ts +40 -0
- package/dist/lib/store/index.d.ts +14 -8
- package/dist/lib/store.d.ts +129 -7
- package/dist/lib/store.js +1352 -218
- package/dist/lib/templates.d.ts +11 -0
- package/dist/lib/workflow-events.d.ts +6 -0
- package/dist/lib/workflow-provenance.d.ts +8 -0
- package/dist/lib/workflow-runner.d.ts +52 -4
- package/dist/mcp/index.js +2074 -657
- package/dist/runner/index.d.ts +20 -2
- package/dist/runner/index.js +2146 -183
- package/dist/sdk/http.d.ts +364 -4
- package/dist/sdk/http.js +379 -1
- package/dist/sdk/index.d.ts +7 -2
- package/dist/sdk/index.js +2341 -742
- package/dist/serve/index.d.ts +14 -0
- package/dist/serve/index.js +13269 -1830
- package/dist/types.d.ts +75 -3
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +32 -32
- package/docs/CUTOVER-RUNBOOK.md +158 -31
- package/docs/DEPLOYMENT_MODES.md +84 -56
- package/docs/RUNTIME_BOUNDARY.md +26 -26
- package/docs/SHARED-DATABASE-TRANSFER.md +95 -0
- package/docs/SHARED_KIT_EXTRACTION_INVENTORY.md +631 -0
- package/docs/TRANSCRIPT_LOOP_PATTERNS.md +3 -3
- package/docs/UNIFIED_PRODUCT_CONTRACT.md +365 -0
- package/docs/USAGE.md +150 -56
- package/docs/workflows/transcript-feedback-to-loops.json +2 -2
- package/package.json +8 -4
- package/dist/lib/storage/pg-runner-claim.d.ts +0 -40
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Loops
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Loops is a local CLI and daemon for persistent loops and workflows: scheduled or recurring work that survives process restarts and records every run.
|
|
4
4
|
|
|
5
|
-
Naming: the product is **
|
|
5
|
+
Naming: the product is **Loops**, published on npm as
|
|
6
6
|
[`@hasna/loops`](https://www.npmjs.com/package/@hasna/loops) and developed in the
|
|
7
7
|
[`hasna/loops`](https://github.com/hasna/loops) repository. The installed
|
|
8
8
|
binaries are `loops`, `loops-daemon`, `loops-api`, `loops-serve`,
|
|
9
9
|
`loops-runner`, and `loops-mcp`.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Loops is the **runtime, scheduler, and workflow engine** for automations —
|
|
12
12
|
not the automation domain model. Specs, triggers, queue ownership, approvals,
|
|
13
13
|
DLQ/replay, and audit live in `@hasna/automations` and `@hasna/actions`; see
|
|
14
14
|
[Runtime Boundary](docs/RUNTIME_BOUNDARY.md) for ownership split and external
|
|
@@ -25,7 +25,7 @@ It supports deterministic command loops, JSON-defined workflows, and guarded CLI
|
|
|
25
25
|
|
|
26
26
|
## Deployment Modes
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Loops has three deployment modes:
|
|
29
29
|
|
|
30
30
|
- `local`: SQLite in `LOOPS_DATA_DIR` is authoritative and `loops-daemon` executes scheduled work.
|
|
31
31
|
- `self_hosted`: the Hasna-owned AWS/RDS control-plane deployment, served by
|
|
@@ -33,23 +33,20 @@ OpenLoops has three deployment modes:
|
|
|
33
33
|
shared by serve, SDK, and tests. This release exposes status, storage-backed
|
|
34
34
|
`/v1` loop CRUD and run listing, runner claim/heartbeat/finalize protocol
|
|
35
35
|
endpoints, a one-shot `loops-runner run-once` execution path for embedded
|
|
36
|
-
control-plane hosts, id-preserving local export/import,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- `cloud`: a hosted control-plane contract. This release exposes client/runner status only; hosted tenant auth and infrastructure live outside this package.
|
|
36
|
+
control-plane hosts, id-preserving local export/import, tenant-bound API-key
|
|
37
|
+
authentication, and forced Postgres row-level tenant isolation.
|
|
38
|
+
- `cloud`: a hosted control-plane contract using the same tenant-bound API.
|
|
40
39
|
|
|
41
40
|
`local` is the default and requires no network, token, Postgres, or hosted
|
|
42
|
-
service. Set `
|
|
43
|
-
`cloud` to choose explicitly. Without an explicit mode,
|
|
44
|
-
|
|
45
|
-
`self_hosted`.
|
|
41
|
+
service. Set `HASNA_LOOPS_STORAGE_MODE` to `local`, `self_hosted`, or
|
|
42
|
+
`cloud` to choose explicitly. Without an explicit mode,
|
|
43
|
+
`HASNA_LOOPS_API_URL` or `HASNA_LOOPS_DATABASE_URL` selects `self_hosted`.
|
|
46
44
|
|
|
47
45
|
The public `@hasna/loops` package owns the local runtime, mode resolver,
|
|
48
|
-
self-hosted API contract,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`LOOPS_CLOUD_TOKEN` or `HASNA_LOOPS_CLOUD_TOKEN`.
|
|
46
|
+
self-hosted API contract, tenant authentication and authorization, runner
|
|
47
|
+
contract, SDK, MCP server, and CLI. Account provisioning and infrastructure
|
|
48
|
+
deployment remain operator responsibilities. Cloud clients use
|
|
49
|
+
`HASNA_LOOPS_API_URL` plus `HASNA_LOOPS_API_KEY`.
|
|
53
50
|
|
|
54
51
|
Scheduler state is explicit in status JSON. `schedulerState.localStore` is
|
|
55
52
|
SQLite plus local run artifact files: authoritative in `local`, cache/spool in
|
|
@@ -73,7 +70,6 @@ loops self-hosted status
|
|
|
73
70
|
loops self-hosted migrate --dry-run
|
|
74
71
|
loops self-hosted push --dry-run
|
|
75
72
|
loops self-hosted pull --dry-run
|
|
76
|
-
loops self-hosted runner-register --runner-id <id> --machine-id <machine> --apply
|
|
77
73
|
loops export --file ./loops-export.json --dry-run
|
|
78
74
|
loops export --file ./loops-export.json
|
|
79
75
|
loops import ./loops-export.json
|
|
@@ -81,16 +77,55 @@ loops import ./loops-export.json --apply
|
|
|
81
77
|
loops cloud status
|
|
82
78
|
loops-api status
|
|
83
79
|
loops-serve version
|
|
84
|
-
|
|
80
|
+
HASNA_LOOPS_MIGRATOR_DATABASE_URL=... loops-serve migrate --dry-run
|
|
81
|
+
loops-serve db-credentials reconcile
|
|
82
|
+
HASNA_LOOPS_DATABASE_URL=... HASNA_LOOPS_AUTH_DATABASE_URL=... loops-serve serve
|
|
85
83
|
loops-runner status
|
|
86
84
|
```
|
|
87
85
|
|
|
86
|
+
`loops self-hosted push` is safe by default: imported workflows are archived and
|
|
87
|
+
imported loops are paused with `nextRunAt`/`retryScheduledFor` cleared. Even
|
|
88
|
+
without `--replace`, the control-plane import boundary may rewrite existing
|
|
89
|
+
same-id workflows/loops into that disabled representation; use explicit
|
|
90
|
+
preserve flags only for an intentional activation.
|
|
91
|
+
|
|
88
92
|
See [Deployment Modes](docs/DEPLOYMENT_MODES.md) for the full package boundary
|
|
89
93
|
and machine-placement contract.
|
|
90
94
|
|
|
95
|
+
### Provider database credentials
|
|
96
|
+
|
|
97
|
+
`loops-serve db-credentials reconcile` is the fixed in-cluster reconciler for
|
|
98
|
+
provider-managed RDS app credentials. It reads the RDS-managed master secret
|
|
99
|
+
with the AWS Secrets Manager SDK, builds `sslmode=verify-full` DSNs only in
|
|
100
|
+
memory, and writes each app DSN to that app's Secrets Manager secret through
|
|
101
|
+
`AWSPENDING` before promoting `AWSCURRENT`. It never accepts database URLs,
|
|
102
|
+
passwords, access keys, profiles, or secret material as command flags.
|
|
103
|
+
|
|
104
|
+
Required environment is intentionally strict: `AWS_REGION`,
|
|
105
|
+
`AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`, `HASNA_LOOPS_RDS_MASTER_SECRET_ARN`,
|
|
106
|
+
`HASNA_LOOPS_MIGRATOR_DATABASE_URL_SECRET_ARN`,
|
|
107
|
+
`HASNA_LOOPS_RUNTIME_DATABASE_URL_SECRET_ARN`,
|
|
108
|
+
`HASNA_LOOPS_AUTH_DATABASE_URL_SECRET_ARN`, `HASNA_LOOPS_RDS_INSTANCE_ID`,
|
|
109
|
+
`HASNA_LOOPS_RDS_ENDPOINT`, `HASNA_LOOPS_RDS_PORT`,
|
|
110
|
+
`HASNA_LOOPS_RDS_DATABASE`, and `HASNA_LOOPS_RDS_MASTER_USERNAME`. Static AWS
|
|
111
|
+
credential environment variables, profile/web-identity inputs, full credential
|
|
112
|
+
URIs, non-RDS-style endpoints, duplicate secret ARNs, cross-region secret ARNs,
|
|
113
|
+
and malformed identifiers fail closed before any secret or database operation.
|
|
114
|
+
|
|
115
|
+
The command normalizes the fixed NOLOGIN database roles
|
|
116
|
+
`open_loops_owner`, `open_loops_migrator`, `open_loops_runtime`, and
|
|
117
|
+
`open_loops_authenticator`; manages exactly three LOGIN users
|
|
118
|
+
`open_loops_migrator_login`, `open_loops_runtime_login`, and
|
|
119
|
+
`open_loops_authenticator_login`; grants only the migrator login exact
|
|
120
|
+
owner/migrator membership plus `CREATEROLE`; and keeps runtime/authenticator
|
|
121
|
+
without memberships until migration `0010_tenant_enforce` has landed. After
|
|
122
|
+
`0010`, runtime and authenticator are attached only to their matching roles.
|
|
123
|
+
If PostgreSQL 16 implicit creator memberships cannot be normalized, the
|
|
124
|
+
reconciler fails before publishing app credentials.
|
|
125
|
+
|
|
91
126
|
## Install
|
|
92
127
|
|
|
93
|
-
**
|
|
128
|
+
**Loops requires the [Bun](https://bun.sh) runtime (`bun >= 1.0`).** The
|
|
94
129
|
`loops`, `loops-daemon`, `loops-api`, `loops-serve`, `loops-runner`, and
|
|
95
130
|
`loops-mcp` binaries run with a `#!/usr/bin/env bun` shebang, so Bun must be on
|
|
96
131
|
`PATH` even when the package is installed with npm. Node.js is not a supported
|
|
@@ -126,7 +161,7 @@ The CLI stores state in `~/.hasna/loops` by default. Set `LOOPS_DATA_DIR` to iso
|
|
|
126
161
|
|
|
127
162
|
## MCP Server
|
|
128
163
|
|
|
129
|
-
|
|
164
|
+
Loops ships a stdio MCP server for safe loop and workflow inspection from
|
|
130
165
|
MCP-capable agents:
|
|
131
166
|
|
|
132
167
|
```bash
|
|
@@ -143,6 +178,7 @@ import { createLoopsMcpServer } from "@hasna/loops/mcp";
|
|
|
143
178
|
Available read tools include `loops_list`, `loops_show`, `loops_runs`,
|
|
144
179
|
`loops_doctor`, `loops_workflows_list`, `loops_workflow_read`, and
|
|
145
180
|
`loops_workflow_validate`.
|
|
181
|
+
`loops_list` and `loops_runs` accept label arrays with AND semantics.
|
|
146
182
|
Resources are available at `loops://runtime` and `loops://tools`.
|
|
147
183
|
Those tools use the same `Store`, public redaction helpers, and workflow parser
|
|
148
184
|
as the CLI and SDK, so read output and validation behavior stay aligned across
|
|
@@ -152,12 +188,16 @@ Mutation tools are disabled by default. Start the server with
|
|
|
152
188
|
`LOOPS_MCP_ALLOW_MUTATIONS=true` only for a trusted local MCP host that should be
|
|
153
189
|
allowed to change loop state. The guarded mutation tools use canonical names:
|
|
154
190
|
`loops_pause`, `loops_resume`, `loops_stop`, `loops_run_now`, `loops_archive`,
|
|
155
|
-
`loops_unarchive`, `loops_create_command`, and
|
|
191
|
+
`loops_unarchive`, `loops_labels_update`, `loops_create_command`, and
|
|
192
|
+
`loops_create_workflow`.
|
|
156
193
|
Deprecated `loop_*` aliases are still registered where compatibility needs them,
|
|
157
194
|
but callers should use the `loops_*` names. Mutation tools do not require or
|
|
158
195
|
accept confirmation-string parameters; the server-side environment opt-in is the
|
|
159
196
|
gate. MCP `loops_run_now` schedules the loop for immediate daemon pickup; inline
|
|
160
197
|
execution remains CLI-only.
|
|
198
|
+
When `showOutput` is enabled, `loops_runs` caps each stdout/stderr field at
|
|
199
|
+
32,000 characters, caps output-bearing pages at 25 runs, and caps the aggregate
|
|
200
|
+
MCP JSON response at 128,000 characters.
|
|
161
201
|
|
|
162
202
|
Keep host-affecting or long-running operations on the CLI: daemon
|
|
163
203
|
start/stop/install/logs, inline `run-now`, `tick`, loop deletion, workflow
|
|
@@ -217,8 +257,41 @@ loops create agent supply-chain-watch \
|
|
|
217
257
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
218
258
|
```
|
|
219
259
|
|
|
260
|
+
Codewith `--auth-profile` is provider-native, not an OpenAccounts selector.
|
|
261
|
+
Local and remote preflight first request `codewith profile list --json` and
|
|
262
|
+
match the requested name exactly against the root `data` or `profiles`
|
|
263
|
+
inventory. That inventory is authoritative: `usable: false` rejects a profile,
|
|
264
|
+
legacy entries without `usable` remain usable, and `currentProfile` does not add
|
|
265
|
+
inventory membership. Loops falls back to the human-readable table,
|
|
266
|
+
including active `*` rows, only when JSON mode is unsupported or its inventory
|
|
267
|
+
is structurally unavailable. Embedded NUL bytes, missing profiles, and
|
|
268
|
+
non-fallback profile-list failures fail closed.
|
|
269
|
+
|
|
270
|
+
## Labels
|
|
271
|
+
|
|
272
|
+
Command, agent, and workflow loops accept repeatable labels:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
loops create command repo-status --every 1m --cmd "git status --short" \
|
|
276
|
+
--label BrowserPlan --label maintenance
|
|
277
|
+
loops list --label browserplan --label maintenance
|
|
278
|
+
loops runs --label browserplan
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Labels are normalized to lowercase, deduplicated, limited to 32 per loop, and
|
|
282
|
+
must match `[a-z0-9][a-z0-9._-]{0,63}` after normalization. Repeated filters use
|
|
283
|
+
AND semantics. Run filtering uses the loop's current labels rather than a
|
|
284
|
+
historical snapshot on each run.
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
loops labels add repo-status urgent
|
|
288
|
+
loops labels remove repo-status maintenance
|
|
289
|
+
loops labels set repo-status browserplan nightly
|
|
290
|
+
loops labels clear repo-status
|
|
291
|
+
```
|
|
292
|
+
|
|
220
293
|
Run an OpenCode loop with an explicit provider/model. OpenCode reads
|
|
221
|
-
`~/.config/opencode/config.json` when no model is supplied, so
|
|
294
|
+
`~/.config/opencode/config.json` when no model is supplied, so Loops rejects
|
|
222
295
|
OpenCode agent targets without `--model` instead of inheriting a stale or
|
|
223
296
|
machine-specific default.
|
|
224
297
|
|
|
@@ -231,6 +304,32 @@ loops create agent opencode-smoke \
|
|
|
231
304
|
--prompt "Reply with exactly OK."
|
|
232
305
|
```
|
|
233
306
|
|
|
307
|
+
Direct agent loops can persist an auditable session contract:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
loops create agent repo-check \
|
|
311
|
+
--provider codewith \
|
|
312
|
+
--every 15m \
|
|
313
|
+
--cwd /path/to/repo \
|
|
314
|
+
--sandbox workspace-write \
|
|
315
|
+
--prompt "Check the repo and report concrete failures." \
|
|
316
|
+
--allow-tool functions.exec_command \
|
|
317
|
+
--allow-command git,bun \
|
|
318
|
+
--safety-reason "isolated repository status inspection"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Tool and command lists are advisory metadata, not provider-enforced policy.
|
|
322
|
+
Loops stores the reason with the direct loop target, adds an explicit
|
|
323
|
+
advisory contract to provider stdin, and exports `LOOPS_AGENT_SESSION_CONTRACT`.
|
|
324
|
+
A direct agent loop has no workflow run, so it does not create a workflow event.
|
|
325
|
+
Agent steps inside workflows that carry an audit contract additionally receive
|
|
326
|
+
one server-derived `agent_session_contract` event per step and workflow run.
|
|
327
|
+
|
|
328
|
+
Codewith/Codex `danger-full-access`, Cursor `sandbox=disabled`, and provider
|
|
329
|
+
bypass modes (Claude, Cursor, AI Copilot, and OpenCode) require both a non-empty
|
|
330
|
+
safety reason and `--manual-break-glass`. A reason cannot replace explicit
|
|
331
|
+
operator approval.
|
|
332
|
+
|
|
234
333
|
For `codewith` and `aicopilot` account isolation, register matching OpenAccounts tools first if they are not built in on the machine:
|
|
235
334
|
|
|
236
335
|
```bash
|
|
@@ -274,7 +373,7 @@ resolve from the workflow JSON file's directory:
|
|
|
274
373
|
}
|
|
275
374
|
```
|
|
276
375
|
|
|
277
|
-
|
|
376
|
+
Loops records `promptSource` metadata and redacts prompt bodies in public
|
|
278
377
|
CLI output by default, including `templates render`. Use
|
|
279
378
|
`~/.hasna/loops/prompts/<stable-name>.md` as the default prompt store for
|
|
280
379
|
production loops.
|
|
@@ -285,7 +384,7 @@ non-secret routing/configuration data.
|
|
|
285
384
|
|
|
286
385
|
## Goals
|
|
287
386
|
|
|
288
|
-
Add `--goal` to wrap a command, agent, or workflow loop in an AI-SDK orchestration layer.
|
|
387
|
+
Add `--goal` to wrap a command, agent, or workflow loop in an AI-SDK orchestration layer. Loops asks the configured model to create a flat DAG plan, then executes ready plan nodes by re-running the underlying target once per node. Each node run is parameterized with that node's objective: agent prompts get the goal and node objective appended, and every wrapped process receives `LOOPS_GOAL_NODE_KEY` and `LOOPS_GOAL_NODE_OBJECTIVE` in its environment. An adversarial achievement audit runs before the goal is marked complete.
|
|
289
388
|
|
|
290
389
|
The goal `autoExecute` mode is honored: `off` plans and persists the DAG without executing any nodes (the run reports the persisted plan), while `readyOnly` (the default) and `aiDirected` run the dependency-driven execution loop over ready nodes.
|
|
291
390
|
|
|
@@ -336,7 +435,7 @@ Workflow JSON can also embed goals at the workflow or step level:
|
|
|
336
435
|
For **workflow loops** (`loops create workflow ...`), prefer a single loop-level
|
|
337
436
|
`--goal` wrapper around the scheduled workflow. Do not also define a top-level
|
|
338
437
|
`"goal"` on the workflow JSON spec: nested loop-level and workflow-level goal
|
|
339
|
-
wrappers deadlocked execution because each layer waited on the other.
|
|
438
|
+
wrappers deadlocked execution because each layer waited on the other. Loops
|
|
340
439
|
now rejects new workflow loops that combine both wrappers at creation time, and
|
|
341
440
|
blocks retargeting a loop-level goal loop onto a workflow that also carries a
|
|
342
441
|
top-level goal.
|
|
@@ -415,7 +514,7 @@ Use `shell: true` only when you intentionally want shell parsing:
|
|
|
415
514
|
## Templates And Task Events
|
|
416
515
|
|
|
417
516
|
Built-in templates turn common orchestration flows into reusable workflow JSON.
|
|
418
|
-
Todos-task routes are
|
|
517
|
+
Todos-task routes are Loops-native runtime admission — see
|
|
419
518
|
[Runtime Boundary](docs/RUNTIME_BOUNDARY.md) for how this differs from
|
|
420
519
|
OpenAutomations queue ownership.
|
|
421
520
|
`todos-task-worker-verifier` performs one todos task and then verifies it.
|
|
@@ -471,7 +570,7 @@ loops templates render routing-remediation \
|
|
|
471
570
|
--var idempotencyKey=routing-health:repo:shard0
|
|
472
571
|
```
|
|
473
572
|
|
|
474
|
-
Custom reusable workflow templates live under the
|
|
573
|
+
Custom reusable workflow templates live under the Loops app data directory:
|
|
475
574
|
`~/.hasna/loops/templates` by default, or `$LOOPS_DATA_DIR/templates` when
|
|
476
575
|
`LOOPS_DATA_DIR` is set. Store templates as declarative JSON files; listing,
|
|
477
576
|
showing, and rendering templates never executes workflow steps or mutates the
|
|
@@ -575,6 +674,8 @@ arguments, and implicit Codewith/Codex full-access defaults. If a custom
|
|
|
575
674
|
Codewith/Codex template uses `permissionMode: "bypass"`, it must also set
|
|
576
675
|
`sandbox` to `workspace-write` or `read-only`. Use built-in templates with
|
|
577
676
|
explicit break-glass handling for emergency workflows that need full access.
|
|
677
|
+
Claude, Cursor, AI Copilot, and OpenCode bypass modes always require explicit
|
|
678
|
+
break-glass plus a non-empty safety reason.
|
|
578
679
|
|
|
579
680
|
For event-driven task automation, `loops routes create todos-task` reads a
|
|
580
681
|
Hasna event envelope from stdin or `HASNA_EVENT_JSON`, records a
|
|
@@ -676,10 +777,10 @@ cat event.json | loops routes create generic \
|
|
|
676
777
|
```
|
|
677
778
|
|
|
678
779
|
This is the intended deterministic-to-agentic path: a producer creates a todos
|
|
679
|
-
task, `@hasna/events` delivers `task.created`,
|
|
680
|
-
and admission item,
|
|
780
|
+
task, `@hasna/events` delivers `task.created`, Loops records the invocation
|
|
781
|
+
and admission item, Loops creates a worker/verifier workflow when admitted,
|
|
681
782
|
and the workflow updates todos with evidence. Use account pools so worker and
|
|
682
|
-
verifier steps do not burn the same profile. For live Codewith drains,
|
|
783
|
+
verifier steps do not burn the same profile. For live Codewith drains, Loops
|
|
683
784
|
spreads each route to the **least-loaded** pool account (counting running steps
|
|
684
785
|
per account) and still keeps the verifier on a different profile than the worker;
|
|
685
786
|
the deterministic hash is the tie-break, so a cold store reproduces the previous
|
|
@@ -692,9 +793,9 @@ Use `--dry-run` to inspect the rendered invocation, work item, workflow, and
|
|
|
692
793
|
loop input without storing anything.
|
|
693
794
|
|
|
694
795
|
Generated worker/verifier workflows fail closed when `sandbox=danger-full-access`
|
|
695
|
-
is requested without `manualBreakGlass=true
|
|
696
|
-
unattended task/event routes. Full
|
|
697
|
-
not a default automation mode.
|
|
796
|
+
is requested without both `manualBreakGlass=true` and a non-empty
|
|
797
|
+
`safetyReason`. Use `workspace-write` for unattended task/event routes. Full
|
|
798
|
+
access is an explicit manual emergency path, not a default automation mode.
|
|
698
799
|
|
|
699
800
|
When a sandboxed Codewith/Codex worker must update app stores outside the repo
|
|
700
801
|
worktree, pass those stores explicitly with `--add-dir` or template `addDirs`.
|
|
@@ -786,7 +887,21 @@ anti-hog caps counted over all routes. Raise a router's `--max-active`
|
|
|
786
887
|
deliberately once counting is per-route; keep `--max-per-profile` set so the
|
|
787
888
|
extra concurrency spreads across subscription accounts.
|
|
788
889
|
|
|
789
|
-
|
|
890
|
+
Route flags or an expanded named policy are authoritative ceilings. Positive
|
|
891
|
+
integer task/event fields (`max_active`, `max_active_per_project`, and
|
|
892
|
+
`max_active_per_project_group`, including camel-case aliases) may only tighten
|
|
893
|
+
an already configured ceiling for that admission attempt; metadata cannot
|
|
894
|
+
create a cap, raise it, or override an explicit `--project-group`. Generated
|
|
895
|
+
workflow prompts and invocation manifests include the resolved route scope,
|
|
896
|
+
project group, and throttle limits so the admission decision is auditable from
|
|
897
|
+
the same evidence the agent sees.
|
|
898
|
+
|
|
899
|
+
These caps are local-store admission controls, not a distributed semaphore. The
|
|
900
|
+
count and admission write are serialized in one transaction for routers sharing
|
|
901
|
+
the same local `loops.db`; different `LOOPS_DATA_DIR` databases or machines do
|
|
902
|
+
not share project-group counts.
|
|
903
|
+
|
|
904
|
+
`--max-active` and related route throttles count Loops routed workflow work
|
|
790
905
|
items; they do not know whether Codewith is already at its live background-agent
|
|
791
906
|
admission limit. Add `--provider-active-cap <n>` (or the Codewith-specific alias
|
|
792
907
|
`--codewith-active-cap <n>`) to make the route run `codewith agent diagnostics
|
|
@@ -811,7 +926,7 @@ Workflow run manifests are written under
|
|
|
811
926
|
|
|
812
927
|
## OpenAutomations Runtime Binding
|
|
813
928
|
|
|
814
|
-
|
|
929
|
+
Loops executes automations that external compilers have materialized; it
|
|
815
930
|
does not own the automation product surface. See
|
|
816
931
|
[Runtime Boundary](docs/RUNTIME_BOUNDARY.md) for the full ownership table,
|
|
817
932
|
three handoff paths (claim-queue, planned `@hasna/actions` upsert-one-shot, and
|
|
@@ -833,7 +948,7 @@ automations queue complete <action-id> --runner open-loops:<worker-id>
|
|
|
833
948
|
```
|
|
834
949
|
|
|
835
950
|
For explicit event workflow routing, OpenAutomations can export the normalized
|
|
836
|
-
event envelope and
|
|
951
|
+
event envelope and Loops can consume it through the existing generic event
|
|
837
952
|
route:
|
|
838
953
|
|
|
839
954
|
```bash
|
|
@@ -841,13 +956,13 @@ automations --json webhooks event <route> --body-json '<json>' \
|
|
|
841
956
|
| loops --json routes create generic
|
|
842
957
|
```
|
|
843
958
|
|
|
844
|
-
This is not automation materialization in
|
|
959
|
+
This is not automation materialization in Loops. It is an explicit
|
|
845
960
|
event-envelope workflow handoff: OpenAutomations owns deterministic automation
|
|
846
|
-
specs, webhook normalization, queue state, approvals, DLQ, and replay;
|
|
961
|
+
specs, webhook normalization, queue state, approvals, DLQ, and replay; Loops
|
|
847
962
|
owns agent workflow invocation after the operator routes the envelope to
|
|
848
963
|
`loops routes create generic`.
|
|
849
964
|
|
|
850
|
-
Do not store automation specs in
|
|
965
|
+
Do not store automation specs in Loops, infer automation triggers from event
|
|
851
966
|
transport alone, or replace the OpenAutomations queue with loop/workflow rows.
|
|
852
967
|
When a loop or workflow is used for execution, keep `HASNA_AUTOMATIONS_DIR`
|
|
853
968
|
pointing at the owning OpenAutomations data root and preserve the runner id in
|
|
@@ -856,7 +971,7 @@ completion/failure calls so OpenAutomations can enforce action leases.
|
|
|
856
971
|
### Planned Workflow Upsert SDK
|
|
857
972
|
|
|
858
973
|
External compilers such as `@hasna/actions` and `@hasna/automations` should not
|
|
859
|
-
write
|
|
974
|
+
write Loops SQLite rows directly. The stable contract should be an
|
|
860
975
|
idempotent CLI/SDK upsert that accepts a fully rendered one-shot workflow loop
|
|
861
976
|
request and returns durable refs.
|
|
862
977
|
|
|
@@ -869,7 +984,7 @@ does not duplicate that spec.
|
|
|
869
984
|
That design doc also defines the planned `@hasna/actions` target binding. The
|
|
870
985
|
binding reuses `ActionManifest`, `ActionInvocation`, `ActionRunStatus`,
|
|
871
986
|
`ActionQueueStatus`, action-owned idempotency keys, action audit events, and
|
|
872
|
-
dead-letter/replay records.
|
|
987
|
+
dead-letter/replay records. Loops only admits and executes the handed-off
|
|
873
988
|
workflow, then returns workflow refs for action-owned evidence.
|
|
874
989
|
|
|
875
990
|
The same design doc covers the planned DLQ/dead-letter lifecycle, including
|
|
@@ -883,7 +998,7 @@ work.
|
|
|
883
998
|
|
|
884
999
|
## Transcript-Driven Loops
|
|
885
1000
|
|
|
886
|
-
|
|
1001
|
+
Loops can turn long-form media or meeting transcripts into recurring workflow work when paired with `iapp-transcriber`. The template at `docs/workflows/transcript-feedback-to-loops.json` transcribes an authorized media URL, asks an agent to extract recurring loop candidates, authors workflow specs, and validates generated workflows before scheduling. Copy it into the target repo, replace `/path/to/repo` with that repo's absolute path, and provide `TRANSCRIBER_SOURCE_URL` through the runner environment or a private, uncommitted workflow copy before storing or scheduling it. Do not commit private or signed media URLs.
|
|
887
1002
|
|
|
888
1003
|
```bash
|
|
889
1004
|
loops workflows validate /path/to/repo/.openloops/transcript-feedback-to-loops.json --preflight
|
|
@@ -993,7 +1108,7 @@ Archived loops are hidden from the default `loops list`, excluded from daemon sc
|
|
|
993
1108
|
|
|
994
1109
|
`loops run-now` reports the manual run source:
|
|
995
1110
|
|
|
996
|
-
- `source=ad_hoc`: the loop was not due yet, so
|
|
1111
|
+
- `source=ad_hoc`: the loop was not due yet, so Loops created a one-off manual slot. This is a single immediate attempt and does not schedule retries or consume the future scheduled slot.
|
|
997
1112
|
- `source=due_slot`: the persisted scheduled slot was already due, so the manual run claims that slot and advances or retries the loop using normal scheduler rules.
|
|
998
1113
|
- `source=retry_slot`: the loop was waiting on a failed slot retry, so the manual run claims that retry slot and advances the loop using normal retry rules.
|
|
999
1114
|
|
|
@@ -1042,16 +1157,84 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
1042
1157
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
1043
1158
|
|
|
1044
1159
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
1045
|
-
- Codewith runs non-interactive `codewith --ask-for-approval never exec --json` sessions by default. exec starts a fresh session per invocation, avoiding the multi-megabyte rollout history that `codewith agent start` reloaded every turn (which drove `context_length_exceeded` silent no-ops), and it keeps network egress for gh/git — the `workspace-write` sandbox opts back into `sandbox_workspace_write.network_access`. Codewith exec is remote-capable like codex.
|
|
1160
|
+
- Codewith runs non-interactive `codewith --ask-for-approval never exec --json` sessions by default. exec starts a fresh session per invocation, avoiding the multi-megabyte rollout history that `codewith agent start` reloaded every turn (which drove `context_length_exceeded` silent no-ops), and it keeps network egress for gh/git — the `workspace-write` sandbox opts back into `sandbox_workspace_write.network_access`. Codewith exec is remote-capable like codex.
|
|
1046
1161
|
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
1047
|
-
- Cursor is CLI-first for now via the standalone `agent -p` binary.
|
|
1162
|
+
- Cursor is CLI-first for now via the standalone `agent -p` binary. Loops no longer falls back to `cursor agent`; install the standalone Cursor Agent CLI so preflight and scheduled runs use the same executable.
|
|
1048
1163
|
- Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
1049
1164
|
- Agent prompts are sent through child stdin instead of argv where the provider supports stdin, including Codewith `exec` (which reads instructions from stdin when no positional prompt is given), so the prompt never lands on argv.
|
|
1050
|
-
- When `--account` or a step `account` is set,
|
|
1051
|
-
- `--auth-profile` and step `authProfile` are provider-native
|
|
1165
|
+
- When `--account` or a step `account` is set, Loops resolves `accounts env <profile> --tool <tool>` before spawning the target, strips inherited tool home/API-key variables, and applies the selected profile only to that process. Missing account profiles fail before the provider binary receives the prompt.
|
|
1166
|
+
- `--auth-profile` and step `authProfile` are provider-native Codewith selectors passed as `--auth-profile <name>` on the `exec` invocation; they do not call OpenAccounts. Local and remote preflight share the JSON-first, exact-name contract described above, use human-table parsing only as a compatibility fallback, and fail closed for unusable or missing profiles, NUL-containing names, and non-fallback list failures.
|
|
1052
1167
|
- `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
|
|
1168
|
+
- `--allow-tool` and `--allow-command` declare advisory, metadata-only
|
|
1169
|
+
restrictions and require `--safety-reason`. The exact contract is persisted
|
|
1170
|
+
and emitted for audit, but Loops does not claim provider-side enforcement.
|
|
1171
|
+
Relaxed sandboxes and native provider bypass modes also require explicit
|
|
1172
|
+
`--manual-break-glass`.
|
|
1053
1173
|
- `--permission-mode` maps `plan`, `auto`, and `bypass` where the provider supports it. Claude uses native permission modes, Cursor maps bypass to `--force`, and OpenCode/AICopilot map bypass to `--dangerously-skip-permissions`.
|
|
1174
|
+
- `extraArgs` is fail-closed: every provider currently rejects non-empty
|
|
1175
|
+
passthrough arguments, including unknown options, positional subcommands,
|
|
1176
|
+
split values, `--option=value`, and attached short-option forms. Configure
|
|
1177
|
+
execution, output, permissions, sandboxing, model, cwd, and other supported
|
|
1178
|
+
behavior through the modeled agent-target fields. A passthrough option must
|
|
1179
|
+
be explicitly reviewed and added to the provider allowlist before use.
|
|
1180
|
+
Legacy persisted targets are not silently accepted or rewritten: execution
|
|
1181
|
+
fails validation until `extraArgs` is removed and replaced with modeled
|
|
1182
|
+
fields. API and migration imports reject those targets instead of stripping
|
|
1183
|
+
the arguments; update the source record and retry the import.
|
|
1054
1184
|
- `--variant` is provider-specific reasoning/model effort. Claude maps it to `--effort`, Codewith/Codex map it to `model_reasoning_effort`, and OpenCode/AICopilot pass `--variant`.
|
|
1055
1185
|
- Daemon and scheduled runs prepend common user executable directories such as `~/.local/bin` and `~/.bun/bin` before resolving provider CLIs.
|
|
1056
1186
|
|
|
1187
|
+
For hosted workflow runs, the control plane derives any required agent session
|
|
1188
|
+
contract from the stored workflow step and persists it before execution. Reusing an
|
|
1189
|
+
older workflow run backfills a missing valid contract idempotently. Stored
|
|
1190
|
+
pre-contract workflows with unsafe/invalid agent options, mismatched stored
|
|
1191
|
+
contracts, duplicate contracts, command-step contracts, and client-fabricated
|
|
1192
|
+
contracts fail closed. Direct agent loops continue to expose their contract
|
|
1193
|
+
through prompt/environment metadata only.
|
|
1194
|
+
|
|
1057
1195
|
For production loops that can mutate repos, use disposable worktrees (`--worktree-mode required` / `worktreeMode=required`) and explicit prompts that name allowed write scope. Worktrees are executor-enforced: when a target carries worktree metadata, the executor prepares and enters the git worktree before spawning the child process, records the worktree it entered, and with `mode=required` fails the run closed instead of falling back to the original checkout when preparation fails. Existing managed worktrees are reused only after top-level and git-common-dir checks; a clean detached or stale-branch checkout may be reattached to the expected generated branch, while dirty or unsafe mismatches fail with cleanup evidence. Remote machine runs with a required worktree apply the same checks on the remote side before the target executes.
|
|
1196
|
+
|
|
1197
|
+
## Immutable ECR Candidate Images
|
|
1198
|
+
|
|
1199
|
+
`.github/workflows/ecr-candidate.yml` is the repository workflow for building
|
|
1200
|
+
an AWS ECR release candidate. It is manual and source-only: it does not update
|
|
1201
|
+
`latest`, ECS task definitions, services, or deployments. The job checks out
|
|
1202
|
+
an exact full commit SHA, proves that commit is reachable from `origin/main`,
|
|
1203
|
+
builds the `Dockerfile` `runner` target natively for `linux/arm64`, blocks on
|
|
1204
|
+
local Trivy critical/high findings, and only then assumes the ECR push role.
|
|
1205
|
+
After the immutable push it waits for ECR scanning and blocks on critical/high
|
|
1206
|
+
findings there as well. The retained artifact contains the local scan report,
|
|
1207
|
+
a CycloneDX SBOM, an unsigned in-toto/SLSA provenance statement, and bounded
|
|
1208
|
+
ECR scan counts. The statement is evidence, not a cryptographic attestation.
|
|
1209
|
+
|
|
1210
|
+
Before the first run, repository administrators must create a protected GitHub
|
|
1211
|
+
environment named `ecr-candidate` with required reviewers and no deployment
|
|
1212
|
+
branches other than `main`. Configure these repository variables (not secrets):
|
|
1213
|
+
|
|
1214
|
+
- `AWS_REGION`: AWS region containing the existing candidate repository.
|
|
1215
|
+
- `AWS_ROLE_ARN`: OIDC role dedicated to this workflow.
|
|
1216
|
+
- `ECR_REPOSITORY`: existing ECR repository name. It must use immutable tags
|
|
1217
|
+
and scan-on-push.
|
|
1218
|
+
|
|
1219
|
+
The OIDC role trust must constrain `token.actions.githubusercontent.com:aud`
|
|
1220
|
+
to `sts.amazonaws.com` and `token.actions.githubusercontent.com:sub` to exactly
|
|
1221
|
+
`repo:hasna/loops:environment:ecr-candidate`. Its permissions should allow
|
|
1222
|
+
`ecr:GetAuthorizationToken` on `*`, and only these actions on the configured
|
|
1223
|
+
repository ARN: `ecr:DescribeRepositories`, `ecr:BatchCheckLayerAvailability`,
|
|
1224
|
+
`ecr:GetDownloadUrlForLayer`, `ecr:BatchGetImage`, `ecr:InitiateLayerUpload`,
|
|
1225
|
+
`ecr:UploadLayerPart`, `ecr:CompleteLayerUpload`, `ecr:PutImage`,
|
|
1226
|
+
`ecr:DescribeImages`, and `ecr:DescribeImageScanFindings`. No static AWS access
|
|
1227
|
+
key is used.
|
|
1228
|
+
|
|
1229
|
+
Dispatch only after the source commit is merged to `main`:
|
|
1230
|
+
|
|
1231
|
+
```bash
|
|
1232
|
+
sha="$(git rev-parse origin/main)"
|
|
1233
|
+
gh workflow run ecr-candidate.yml --repo hasna/loops --ref main \
|
|
1234
|
+
-f source_sha="${sha}" -f confirmation="push ${sha}"
|
|
1235
|
+
```
|
|
1236
|
+
|
|
1237
|
+
Dispatch is an AWS mutation and requires the applicable operator approval and
|
|
1238
|
+
fresh release preflight. Do not run it merely because the source workflow has
|
|
1239
|
+
merged. Review the workflow summary and retained evidence; promote only by
|
|
1240
|
+
the reported immutable digest through the separate deployment process.
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { type CircuitBreakerThreshold } from "../lib/advancement.js";
|
|
2
3
|
import type { LoopStorageContract } from "../lib/storage/contract.js";
|
|
4
|
+
import { type RoutePolicy } from "../lib/auth/route-policy.js";
|
|
5
|
+
import type { TenantAuthContext, TenantAuthDecision } from "../lib/auth/tenant-auth.js";
|
|
3
6
|
/** The serve OpenAPI document (source of the generated SDK), version-synced. */
|
|
4
7
|
export declare function openApiDocument(): Record<string, unknown>;
|
|
5
8
|
export declare function apiStatus(): {
|
|
@@ -7,23 +10,12 @@ export declare function apiStatus(): {
|
|
|
7
10
|
service: string;
|
|
8
11
|
status: import("../index.js").LoopDeploymentStatus;
|
|
9
12
|
};
|
|
10
|
-
/**
|
|
11
|
-
* Framework-agnostic API-key verifier shape (matches
|
|
12
|
-
* `@hasna/contracts/auth` `ApiKeyVerifier`). Kept structural so the api module
|
|
13
|
-
* has no hard dependency on the auth package: the serve entry injects the real
|
|
14
|
-
* verifier built from the vendored kit's client + the HMAC signing secret.
|
|
15
|
-
*/
|
|
16
13
|
export interface ApiAuthenticator {
|
|
17
|
-
authenticate(headers: Headers, context
|
|
18
|
-
method
|
|
19
|
-
path
|
|
20
|
-
|
|
21
|
-
}): Promise<
|
|
22
|
-
ok: boolean;
|
|
23
|
-
status: number;
|
|
24
|
-
reason?: string;
|
|
25
|
-
message?: string;
|
|
26
|
-
}>;
|
|
14
|
+
authenticate(headers: Headers, context: {
|
|
15
|
+
method: string;
|
|
16
|
+
path: string;
|
|
17
|
+
policy: RoutePolicy;
|
|
18
|
+
}): Promise<TenantAuthDecision>;
|
|
27
19
|
}
|
|
28
20
|
export interface LoopsApiServerOptions {
|
|
29
21
|
host?: string;
|
|
@@ -33,21 +25,30 @@ export interface LoopsApiServerOptions {
|
|
|
33
25
|
evidenceLimitBytes?: number;
|
|
34
26
|
importLimitBytes?: number;
|
|
35
27
|
now?: () => Date;
|
|
28
|
+
random?: () => number;
|
|
29
|
+
circuitBreakerThreshold?: CircuitBreakerThreshold;
|
|
36
30
|
/**
|
|
37
31
|
* API-key verifier (from `@hasna/contracts/auth`). When present, every
|
|
38
32
|
* request outside the open foundation probes (`/health`, `/ready`,
|
|
39
|
-
* `/version`, `/
|
|
33
|
+
* `/version`, `/openapi.json`) must present a valid `loops:*` scoped key. This is
|
|
40
34
|
* the internet-facing auth path (no bearer token, no loopback bypass).
|
|
41
35
|
*/
|
|
42
36
|
authenticator?: ApiAuthenticator;
|
|
37
|
+
withTenantStorage?: <T>(principal: TenantAuthContext, fn: (storage: LoopStorageContract) => Promise<T>) => Promise<T>;
|
|
43
38
|
/**
|
|
44
39
|
* Readiness probe. Should prove the storage backend is reachable AND fully
|
|
45
|
-
* migrated. Returns
|
|
40
|
+
* migrated. Returns a stable public code only. Defaults to a storage list probe.
|
|
46
41
|
*/
|
|
47
42
|
readyCheck?: () => Promise<{
|
|
48
43
|
ready: boolean;
|
|
49
|
-
|
|
44
|
+
code?: "storage_unconfigured" | "storage_unreachable" | "auth_unreachable" | "unsafe_database_role" | "pending_migrations" | "unknown_migrations" | "migration_checksum_mismatch";
|
|
50
45
|
}>;
|
|
51
46
|
}
|
|
47
|
+
export declare function contractHealthResponse(env?: Record<string, string | undefined>): {
|
|
48
|
+
status: "ok";
|
|
49
|
+
version: string;
|
|
50
|
+
mode: "local" | "cloud";
|
|
51
|
+
};
|
|
52
52
|
export declare function createLoopsApiServer(opts?: LoopsApiServerOptions): Bun.Server<undefined>;
|
|
53
|
+
export declare function logApiCommandFailure(error: unknown): void;
|
|
53
54
|
export declare function main(argv?: string[]): Promise<void>;
|