@trevonistrevon/pi-loop 0.4.9 → 0.4.11

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/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  ## Install
7
7
 
8
8
  ```bash
9
- pi install @trevonistrevon/pi-loop
9
+ pi install npm:@trevonistrevon/pi-loop
10
10
  ```
11
11
 
12
12
  ## Quick start
package/dist/index.js CHANGED
@@ -403,6 +403,18 @@ export default function (pi) {
403
403
  showPersistedLoops(isResume);
404
404
  widget.update();
405
405
  });
406
+ pi.on("tool_execution_end", async (event, ctx) => {
407
+ _latestCtx = ctx;
408
+ widget.setUICtx(ctx.ui);
409
+ if (event?.toolName !== "bash" || event?.isError)
410
+ return;
411
+ const command = event?.args?.command ?? event?.input?.command;
412
+ if (typeof command !== "string")
413
+ return;
414
+ if (!/^\s*git\s+commit\b/i.test(command))
415
+ return;
416
+ await cleanDoneTasks();
417
+ });
406
418
  // ── Dynamic loop pump — fires cron/hybrid loops on idle instead of wall-clock timers ──
407
419
  async function pumpLoops() {
408
420
  const pendingTasks = new Map();
@@ -571,11 +583,24 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
571
583
  },
572
584
  });
573
585
  function handleMonitorDoneLoop(doneLoop, monitorId) {
574
- triggerSystem.add(doneLoop);
586
+ const deliver = () => {
587
+ const current = store.get(doneLoop.id);
588
+ if (!current)
589
+ return;
590
+ debug(`onDone loop #${doneLoop.id} — monitor #${monitorId} completed, delivering directly`);
591
+ onLoopFire(current);
592
+ store.delete(doneLoop.id);
593
+ };
594
+ const registered = monitorManager.onComplete(monitorId, deliver);
595
+ if (registered)
596
+ return;
575
597
  const monitor = monitorManager.get(monitorId);
576
598
  if (monitor && monitor.status !== "running") {
599
+ if (monitor.status === "completed") {
600
+ deliver();
601
+ return;
602
+ }
577
603
  debug(`onDone loop #${doneLoop.id} — monitor #${monitorId} already ${monitor.status}, expiring`);
578
- triggerSystem.remove(doneLoop.id);
579
604
  store.delete(doneLoop.id);
580
605
  }
581
606
  }
@@ -9,5 +9,6 @@ export declare class MonitorManager {
9
9
  get(id: string): MonitorEntry | undefined;
10
10
  list(): MonitorEntry[];
11
11
  stop(id: string): Promise<boolean>;
12
+ onComplete(id: string, callback: () => void): boolean;
12
13
  getProcess(id: string): MonitorProcess | undefined;
13
14
  }
@@ -30,6 +30,7 @@ export class MonitorManager {
30
30
  proc: child,
31
31
  abortController,
32
32
  waiters: [],
33
+ completionCallbacks: [],
33
34
  };
34
35
  child.stdout?.on("data", (data) => {
35
36
  const lines = data.toString().split("\n");
@@ -70,6 +71,11 @@ export class MonitorManager {
70
71
  exitCode: code,
71
72
  outputLines: entry.outputLines,
72
73
  });
74
+ if (status === "completed") {
75
+ for (const callback of bp.completionCallbacks)
76
+ callback();
77
+ }
78
+ bp.completionCallbacks = [];
73
79
  for (const resolve of bp.waiters)
74
80
  resolve();
75
81
  bp.waiters = [];
@@ -90,6 +96,7 @@ export class MonitorManager {
90
96
  monitorId: id,
91
97
  error: err.message,
92
98
  });
99
+ bp.completionCallbacks = [];
93
100
  for (const resolve of bp.waiters)
94
101
  resolve();
95
102
  bp.waiters = [];
@@ -134,11 +141,25 @@ export class MonitorManager {
134
141
  });
135
142
  });
136
143
  bp.entry.completedAt = Date.now();
144
+ bp.completionCallbacks = [];
137
145
  for (const resolve of bp.waiters)
138
146
  resolve();
139
147
  bp.waiters = [];
140
148
  return true;
141
149
  }
150
+ onComplete(id, callback) {
151
+ const bp = this.processes.get(id);
152
+ if (!bp)
153
+ return false;
154
+ if (bp.entry.status === "completed") {
155
+ callback();
156
+ return true;
157
+ }
158
+ if (bp.entry.status !== "running")
159
+ return false;
160
+ bp.completionCallbacks.push(callback);
161
+ return true;
162
+ }
142
163
  getProcess(id) {
143
164
  return this.processes.get(id);
144
165
  }
