@trevonistrevon/pi-loop 0.5.0 → 0.5.2

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 (81) hide show
  1. package/README.md +11 -10
  2. package/dist/commands/loop-command.d.ts +22 -0
  3. package/dist/commands/loop-command.js +148 -0
  4. package/dist/commands/tasks-command.d.ts +15 -0
  5. package/dist/commands/tasks-command.js +117 -0
  6. package/dist/goal-coordinator.d.ts +22 -0
  7. package/dist/goal-coordinator.js +28 -0
  8. package/dist/goal-reducer.d.ts +89 -0
  9. package/dist/goal-reducer.js +181 -0
  10. package/dist/goal-store.d.ts +33 -0
  11. package/dist/goal-store.js +310 -0
  12. package/dist/goal-types.d.ts +4 -0
  13. package/dist/index.js +125 -1212
  14. package/dist/runtime/monitor-ondone-runtime.d.ts +13 -0
  15. package/dist/runtime/monitor-ondone-runtime.js +49 -0
  16. package/dist/runtime/notification-runtime.d.ts +34 -0
  17. package/dist/runtime/notification-runtime.js +152 -0
  18. package/dist/runtime/scope.d.ts +8 -0
  19. package/dist/runtime/scope.js +33 -0
  20. package/dist/runtime/session-runtime.d.ts +39 -0
  21. package/dist/runtime/session-runtime.js +110 -0
  22. package/dist/runtime/task-backlog-runtime.d.ts +36 -0
  23. package/dist/runtime/task-backlog-runtime.js +105 -0
  24. package/dist/runtime/task-rpc.d.ts +19 -0
  25. package/dist/runtime/task-rpc.js +118 -0
  26. package/dist/store.d.ts +7 -4
  27. package/dist/store.js +66 -48
  28. package/dist/task-store.d.ts +8 -4
  29. package/dist/task-store.js +80 -51
  30. package/dist/tools/loop-tools.d.ts +41 -0
  31. package/dist/tools/loop-tools.js +241 -0
  32. package/dist/tools/monitor-tools.d.ts +25 -0
  33. package/dist/tools/monitor-tools.js +110 -0
  34. package/dist/tools/native-task-tools.d.ts +15 -0
  35. package/dist/tools/native-task-tools.js +130 -0
  36. package/dist/ui/widget.d.ts +0 -1
  37. package/dist/ui/widget.js +1 -14
  38. package/package.json +1 -1
  39. package/src/commands/loop-command.ts +184 -0
  40. package/src/commands/tasks-command.ts +135 -0
  41. package/src/goal-coordinator.ts +58 -0
  42. package/src/goal-reducer.ts +280 -0
  43. package/src/goal-store.ts +326 -0
  44. package/src/goal-types.ts +5 -0
  45. package/src/index.ts +129 -1299
  46. package/src/runtime/monitor-ondone-runtime.ts +75 -0
  47. package/src/runtime/notification-runtime.ts +212 -0
  48. package/src/runtime/scope.ts +37 -0
  49. package/src/runtime/session-runtime.ts +168 -0
  50. package/src/runtime/task-backlog-runtime.ts +163 -0
  51. package/src/runtime/task-rpc.ts +150 -0
  52. package/src/store.ts +66 -49
  53. package/src/task-store.ts +76 -50
  54. package/src/tools/loop-tools.ts +304 -0
  55. package/src/tools/monitor-tools.ts +145 -0
  56. package/src/tools/native-task-tools.ts +147 -0
  57. package/src/ui/widget.ts +1 -16
  58. package/tmp/perf-cap-data/loops.json +455 -0
  59. package/tmp/perf-cap-data/tasks.json +1 -0
  60. package/tmp/perf-data/loops-0.json +1 -0
  61. package/tmp/perf-data/loops-10.json +1 -0
  62. package/tmp/perf-data/loops-100.json +1 -0
  63. package/tmp/perf-data/loops-1000.json +1 -0
  64. package/tmp/perf-data/loops-500.json +1 -0
  65. package/tmp/perf-data/tasks-0.json +1 -0
  66. package/tmp/perf-data/tasks-10.json +1 -0
  67. package/tmp/perf-data/tasks-100.json +1 -0
  68. package/tmp/perf-data/tasks-1000.json +1 -0
  69. package/tmp/perf-data/tasks-500.json +1 -0
  70. package/tmp/perf-data-session/loops-0.json +4 -0
  71. package/tmp/perf-data-session/loops-100.json +1805 -0
  72. package/tmp/perf-data-session/loops-1000.json +18005 -0
  73. package/tmp/perf-data-session/loops-25.json +455 -0
  74. package/tmp/perf-data-session/loops-500.json +9005 -0
  75. package/tmp/perf-data-session/tasks-0.json +1 -0
  76. package/tmp/perf-data-session/tasks-100.json +1 -0
  77. package/tmp/perf-data-session/tasks-1000.json +1 -0
  78. package/tmp/perf-data-session/tasks-25.json +1 -0
  79. package/tmp/perf-data-session/tasks-500.json +1 -0
  80. package/tmp/perf-session-init.js +98 -0
  81. package/tmp/perf-startup-audit.js +80 -0
