@hasna/loops 0.3.55 → 0.3.57

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/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; requeue them through the original task/event route after fixing the
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 worker/verifier
321
- and task-lifecycle templates; use `timeoutMs: null` in workflow JSON, or
322
- `--timeout none` / `--timeout unlimited` for CLI-created targets, when a step
323
- may need hours or days. Use a positive numeric `timeoutMs` only when an agentic
324
- step is intentionally bounded.
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 by re-delivering or draining the original task/event route.
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/approval-required, the next agent step is not started.
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. Apply mode writes a SQLite backup under
702
- `<LOOPS_DATA_DIR>/backups` before changing loop names. `hygiene duplicates`
703
- groups loops with the same normalized name, cwd, and schedule. `hygiene scripts`
704
- inventories loops whose command still references `~/.hasna/loops/scripts`; use
705
- it as a migration gate before deleting local scripts. `hygiene route-tasks`
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.55",
3
+ "version": "0.3.57",
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",