@trevonistrevon/pi-loop 0.1.1 → 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 +7 -0
- package/README.md +3 -2
- package/benchmarks/experiment-sim.js +30 -0
- package/dist/index.js +26 -7
- package/package.json +1 -1
- package/src/index.ts +27 -7
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,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
|
-
|
|
410
|
-
|
|
411
|
-
-
|
|
412
|
-
|
|
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
|
// ──────────────────────────────────────────────────
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -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
|
-
|
|
447
|
-
|
|
448
|
-
-
|
|
449
|
-
|
|
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
|
});
|