@alphacoder-v0/pi-loops 0.22.7 → 0.22.8
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 +17 -0
- package/package.json +1 -1
- package/src/host-runtime.ts +2 -1
- package/src/scheduler.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ All notable changes to pi-loops are documented here. The format follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.22.8] - 2026-10-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **A slot is not run twice when the clock changes hands.** A headless host that read `jobs.json`
|
|
12
|
+
just before an interactive pi took the clock could still dispatch from that snapshot. The pi
|
|
13
|
+
had already run the slot and cleared `running`, so the host claimed and ran it a second time,
|
|
14
|
+
and in one variant wrote an older `lastDueAt` back so a newer slot was owed again. A claim now
|
|
15
|
+
fails when the job's `lastDueAt` or `lastFiredAt` moved since the snapshot. Found with a TLA+
|
|
16
|
+
model of the scheduler. [#51](https://github.com/alphacoder-v0/pi-loops/issues/51),
|
|
17
|
+
[#87](https://github.com/alphacoder-v0/pi-loops/pull/87)
|
|
18
|
+
- **A host that learns from its own tick that it lost the clock leaves cleanly.** It awaited
|
|
19
|
+
`exit()` inside the tick, and `exit()` stops the scheduler, which waits for that same tick. The
|
|
20
|
+
host then left only through the 10 s `process.exit` fallback: its runs in flight wrote no
|
|
21
|
+
record and no `run_end` event, and `host.json` was left behind, so the next pi reported a
|
|
22
|
+
crashed host. [#52](https://github.com/alphacoder-v0/pi-loops/issues/52),
|
|
23
|
+
[#87](https://github.com/alphacoder-v0/pi-loops/pull/87)
|
|
24
|
+
|
|
8
25
|
## [0.22.7] - 2026-10-28
|
|
9
26
|
|
|
10
27
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alphacoder-v0/pi-loops",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.8",
|
|
4
4
|
"description": "An automation layer for pi: cron + stateful loops with a triage inbox, maker/checker verification, dynamic triggers, MCP notifications and tools, lifecycle hooks, session archives. A non-invasive pi extension.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/host-runtime.ts
CHANGED
|
@@ -139,7 +139,8 @@ export function createHostRuntime(deps: HostRuntimeDeps): HostRuntime {
|
|
|
139
139
|
if (exiting) return;
|
|
140
140
|
exiting = true;
|
|
141
141
|
log("an interactive pi owns the clock; exiting");
|
|
142
|
-
|
|
142
|
+
// Not awaited: exit() stops the scheduler, and scheduler.stop() waits for this tick.
|
|
143
|
+
Promise.resolve(deps.exit(0)).catch((err: any) => log(`exit failed: ${err?.message ?? err}`));
|
|
143
144
|
return;
|
|
144
145
|
}
|
|
145
146
|
await triggers.tick(now, leader);
|
package/src/scheduler.ts
CHANGED
|
@@ -776,6 +776,9 @@ export class LoopScheduler {
|
|
|
776
776
|
const priorFiredAt = job.lastFiredAt;
|
|
777
777
|
const claimed = await this.store.update(job.id, (j) => {
|
|
778
778
|
if (j.running) return;
|
|
779
|
+
// The slot was decided from a snapshot. If another process ran or skipped this job since,
|
|
780
|
+
// its stamps moved: that slot is spent, and running it again would run it twice.
|
|
781
|
+
if (j.lastDueAt !== priorDueAt || j.lastFiredAt !== priorFiredAt) return;
|
|
779
782
|
j.running = { runId, pid: process.pid, host: this.self.host, startedAt };
|
|
780
783
|
if (dueIso) j.lastDueAt = dueIso;
|
|
781
784
|
j.lastFiredAt = startedAt;
|