@hasna/loops 0.3.56 → 0.3.58
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 +188 -14
- package/dist/cli/index.js +1421 -176
- package/dist/daemon/index.js +164 -20
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4049 -2874
- package/dist/lib/executor.d.ts +2 -0
- package/dist/lib/health.d.ts +2 -2
- package/dist/lib/store.d.ts +4 -0
- package/dist/lib/store.js +61 -8
- package/dist/lib/templates.d.ts +4 -0
- package/dist/mcp/index.d.ts +12 -0
- package/dist/mcp/index.js +5841 -0
- package/dist/sdk/index.js +155 -17
- package/docs/AUTOMATION_RUNTIME_DESIGN.md +228 -0
- package/docs/USAGE.md +123 -15
- package/package.json +9 -3
package/docs/USAGE.md
CHANGED
|
@@ -41,6 +41,41 @@ bun link
|
|
|
41
41
|
|
|
42
42
|
The CLI stores state in `~/.hasna/loops` by default. Set `LOOPS_DATA_DIR` to isolate state for tests or another profile.
|
|
43
43
|
|
|
44
|
+
## MCP Server
|
|
45
|
+
|
|
46
|
+
OpenLoops ships a stdio MCP server for safe loop and workflow inspection from
|
|
47
|
+
MCP-capable agents:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
loops-mcp list-tools
|
|
51
|
+
loops-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The package also exports the server factory for embedded hosts:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createLoopsMcpServer } from "@hasna/loops/mcp";
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Available read tools include `loops_list`, `loops_show`, `loop_runs`,
|
|
61
|
+
`loops_doctor`, `workflows_list`, `workflow_read`, and `workflow_validate`.
|
|
62
|
+
Resources are available at `loops://runtime` and `loops://tools`.
|
|
63
|
+
Those tools use the same `Store`, public redaction helpers, and workflow parser
|
|
64
|
+
as the CLI and SDK, so read output and validation behavior stay aligned across
|
|
65
|
+
surfaces.
|
|
66
|
+
|
|
67
|
+
Mutation tools are disabled by default. Start the server with
|
|
68
|
+
`LOOPS_MCP_ALLOW_MUTATIONS=true` only for a trusted local MCP host that should be
|
|
69
|
+
allowed to change loop state. Even then, mutation tools require exact
|
|
70
|
+
confirmation strings: `loop_pause`, `loop_resume`, `loop_run_now`,
|
|
71
|
+
`loop_create_command`, and `loop_create_workflow`. MCP `loop_run_now` schedules
|
|
72
|
+
the loop for immediate daemon pickup; inline execution remains CLI-only.
|
|
73
|
+
|
|
74
|
+
Keep host-affecting or long-running operations on the CLI: daemon
|
|
75
|
+
start/stop/install/logs, inline `run-now`, `tick`, loop removal/archive
|
|
76
|
+
maintenance, workflow create/migrate/cancel/recover, agent loop creation,
|
|
77
|
+
template materialization, and event-route drains.
|
|
78
|
+
|
|
44
79
|
## Create Loops
|
|
45
80
|
|
|
46
81
|
Run a deterministic command every minute:
|
|
@@ -124,6 +159,20 @@ loops create agent supply-chain-watch \
|
|
|
124
159
|
--prompt "Check for suspicious dependency or supply-chain changes. Report only concrete findings."
|
|
125
160
|
```
|
|
126
161
|
|
|
162
|
+
Run an OpenCode loop with an explicit provider/model. OpenCode reads
|
|
163
|
+
`~/.config/opencode/config.json` when no model is supplied, so OpenLoops rejects
|
|
164
|
+
OpenCode agent targets without `--model` instead of inheriting a stale or
|
|
165
|
+
machine-specific default.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
loops create agent opencode-smoke \
|
|
169
|
+
--provider opencode \
|
|
170
|
+
--model openrouter/google/gemini-2.5-flash \
|
|
171
|
+
--at "$(date -u -d '+1 minute' +%Y-%m-%dT%H:%M:%SZ)" \
|
|
172
|
+
--cwd /path/to/repo \
|
|
173
|
+
--prompt "Reply with exactly OK."
|
|
174
|
+
```
|
|
175
|
+
|
|
127
176
|
Agent loops can also carry advisory per-session allowlist metadata:
|
|
128
177
|
|
|
129
178
|
```bash
|
|
@@ -247,8 +296,8 @@ loops create workflow repo-morning-loop --workflow repo-morning --cron "0 8 * *
|
|
|
247
296
|
|
|
248
297
|
Use `recover` only for interrupted `running` workflow runs whose recorded child
|
|
249
298
|
process is gone. Terminal `timed_out` task/event workflow runs are audit
|
|
250
|
-
history;
|
|
251
|
-
cause.
|
|
299
|
+
history; use `loops routes requeue <work-item-id> --reason "<cause fixed>"`
|
|
300
|
+
after fixing the cause, then redeliver or drain the original task/event route.
|
|
252
301
|
|
|
253
302
|
Workflow specs are stored separately from loops. A loop can schedule a workflow, but workflow runs and step runs have their own durable rows and events. Steps run in dependency order and a scheduled workflow run is idempotent per loop slot.
|
|
254
303
|
|
|
@@ -317,11 +366,14 @@ registry.
|
|
|
317
366
|
|
|
318
367
|
Timeout policy is explicit. Deterministic command/check steps should normally
|
|
319
368
|
keep finite `timeoutMs`/`idleTimeoutMs` guards so broken shell work cannot run
|
|
320
|
-
forever. Agentic work steps default to no timeout in built-in
|
|
321
|
-
and task-lifecycle templates; use `timeoutMs: null` in workflow
|
|
322
|
-
`--timeout none` / `--timeout unlimited` for CLI-created targets, when
|
|
323
|
-
may need hours or days.
|
|
324
|
-
|
|
369
|
+
forever. Agentic work steps default to no wall-clock timeout in built-in
|
|
370
|
+
worker/verifier and task-lifecycle templates; use `timeoutMs: null` in workflow
|
|
371
|
+
JSON, or `--timeout none` / `--timeout unlimited` for CLI-created targets, when
|
|
372
|
+
a step may need hours or days. Verifier/evaluator steps add a 15 minute
|
|
373
|
+
`idleTimeoutMs` watchdog by default so a verifier cannot hang silently after the
|
|
374
|
+
worker finishes; pass `--verifier-idle-timeout none` or template variable
|
|
375
|
+
`verifierIdleTimeoutMs=none` only when another heartbeat is guaranteed. Use a
|
|
376
|
+
positive numeric `timeoutMs` only when an agentic step is intentionally bounded.
|
|
325
377
|
|
|
326
378
|
To migrate existing workflow loops, do not edit `workflow_specs.steps_json`
|
|
327
379
|
directly because historical workflow runs must keep pointing at their original
|
|
@@ -337,7 +389,8 @@ with the requested agent timeout policy, retargets only future executions of
|
|
|
337
389
|
eligible non-running workflow loops, and leaves terminal timed-out workflow runs
|
|
338
390
|
as audit history. Use `loops workflows recover` only for interrupted `running`
|
|
339
391
|
workflow runs whose recorded child process is gone; terminal `timed_out` runs
|
|
340
|
-
must be requeued
|
|
392
|
+
must be requeued with `loops routes requeue <work-item-id> --reason "<cause fixed>"` before
|
|
393
|
+
re-delivering or draining the original task/event route.
|
|
341
394
|
|
|
342
395
|
```json
|
|
343
396
|
{
|
|
@@ -402,6 +455,12 @@ The generated agent target includes worktree metadata (`mode`, `cwd`, `path`,
|
|
|
402
455
|
`branch`, `originalCwd`) so dry-runs and workflow inspection expose the exact
|
|
403
456
|
checkout.
|
|
404
457
|
|
|
458
|
+
Before a worker starts, `prepare-worktree` verifies that any existing managed
|
|
459
|
+
path is a real git worktree with the same top-level checkout, the same git
|
|
460
|
+
common directory as the source repo, and the expected generated branch. A
|
|
461
|
+
detached HEAD or unexpected branch fails closed with evidence instead of
|
|
462
|
+
silently running a mutating workflow in the wrong state.
|
|
463
|
+
|
|
405
464
|
Use explicit main/default checkout mode only when the task truly requires it:
|
|
406
465
|
|
|
407
466
|
```bash
|
|
@@ -416,6 +475,10 @@ non-git/non-mutating project is expected and the fallback is recorded. Use
|
|
|
416
475
|
`worktreeMode=off` for non-git projects. `worktreeRoot` and
|
|
417
476
|
`worktreeBranchPrefix` can override the storage root and branch prefix.
|
|
418
477
|
|
|
478
|
+
PR review and merge route workers may fetch, rebase, or merge base branches
|
|
479
|
+
inside the isolated worktree when the task requires it, but they must not
|
|
480
|
+
mutate the primary main checkout.
|
|
481
|
+
|
|
419
482
|
For event-driven task automation, `loops events handle todos-task` reads a
|
|
420
483
|
Hasna event envelope from stdin or `HASNA_EVENT_JSON`, records a
|
|
421
484
|
`WorkflowInvocation`, upserts an admission work item, and admits that work item
|
|
@@ -441,16 +504,50 @@ approval-required, or `no-auto` tasks. This guard exists even when the upstream
|
|
|
441
504
|
`@hasna/events` webhook filter is misconfigured, so task existence alone is not
|
|
442
505
|
permission to execute agent work.
|
|
443
506
|
|
|
507
|
+
Task route drains can select providers from task metadata instead of running one
|
|
508
|
+
fixed provider/account pool for the whole drain. Add one or more
|
|
509
|
+
`--provider-rule field=value:provider[:profile1,profile2]` flags; the first
|
|
510
|
+
matching rule wins. Rule profiles become a Codewith auth-profile pool for
|
|
511
|
+
`provider=codewith` and an OpenAccounts account pool for other providers. Tasks
|
|
512
|
+
can also carry `provider_hint`/`route_provider`, `auth_profile_pool`, or
|
|
513
|
+
`account_pool` metadata. Dry-run, drain evidence, and route invocation scope
|
|
514
|
+
include `providerRouting` so operators can see why a provider/account was
|
|
515
|
+
selected.
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
loops routes drain todos-task \
|
|
519
|
+
--dry-run \
|
|
520
|
+
--provider-rule area=frontend:claude:claude-ui-a,claude-ui-b \
|
|
521
|
+
--provider-rule area=backend:codewith:account004,account005 \
|
|
522
|
+
--worktree-mode required
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
PR approval or merge tasks that need a branch-protection review must carry
|
|
526
|
+
explicit non-author GitHub reviewer evidence before a worker is created. When a
|
|
527
|
+
task has a PR reference plus `reviewDecision=REVIEW_REQUIRED`,
|
|
528
|
+
`mergeStateStatus=BLOCKED`, branch-protection review language, or similar
|
|
529
|
+
approval/merge intent, routing is skipped unless `--github-reviewer`,
|
|
530
|
+
`--github-reviewer-pool`, or task metadata such as `github_reviewer` /
|
|
531
|
+
`github_reviewer_pool` names at least one GitHub login different from the PR
|
|
532
|
+
author. This blocks self-review routes before they can spawn an impossible
|
|
533
|
+
worker, and dry-run/admission JSON includes `prReviewRouting` evidence.
|
|
534
|
+
|
|
444
535
|
By default, `todos-task` routes use `todos-task-worker-verifier` for backwards
|
|
445
536
|
compatibility. Use `--template task-lifecycle` when the task should run the full
|
|
446
537
|
triage -> planner -> worker -> verifier lifecycle. The route rejects unrelated
|
|
447
538
|
workflow templates such as `pr-review` so a todos task cannot accidentally use a
|
|
448
539
|
template with the wrong contract.
|
|
540
|
+
The default worker/verifier template starts with a deterministic
|
|
541
|
+
`source-task-gate` command that runs `todos --project <source-store> --json
|
|
542
|
+
inspect <task-id>` before the worker. If the routed source task cannot be
|
|
543
|
+
resolved in the intended Todos store, the workflow fails before repo-mutating
|
|
544
|
+
agent work starts.
|
|
449
545
|
The lifecycle template inserts deterministic gate steps after triage and after
|
|
450
546
|
planning. If either agent marks the task blocked, omits its contextual
|
|
451
547
|
`openloops:triage=go task=<id> event=<event-id>` /
|
|
452
548
|
`openloops:planner=go task=<id> event=<event-id>` marker comment, or the task
|
|
453
|
-
is marked no-auto/manual/
|
|
549
|
+
is marked blocked/completed/done/cancelled/failed/archived/no-auto/manual/
|
|
550
|
+
approval-required, the next agent step is not started.
|
|
454
551
|
Use `--triage-auth-profile`, `--planner-auth-profile`,
|
|
455
552
|
`--worker-auth-profile`, and `--verifier-auth-profile` for exact Codewith role
|
|
456
553
|
profiles, or use `--auth-profile-pool` for deterministic role rotation.
|
|
@@ -479,6 +576,12 @@ Re-delivering the event later is safe because handlers dedupe by the work-item
|
|
|
479
576
|
idempotency key before rendering worktree plans or checking route limits. In
|
|
480
577
|
dry-run mode, throttle counts are not evaluated because opening the live loop
|
|
481
578
|
store can create or migrate the local database.
|
|
579
|
+
Terminal routed work items such as failed, dead-letter, cancelled, or succeeded
|
|
580
|
+
history remain deduped until an operator runs `loops routes requeue
|
|
581
|
+
<work-item-id> --reason "<cause fixed>"`; a later drain should not create
|
|
582
|
+
another worker just because the previous workflow ended. The next route-created
|
|
583
|
+
output records `requeue` evidence with the previous work item id, previous
|
|
584
|
+
attempts, operator reason, new attempt, workflow id, and loop id.
|
|
482
585
|
|
|
483
586
|
When a sandboxed Codewith/Codex worker must update app stores outside the repo
|
|
484
587
|
worktree, pass those stores explicitly with `--add-dir` or template `addDirs`.
|
|
@@ -498,6 +601,7 @@ loops routes drain todos-task --task-list oss --max-dispatch 2 --compact
|
|
|
498
601
|
loops routes schedule todos-task route-drain-oss-5m --every 5m --task-list oss --max-dispatch 1 --compact
|
|
499
602
|
loops routes list --route-key todos-task
|
|
500
603
|
loops routes show <work-item-id>
|
|
604
|
+
loops routes requeue <work-item-id> --reason "fixed upstream blocker"
|
|
501
605
|
loops routes invocations
|
|
502
606
|
```
|
|
503
607
|
|
|
@@ -698,11 +802,15 @@ loops hygiene route-tasks --checks names,duplicates,scripts --dry-run --json
|
|
|
698
802
|
```
|
|
699
803
|
|
|
700
804
|
`hygiene names` reports canonical `machine-*` or `repo-<name>-*` loop names and
|
|
701
|
-
only renames when `--apply` is present.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
805
|
+
only renames when `--apply` is present. Cadence/timer suffixes such as `-5m`,
|
|
806
|
+
`-15m`, `-6h`, `-hourly`, and `-daily` are removed from canonical names; cadence
|
|
807
|
+
belongs in schedule metadata and the human `loops list` `cadence=` column. Apply
|
|
808
|
+
mode writes a SQLite backup under `<LOOPS_DATA_DIR>/backups` before changing loop
|
|
809
|
+
names. New loops get a compact default description with Why/How/Outcome text
|
|
810
|
+
unless the operator supplies `--description`. `hygiene duplicates` groups loops
|
|
811
|
+
with the same normalized name, cwd, and schedule. `hygiene scripts` inventories
|
|
812
|
+
loops whose command still references `~/.hasna/loops/scripts`; use it as a
|
|
813
|
+
migration gate before deleting local scripts. `hygiene route-tasks`
|
|
706
814
|
upserts deduped Todos tasks for hygiene findings with stable fingerprints and
|
|
707
815
|
`no_tmux_dispatch=true` metadata; use `--dry-run --json` before enabling it as a
|
|
708
816
|
production loop. Route commands store a small cursor in
|
|
@@ -786,7 +894,7 @@ The adapters intentionally use provider command surfaces instead of pretending e
|
|
|
786
894
|
|
|
787
895
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
788
896
|
- Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories.
|
|
789
|
-
- AI Copilot and OpenCode use `run --format json --pure`.
|
|
897
|
+
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
790
898
|
- Cursor is CLI-first for now via the standalone `agent -p` binary. OpenLoops no longer falls back to `cursor agent`; install the standalone Cursor Agent CLI so preflight and scheduled runs use the same executable.
|
|
791
899
|
- Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
792
900
|
- Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/loops",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.58",
|
|
4
4
|
"description": "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
9
|
"loops": "dist/cli/index.js",
|
|
10
|
-
"loops-daemon": "dist/daemon/index.js"
|
|
10
|
+
"loops-daemon": "dist/daemon/index.js",
|
|
11
|
+
"loops-mcp": "dist/mcp/index.js"
|
|
11
12
|
},
|
|
12
13
|
"exports": {
|
|
13
14
|
".": {
|
|
@@ -18,6 +19,10 @@
|
|
|
18
19
|
"types": "./dist/sdk/index.d.ts",
|
|
19
20
|
"import": "./dist/sdk/index.js"
|
|
20
21
|
},
|
|
22
|
+
"./mcp": {
|
|
23
|
+
"types": "./dist/mcp/index.d.ts",
|
|
24
|
+
"import": "./dist/mcp/index.js"
|
|
25
|
+
},
|
|
21
26
|
"./storage": {
|
|
22
27
|
"types": "./dist/lib/store.d.ts",
|
|
23
28
|
"import": "./dist/lib/store.js"
|
|
@@ -30,7 +35,7 @@
|
|
|
30
35
|
"LICENSE"
|
|
31
36
|
],
|
|
32
37
|
"scripts": {
|
|
33
|
-
"build": "rm -rf dist && bun build src/cli/index.ts src/daemon/index.ts src/index.ts src/sdk/index.ts src/lib/store.ts --outdir dist --target bun --packages external && chmod +x dist/cli/index.js dist/daemon/index.js && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
|
|
38
|
+
"build": "rm -rf dist && bun build src/cli/index.ts src/daemon/index.ts src/mcp/index.ts src/index.ts src/sdk/index.ts src/lib/store.ts --outdir dist --target bun --packages external && chmod +x dist/cli/index.js dist/daemon/index.js dist/mcp/index.js && tsc -p tsconfig.build.json --emitDeclarationOnly --outDir dist",
|
|
34
39
|
"build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
|
|
35
40
|
"typecheck": "tsc --noEmit",
|
|
36
41
|
"test": "bun test",
|
|
@@ -66,6 +71,7 @@
|
|
|
66
71
|
"dependencies": {
|
|
67
72
|
"@hasna/events": "^0.1.9",
|
|
68
73
|
"@hasna/machines": "0.0.49",
|
|
74
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
69
75
|
"@openrouter/ai-sdk-provider": "2.9.1",
|
|
70
76
|
"ai": "6.0.204",
|
|
71
77
|
"commander": "^13.1.0",
|