@trevonistrevon/pi-loop 0.1.1 → 0.1.4

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.2]
4
+
5
+ - Added `onDone` parameter to `MonitorCreate` — auto-creates a completion loop so the agent is notified when a background process finishes, no polling needed
6
+ - Updated tool descriptions and prompt guidelines for the MonitorCreate + LoopCreate pairing
7
+
8
+
9
+
3
10
  ## [0.1.1]
4
11
 
5
12
  - Migrated peer dependencies from `@mariozechner/pi-*` to `@earendil-works/pi-*`
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <h1 align="center">@pi-loop</h1>
2
+ <h1 align="center">@trevonistrevon/pi-loop</h1>
3
3
  <h6 align="center">Cron and event loops for the pi coding agent. Background monitors, scheduled re-wakes, pi-tasks integration.</h6>
4
4
  </p>
5
5
 
@@ -20,6 +20,7 @@ LoopDelete id="1"
20
20
 
21
21
  ```
22
22
  MonitorCreate command="tail -n0 -f build.log" description="Watch build"
23
+ MonitorCreate command="python train.py" onDone="Analyze results and report best loss"
23
24
  MonitorList
24
25
  MonitorStop monitorId="1"
25
26
  ```
@@ -40,7 +41,7 @@ MonitorStop monitorId="1"
40
41
  | `LoopCreate` | Schedule a prompt on a cron timer, a pi event, or both with debounce |
41
42
  | `LoopList` | Show active loops with IDs, triggers, and next-fire times |
42
43
  | `LoopDelete` | Delete or pause a loop |
43
- | `MonitorCreate` | Run a background command, stream output as `monitor:output` events |
44
+ | `MonitorCreate` | Run a background command, stream output as `monitor:output` events. Use `onDone` for auto-notify on completion |
44
45
  | `MonitorList` | Show monitors with status, uptime, and output line count |
45
46
  | `MonitorStop` | Stop a monitor (SIGTERM → 5s → SIGKILL) |
