@hasna/loops 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +75 -33
- package/dist/api/index.d.ts +17 -0
- package/dist/api/index.js +1290 -0
- package/dist/cli/index.js +738 -201
- package/dist/daemon/index.js +64 -15
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1005 -155
- package/dist/lib/format.d.ts +3 -1
- package/dist/lib/mode.d.ts +48 -0
- package/dist/lib/mode.js +276 -0
- package/dist/lib/route/todos-cli.d.ts +1 -0
- package/dist/lib/route/types.d.ts +10 -0
- package/dist/lib/storage/contract.d.ts +133 -0
- package/dist/lib/storage/contract.js +1 -0
- package/dist/lib/storage/index.d.ts +6 -0
- package/dist/lib/storage/index.js +4612 -0
- package/dist/lib/storage/postgres-schema.d.ts +4 -0
- package/dist/lib/storage/postgres-schema.js +328 -0
- package/dist/lib/storage/postgres.d.ts +22 -0
- package/dist/lib/storage/postgres.js +423 -0
- package/dist/lib/storage/sqlite.d.ts +84 -0
- package/dist/lib/storage/sqlite.js +4187 -0
- package/dist/lib/store.d.ts +3 -0
- package/dist/lib/store.js +27 -10
- package/dist/lib/template-kit.d.ts +10 -8
- package/dist/lib/templates.d.ts +1 -0
- package/dist/mcp/index.js +64 -15
- package/dist/runner/index.d.ts +32 -0
- package/dist/runner/index.js +2037 -0
- package/dist/sdk/index.js +33 -15
- package/dist/types.d.ts +1 -1
- package/docs/DEPLOYMENT_MODES.md +148 -0
- package/docs/USAGE.md +66 -32
- package/package.json +34 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ documented in this file. Version entries are generated from the
|
|
|
5
5
|
conventional-commit git history; one commit maps to one released patch version
|
|
6
6
|
unless noted.
|
|
7
7
|
|
|
8
|
+
## 0.4.1 (2026-07-03)
|
|
9
|
+
|
|
10
|
+
Contract-foundation release for the Mailery-style local/self-hosted/cloud
|
|
11
|
+
deployment split.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Deployment-mode contract docs covering `local`, `self_hosted`, and `cloud`
|
|
16
|
+
authority, cache/spool, API, runner, and hosted-boundary semantics.
|
|
17
|
+
- Public mode resolver and status helpers exported from `@hasna/loops` and the
|
|
18
|
+
`@hasna/loops/mode` subpath.
|
|
19
|
+
- CLI status surfaces: `loops mode`, `loops self-hosted status`, and
|
|
20
|
+
`loops cloud status`.
|
|
21
|
+
- Foundation binaries and package subpaths for `loops-api` / `@hasna/loops/api`
|
|
22
|
+
and `loops-runner` / `@hasna/loops/runner`.
|
|
23
|
+
- Private-hosted-boundary test coverage to keep hosted implementation details
|
|
24
|
+
and obvious credential patterns out of the public package.
|
|
25
|
+
|
|
8
26
|
## 0.4.0 (2026-07-02)
|
|
9
27
|
|
|
10
28
|
Audit-hardening release: write-path secret scrubbing, process-group reaping,
|
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@ OpenLoops is a local CLI and daemon for persistent loops and workflows: schedule
|
|
|
5
5
|
Naming: the product is **OpenLoops**, 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
|
-
binaries are `loops`, `loops-daemon`,
|
|
8
|
+
binaries are `loops`, `loops-daemon`, `loops-api`, `loops-runner`, and
|
|
9
|
+
`loops-mcp`.
|
|
9
10
|
|
|
10
11
|
It supports deterministic command loops, JSON-defined workflows, and guarded CLI adapters for headless coding agents:
|
|
11
12
|
|
|
@@ -16,12 +17,47 @@ It supports deterministic command loops, JSON-defined workflows, and guarded CLI
|
|
|
16
17
|
- `opencode run`
|
|
17
18
|
- `codex exec`
|
|
18
19
|
|
|
20
|
+
## Deployment Modes
|
|
21
|
+
|
|
22
|
+
OpenLoops has three deployment modes:
|
|
23
|
+
|
|
24
|
+
- `local`: SQLite in `LOOPS_DATA_DIR` is authoritative and `loops-daemon` executes scheduled work.
|
|
25
|
+
- `self_hosted`: a user-operated `loops-api` control plane contract. This release exposes status, storage-backed `/v1` loop CRUD and run listing, runner claim/heartbeat/finalize protocol endpoints, and a one-shot `loops-runner run-once` execution path for embedded control-plane hosts; full fleet rollout and migration tooling are follow-up work.
|
|
26
|
+
- `cloud`: a hosted control-plane contract. This release exposes client/runner status only; hosted tenant auth and infrastructure live outside this package.
|
|
27
|
+
|
|
28
|
+
`local` is the default and requires no network, token, Postgres, or hosted
|
|
29
|
+
service. Set `LOOPS_MODE` or `HASNA_LOOPS_MODE` to `local`, `self_hosted`, or
|
|
30
|
+
`cloud` to choose explicitly. Without an explicit mode, `LOOPS_CLOUD_API_URL`
|
|
31
|
+
selects `cloud`, while `LOOPS_API_URL` or `LOOPS_DATABASE_URL` selects
|
|
32
|
+
`self_hosted`.
|
|
33
|
+
|
|
34
|
+
The public `@hasna/loops` package owns the local runtime, mode resolver,
|
|
35
|
+
self-hosted API contract, runner contract, SDK, MCP server, and CLI. Hosted
|
|
36
|
+
tenant auth, account administration, and infrastructure live outside this
|
|
37
|
+
package. Cloud mode is a public contract until a cloud-specific hosted URL and
|
|
38
|
+
cloud token are configured through `LOOPS_CLOUD_API_URL` plus
|
|
39
|
+
`LOOPS_CLOUD_TOKEN` or `HASNA_LOOPS_CLOUD_TOKEN`.
|
|
40
|
+
|
|
41
|
+
Useful status commands:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
loops mode
|
|
45
|
+
loops --json mode
|
|
46
|
+
loops self-hosted status
|
|
47
|
+
loops cloud status
|
|
48
|
+
loops-api status
|
|
49
|
+
loops-runner status
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
See [Deployment Modes](docs/DEPLOYMENT_MODES.md) for the full package boundary
|
|
53
|
+
and machine-placement contract.
|
|
54
|
+
|
|
19
55
|
## Install
|
|
20
56
|
|
|
21
57
|
**OpenLoops requires the [Bun](https://bun.sh) runtime (`bun >= 1.0`).** The
|
|
22
|
-
`loops`, `loops-daemon`, and `loops-mcp` binaries
|
|
23
|
-
shebang, so Bun must be on `PATH` even when the
|
|
24
|
-
Node.js is not a supported runtime.
|
|
58
|
+
`loops`, `loops-daemon`, `loops-api`, `loops-runner`, and `loops-mcp` binaries
|
|
59
|
+
run with a `#!/usr/bin/env bun` shebang, so Bun must be on `PATH` even when the
|
|
60
|
+
package is installed with npm. Node.js is not a supported runtime.
|
|
25
61
|
|
|
26
62
|
From npm (with Bun installed):
|
|
27
63
|
|
|
@@ -67,8 +103,9 @@ The package also exports the server factory for embedded hosts:
|
|
|
67
103
|
import { createLoopsMcpServer } from "@hasna/loops/mcp";
|
|
68
104
|
```
|
|
69
105
|
|
|
70
|
-
Available read tools include `loops_list`, `loops_show`, `
|
|
71
|
-
`loops_doctor`, `
|
|
106
|
+
Available read tools include `loops_list`, `loops_show`, `loops_runs`,
|
|
107
|
+
`loops_doctor`, `loops_workflows_list`, `loops_workflow_read`, and
|
|
108
|
+
`loops_workflow_validate`.
|
|
72
109
|
Resources are available at `loops://runtime` and `loops://tools`.
|
|
73
110
|
Those tools use the same `Store`, public redaction helpers, and workflow parser
|
|
74
111
|
as the CLI and SDK, so read output and validation behavior stay aligned across
|
|
@@ -76,15 +113,19 @@ surfaces.
|
|
|
76
113
|
|
|
77
114
|
Mutation tools are disabled by default. Start the server with
|
|
78
115
|
`LOOPS_MCP_ALLOW_MUTATIONS=true` only for a trusted local MCP host that should be
|
|
79
|
-
allowed to change loop state.
|
|
80
|
-
|
|
81
|
-
`
|
|
82
|
-
|
|
116
|
+
allowed to change loop state. The guarded mutation tools use canonical names:
|
|
117
|
+
`loops_pause`, `loops_resume`, `loops_stop`, `loops_run_now`, `loops_archive`,
|
|
118
|
+
`loops_unarchive`, `loops_create_command`, and `loops_create_workflow`.
|
|
119
|
+
Deprecated `loop_*` aliases are still registered where compatibility needs them,
|
|
120
|
+
but callers should use the `loops_*` names. Mutation tools do not require or
|
|
121
|
+
accept confirmation-string parameters; the server-side environment opt-in is the
|
|
122
|
+
gate. MCP `loops_run_now` schedules the loop for immediate daemon pickup; inline
|
|
123
|
+
execution remains CLI-only.
|
|
83
124
|
|
|
84
125
|
Keep host-affecting or long-running operations on the CLI: daemon
|
|
85
|
-
start/stop/install/logs, inline `run-now`, `tick`, loop
|
|
86
|
-
|
|
87
|
-
|
|
126
|
+
start/stop/install/logs, inline `run-now`, `tick`, loop deletion, workflow
|
|
127
|
+
create/migrate/cancel/recover, agent loop creation, template materialization,
|
|
128
|
+
and event-route drains.
|
|
88
129
|
|
|
89
130
|
## Create Loops
|
|
90
131
|
|
|
@@ -236,7 +277,7 @@ Inspect configured and runtime goal state:
|
|
|
236
277
|
|
|
237
278
|
```bash
|
|
238
279
|
loops goal show <loop-or-goal-id>
|
|
239
|
-
loops goal
|
|
280
|
+
loops goal show <goal-run-id-or-loop-run-id>
|
|
240
281
|
```
|
|
241
282
|
|
|
242
283
|
Workflow JSON can also embed goals at the workflow or step level:
|
|
@@ -339,11 +380,11 @@ loops templates render todos-task-worker-verifier \
|
|
|
339
380
|
--var taskTitle="Fix parser" \
|
|
340
381
|
--var projectPath=/path/to/repo \
|
|
341
382
|
--var provider=codewith \
|
|
342
|
-
--var authProfilePool=
|
|
383
|
+
--var authProfilePool=account001,account002,account003 \
|
|
343
384
|
--var sandbox=workspace-write \
|
|
344
385
|
--var todosProjectPath=$HOME/.hasna/loops \
|
|
345
386
|
--var addDirs=$HOME/.hasna/todos,$HOME/.hasna/loops
|
|
346
|
-
loops
|
|
387
|
+
loops workflows create --template todos-task-worker-verifier \
|
|
347
388
|
--var taskId=<task-id> \
|
|
348
389
|
--var projectPath=/path/to/repo
|
|
349
390
|
loops templates render event-worker-verifier \
|
|
@@ -356,7 +397,7 @@ loops templates render bounded-agent-worker-verifier \
|
|
|
356
397
|
--var objective="Check docs drift and queue tasks for gaps" \
|
|
357
398
|
--var projectPath=/path/to/repo \
|
|
358
399
|
--var provider=codewith \
|
|
359
|
-
--var authProfilePool=
|
|
400
|
+
--var authProfilePool=account001,account002 \
|
|
360
401
|
--var sandbox=workspace-write \
|
|
361
402
|
--var worktreeMode=required
|
|
362
403
|
loops templates render pr-review \
|
|
@@ -441,30 +482,31 @@ loops templates show custom-report
|
|
|
441
482
|
loops templates render custom-report \
|
|
442
483
|
--var objective="Check docs drift" \
|
|
443
484
|
--var projectPath=/path/to/repo
|
|
444
|
-
loops
|
|
485
|
+
loops workflows create --template custom-report \
|
|
445
486
|
--var objective="Check docs drift" \
|
|
446
487
|
--var projectPath=/path/to/repo
|
|
447
488
|
```
|
|
448
489
|
|
|
449
490
|
Use `--source builtin`, `--source custom`, or `--source all` on
|
|
450
|
-
`list`, `show`, `render`, and
|
|
451
|
-
|
|
491
|
+
`templates list`, `templates show`, `templates render`, and
|
|
492
|
+
`workflows create --template` when automation needs an explicit source. Custom
|
|
493
|
+
template ids and names cannot override built-ins.
|
|
452
494
|
Custom templates fail closed for `danger-full-access`, dangerous passthrough
|
|
453
495
|
arguments, and implicit Codewith/Codex full-access defaults. If a custom
|
|
454
496
|
Codewith/Codex template uses `permissionMode: "bypass"`, it must also set
|
|
455
497
|
`sandbox` to `workspace-write` or `read-only`. Use built-in templates with
|
|
456
498
|
explicit break-glass handling for emergency workflows that need full access.
|
|
457
499
|
|
|
458
|
-
For event-driven task automation, `loops
|
|
500
|
+
For event-driven task automation, `loops routes create todos-task` reads a
|
|
459
501
|
Hasna event envelope from stdin or `HASNA_EVENT_JSON`, records a
|
|
460
502
|
`WorkflowInvocation`, upserts an admission work item, and admits that work item
|
|
461
503
|
into a deduped one-shot workflow loop when route capacity allows:
|
|
462
504
|
|
|
463
505
|
```bash
|
|
464
|
-
cat task-created-event.json | loops
|
|
506
|
+
cat task-created-event.json | loops routes create todos-task \
|
|
465
507
|
--template task-lifecycle \
|
|
466
508
|
--provider codewith \
|
|
467
|
-
--auth-profile-pool
|
|
509
|
+
--auth-profile-pool account001,account002,account003 \
|
|
468
510
|
--permission-mode bypass \
|
|
469
511
|
--sandbox workspace-write \
|
|
470
512
|
--todos-project "$HOME/.hasna/loops" \
|
|
@@ -497,7 +539,7 @@ selected.
|
|
|
497
539
|
loops routes drain todos-task \
|
|
498
540
|
--dry-run \
|
|
499
541
|
--provider-rule area=frontend:claude:claude-ui-a,claude-ui-b \
|
|
500
|
-
--provider-rule area=backend:codewith:
|
|
542
|
+
--provider-rule area=backend:codewith:account001,account002 \
|
|
501
543
|
--worktree-mode required
|
|
502
544
|
```
|
|
503
545
|
|
|
@@ -534,9 +576,9 @@ For other Hasna apps that expose `@hasna/events` webhooks, use the generic
|
|
|
534
576
|
handler:
|
|
535
577
|
|
|
536
578
|
```bash
|
|
537
|
-
cat event.json | loops
|
|
579
|
+
cat event.json | loops routes create generic \
|
|
538
580
|
--provider codewith \
|
|
539
|
-
--auth-profile-pool
|
|
581
|
+
--auth-profile-pool account001,account002,account003 \
|
|
540
582
|
--permission-mode bypass \
|
|
541
583
|
--sandbox workspace-write \
|
|
542
584
|
--project-path /path/to/repo \
|
|
@@ -579,7 +621,7 @@ loops routes requeue <work-item-id> --reason "fixed upstream blocker"
|
|
|
579
621
|
loops routes invocations
|
|
580
622
|
```
|
|
581
623
|
|
|
582
|
-
For
|
|
624
|
+
For OSS task-created routing, use a deterministic drain instead of tmux
|
|
583
625
|
dispatch:
|
|
584
626
|
|
|
585
627
|
```bash
|
|
@@ -587,10 +629,10 @@ loops routes schedule todos-task oss-task-route-drain \
|
|
|
587
629
|
--every 5m \
|
|
588
630
|
--todos-project "$HOME/.hasna/loops" \
|
|
589
631
|
--template task-lifecycle \
|
|
590
|
-
--project-path-prefix /
|
|
632
|
+
--project-path-prefix "$HOME/workspace/example/opensource" \
|
|
591
633
|
--tags auto:route \
|
|
592
634
|
--provider codewith \
|
|
593
|
-
--auth-profile-pool
|
|
635
|
+
--auth-profile-pool account001,account002,account003 \
|
|
594
636
|
--add-dir "$HOME/.hasna/todos,$HOME/.hasna/loops" \
|
|
595
637
|
--project-group oss \
|
|
596
638
|
--max-dispatch 2 \
|
|
@@ -633,7 +675,7 @@ import { openAutomationsRuntimeBinding } from "@hasna/loops";
|
|
|
633
675
|
|
|
634
676
|
const binding = openAutomationsRuntimeBinding();
|
|
635
677
|
console.log(binding.handoff); // "claim-queue"
|
|
636
|
-
console.log(binding.eventHandoff.handlerCommand); // "loops
|
|
678
|
+
console.log(binding.eventHandoff.handlerCommand); // "loops routes create generic"
|
|
637
679
|
```
|
|
638
680
|
|
|
639
681
|
The claim-queue handoff uses the OpenAutomations CLI or SDK:
|
|
@@ -646,18 +688,18 @@ automations queue fail <action-id> --runner open-loops:<worker-id> --code <code>
|
|
|
646
688
|
|
|
647
689
|
For explicit event workflow routing, OpenAutomations can export the normalized
|
|
648
690
|
event envelope and OpenLoops can consume it through the existing generic event
|
|
649
|
-
|
|
691
|
+
route:
|
|
650
692
|
|
|
651
693
|
```bash
|
|
652
694
|
automations --json webhooks event <route> --body-json '<json>' \
|
|
653
|
-
| loops --json
|
|
695
|
+
| loops --json routes create generic
|
|
654
696
|
```
|
|
655
697
|
|
|
656
698
|
This is not automation materialization in OpenLoops. It is an explicit
|
|
657
699
|
event-envelope workflow handoff: OpenAutomations owns deterministic automation
|
|
658
700
|
specs, webhook normalization, queue state, approvals, DLQ, and replay; OpenLoops
|
|
659
701
|
owns agent workflow invocation after the operator routes the envelope to
|
|
660
|
-
`loops
|
|
702
|
+
`loops routes create generic`.
|
|
661
703
|
|
|
662
704
|
Do not store automation specs in OpenLoops, infer automation triggers from event
|
|
663
705
|
transport alone, or replace the OpenAutomations queue with loop/workflow rows.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import type { LoopStorageContract } from "../lib/storage/contract.js";
|
|
3
|
+
export declare function apiStatus(): {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
service: string;
|
|
6
|
+
status: import("../index.js").LoopDeploymentStatus;
|
|
7
|
+
};
|
|
8
|
+
export interface LoopsApiServerOptions {
|
|
9
|
+
host?: string;
|
|
10
|
+
port?: number;
|
|
11
|
+
storage?: LoopStorageContract;
|
|
12
|
+
bodyLimitBytes?: number;
|
|
13
|
+
evidenceLimitBytes?: number;
|
|
14
|
+
now?: () => Date;
|
|
15
|
+
}
|
|
16
|
+
export declare function createLoopsApiServer(opts?: LoopsApiServerOptions): Bun.Server<undefined>;
|
|
17
|
+
export declare function main(argv?: string[]): Promise<void>;
|