@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 +7 -0
- package/README.md +3 -2
- package/benchmarks/experiment-sim.js +30 -0
- package/dist/index.js +31 -7
- package/package.json +1 -1
- package/src/index.ts +32 -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,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: `
|
|
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
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
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: `
|
|
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
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
});
|