46
47
 
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Synthetic benchmark — mimics a research experiment with periodic progress output.
4
+ * Prints "iteration N/M, metric=0.XX" every 200ms for 30 iterations.
5
+ */
6
+
7
+ const TOTAL = 30;
8
+ const INTERVAL_MS = 200;
9
+
10
+ let iteration = 0;
11
+
12
+ const timer = setInterval(() => {
13
+ iteration++;
14
+ const metric = (Math.random() * 0.5 + 0.3).toFixed(4);
15
+ console.log(`iteration ${iteration}/${TOTAL}, loss=${metric}, timestamp=${Date.now()}`);
16
+
17
+ if (iteration >= TOTAL) {
18
+ console.log(`experiment complete: best loss=0.${Math.floor(Math.random() * 1000).toString().padStart(4, "0")}`);
19
+ clearInterval(timer);
20
+ process.exit(0);
21
+ }
22
+ }, INTERVAL_MS);
23
+
24
+ process.on("SIGTERM", () => {
25
+ console.log("experiment interrupted — saving checkpoint");
26
+ clearInterval(timer);
27
+ process.exit(0);
28
+ });
29
+
30
+ console.log("experiment starting: total_iterations=30, interval_ms=200");
package/dist/index.js CHANGED
@@ -402,18 +402,35 @@ Use "pause" to temporarily stop a loop without removing it. Use "delete" to perm
402
402
  pi.registerTool({
403
403
  name: "MonitorCreate",
404
404
  label: "MonitorCreate",
405
- description: `Start a background command and stream its output via pi events.
405
+ description: `Run a shell command in the background and get notified when it finishes. The core tool for async/parallel work.
406
406
 
407
- The monitor runs a shell command in the background. Each output line is emitted as a "monitor:output" pi event. Use this to watch logs, tail files, poll APIs, etc.
407
+ Fire off a build check, CI monitor, experiment, script, or any slow command then keep working. Output streams back as "monitor:output" events. When the process exits, "monitor:done" fires (or "monitor:error" on failure).
408
408
 
409
- Events emitted:
410
- - "monitor:output" — { monitorId, line, timestamp } for each output line
411
- - "monitor:done" { monitorId, exitCode } on clean exit
412
- - "monitor:error" — { monitorId, error } on failure`,
409
+ If you pass onDone with a prompt, the monitor auto-creates a one-shot completion loop — you get a system reminder with the exit code and output line count. No need to poll or create a separate loop.
410
+
411
+ DO NOT use raw Bash while/sleep/for loops to watch something. DO NOT run slow commands inline that could be offloaded. Use MonitorCreate to run work in parallel while you continue.
412
+
413
+ ## When to Use
414
+
415
+ Default to MonitorCreate for any long-running or background work:\n- Watch a CI/CD build (hut, gh, curl polling) while you work on something else\n- Run experiments, benchmarks, or training scripts in parallel\n- Tail a log or poll an API endpoint\n- Fire off a slow curl/fetch and check the result later\n- Run any script or command you don't need to wait on inline
416
+
417
+ ## Events emitted
418
+
419
+ - "monitor:output" — { monitorId, line, timestamp } for each output line\n- "monitor:done" — { monitorId, exitCode, outputLines } on clean exit\n- "monitor:error" — { monitorId, error } on failure
420
+
421
+ ## onDone — auto-notify on completion
422
+
423
+ Pass onDone with a prompt and the monitor auto-creates a one-shot loop that fires when the process exits. The system reminder includes the exit code and output line count.\n\nExample: MonitorCreate command="python train.py" onDone="Check training results and report best loss"\nExample: MonitorCreate command="hut builds show 1769753" onDone="Analyze the build result and report status"`,
424
+ promptGuidelines: [
425
+ "Default to MonitorCreate for any long-running or background command — releases the agent to keep working on other tasks in parallel.",
426
+ "When the user asks to monitor CI builds, watch a build, check a remote job, or run an experiment, use MonitorCreate instead of inline bash/curl/wait.",
427
+ "Use onDone to auto-notify when a background command finishes — the agent will pick up the results automatically.",
428
+ ],
413
429
  parameters: Type.Object({
414
430
  command: Type.String({ description: "Shell command to run in background" }),
415
431
  description: Type.Optional(Type.String({ description: "Human-readable description" })),
416
432
  timeout: Type.Optional(Type.Number({ description: "Auto-stop after N ms (default: 300000, 0 = no timeout)", default: 300000 })),
433
+ onDone: Type.Optional(Type.String({ description: "Prompt to run when the monitor completes. Auto-creates a one-shot completion loop — no need for a separate LoopCreate." })),
417
434
  }),
418
435
  execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
419
436
  if (monitorManager.list().filter(m => m.status === "running").length >= 25) {
@@ -421,9 +438,16 @@ Events emitted:
421
438
  }
422
439
  const entry = monitorManager.create(params.command, params.description, params.timeout);
423
440
  widget.update();
441
+ let onDoneMsg = "";
442
+ if (params.onDone) {
443
+ const doneTrigger = { type: "event", source: "monitor:done", filter: JSON.stringify({ monitorId: entry.id }) };
444
+ const doneLoop = store.create(doneTrigger, params.onDone, { recurring: false });
445
+ triggerSystem.add(doneLoop);
446
+ onDoneMsg = `\nonDone loop #${doneLoop.id}: fires when monitor completes — no polling needed`;
447
+ }
424
448
  return Promise.resolve(textResult(`Monitor #${entry.id} started: ${entry.command.slice(0, 60)}\n` +
425
449
  `Output stream: monitor:output (monitorId: ${entry.id})\n` +
426
- `Timeout: ${params.timeout ? `${params.timeout / 1000}s` : "none"}`));
450
+ `Timeout: ${params.timeout ? `${params.timeout / 1000}s` : "none"}${onDoneMsg}`));
427
451
  },
428
452
  });
