@trevonistrevon/pi-loop 0.1.0 → 0.1.3

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,17 @@
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
+
10
+ ## [0.1.1]
11
+
12
+ - Migrated peer dependencies from `@mariozechner/pi-*` to `@earendil-works/pi-*`
13
+ - Fixed `.npmignore` to include `src/` and `dist/` directories
14
+
3
15
  ## [0.1.0] — Initial Release
4
16
 
5
17
  ### Tools
package/README.md CHANGED
@@ -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.d.ts CHANGED
@@ -13,5 +13,5 @@
13
13
  * /loop — Schedule a re-wake loop: /loop [interval] [prompt]
14
14
  * /loops — Interactive menu: view, create, cancel, settings
15
15
  */
16
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
16
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
17
17
  export default function (pi: ExtensionAPI): void;
package/dist/index.js CHANGED
@@ -402,18 +402,30 @@ 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: `Start a background command and stream its output via pi events. Optionally auto-notify when done.
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
+ 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, run experiments, etc.
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
+ ## When to Use
410
+
411
+ Use this tool to:\n- Start a long-running experiment and get notified when it finishes\n- Watch a log file or build output in the background\n- Poll an API and stream results\n- Run any background command whose output you want to track
412
+
413
+ ## Events emitted
414
+
415
+ - "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
416
+
417
+ ## onDone — auto-notify on completion
418
+
419
+ 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. No need to create a separate LoopCreate or poll MonitorList.\n\nExample: MonitorCreate command="python train.py" onDone="Check training results and report best loss"`,
420
+ promptGuidelines: [
421
+ "Use onDone to auto-notify when a long-running command finishes — no need for a separate LoopCreate.",
422
+ "When starting experiments or benchmarks, pass onDone so the agent automatically picks up the results.",
423
+ ],
413
424
  parameters: Type.Object({
414
425
  command: Type.String({ description: "Shell command to run in background" }),
415
426
  description: Type.Optional(Type.String({ description: "Human-readable description" })),
416
427
  timeout: Type.Optional(Type.Number({ description: "Auto-stop after N ms (default: 300000, 0 = no timeout)", default: 300000 })),
428
+ 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
429
  }),
418
430
  execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
419
431
  if (monitorManager.list().filter(m => m.status === "running").length >= 25) {
@@ -421,9 +433,16 @@ Events emitted:
421
433
  }
422
434
  const entry = monitorManager.create(params.command, params.description, params.timeout);
423
435
  widget.update();
436
+ let onDoneMsg = "";
437
+ if (params.onDone) {
438
+ const doneTrigger = { type: "event", source: "monitor:done", filter: JSON.stringify({ monitorId: entry.id }) };
439
+ const doneLoop = store.create(doneTrigger, params.onDone, { recurring: false });
440
+ triggerSystem.add(doneLoop);
441
+ onDoneMsg = `\nonDone loop #${doneLoop.id}: fires when monitor completes — no polling needed`;
442
+ }
424
443
  return Promise.resolve(textResult(`Monitor #${entry.id} started: ${entry.command.slice(0, 60)}\n` +
425
444
  `Output stream: monitor:output (monitorId: ${entry.id})\n` +
426
- `Timeout: ${params.timeout ? `${params.timeout / 1000}s` : "none"}`));
445
+ `Timeout: ${params.timeout ? `${params.timeout / 1000}s` : "none"}${onDoneMsg}`));
427
446
  },
428
447
  });
