@hank-warren/pi-loop 0.2.0 → 0.2.1
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 +6 -0
- package/package.json +1 -1
- package/src/state.ts +27 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @hank-warren/pi-loop
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2b22273: Stop the loop with "goal completed" when pi-goal clears its state entry after completion. pi-goal persists the finished goal (status complete) and then writes a clear (goal: null), so by the loop's next tick the last goal-state entry was the clear and the loop paused as goal-missing instead of stopping as goal-complete. readGoalSnapshot now reads a completed goal through its completion clear; a clear over any non-complete goal (user /goal clear mid-flight) still pauses the loop.
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hank-warren/pi-loop",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Interval wakeups for Pi: recurring prompt re-runs, stall rescue toward an active pi-goal goal, and loop-aware compaction that survives long sessions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/src/state.ts
CHANGED
|
@@ -87,11 +87,17 @@ interface SessionEntryLike {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function lastCustomEntryData(entries: unknown[], customType: string): unknown {
|
|
90
|
-
|
|
90
|
+
return lastCustomEntryDatas(entries, customType, 1)[0];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Newest-first data of the last `limit` custom entries of `customType`. */
|
|
94
|
+
function lastCustomEntryDatas(entries: unknown[], customType: string, limit: number): unknown[] {
|
|
95
|
+
const datas: unknown[] = [];
|
|
96
|
+
for (let index = entries.length - 1; index >= 0 && datas.length < limit; index -= 1) {
|
|
91
97
|
const entry = entries[index] as SessionEntryLike | undefined;
|
|
92
|
-
if (entry?.type === "custom" && entry.customType === customType)
|
|
98
|
+
if (entry?.type === "custom" && entry.customType === customType) datas.push(entry.data);
|
|
93
99
|
}
|
|
94
|
-
return
|
|
100
|
+
return datas;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
/** Restore the persisted loop state from a session branch, fail-open. */
|
|
@@ -120,11 +126,26 @@ export interface GoalSnapshot {
|
|
|
120
126
|
* entry shape is not recognizably a goal. Only fields pi-loop consumes are
|
|
121
127
|
* extracted; unknown statuses are preserved verbatim so the caller can treat
|
|
122
128
|
* anything outside its known sets conservatively.
|
|
129
|
+
*
|
|
130
|
+
* Completion race: pi-goal persists the finished goal (status "complete") and
|
|
131
|
+
* then clears the entry (goal: null), so by the loop's next tick the last
|
|
132
|
+
* entry is the clear. A clear whose immediately preceding entry is a complete
|
|
133
|
+
* goal therefore reports that complete goal — the loop must stop with
|
|
134
|
+
* "goal completed", not pause as goal-missing. A clear over any other status
|
|
135
|
+
* (user /goal clear mid-flight) still reads as no goal.
|
|
123
136
|
*/
|
|
124
137
|
export function readGoalSnapshot(entries: unknown[]): GoalSnapshot | undefined {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
const datas = lastCustomEntryDatas(entries, GOAL_STATE_ENTRY_TYPE, 2);
|
|
139
|
+
const newest = ownRecord(datas[0]);
|
|
140
|
+
if (!newest) return undefined;
|
|
141
|
+
const goal = parseGoalSnapshot(newest.goal);
|
|
142
|
+
if (goal) return goal;
|
|
143
|
+
const cleared = parseGoalSnapshot(ownRecord(datas[1])?.goal);
|
|
144
|
+
return cleared?.status === "complete" ? cleared : undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function parseGoalSnapshot(value: unknown): GoalSnapshot | undefined {
|
|
148
|
+
const goal = ownRecord(value);
|
|
128
149
|
if (!goal) return undefined;
|
|
129
150
|
const status = typeof goal.status === "string" ? goal.status : undefined;
|
|
130
151
|
const text = typeof goal.text === "string" ? goal.text.trim() : "";
|