429
453
  // ──────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
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
@@ -439,18 +439,35 @@ Use "pause" to temporarily stop a loop without removing it. Use "delete" to perm
439
439
  pi.registerTool({
440
440
  name: "MonitorCreate",
441
441
  label: "MonitorCreate",
442
- description: `Start a background command and stream its output via pi events.
442
+ description: `Run a shell command in the background and get notified when it finishes. The core tool for async/parallel work.
443
443
 
444
- The monitor runs a shell command in the background. Each output line is emitted as a "monitor:output" pi event. Use this to watch logs, tail files, poll APIs, etc.
444
+ Fire off a build check, CI monitor, experiment, script, or any slow command then keep working. Output streams back as "monitor:output" events. When the process exits, "monitor:done" fires (or "monitor:error" on failure).
445
445
 
446
- Events emitted:
447
- - "monitor:output" — { monitorId, line, timestamp } for each output line
448
- - "monitor:done" { monitorId, exitCode } on clean exit
449
- - "monitor:error" — { monitorId, error } on failure`,
446
+ If you pass onDone with a prompt, the monitor auto-creates a one-shot completion loop — you get a system reminder with the exit code and output line count. No need to poll or create a separate loop.
447
+
448
+ DO NOT use raw Bash while/sleep/for loops to watch something. DO NOT run slow commands inline that could be offloaded. Use MonitorCreate to run work in parallel while you continue.
449
+
450
+ ## When to Use
451
+
452
+ Default to MonitorCreate for any long-running or background work:\n- Watch a CI/CD build (hut, gh, curl polling) while you work on something else\n- Run experiments, benchmarks, or training scripts in parallel\n- Tail a log or poll an API endpoint\n- Fire off a slow curl/fetch and check the result later\n- Run any script or command you don't need to wait on inline
453
+
454
+ ## Events emitted
455
+
456
+ - "monitor:output" — { monitorId, line, timestamp } for each output line\n- "monitor:done" — { monitorId, exitCode, outputLines } on clean exit\n- "monitor:error" — { monitorId, error } on failure
457
+
458
+ ## onDone — auto-notify on completion
459
+
460
+ Pass onDone with a prompt and the monitor auto-creates a one-shot loop that fires when the process exits. The system reminder includes the exit code and output line count.\n\nExample: MonitorCreate command="python train.py" onDone="Check training results and report best loss"\nExample: MonitorCreate command="hut builds show 1769753" onDone="Analyze the build result and report status"`,
461
+ promptGuidelines: [
462
+ "Default to MonitorCreate for any long-running or background command — releases the agent to keep working on other tasks in parallel.",
463
+ "When the user asks to monitor CI builds, watch a build, check a remote job, or run an experiment, use MonitorCreate instead of inline bash/curl/wait.",
464
+ "Use onDone to auto-notify when a background command finishes — the agent will pick up the results automatically.",
465
+ ],
450
466
  parameters: Type.Object({
451
467
  command: Type.String({ description: "Shell command to run in background" }),
452
468
  description: Type.Optional(Type.String({ description: "Human-readable description" })),
453
469
  timeout: Type.Optional(Type.Number({ description: "Auto-stop after N ms (default: 300000, 0 = no timeout)", default: 300000 })),
470
+ onDone: Type.Optional(Type.String({ description: "Prompt to run when the monitor completes. Auto-creates a one-shot completion loop — no need for a separate LoopCreate." })),
454
471
  }),
455
472
 
456
473
  execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
@@ -461,10 +478,18 @@ Events emitted:
461
478
  const entry = monitorManager.create(params.command, params.description, params.timeout);
462
479
  widget.update();
463
480
 
481
+ let onDoneMsg = "";
482
+ if (params.onDone) {
483
+ const doneTrigger: Trigger = { type: "event", source: "monitor:done", filter: JSON.stringify({ monitorId: entry.id }) };
484
+ const doneLoop = store.create(doneTrigger, params.onDone, { recurring: false });
485
+ triggerSystem.add(doneLoop);
486
+ onDoneMsg = `\nonDone loop #${doneLoop.id}: fires when monitor completes — no polling needed`;
487
+ }
488
+
464
489
  return Promise.resolve(textResult(
465
490
  `Monitor #${entry.id} started: ${entry.command.slice(0, 60)}\n` +
466
491
  `Output stream: monitor:output (monitorId: ${entry.id})\n` +
467
- `Timeout: ${params.timeout ? `${params.timeout / 1000}s` : "none"}`
492
+ `Timeout: ${params.timeout ? `${params.timeout / 1000}s` : "none"}${onDoneMsg}`
468
493
  ));
469
494
  },
470
495
  });