@hasna/loops 0.4.5 → 0.4.7
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 +40 -0
- package/README.md +2 -2
- package/dist/api/index.js +19 -10
- package/dist/cli/index.js +613 -230
- package/dist/daemon/index.js +367 -170
- package/dist/index.js +413 -182
- package/dist/lib/executor.d.ts +14 -1
- package/dist/lib/mode.js +1 -1
- package/dist/lib/route/types.d.ts +6 -0
- package/dist/lib/storage/index.js +57 -5
- package/dist/lib/storage/sqlite.js +57 -5
- package/dist/lib/store.d.ts +7 -0
- package/dist/lib/store.js +57 -5
- package/dist/lib/template-kit.d.ts +6 -0
- package/dist/lib/templates.d.ts +10 -0
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +371 -173
- package/dist/runner/index.d.ts +3 -1
- package/dist/runner/index.js +180 -19
- package/dist/sdk/index.js +370 -174
- package/docs/USAGE.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,46 @@ 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.7 (2026-07-04)
|
|
9
|
+
|
|
10
|
+
Routing hardening for PR merge/drain automation.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Todos drain:** admit registered repo-project task sources while pinning
|
|
15
|
+
routed work to the scanned source project, preventing task-controlled nested
|
|
16
|
+
route paths from moving worker execution into another repository.
|
|
17
|
+
- **PR lifecycle routing:** propagate PR author and reviewer evidence into
|
|
18
|
+
reviewer and merger lifecycle prompts so merge routing can select a valid
|
|
19
|
+
non-author reviewer and keep reviewer/merger steps separate.
|
|
20
|
+
|
|
21
|
+
## 0.4.6 (2026-07-03)
|
|
22
|
+
|
|
23
|
+
Reliability hardening from a full audit of the runtime, control surfaces, and
|
|
24
|
+
todos routing integration.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Runtime:** repair a due slot that holds a terminal run (idempotent
|
|
29
|
+
`advanceLoop`) so a daemon death between `finalizeRun` and `advanceLoop` can no
|
|
30
|
+
longer wedge a loop into never running again.
|
|
31
|
+
- **Workflow runs:** recover dead-pid steps on resumed idempotent runs and wrap
|
|
32
|
+
the step loop so an unexpected error finalizes the run `failed` instead of
|
|
33
|
+
leaving it `running` forever.
|
|
34
|
+
- **Runner:** abort execution after repeated heartbeat failures on a lost lease.
|
|
35
|
+
- **Daemon:** `stopDaemon` terminates a live pidfile process even when its lease
|
|
36
|
+
is expired/mismatched; startup inline-owner check verifies kernel start time.
|
|
37
|
+
- **Store:** `deleteLoop` removes child run history; `finalizeRun` guards
|
|
38
|
+
`status='running'`; ambiguous-name resolution ignores archived namesakes;
|
|
39
|
+
rerunning a terminal manual goal creates a fresh goal.
|
|
40
|
+
- **CLI/API/SDK/MCP:** add `daemon logs --tail`; `PATCH /v1/loops/:id` no longer
|
|
41
|
+
wipes schedule fields or 500s; resume-from-stopped recomputes `nextRunAt` so the
|
|
42
|
+
loop is actually due; mutation paths reject ambiguous names; numeric flags are
|
|
43
|
+
validated.
|
|
44
|
+
- **Routing:** honor the `route:enabled` opt-in tag; surface fatal drain errors
|
|
45
|
+
(non-zero exit, `fatal` count) instead of a green route-nothing run; tag routed
|
|
46
|
+
failure tasks `loops` and emit the correct `todos task upsert` command.
|
|
47
|
+
|
|
8
48
|
## 0.4.5 (2026-07-03)
|
|
9
49
|
|
|
10
50
|
Upgrade-path fix for existing `0.4.1`-era local stores.
|
package/README.md
CHANGED
|
@@ -882,11 +882,11 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
882
882
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
883
883
|
|
|
884
884
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
885
|
-
- 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.
|
|
885
|
+
- 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. While the step is running, OpenLoops records bounded agent id/status updates as `step_progress` workflow events and as the running step's compact stdout; on terminal completion it 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.
|
|
886
886
|
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
887
887
|
- 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.
|
|
888
888
|
- Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
889
|
-
- 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.
|
|
889
|
+
- 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 prompt text and raw event payloads from workflow stdout.
|
|
890
890
|
- 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.
|
|
891
891
|
- `--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.
|
|
892
892
|
- `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
|
package/dist/api/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@hasna/loops",
|
|
6
|
-
version: "0.4.
|
|
6
|
+
version: "0.4.7",
|
|
7
7
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
8
8
|
type: "module",
|
|
9
9
|
main: "dist/index.js",
|
|
@@ -889,12 +889,16 @@ async function handleLoopsRequest(ctx, segments) {
|
|
|
889
889
|
}
|
|
890
890
|
if (segments.length === 1 && ctx.request.method === "PATCH") {
|
|
891
891
|
const body = await readJsonBody(ctx.request, ctx.bodyLimitBytes);
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
892
|
+
const patch = {};
|
|
893
|
+
if ("status" in body && body.status !== undefined)
|
|
894
|
+
patch.status = body.status;
|
|
895
|
+
if ("nextRunAt" in body)
|
|
896
|
+
patch.nextRunAt = body.nextRunAt === null ? undefined : body.nextRunAt;
|
|
897
|
+
if ("retryScheduledFor" in body)
|
|
898
|
+
patch.retryScheduledFor = body.retryScheduledFor === null ? undefined : body.retryScheduledFor;
|
|
899
|
+
if ("expiresAt" in body)
|
|
900
|
+
patch.expiresAt = body.expiresAt === null ? undefined : body.expiresAt;
|
|
901
|
+
const loop = await storage.updateLoop(id, patch);
|
|
898
902
|
return ok({ loop: publicLoop(loop) });
|
|
899
903
|
}
|
|
900
904
|
if (segments.length === 1 && ctx.request.method === "DELETE") {
|
|
@@ -923,15 +927,20 @@ async function handleRunsRequest(ctx, segments) {
|
|
|
923
927
|
if (segments.length === 2 && id && segments[1] === "recover" && ctx.request.method === "POST") {
|
|
924
928
|
const storage2 = requireStorage(ctx.storage);
|
|
925
929
|
const now = ctx.now();
|
|
930
|
+
const target = await storage2.getRun(id);
|
|
931
|
+
if (!target)
|
|
932
|
+
return fail("run_not_found", 404);
|
|
926
933
|
const recovered = await storage2.recoverExpiredRunLeasesDetailed(now);
|
|
927
|
-
|
|
934
|
+
const abandoned = recovered.abandoned.filter((run) => run.id === id);
|
|
935
|
+
const deferred = recovered.deferred.filter((run) => run.id === id);
|
|
936
|
+
for (const run of abandoned) {
|
|
928
937
|
const loop = await storage2.getLoop(run.loopId);
|
|
929
938
|
if (loop)
|
|
930
939
|
await advanceLoopAfterRun(storage2, loop, run, new Date(run.finishedAt ?? now), false);
|
|
931
940
|
}
|
|
932
941
|
return ok({
|
|
933
|
-
abandoned:
|
|
934
|
-
deferred:
|
|
942
|
+
abandoned: abandoned.map((run) => publicRun(run, false, { redactError: true })),
|
|
943
|
+
deferred: deferred.map((run) => publicRun(run, false, { redactError: true }))
|
|
935
944
|
});
|
|
936
945
|
}
|
|
937
946
|
const storage = requireStorage(ctx.storage);
|