@@ -0,0 +1,33 @@
1
+ import type { GoalCriteria, GoalEntry, GoalProgressSnapshot, GoalScope } from "./goal-types.js";
2
+ export declare class GoalStore {
3
+ private filePath;
4
+ private lockPath;
5
+ private lastLoadedSignature;
6
+ private nextId;
7
+ private goals;
8
+ constructor(listIdOrPath?: string);
9
+ private getFileSignature;
10
+ private load;
11
+ private save;
12
+ private withLock;
13
+ private toReducerState;
14
+ private applyReducerEvent;
15
+ create(title: string, description: string, scope: GoalScope, criteria: GoalCriteria, metadata?: Record<string, unknown>): GoalEntry;
16
+ get(id: string): GoalEntry | undefined;
17
+ list(): GoalEntry[];
18
+ activate(id: string): GoalEntry | undefined;
19
+ recordProgress(id: string, progress: GoalProgressSnapshot): GoalEntry | undefined;
20
+ markVerificationStarted(id: string): GoalEntry | undefined;
21
+ markVerified(id: string, reason: string, progress?: GoalProgressSnapshot): GoalEntry | undefined;
22
+ markFailed(id: string, reason: string, progress?: GoalProgressSnapshot): GoalEntry | undefined;
23
+ markBlocked(id: string, reason: string, progress?: GoalProgressSnapshot): GoalEntry | undefined;
24
+ unblock(id: string): GoalEntry | undefined;
25
+ updateDetails(id: string, fields: {
26
+ title?: string;
27
+ description?: string;
28
+ scope?: GoalScope;
29
+ criteria?: GoalCriteria;
30
+ metadata?: Record<string, unknown>;
31
+ }): GoalEntry | undefined;
32
+ archive(id: string, reason?: string): GoalEntry | undefined;
33
+ }
@@ -0,0 +1,310 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, isAbsolute, join } from "node:path";
4
+ import { reduceGoalState } from "./goal-reducer.js";
5
+ const GOALS_DIR = join(homedir(), ".pi", "goals");
6
+ const LOCK_RETRY_MS = 50;
7
+ const LOCK_MAX_RETRIES = 100;
8
+ const MAX_GOALS = 200;
9
+ function acquireLock(lockPath) {
10
+ for (let i = 0; i < LOCK_MAX_RETRIES; i++) {
11
+ try {
12
+ writeFileSync(lockPath, `${process.pid}`, { flag: "wx" });
13
+ return;
14
+ }
15
+ catch (e) {
16
+ if (e.code === "EEXIST") {
17
+ try {
18
+ const pid = parseInt(readFileSync(lockPath, "utf-8"), 10);
19
+ if (!pid || !isProcessRunning(pid)) {
20
+ try {
21
+ unlinkSync(lockPath);
22
+ }
23
+ catch { /* ignore */ }
24
+ continue;
25
+ }
26
+ }
27
+ catch { /* ignore read errors */ }
28
+ const start = Date.now();
29
+ while (Date.now() - start < LOCK_RETRY_MS) { /* busy wait */ }
30
+ continue;
31
+ }
32
+ throw e;
33
+ }
34
+ }
35
+ throw new Error(`Failed to acquire lock: ${lockPath}`);
36
+ }
37
+ function releaseLock(lockPath) {
38
+ try {
39
+ unlinkSync(lockPath);
40
+ }
41
+ catch { /* ignore */ }
42
+ }
43
+ function isProcessRunning(pid) {
44
+ try {
45
+ process.kill(pid, 0);
46
+ return true;
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ }
52
+ export class GoalStore {
53
+ filePath;
54
+ lockPath;
55
+ lastLoadedSignature;
56
+ nextId = 1;
57
+ goals = new Map();
58
+ constructor(listIdOrPath) {
59
+ if (!listIdOrPath)
60
+ return;
61
+ const isAbsPath = isAbsolute(listIdOrPath);
62
+ const filePath = isAbsPath ? listIdOrPath : join(GOALS_DIR, `${listIdOrPath}.json`);
63
+ mkdirSync(dirname(filePath), { recursive: true });
64
+ this.filePath = filePath;
65
+ this.lockPath = filePath + ".lock";
66
+ this.load();
67
+ }
68
+ getFileSignature() {
69
+ if (!this.filePath || !existsSync(this.filePath))
70
+ return undefined;
71
+ const stat = statSync(this.filePath);
72
+ return `${stat.mtimeMs}:${stat.size}`;
73
+ }
74
+ load(force = false) {
75
+ if (!this.filePath)
76
+ return;
77
+ const signature = this.getFileSignature();
78
+ if (!signature)
79
+ return;
80
+ if (!force && signature === this.lastLoadedSignature)
81
+ return;
82
+ try {
83
+ const data = JSON.parse(readFileSync(this.filePath, "utf-8"));
84
+ this.nextId = data.nextId;
85
+ this.goals.clear();
86
+ for (const goal of data.goals) {
87
+ this.goals.set(goal.id, goal);
88
+ }
89
+ this.lastLoadedSignature = signature;
90
+ }
91
+ catch { /* corrupt file — start fresh */ }
92
+ }
93
+ save() {
94
+ if (!this.filePath)
95
+ return;
96
+ const data = {
97
+ nextId: this.nextId,
98
+ goals: Array.from(this.goals.values()),
99
+ };
100
+ const tmpPath = this.filePath + ".tmp";
101
+ writeFileSync(tmpPath, JSON.stringify(data, null, 2));
102
+ renameSync(tmpPath, this.filePath);
103
+ this.lastLoadedSignature = this.getFileSignature();
104
+ }
105
+ withLock(fn) {
106
+ if (!this.lockPath)
107
+ return fn();
108
+ acquireLock(this.lockPath);
109
+ try {
110
+ this.load(true);
111
+ const result = fn();
112
+ this.save();
113
+ return result;
114
+ }
115
+ finally {
116
+ releaseLock(this.lockPath);
117
+ }
118
+ }
119
+ toReducerState() {
120
+ return {
121
+ nextId: this.nextId,
122
+ goalsById: Object.fromEntries(this.goals.entries()),
123
+ };
124
+ }
125
+ applyReducerEvent(event) {
126
+ const result = reduceGoalState(this.toReducerState(), event);
127
+ this.nextId = result.state.nextId;
128
+ this.goals = new Map(Object.entries(result.state.goalsById));
129
+ }
130
+ create(title, description, scope, criteria, metadata) {
131
+ return this.withLock(() => {
132
+ if (this.goals.size >= MAX_GOALS) {
133
+ throw new Error(`Maximum of ${MAX_GOALS} goals reached. Archive some before creating new ones.`);
134
+ }
135
+ this.applyReducerEvent({
136
+ type: "GOAL_CREATED",
137
+ at: Date.now(),
138
+ source: "tool",
139
+ entityType: "goal",
140
+ payload: {
141
+ title,
142
+ description,
143
+ scope,
144
+ criteria,
145
+ metadata,
146
+ },
147
+ });
148
+ return this.goals.get(String(this.nextId - 1));
149
+ });
150
+ }
151
+ get(id) {
152
+ if (this.filePath)
153
+ this.load();
154
+ return this.goals.get(id);
155
+ }
156
+ list() {
157
+ if (this.filePath)
158
+ this.load();
159
+ return Array.from(this.goals.values()).sort((a, b) => Number(a.id) - Number(b.id));
160
+ }
161
+ activate(id) {
162
+ return this.withLock(() => {
163
+ if (!this.goals.has(id))
164
+ return undefined;
165
+ this.applyReducerEvent({
166
+ type: "GOAL_ACTIVATED",
167
+ at: Date.now(),
168
+ source: "tool",
169
+ entityType: "goal",
170
+ entityId: id,
171
+ payload: { id },
172
+ });
173
+ return this.goals.get(id);
174
+ });
175
+ }
176
+ recordProgress(id, progress) {
177
+ return this.withLock(() => {
178
+ if (!this.goals.has(id))
179
+ return undefined;
180
+ this.applyReducerEvent({
181
+ type: "GOAL_PROGRESS_RECORDED",
182
+ at: Date.now(),
183
+ source: "coordinator",
184
+ entityType: "goal",
185
+ entityId: id,
186
+ payload: { id, progress },
187
+ });
188
+ return this.goals.get(id);
189
+ });
190
+ }
191
+ markVerificationStarted(id) {
192
+ return this.withLock(() => {
193
+ if (!this.goals.has(id))
194
+ return undefined;
195
+ this.applyReducerEvent({
196
+ type: "GOAL_VERIFICATION_STARTED",
197
+ at: Date.now(),
198
+ source: "coordinator",
199
+ entityType: "goal",
200
+ entityId: id,
201
+ payload: { id },
202
+ });
203
+ return this.goals.get(id);
204
+ });
205
+ }
206
+ markVerified(id, reason, progress) {
207
+ return this.withLock(() => {
208
+ if (!this.goals.has(id))
209
+ return undefined;
210
+ this.applyReducerEvent({
211
+ type: "GOAL_VERIFICATION_PASSED",
212
+ at: Date.now(),
213
+ source: "coordinator",
214
+ entityType: "goal",
215
+ entityId: id,
216
+ payload: { id, reason, progress },
217
+ });
218
+ return this.goals.get(id);
219
+ });
220
+ }
221
+ markFailed(id, reason, progress) {
222
+ return this.withLock(() => {
223
+ if (!this.goals.has(id))
224
+ return undefined;
225
+ this.applyReducerEvent({
226
+ type: "GOAL_FAILED",
227
+ at: Date.now(),
228
+ source: "coordinator",
229
+ entityType: "goal",
230
+ entityId: id,
231
+ payload: { id, reason, progress },
232
+ });
233
+ return this.goals.get(id);
234
+ });
235
+ }
236
+ markBlocked(id, reason, progress) {
237
+ return this.withLock(() => {
238
+ if (!this.goals.has(id))
239
+ return undefined;
240
+ this.applyReducerEvent({
241
+ type: "GOAL_BLOCKED",
242
+ at: Date.now(),
243
+ source: "coordinator",
244
+ entityType: "goal",
245
+ entityId: id,
246
+ payload: { id, reason, progress },
247
+ });
248
+ return this.goals.get(id);
249
+ });
250
+ }
251
+ unblock(id) {
252
+ return this.withLock(() => {
253
+ if (!this.goals.has(id))
254
+ return undefined;
255
+ this.applyReducerEvent({
256
+ type: "GOAL_UNBLOCKED",
257
+ at: Date.now(),
258
+ source: "coordinator",
259
+ entityType: "goal",
260
+ entityId: id,
261
+ payload: { id },
262
+ });
263
+ return this.goals.get(id);
264
+ });
265
+ }
266
+ updateDetails(id, fields) {
267
+ return this.withLock(() => {
268
+ if (!this.goals.has(id))
269
+ return undefined;
270
+ if (fields.title === undefined
271
+ && fields.description === undefined
272
+ && fields.scope === undefined
273
+ && fields.criteria === undefined
274
+ && fields.metadata === undefined) {
275
+ return this.goals.get(id);
276
+ }
277
+ this.applyReducerEvent({
278
+ type: "GOAL_UPDATED",
279
+ at: Date.now(),
280
+ source: "tool",
281
+ entityType: "goal",
282
+ entityId: id,
283
+ payload: {
284
+ id,
285
+ title: fields.title,
286
+ description: fields.description,
287
+ scope: fields.scope,
288
+ criteria: fields.criteria,
289
+ metadata: fields.metadata,
290
+ },
291
+ });
292
+ return this.goals.get(id);
293
+ });
294
+ }
295
+ archive(id, reason) {
296
+ return this.withLock(() => {
297
+ if (!this.goals.has(id))
298
+ return undefined;
299
+ this.applyReducerEvent({
300
+ type: "GOAL_ARCHIVED",
301
+ at: Date.now(),
302
+ source: "tool",
303
+ entityType: "goal",
304
+ entityId: id,
305
+ payload: { id, reason },
306
+ });
307
+ return this.goals.get(id);
308
+ });
309
+ }
310
+ }
@@ -76,3 +76,7 @@ export interface GoalReducerState {
76
76
  nextId: number;
77
77
  goalsById: Record<string, GoalEntry>;
78
78
  }
79
+ export interface GoalStoreData {
80
+ nextId: number;
81
+ goals: GoalEntry[];
82
+ }