@trevonistrevon/pi-loop 0.1.8 → 0.2.0

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/CONTRIBUTING.md CHANGED
@@ -5,7 +5,7 @@ Thanks for contributing! This document covers local dev setup, conventions, and
5
5
  ## Getting Started
6
6
 
7
7
  ```bash
8
- git clone https://github.com/trevonistrevon/pi-loop.git
8
+ git clone https://github.com/trvon/pi-loop.git
9
9
  cd pi-loop
10
10
  npm install
11
11
  ```
package/dist/index.js CHANGED
@@ -30,8 +30,6 @@ function debug(...args) {
30
30
  function textResult(msg) {
31
31
  return { content: [{ type: "text", text: msg }], details: undefined };
32
32
  }
33
- const LOOP_TOOL_NAMES = new Set(["LoopCreate", "LoopList", "LoopDelete", "MonitorCreate", "MonitorList", "MonitorStop"]);
34
- const REMINDER_INTERVAL = 3;
35
33
  const SYSTEM_REMINDER_TEMPLATE = `<system-reminder>
36
34
  Scheduled loop "%propmpt%" fired. Trigger: %trigger_info%.
37
35
  [loop:%loop_id%]
@@ -183,9 +181,7 @@ export default function (pi) {
183
181
  showPersistedLoops(isResume);
184
182
  });
185
183
  // ── System-reminder injection for loop fires ──
186
- let currentTurn = 0;
187
- let lastLoopToolUseTurn = 0;
188
- let reminderInjectedThisCycle = false;
184
+ let canInjectReminder = true;
189
185
  const pendingReminders = [];
190
186
  pi.on("loop:fire", (data) => {
191
187
  const triggerInfo = typeof data.trigger === "string"
@@ -201,22 +197,15 @@ export default function (pi) {
201
197
  .replace("%loop_id%", data.loopId || "unknown");
202
198
  pendingReminders.push(reminder);
203
199
  });
200
+ // Allow one reminder injection per agent turn (at the first tool call),
201
+ // so the reminder arrives between batches of work, not mid-execution.
204
202
  pi.on("turn_start", async () => {
205
- currentTurn++;
203
+ canInjectReminder = true;
206
204
  });
207
205
  pi.on("tool_result", async (event) => {
208
- if (LOOP_TOOL_NAMES.has(event.toolName)) {
209
- lastLoopToolUseTurn = currentTurn;
210
- reminderInjectedThisCycle = false;
206
+ if (!canInjectReminder || pendingReminders.length === 0)
211
207
  return {};
212
- }
213
- if (currentTurn - lastLoopToolUseTurn < REMINDER_INTERVAL || reminderInjectedThisCycle) {
214
- return {};
215
- }
216
- if (pendingReminders.length === 0)
217
- return {};
218
- reminderInjectedThisCycle = true;
219
- lastLoopToolUseTurn = currentTurn;
208
+ canInjectReminder = false;
220
209
  const reminder = pendingReminders.shift();
221
210
  return {
222
211
  content: [...event.content, { type: "text", text: reminder }],
@@ -237,6 +226,7 @@ Use this tool whenever the user asks to:
237
226
  - "watch for..." or "when X happens, do Y"
238
227
  - "schedule a recurring check"
239
228
  - set up a periodic monitor or poller
229
+ - has a task list with open items — create a loop to work through tasks automatically
240
230
 
241
231
  DO NOT use raw Bash loops (for/sleep/while). Use LoopCreate instead — it integrates with the session lifecycle, survives across turns, and the scheduler handles timing.
242
232
 
@@ -261,6 +251,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
261
251
  "When the user asks for a loop, repeating task, periodic check, or scheduled reminder, use LoopCreate — not raw Bash for/sleep/while.",
262
252
  "Use LoopCreate for any 'every X seconds/minutes/hours' requests.",
263
253
  "After creating a loop, tell the user the loop ID so they can cancel it with LoopDelete.",
254
+ "After creating tasks with TaskCreate, create a loop to keep working through them: LoopCreate trigger='30s' prompt='Run TaskList, pick the next available pending task, work on it. Continue until all tasks are completed.' This ensures the agent keeps making progress across turns instead of stopping after one task.",
264
255
  ],
265
256
  parameters: Type.Object({
266
257
  trigger: Type.String({ description: "Cron expression (e.g., '5m', '1h', '0 9 * * 1-5'), event source (e.g., 'tool_execution_start'), or JSON hybrid spec" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
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
@@ -35,9 +35,6 @@ function textResult(msg: string) {
35
35
  return { content: [{ type: "text" as const, text: msg }], details: undefined as any };
36
36
  }
37
37
 
38
- const LOOP_TOOL_NAMES = new Set(["LoopCreate", "LoopList", "LoopDelete", "MonitorCreate", "MonitorList", "MonitorStop"]);
39
- const REMINDER_INTERVAL = 3;
40
-
41
38
  const SYSTEM_REMINDER_TEMPLATE = `<system-reminder>
42
39
  Scheduled loop "%propmpt%" fired. Trigger: %trigger_info%.
43
40
  [loop:%loop_id%]
@@ -197,9 +194,7 @@ export default function (pi: ExtensionAPI) {
197
194
 
198
195
  // ── System-reminder injection for loop fires ──
199
196
 
200
- let currentTurn = 0;
201
- let lastLoopToolUseTurn = 0;
202
- let reminderInjectedThisCycle = false;
197
+ let canInjectReminder = true;
203
198
  const pendingReminders: string[] = [];
204
199
 
205
200
  pi.on("loop:fire" as any, (data: any) => {
@@ -219,25 +214,16 @@ export default function (pi: ExtensionAPI) {
219
214
  pendingReminders.push(reminder);
220
215
  });
221
216
 
217
+ // Allow one reminder injection per agent turn (at the first tool call),
218
+ // so the reminder arrives between batches of work, not mid-execution.
222
219
  pi.on("turn_start", async () => {
223
- currentTurn++;
220
+ canInjectReminder = true;
224
221
  });
225
222
 
226
223
  pi.on("tool_result", async (event) => {
227
- if (LOOP_TOOL_NAMES.has(event.toolName)) {
228
- lastLoopToolUseTurn = currentTurn;
229
- reminderInjectedThisCycle = false;
230
- return {};
231
- }
232
-
233
- if (currentTurn - lastLoopToolUseTurn < REMINDER_INTERVAL || reminderInjectedThisCycle) {
234
- return {};
235
- }
236
-
237
- if (pendingReminders.length === 0) return {};
224
+ if (!canInjectReminder || pendingReminders.length === 0) return {};
238
225
 
239
- reminderInjectedThisCycle = true;
240
- lastLoopToolUseTurn = currentTurn;
226
+ canInjectReminder = false;
241
227
  const reminder = pendingReminders.shift()!;
242
228
  return {
243
229
  content: [...event.content, { type: "text" as const, text: reminder }],
@@ -260,6 +246,7 @@ Use this tool whenever the user asks to:
260
246
  - "watch for..." or "when X happens, do Y"
261
247
  - "schedule a recurring check"
262
248
  - set up a periodic monitor or poller
249
+ - has a task list with open items — create a loop to work through tasks automatically
263
250
 
264
251
  DO NOT use raw Bash loops (for/sleep/while). Use LoopCreate instead — it integrates with the session lifecycle, survives across turns, and the scheduler handles timing.
265
252
 
@@ -284,6 +271,7 @@ Skip this tool when the task is a one-off check (just do it directly) or when th
284
271
  "When the user asks for a loop, repeating task, periodic check, or scheduled reminder, use LoopCreate — not raw Bash for/sleep/while.",
285
272
  "Use LoopCreate for any 'every X seconds/minutes/hours' requests.",
286
273
  "After creating a loop, tell the user the loop ID so they can cancel it with LoopDelete.",
274
+ "After creating tasks with TaskCreate, create a loop to keep working through them: LoopCreate trigger='30s' prompt='Run TaskList, pick the next available pending task, work on it. Continue until all tasks are completed.' This ensures the agent keeps making progress across turns instead of stopping after one task.",
287
275
  ],
288
276
  parameters: Type.Object({
289
277
  trigger: Type.String({ description: "Cron expression (e.g., '5m', '1h', '0 9 * * 1-5'), event source (e.g., 'tool_execution_start'), or JSON hybrid spec" }),