@hasna/loops 0.3.59 → 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.
- package/CHANGELOG.md +247 -0
- package/LICENSE +197 -13
- package/README.md +28 -60
- package/dist/cli/index.js +7269 -5776
- package/dist/daemon/control.d.ts +21 -1
- package/dist/daemon/daemon.d.ts +5 -0
- package/dist/daemon/index.js +2985 -1072
- package/dist/daemon/install.d.ts +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.js +5906 -4580
- package/dist/lib/accounts.d.ts +6 -1
- package/dist/lib/agent-adapter.d.ts +74 -0
- package/dist/lib/backup.d.ts +25 -0
- package/dist/lib/errors.d.ts +21 -0
- package/dist/lib/executor.d.ts +10 -1
- package/dist/lib/goal/metadata.d.ts +16 -0
- package/dist/lib/goal/model-factory.d.ts +1 -0
- package/dist/lib/goal/prompts.d.ts +3 -1
- package/dist/lib/goal/types.d.ts +3 -0
- package/dist/lib/health.d.ts +1 -1
- package/dist/lib/ids.d.ts +8 -1
- package/dist/lib/machines.d.ts +6 -0
- package/dist/lib/process-identity.d.ts +16 -0
- package/dist/lib/{schedule.d.ts → recurrence.d.ts} +1 -0
- package/dist/lib/redact.d.ts +21 -0
- package/dist/lib/route/cursors.d.ts +18 -0
- package/dist/lib/route/drain.d.ts +6 -0
- package/dist/lib/route/fields.d.ts +27 -0
- package/dist/lib/route/gates.d.ts +26 -0
- package/dist/lib/route/index.d.ts +18 -0
- package/dist/lib/route/options.d.ts +31 -0
- package/dist/lib/route/parse.d.ts +8 -0
- package/dist/lib/route/pr-review.d.ts +11 -0
- package/dist/lib/route/provider.d.ts +41 -0
- package/dist/lib/route/route-event.d.ts +23 -0
- package/dist/lib/route/route-tasks.d.ts +75 -0
- package/dist/lib/route/throttle.d.ts +47 -0
- package/dist/lib/route/todos-cli.d.ts +21 -0
- package/dist/lib/route/types.d.ts +88 -0
- package/dist/lib/run-artifacts.d.ts +17 -2
- package/dist/lib/run-envelope.d.ts +41 -0
- package/dist/lib/scheduler.d.ts +78 -2
- package/dist/lib/store.d.ts +63 -0
- package/dist/lib/store.js +1011 -266
- package/dist/lib/template-kit.d.ts +70 -0
- package/dist/lib/templates-custom.d.ts +34 -0
- package/dist/lib/templates.d.ts +13 -81
- package/dist/lib/workflow-runner.d.ts +2 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +3313 -1165
- package/dist/sdk/index.d.ts +29 -3
- package/dist/sdk/index.js +2910 -897
- package/dist/test-helpers.d.ts +37 -0
- package/dist/types.d.ts +2 -0
- package/docs/USAGE.md +40 -18
- 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
package/docs/USAGE.md
CHANGED
|
@@ -6,7 +6,7 @@ It supports deterministic command loops, JSON-defined workflows, and guarded CLI
|
|
|
6
6
|
|
|
7
7
|
- `claude`
|
|
8
8
|
- `agent` (Cursor Agent CLI)
|
|
9
|
-
- `codewith
|
|
9
|
+
- `codewith agent start`
|
|
10
10
|
- `aicopilot run`
|
|
11
11
|
- `opencode run`
|
|
12
12
|
- `codex exec`
|
|
@@ -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,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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`, `
|
|
789
|
-
`
|
|
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
|
|
|
@@ -893,17 +906,26 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
893
906
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
894
907
|
|
|
895
908
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
896
|
-
- Codewith uses `codewith --ask-for-approval never exec
|
|
909
|
+
- Codewith uses durable `codewith --ask-for-approval never agent start` background-agent runs by default, then polls `codewith agent read` until the run is terminal and records compact status/event evidence. Remote Codewith agent steps fail closed until remote durable readback is implemented, so workflows do not advance immediately after enqueue. OpenLoops rejects Codewith `extraArgs` that try to force `exec`, `--ephemeral`, or other non-durable exec-only flags for task-lifecycle, planner, worker, verifier, reviewer, release, or other long-running agentic work.
|
|
897
910
|
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
898
911
|
- 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.
|
|
899
912
|
- Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
900
|
-
- Agent prompts are sent through child stdin instead of argv so
|
|
913
|
+
- Agent prompts are sent through child stdin instead of argv where the provider supports stdin. Codewith durable background agents currently accept prompts as native `agent start` arguments, so OpenLoops stores only bounded status/event evidence and omits raw Codewith event payloads from workflow stdout.
|
|
901
914
|
- When `--account` or a step `account` is set, OpenLoops 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.
|
|
902
|
-
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `
|
|
915
|
+
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `agent start/read/logs`; they do not call OpenAccounts.
|
|
903
916
|
- `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
|
|
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
|
+
"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"
|