@hasna/loops 0.3.60 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/LICENSE +197 -13
  3. package/README.md +24 -56
  4. package/dist/cli/index.js +7474 -6295
  5. package/dist/daemon/control.d.ts +21 -1
  6. package/dist/daemon/daemon.d.ts +5 -0
  7. package/dist/daemon/index.js +2808 -1209
  8. package/dist/daemon/install.d.ts +1 -1
  9. package/dist/index.d.ts +21 -8
  10. package/dist/index.js +5305 -4293
  11. package/dist/lib/accounts.d.ts +6 -1
  12. package/dist/lib/agent-adapter.d.ts +74 -0
  13. package/dist/lib/backup.d.ts +25 -0
  14. package/dist/lib/errors.d.ts +21 -0
  15. package/dist/lib/executor.d.ts +10 -1
  16. package/dist/lib/goal/metadata.d.ts +16 -0
  17. package/dist/lib/goal/model-factory.d.ts +1 -0
  18. package/dist/lib/goal/prompts.d.ts +3 -1
  19. package/dist/lib/goal/types.d.ts +3 -0
  20. package/dist/lib/health.d.ts +1 -1
  21. package/dist/lib/ids.d.ts +8 -1
  22. package/dist/lib/machines.d.ts +6 -0
  23. package/dist/lib/process-identity.d.ts +16 -0
  24. package/dist/lib/{schedule.d.ts → recurrence.d.ts} +1 -0
  25. package/dist/lib/redact.d.ts +21 -0
  26. package/dist/lib/route/cursors.d.ts +18 -0
  27. package/dist/lib/route/drain.d.ts +6 -0
  28. package/dist/lib/route/fields.d.ts +27 -0
  29. package/dist/lib/route/gates.d.ts +26 -0
  30. package/dist/lib/route/index.d.ts +18 -0
  31. package/dist/lib/route/options.d.ts +31 -0
  32. package/dist/lib/route/parse.d.ts +8 -0
  33. package/dist/lib/route/pr-review.d.ts +11 -0
  34. package/dist/lib/route/provider.d.ts +41 -0
  35. package/dist/lib/route/route-event.d.ts +23 -0
  36. package/dist/lib/route/route-tasks.d.ts +75 -0
  37. package/dist/lib/route/throttle.d.ts +47 -0
  38. package/dist/lib/route/todos-cli.d.ts +21 -0
  39. package/dist/lib/route/types.d.ts +88 -0
  40. package/dist/lib/run-artifacts.d.ts +17 -2
  41. package/dist/lib/run-envelope.d.ts +41 -0
  42. package/dist/lib/scheduler.d.ts +78 -2
  43. package/dist/lib/store.d.ts +63 -0
  44. package/dist/lib/store.js +1007 -287
  45. package/dist/lib/template-kit.d.ts +70 -0
  46. package/dist/lib/templates-custom.d.ts +34 -0
  47. package/dist/lib/templates.d.ts +13 -81
  48. package/dist/lib/workflow-runner.d.ts +2 -0
  49. package/dist/mcp/index.d.ts +3 -0
  50. package/dist/mcp/index.js +3216 -1382
  51. package/dist/sdk/index.d.ts +29 -3
  52. package/dist/sdk/index.js +2740 -1041
  53. package/dist/test-helpers.d.ts +37 -0
  54. package/dist/types.d.ts +2 -0
  55. package/docs/USAGE.md +36 -14
  56. package/package.json +6 -3
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared deadline-based polling and process-kill verification helpers for the
3
+ * test suite. Test-only: nothing in src/ production code may import this file.
4
+ */
5
+ export interface WaitUntilOptions {
6
+ /** Total time to wait before failing (default 5s). */
7
+ timeoutMs?: number;
8
+ /** Poll interval (default 5ms). */
9
+ intervalMs?: number;
10
+ /** Included in the timeout error to identify the stuck condition. */
11
+ label?: string;
12
+ }
13
+ /**
14
+ * Poll `check` until it returns a truthy value, with a hard deadline.
15
+ * Returns the first truthy value; throws once the deadline passes so a stuck
16
+ * condition fails the test instead of silently falling through.
17
+ */
18
+ export declare function waitUntil<T>(check: () => T | undefined | null | false | Promise<T | undefined | null | false>, opts?: WaitUntilOptions): Promise<T>;
19
+ /**
20
+ * Shell command that blocks until `gate` exists, then writes `text` to
21
+ * `marker`. Replaces fixed `sleep N; printf ...` fixtures: the child stays
22
+ * alive (blocked on the gate) for exactly as long as the test needs, and a
23
+ * kill can be proven by opening the gate and observing that the marker never
24
+ * appears (see {@link expectMarkerNeverWritten}).
25
+ */
26
+ export declare function gatedWriteCommand(gate: string, marker: string, opts?: {
27
+ text?: string;
28
+ append?: boolean;
29
+ }): string;
30
+ /** Open a {@link gatedWriteCommand} gate so a still-alive child can finish. */
31
+ export declare function openGate(gate: string): void;
32
+ /**
33
+ * Prove a gated child process was killed: open the gate and verify the marker
34
+ * still does not appear. A surviving child polls the gate every 20ms, so the
35
+ * default 250ms settle window gives it >10x the time it would need to write.
36
+ */
37
+ export declare function expectMarkerNeverWritten(gate: string, marker: string, settleMs?: number): Promise<void>;
package/dist/types.d.ts CHANGED
@@ -370,6 +370,8 @@ export interface LoopRun {
370
370
  claimedBy?: string;
371
371
  leaseExpiresAt?: string;
372
372
  pid?: number;
373
+ pgid?: number;
374
+ processStartedAt?: string;
373
375
  exitCode?: number;
374
376
  durationMs?: number;
375
377
  stdout?: string;
package/docs/USAGE.md CHANGED
@@ -313,6 +313,15 @@ Use `shell: true` only when you intentionally want shell parsing:
313
313
  { "type": "command", "command": "git status --short", "shell": true }
314
314
  ```
315
315
 
316
+ Gate steps can end a workflow early without failing it. A step opts into
317
+ blocked-exit semantics either explicitly via `"blockedExitCodes": [12]` on the
318
+ step, or by naming convention: a step whose `id` or `name` contains "gate" as a
319
+ standalone word (for example `gate`, `triage-gate`, `gate_check`) treats exit
320
+ code 12 as "blocked" — the step and its dependents are recorded as skipped and
321
+ the workflow still succeeds. Substring matches such as `gateway`, `aggregate`,
322
+ or `delegate` do NOT inherit gate behavior; use `blockedExitCodes` explicitly
323
+ for those. Set `"blockedExitCodes": []` on a gate-named step to opt out.
324
+
316
325
  ## Templates And Task Events
317
326
 
318
327
  Built-in templates turn common orchestration flows into reusable workflow JSON.
@@ -448,18 +457,21 @@ explicit break-glass handling for emergency workflows that need full access.
448
457
 
449
458
  Repo-mutating task/event routes should set `worktreeMode=required` so the
450
459
  workflow fails fast instead of falling back to the main checkout. When
451
- `projectPath` is an existing git repository, OpenLoops inserts a
452
- `prepare-worktree` command step before the worker and runs the worker/verifier
453
- from a deterministic worktree under `~/.hasna/loops/worktrees/<repo>/<run>`.
454
- The generated agent target includes worktree metadata (`mode`, `cwd`, `path`,
455
- `branch`, `originalCwd`) so dry-runs and workflow inspection expose the exact
456
- checkout.
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.
460
+ `projectPath` is an existing git repository, the executor prepares and enters
461
+ a deterministic worktree under `~/.hasna/loops/worktrees/<repo>/<run>` before
462
+ spawning the worker/verifier locally and on machine-assigned (remote) loops,
463
+ where the dispatch script runs the equivalent `git worktree add`/reuse checks
464
+ on the remote machine. The generated agent target includes worktree metadata
465
+ (`mode`, `cwd`, `path`, `branch`, `originalCwd`) so dry-runs and workflow
466
+ inspection expose the exact checkout.
467
+
468
+ Before a worker starts, worktree preparation verifies that any existing
469
+ managed path is a real git worktree with the same top-level checkout, the same
470
+ git common directory as the source repo, and the expected generated branch. A
471
+ detached HEAD or unexpected branch fails closed with evidence
472
+ (`worktreeMode=required`) instead of silently running a mutating workflow in
473
+ the wrong state; `worktreeMode=auto` falls back to the original checkout and
474
+ records the fallback.
463
475
 
464
476
  Use explicit main/default checkout mode only when the task truly requires it:
465
477
 
@@ -785,8 +797,9 @@ record an `auto_route_skipped_reason`. Without `--auto-route`, route commands
785
797
  only upsert deduped tasks and do not launch agents.
786
798
 
787
799
  Failure classifications are: `rate_limit`, `auth`, `model_not_found`,
788
- `context_length`, `schema_response_format`, `node_init`, `timeout`, `sigsegv`,
789
- `skipped_previous_active`, and `unknown`.
800
+ `context_length`, `schema_response_format`, `node_init`, `preflight`,
801
+ `route_functional`, `timeout`, `sigsegv`, `skipped_previous_active`,
802
+ `circuit_breaker`, and `unknown`.
790
803
 
791
804
  ## Hygiene
792
805
 
@@ -904,6 +917,15 @@ The adapters intentionally use provider command surfaces instead of pretending e
904
917
  - `--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`.
905
918
  - `--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`.
906
919
  - Daemon and scheduled runs prepend common user executable directories such as `~/.local/bin` and `~/.bun/bin` before resolving provider CLIs.
920
+ - Agent targets that set neither `timeoutMs` nor `idleTimeoutMs` get a default
921
+ idle watchdog: 30 minutes without stdout/stderr for streaming providers
922
+ (codex, cursor), and 4 hours for providers whose CLIs buffer all output until
923
+ completion (claude, opencode, aicopilot) or whose durable progress
924
+ fingerprint can stay flat during long work (codewith). Override the default
925
+ with `LOOPS_AGENT_IDLE_TIMEOUT_MS=<ms>`, disable it with
926
+ `LOOPS_AGENT_IDLE_TIMEOUT_MS=0` (or `none`/`off`), or set explicit
927
+ `timeoutMs`/`idleTimeoutMs` per target — `"timeoutMs": null` opts a target
928
+ out of both the wall-clock default and the idle watchdog.
907
929
 
908
930
  For production loops that can mutate repos, prefer the built-in
909
931
  `worktreeMode=auto`/`required` path and explicit prompts that name allowed write
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.3.60",
3
+ "version": "0.4.0",
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",
@@ -31,6 +31,7 @@
31
31
  "files": [
32
32
  "dist",
33
33
  "README.md",
34
+ "CHANGELOG.md",
34
35
  "docs",
35
36
  "LICENSE"
36
37
  ],
@@ -39,7 +40,7 @@
39
40
  "build:bin": "bun build src/cli/index.ts --compile --outfile dist/loops",
40
41
  "typecheck": "tsc --noEmit",
41
42
  "test": "bun test",
42
- "prepare": "bun run build",
43
+ "prepare": "test -d dist || bun run build",
43
44
  "dev:cli": "bun run src/cli/index.ts",
44
45
  "dev:daemon": "bun run src/daemon/index.ts",
45
46
  "prepublishOnly": "bun run build"
@@ -70,13 +71,15 @@
70
71
  },
71
72
  "dependencies": {
72
73
  "@hasna/events": "^0.1.9",
73
- "@hasna/machines": "0.0.49",
74
74
  "@modelcontextprotocol/sdk": "^1.29.0",
75
75
  "@openrouter/ai-sdk-provider": "2.9.1",
76
76
  "ai": "6.0.204",
77
77
  "commander": "^13.1.0",
78
78
  "zod": "4.4.3"
79
79
  },
80
+ "optionalDependencies": {
81
+ "@hasna/machines": "0.0.49"
82
+ },
80
83
  "devDependencies": {
81
84
  "@types/bun": "latest",
82
85
  "typescript": "^5.7.3"