429
448
  // ──────────────────────────────────────────────────
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { MonitorEntry, MonitorProcess } from "./types.js";
3
3
  export declare class MonitorManager {
4
4
  private pi;
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { CronScheduler } from "./scheduler.js";
3
3
  import type { LoopEntry } from "./types.js";
4
4
  export declare class TriggerSystem {
package/dist/ui/widget.js CHANGED
@@ -1,4 +1,4 @@
1
- import { truncateToWidth } from "@mariozechner/pi-tui";
1
+ import { truncateToWidth } from "@earendil-works/pi-tui";
2
2
  const MAX_VISIBLE = 6;
3
3
  export class LoopWidget {
4
4
  store;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trevonistrevon/pi-loop",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
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",
@@ -18,8 +18,8 @@
18
18
  "scheduler"
19
19
  ],
20
20
  "peerDependencies": {
21
- "@mariozechner/pi-coding-agent": ">=0.70.5",
22
- "@mariozechner/pi-tui": ">=0.70.5"
21
+ "@earendil-works/pi-coding-agent": "^0.75.5",
22
+ "@earendil-works/pi-tui": "^0.75.5"
23
23
  },
24
24
  "dependencies": {
25
25
  "typebox": "^1.1.34"
@@ -34,9 +34,9 @@
34
34
  "lint:fix": "biome check --fix src/ test/"
35
35
  },
36
36
  "devDependencies": {
37
+ "@biomejs/biome": "^2.4.13",
37
38
  "@types/node": "^25.0.0",
38
39
  "typescript": "^6.0.0",
39
- "@biomejs/biome": "^2.4.13",
40
40
  "vitest": "^4.1.5"
41
41
  },
42
42
  "pi": {
package/src/index.ts CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { randomUUID } from "node:crypto";
18
18
  import { join, resolve } from "node:path";
19
- import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
19
+ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
20
20
  import { Type } from "typebox";
21
21
  import { parseInterval } from "./loop-parse.js";
22
22
  import { MonitorManager } from "./monitor-manager.js";
@@ -439,18 +439,30 @@ 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: `Start a background command and stream its output via pi events. Optionally auto-notify when done.
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
+ 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, run experiments, etc.
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
+ ## When to Use
447
+
448
+ Use this tool to:\n- Start a long-running experiment and get notified when it finishes\n- Watch a log file or build output in the background\n- Poll an API and stream results\n- Run any background command whose output you want to track
449
+
450
+ ## Events emitted
451
+
452
+ - "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
453
+
454
+ ## onDone — auto-notify on completion
455
+
456
+ 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. No need to create a separate LoopCreate or poll MonitorList.\n\nExample: MonitorCreate command="python train.py" onDone="Check training results and report best loss"`,
457
+ promptGuidelines: [
458
+ "Use onDone to auto-notify when a long-running command finishes — no need for a separate LoopCreate.",
459
+ "When starting experiments or benchmarks, pass onDone so the agent automatically picks up the results.",
460
+ ],
450
461
  parameters: Type.Object({
451
462
  command: Type.String({ description: "Shell command to run in background" }),
452
463
  description: Type.Optional(Type.String({ description: "Human-readable description" })),
453
464
  timeout: Type.Optional(Type.Number({ description: "Auto-stop after N ms (default: 300000, 0 = no timeout)", default: 300000 })),
465
+ 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
466
  }),
455
467
 
456
468
  execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
@@ -461,10 +473,18 @@ Events emitted:
461
473
  const entry = monitorManager.create(params.command, params.description, params.timeout);
462
474
  widget.update();
463
475
 
476
+ let onDoneMsg = "";
477
+ if (params.onDone) {
478
+ const doneTrigger: Trigger = { type: "event", source: "monitor:done", filter: JSON.stringify({ monitorId: entry.id }) };
479
+ const doneLoop = store.create(doneTrigger, params.onDone, { recurring: false });
480
+ triggerSystem.add(doneLoop);
481
+ onDoneMsg = `\nonDone loop #${doneLoop.id}: fires when monitor completes — no polling needed`;
482
+ }
483
+
464
484
  return Promise.resolve(textResult(
465
485
  `Monitor #${entry.id} started: ${entry.command.slice(0, 60)}\n` +
466
486
  `Output stream: monitor:output (monitorId: ${entry.id})\n` +
467
- `Timeout: ${params.timeout ? `${params.timeout / 1000}s` : "none"}`
487
+ `Timeout: ${params.timeout ? `${params.timeout / 1000}s` : "none"}${onDoneMsg}`
468
488
  ));
469
489
  },
470
490
  });
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { spawn } from "node:child_process";
3
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import type { MonitorEntry, MonitorProcess } from "./types.js";
5
5
 
6
6
  export class MonitorManager {
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import type { CronScheduler } from "./scheduler.js";
3
3
  import type { LoopEntry } from "./types.js";
4
4
 
package/src/ui/widget.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { truncateToWidth } from "@mariozechner/pi-tui";
1
+ import { truncateToWidth } from "@earendil-works/pi-tui";
2
2
  import type { MonitorManager } from "../monitor-manager.js";
3
3
  import type { CronScheduler } from "../scheduler.js";
4
4
  import type { LoopStore } from "../store.js";