package/dist/types.d.ts CHANGED
@@ -55,4 +55,5 @@ export interface MonitorProcess {
55
55
  proc: import("node:child_process").ChildProcess;
56
56
  abortController: AbortController;
57
57
  waiters: Array<() => void>;
58
+ completionCallbacks: Array<() => void>;
58
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "description": "A pi extension for cron/event-based agent re-wake loops and background process monitoring.",
5
5
  "author": "trevonistrevon",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -441,6 +441,19 @@ export default function (pi: ExtensionAPI) {
441
441
  widget.update();
442
442
  });
443
443
 
444
+ pi.on("tool_execution_end", async (event: any, ctx: ExtensionContext) => {
445
+ _latestCtx = ctx;
446
+ widget.setUICtx(ctx.ui);
447
+
448
+ if (event?.toolName !== "bash" || event?.isError) return;
449
+
450
+ const command = event?.args?.command ?? event?.input?.command;
451
+ if (typeof command !== "string") return;
452
+ if (!/^\s*git\s+commit\b/i.test(command)) return;
453
+
454
+ await cleanDoneTasks();
455
+ });
456
+
444
457
  // ── Dynamic loop pump — fires cron/hybrid loops on idle instead of wall-clock timers ──
445
458
 
446
459
  async function pumpLoops(): Promise<void> {
@@ -621,11 +634,24 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
621
634
  });
622
635
 
623
636
  function handleMonitorDoneLoop(doneLoop: LoopEntry, monitorId: string): void {
624
- triggerSystem.add(doneLoop);
637
+ const deliver = () => {
638
+ const current = store.get(doneLoop.id);
639
+ if (!current) return;
640
+ debug(`onDone loop #${doneLoop.id} — monitor #${monitorId} completed, delivering directly`);
641
+ onLoopFire(current);
642
+ store.delete(doneLoop.id);
643
+ };
644
+
645
+ const registered = monitorManager.onComplete(monitorId, deliver);
646
+ if (registered) return;
647
+
625
648
  const monitor = monitorManager.get(monitorId);
626
649
  if (monitor && monitor.status !== "running") {
650
+ if (monitor.status === "completed") {
651
+ deliver();
652
+ return;
653
+ }
627
654
  debug(`onDone loop #${doneLoop.id} — monitor #${monitorId} already ${monitor.status}, expiring`);
628
- triggerSystem.remove(doneLoop.id);
629
655
  store.delete(doneLoop.id);
630
656
  }
631
657
  }
@@ -35,6 +35,7 @@ export class MonitorManager {
35
35
  proc: child,
36
36
  abortController,
37
37
  waiters: [],
38
+ completionCallbacks: [],
38
39
  };
39
40
 
40
41
  child.stdout?.on("data", (data: Buffer) => {
@@ -74,6 +75,10 @@ export class MonitorManager {
74
75
  exitCode: code,
75
76
  outputLines: entry.outputLines,
76
77
  });
78
+ if (status === "completed") {
79
+ for (const callback of bp.completionCallbacks) callback();
80
+ }
81
+ bp.completionCallbacks = [];
77
82
  for (const resolve of bp.waiters) resolve();
78
83
  bp.waiters = [];
79
84
  // Remove completed/errored monitors after a brief delay so tool
@@ -95,6 +100,7 @@ export class MonitorManager {
95
100
  monitorId: id,
96
101
  error: err.message,
97
102
  });
103
+ bp.completionCallbacks = [];
98
104
  for (const resolve of bp.waiters) resolve();
99
105
  bp.waiters = [];
100
106
  }
@@ -143,11 +149,24 @@ export class MonitorManager {
143
149
  });
144
150
 
145
151
  bp.entry.completedAt = Date.now();
152
+ bp.completionCallbacks = [];
146
153
  for (const resolve of bp.waiters) resolve();
147
154
  bp.waiters = [];
148
155
  return true;
149
156
  }
150
157
 
158
+ onComplete(id: string, callback: () => void): boolean {
159
+ const bp = this.processes.get(id);
160
+ if (!bp) return false;
161
+ if (bp.entry.status === "completed") {
162
+ callback();
163
+ return true;
164
+ }
165
+ if (bp.entry.status !== "running") return false;
166
+ bp.completionCallbacks.push(callback);
167
+ return true;
168
+ }
169
+
151
170
  getProcess(id: string): MonitorProcess | undefined {
152
171
  return this.processes.get(id);
153
172
  }
package/src/types.ts CHANGED
@@ -60,4 +60,5 @@ export interface MonitorProcess {
60
60
  proc: import("node:child_process").ChildProcess;
61
61
  abortController: AbortController;
62
62
  waiters: Array<() => void>;
63
+ completionCallbacks: Array<() => void>;
63
64
  }