@trevonistrevon/pi-loop 0.5.9 → 0.6.1
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 +72 -0
- package/README.md +64 -5
- package/dist/api.d.ts +11 -0
- package/dist/api.js +10 -0
- package/dist/commands/loop-command.d.ts +9 -2
- package/dist/commands/loop-command.js +81 -51
- package/dist/commands/tasks-command.js +3 -3
- package/dist/coordinator.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +89 -19
- package/dist/loop-format.d.ts +3 -0
- package/dist/loop-format.js +20 -0
- package/dist/loop-parse.d.ts +1 -0
- package/dist/loop-parse.js +59 -6
- package/dist/loop-reducer.d.ts +13 -1
- package/dist/loop-reducer.js +26 -0
- package/dist/monitor-manager.js +1 -1
- package/dist/notification-reducer.d.ts +2 -0
- package/dist/reducer-backed-store.d.ts +1 -1
- package/dist/reducer-backed-store.js +1 -1
- package/dist/rpc/channels.d.ts +73 -0
- package/dist/rpc/channels.js +30 -0
- package/dist/rpc/cross-extension-rpc.d.ts +59 -0
- package/dist/rpc/cross-extension-rpc.js +117 -0
- package/dist/runtime/loop-events.d.ts +26 -0
- package/dist/runtime/loop-events.js +26 -0
- package/dist/runtime/monitor-ondone-runtime.js +1 -1
- package/dist/runtime/native-task-rpc.d.ts +22 -0
- package/dist/runtime/native-task-rpc.js +50 -0
- package/dist/runtime/notification-runtime.d.ts +2 -1
- package/dist/runtime/notification-runtime.js +19 -7
- package/dist/runtime/task-backlog-runtime.d.ts +5 -1
- package/dist/runtime/task-backlog-runtime.js +14 -7
- package/dist/runtime/task-mutations.d.ts +39 -0
- package/dist/runtime/task-mutations.js +68 -0
- package/dist/runtime/task-rpc.d.ts +4 -0
- package/dist/runtime/task-rpc.js +40 -58
- package/dist/scheduler.js +25 -7
- package/dist/store.d.ts +9 -1
- package/dist/store.js +57 -0
- package/dist/tools/loop-tools.d.ts +8 -0
- package/dist/tools/loop-tools.js +105 -15
- package/dist/tools/monitor-tools.js +1 -3
- package/dist/tools/native-task-tools.d.ts +2 -6
- package/dist/tools/native-task-tools.js +25 -55
- package/dist/tools/tool-result.d.ts +8 -0
- package/dist/tools/tool-result.js +4 -0
- package/dist/trigger-system.js +1 -1
- package/dist/types.d.ts +24 -1
- package/package.json +15 -2
- package/src/api.ts +36 -0
- package/src/commands/loop-command.ts +100 -55
- package/src/commands/tasks-command.ts +3 -3
- package/src/coordinator.ts +1 -1
- package/src/index.ts +92 -19
- package/src/loop-format.ts +22 -0
- package/src/loop-parse.ts +62 -8
- package/src/loop-reducer.ts +41 -1
- package/src/monitor-manager.ts +1 -1
- package/src/notification-reducer.ts +3 -0
- package/src/reducer-backed-store.ts +3 -3
- package/src/rpc/channels.ts +97 -0
- package/src/rpc/cross-extension-rpc.ts +152 -0
- package/src/runtime/loop-events.ts +60 -0
- package/src/runtime/monitor-ondone-runtime.ts +1 -1
- package/src/runtime/native-task-rpc.ts +139 -0
- package/src/runtime/notification-runtime.ts +21 -8
- package/src/runtime/task-backlog-runtime.ts +25 -7
- package/src/runtime/task-mutations.ts +103 -0
- package/src/runtime/task-rpc.ts +48 -54
- package/src/scheduler.ts +25 -8
- package/src/store.ts +59 -2
- package/src/tools/loop-tools.ts +129 -16
- package/src/tools/monitor-tools.ts +1 -4
- package/src/tools/native-task-tools.ts +31 -52
- package/src/tools/tool-result.ts +4 -0
- package/src/trigger-system.ts +5 -5
- package/src/types.ts +29 -1
- package/.release-please-manifest.json +0 -3
- package/AGENTS.md +0 -83
- package/CONTRIBUTING.md +0 -69
- package/Dockerfile +0 -17
- package/benchmarks/experiment-sim.js +0 -30
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -33
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -176
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/commands/index.html +0 -131
- package/coverage/src/commands/loop-command.ts.html +0 -634
- package/coverage/src/commands/tasks-command.ts.html +0 -490
- package/coverage/src/coordinator.ts.html +0 -430
- package/coverage/src/goal-coordinator.ts.html +0 -259
- package/coverage/src/goal-reducer.ts.html +0 -982
- package/coverage/src/goal-store.ts.html +0 -742
- package/coverage/src/goal-verifier.ts.html +0 -808
- package/coverage/src/index.html +0 -386
- package/coverage/src/index.ts.html +0 -1111
- package/coverage/src/loop-parse.ts.html +0 -574
- package/coverage/src/loop-reducer.ts.html +0 -559
- package/coverage/src/monitor-completion-coordinator.ts.html +0 -157
- package/coverage/src/monitor-manager.ts.html +0 -940
- package/coverage/src/monitor-reducer.ts.html +0 -583
- package/coverage/src/notification-reducer.ts.html +0 -550
- package/coverage/src/reducer-backed-store.ts.html +0 -589
- package/coverage/src/runtime/index.html +0 -206
- package/coverage/src/runtime/monitor-ondone-runtime.ts.html +0 -310
- package/coverage/src/runtime/notification-runtime.ts.html +0 -721
- package/coverage/src/runtime/scope.ts.html +0 -196
- package/coverage/src/runtime/session-runtime.ts.html +0 -679
- package/coverage/src/runtime/task-backlog-runtime.ts.html +0 -574
- package/coverage/src/runtime/task-events.ts.html +0 -208
- package/coverage/src/runtime/task-rpc.ts.html +0 -541
- package/coverage/src/scheduler.ts.html +0 -400
- package/coverage/src/store.ts.html +0 -667
- package/coverage/src/task-backlog-coordinator.ts.html +0 -181
- package/coverage/src/task-reducer.ts.html +0 -550
- package/coverage/src/task-store.ts.html +0 -526
- package/coverage/src/tools/index.html +0 -146
- package/coverage/src/tools/loop-tools.ts.html +0 -1000
- package/coverage/src/tools/monitor-tools.ts.html +0 -547
- package/coverage/src/tools/native-task-tools.ts.html +0 -556
- package/coverage/src/trigger-system.ts.html +0 -547
- package/coverage/src/ui/index.html +0 -116
- package/coverage/src/ui/widget.ts.html +0 -292
- package/dist/goal-coordinator.d.ts +0 -22
- package/dist/goal-coordinator.js +0 -28
- package/dist/goal-reducer.d.ts +0 -98
- package/dist/goal-reducer.js +0 -190
- package/dist/goal-store.d.ts +0 -22
- package/dist/goal-store.js +0 -188
- package/dist/goal-types.d.ts +0 -82
- package/dist/goal-types.js +0 -1
- package/dist/goal-verifier.d.ts +0 -20
- package/dist/goal-verifier.js +0 -198
- package/docker-compose.yml +0 -12
- package/docs/architecture/goal-state-schema.md +0 -505
- package/docs/architecture/state-machine-migration.md +0 -546
- package/docs/architecture/state-machine-reducer-event-model.md +0 -823
- package/docs/architecture/state-machine-test-matrix.md +0 -249
- package/docs/architecture/state-machine-transition-map.md +0 -436
- package/release-please-config.json +0 -9
- package/src/goal-coordinator.ts +0 -58
- package/src/goal-reducer.ts +0 -299
- package/src/goal-store.ts +0 -219
- package/src/goal-types.ts +0 -104
- package/src/goal-verifier.ts +0 -241
- package/vitest.config.ts +0 -29
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.1](https://github.com/trvon/pi-loop/compare/pi-loop-v0.6.0...pi-loop-v0.6.1) (2026-07-16)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **loop:** add dynamic goal loops ([44e578f](https://github.com/trvon/pi-loop/commit/44e578f5e7cdeaf6aee765835892dd975d2fe913))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* fast failing monitor hit error before onDone registered. Orca/agent never woke. ([56cd43d](https://github.com/trvon/pi-loop/commit/56cd43d2b41a6b0df5c10cccb11c0fa04981d83a))
|
|
14
|
+
* **loop:** harden dynamic goal loops ([036fbaf](https://github.com/trvon/pi-loop/commit/036fbaff9a8c7c60acefce4dec757bfc1e6ff8d0))
|
|
15
|
+
|
|
16
|
+
## [0.6.0](https://github.com/trvon/pi-loop/compare/pi-loop-v0.5.8...pi-loop-v0.6.0) (2026-07-02)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### ⚠ BREAKING CHANGES
|
|
20
|
+
|
|
21
|
+
* package exports map blocks deep src/ imports — consumers must use @trevonistrevon/pi-loop/api. tasks:updated now reports previousStatus at edit time, not pre-transition.
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* add maxFires for self-limiting loops, event-driven prompt steering ([52b50f0](https://github.com/trvon/pi-loop/commit/52b50f0d2b8c0b1df6aa74b09a8561807b549fe6))
|
|
26
|
+
* add native task fallback and compact task tracker ([6b5b6ff](https://github.com/trvon/pi-loop/commit/6b5b6fff53cc7810242e4f9081fe6d21b318c654))
|
|
27
|
+
* add task decomposition guidance to TaskCreate prompt ([4c51e72](https://github.com/trvon/pi-loop/commit/4c51e72a7317e1433cdb5bed6cfc9dfa521b5bfb))
|
|
28
|
+
* add tasks:rpc:clean RPC for sweeping done tasks ([73812ed](https://github.com/trvon/pi-loop/commit/73812ed4c3efeb08e97908e6de6435f65c768440))
|
|
29
|
+
* auto-create task worker loop at backlog threshold ([430663a](https://github.com/trvon/pi-loop/commit/430663a8042560688e08bd08129ea57b190ccecc))
|
|
30
|
+
* canonical rpc module + init-time task server ([7335eff](https://github.com/trvon/pi-loop/commit/7335eff6440452178ae9db3cb22a49098ad3953d))
|
|
31
|
+
* generalize task backlog loop cleanup ([9132948](https://github.com/trvon/pi-loop/commit/9132948dc9e22cda32adf6e8c5e13a7203f9c864))
|
|
32
|
+
* goal prompt and loading refinements ([b31df23](https://github.com/trvon/pi-loop/commit/b31df23c240f0bde7fb69fb8ea702429576276c7))
|
|
33
|
+
* prune completed tasks after successful git commit ([cba120b](https://github.com/trvon/pi-loop/commit/cba120be3a7fcdf71b7f7649b414577412d26a94))
|
|
34
|
+
* refining monitor interface ([66a6007](https://github.com/trvon/pi-loop/commit/66a60077093f40c258efeeb971f6f8db74e24f83))
|
|
35
|
+
* show worker-loop hint when pending tasks reach 5+ ([74dbf2c](https://github.com/trvon/pi-loop/commit/74dbf2cbf7f55a4206762eee3731798527537ab5))
|
|
36
|
+
* skip loop fires when autoTask loop has no pending tasks ([f2feb07](https://github.com/trvon/pi-loop/commit/f2feb079d5b520ed4c4dd0dc85d5b385efc45c45))
|
|
37
|
+
* **tasks:** add native rpc update and backlog signals ([31d06f6](https://github.com/trvon/pi-loop/commit/31d06f61fce1333c359f0d38def7406474144f8b))
|
|
38
|
+
* **tasks:** emit native task events with previousStatus tracking ([61b49ea](https://github.com/trvon/pi-loop/commit/61b49ea5703597b02754998269edfdc5b2018ebb))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Bug Fixes
|
|
42
|
+
|
|
43
|
+
* add trigger validation, readOnly flag, and edge-case tests ([91baa07](https://github.com/trvon/pi-loop/commit/91baa07884bc20d760c7a3642c3ec462997fc0d3))
|
|
44
|
+
* auto-delete worker loop when task backlog clears ([502fa10](https://github.com/trvon/pi-loop/commit/502fa106b8862c06648e6be373e5980d2af18f6f))
|
|
45
|
+
* auto-expire monitor:done loops, buffer output, show completed monitors ([a3ec5de](https://github.com/trvon/pi-loop/commit/a3ec5de5847d67bddee9c572079b559b03e35f3f))
|
|
46
|
+
* deduplicate loop follow-up messages to prevent flood ([1613511](https://github.com/trvon/pi-loop/commit/1613511a2453d346edb59919beed2bdb18c05f63))
|
|
47
|
+
* delete done loops/monitors immediately instead of marking expired ([43f5220](https://github.com/trvon/pi-loop/commit/43f5220a3e6d807234e4bdb7611e44c0fa7468ec))
|
|
48
|
+
* delete event maxFires loops immediately ([12335e7](https://github.com/trvon/pi-loop/commit/12335e75dcf8d17a2c887966cae180d2b2f848da))
|
|
49
|
+
* deliver monitor onDone wakes without event dependency ([79d6a8b](https://github.com/trvon/pi-loop/commit/79d6a8b925aa98e8de0ea1b12addd5e0d2db0c47))
|
|
50
|
+
* derive jitter ceiling from cron step, delete expired event loops ([6d13935](https://github.com/trvon/pi-loop/commit/6d13935e79d2367072bec027d5ef2cb430eff44a))
|
|
51
|
+
* flush buffered worker wakes on agent end ([7a7dedf](https://github.com/trvon/pi-loop/commit/7a7dedf3ec7f16eb63edbfaf93e5dae9ed7a8f25))
|
|
52
|
+
* harden TaskUpdate prompt to prevent taskId alias errors ([5dfefd9](https://github.com/trvon/pi-loop/commit/5dfefd96bb08c29d192de472c6224ac434394b6f))
|
|
53
|
+
* **injection:** use before_agent_start message, not tool_result ([99a6317](https://github.com/trvon/pi-loop/commit/99a6317aabde8ce9d4f9e0c3065f42b6c6111259))
|
|
54
|
+
* loop trigger fix ([619f32c](https://github.com/trvon/pi-loop/commit/619f32c48134d9cc1376a89a62a7c8d729321143))
|
|
55
|
+
* **monitor:** make MonitorManager spawn-injectable, fix CI test timeouts ([f277646](https://github.com/trvon/pi-loop/commit/f277646b95369b43508fbbc8cc7eef5e37580781))
|
|
56
|
+
* **monitor:** prune stopped/timed-out monitors ([a46f6e8](https://github.com/trvon/pi-loop/commit/a46f6e8f2b99fa39bd11ff22d1a34999e1ac4434))
|
|
57
|
+
* only dedup recurring loop fires, always deliver one-shot events ([352e50e](https://github.com/trvon/pi-loop/commit/352e50e1de14ecd8d884b18a8e72572270ca90e9))
|
|
58
|
+
* **persist:** expire event loops on session start, clean stale monitors ([7f0876d](https://github.com/trvon/pi-loop/commit/7f0876d821e93e663f43ec2d5351bc9a37223b4b))
|
|
59
|
+
* recommend 5m default interval in LoopCreate task-continuation prompt ([1af3bcd](https://github.com/trvon/pi-loop/commit/1af3bcd35a2782f9fc333559d293c665db76c234))
|
|
60
|
+
* release please fix ([16e2304](https://github.com/trvon/pi-loop/commit/16e2304ea3112664c12c65b4e098e04d390f93f0))
|
|
61
|
+
* **reminders:** make loop reminder directive, not informational ([e21174d](https://github.com/trvon/pi-loop/commit/e21174df3d1d9403684e094f5763db33ed9cb732))
|
|
62
|
+
* repair native task fallback compilation ([a8cef04](https://github.com/trvon/pi-loop/commit/a8cef04eabd8a8af404d27ffa756266ee6a188ec))
|
|
63
|
+
* **runtime:** unref retention timer, swallow heartbeat pump errors ([602816b](https://github.com/trvon/pi-loop/commit/602816b3abb06859a0268c02119cc753de03decb))
|
|
64
|
+
* scope native task files by session, prevent cross-session leakage ([0436710](https://github.com/trvon/pi-loop/commit/04367102414dd56f7668e1522b91c6f089490979))
|
|
65
|
+
* **tasks:** gate late native RPC probes ([c97f4ad](https://github.com/trvon/pi-loop/commit/c97f4ad6800c00b9858c1a8e640f13e699e0c26a))
|
|
66
|
+
* **tasks:** guard native fallback registration ([7d3b74a](https://github.com/trvon/pi-loop/commit/7d3b74abfa637bbe370ae232b8067e7bc5205ea1))
|
|
67
|
+
* **trigger:** auto-expire non-recurring event loops ([178f9fd](https://github.com/trvon/pi-loop/commit/178f9fd6a6548a04307f3ed83935068a235c24d3))
|
|
68
|
+
* use pi.hasPendingMessages() instead of bespoke tracking Set ([dab60d4](https://github.com/trvon/pi-loop/commit/dab60d46340fab04ae74b80044ad03b6b0ca9fe8))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
### Performance Improvements
|
|
72
|
+
|
|
73
|
+
* **test:** replace real 6.1s waits with fake-timer advance in onDone tests ([c713ad3](https://github.com/trvon/pi-loop/commit/c713ad36f958d53e75c4c7d7fd723c06ee420543))
|
|
74
|
+
|
|
3
75
|
## [0.1.2]
|
|
4
76
|
|
|
5
77
|
- Added `onDone` parameter to `MonitorCreate` — auto-creates a completion loop so the agent is notified when a background process finishes, no polling needed
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<h1 align="center">@trevonistrevon/pi-loop</h1>
|
|
3
|
-
<h6 align="center">Cron and
|
|
3
|
+
<h6 align="center">Cron, event, and dynamic goal loops for the pi coding agent. Background monitors, scheduled re-wakes, pi-tasks integration, and native task fallback.</h6>
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
6
|
## Install
|
|
@@ -36,13 +36,24 @@ TaskDelete id="1"
|
|
|
36
36
|
|
|
37
37
|
## Commands
|
|
38
38
|
|
|
39
|
-
`/loop
|
|
39
|
+
`/loop` creates scheduled, event-triggered, or self-paced dynamic goal loops.
|
|
40
40
|
|
|
41
41
|
```text
|
|
42
|
-
/loop
|
|
43
|
-
/loop 5m check the deploy
|
|
42
|
+
/loop # menu
|
|
43
|
+
/loop 5m check the deploy # 5-minute cron loop
|
|
44
|
+
/loop event tasks:created process backlog # event loop
|
|
45
|
+
/loop finish the release # dynamic goal loop
|
|
44
46
|
```
|
|
45
47
|
|
|
48
|
+
Dynamic goal loops wake immediately when the agent is idle. After each iteration, the agent calls `LoopUpdate` with one of:
|
|
49
|
+
|
|
50
|
+
- `status="continue"` to save progress and wake again when idle
|
|
51
|
+
- `status="continue" nextInterval="3m"` to schedule a timed next wake
|
|
52
|
+
- `status="paused"` when blocked
|
|
53
|
+
- `status="completed"` to finish and delete the loop
|
|
54
|
+
|
|
55
|
+
Paused dynamic loops can be resumed from the `/loop` menu. Dynamic loops recover their wake after a process restart or session switch if an in-memory notification was lost.
|
|
56
|
+
|
|
46
57
|
`/tasks` — interactive native task viewer/manager, only registered when `pi-tasks` is absent.
|
|
47
58
|
|
|
48
59
|
```text
|
|
@@ -56,6 +67,7 @@ TaskDelete id="1"
|
|
|
56
67
|
|---|---|
|
|
57
68
|
| `LoopCreate` | Schedule a prompt on a cron timer, a pi event, or both with debounce |
|
|
58
69
|
| `LoopList` | Show active loops with IDs, triggers, and next-fire times |
|
|
70
|
+
| `LoopUpdate` | Continue, pause, or complete a dynamic goal loop and save its progress |
|
|
59
71
|
| `LoopDelete` | Delete or pause a loop |
|
|
60
72
|
| `MonitorCreate` | Run a background command, stream output as `monitor:output` events. Use `onDone` for auto-notify on completion |
|
|
61
73
|
| `MonitorList` | Show monitors with status, uptime, and output line count |
|
|
@@ -65,7 +77,7 @@ TaskDelete id="1"
|
|
|
65
77
|
| `TaskUpdate` | Update native fallback task status/details |
|
|
66
78
|
| `TaskDelete` | Delete a native fallback task |
|
|
67
79
|
|
|
68
|
-
Trigger types: `cron` (`5m`, `1h`, `0 9 * * 1-5`), `event` (any pi event source),
|
|
80
|
+
Trigger types: `cron` (`5m`, `1h`, `0 9 * * 1-5`), `event` (any pi event source), `hybrid` (both, debounced), or `dynamic` (self-paced goal loops created with `/loop <goal>`).
|
|
69
81
|
|
|
70
82
|
## Tasks
|
|
71
83
|
|
|
@@ -81,9 +93,56 @@ If `pi-tasks` does not respond during startup detection, `pi-loop` registers a n
|
|
|
81
93
|
- `TaskCreate`, `TaskList`, `TaskUpdate`, `TaskDelete`
|
|
82
94
|
- `/tasks` interactive viewer
|
|
83
95
|
- compact status-line task tracking
|
|
96
|
+
- native task RPC replies on `tasks:rpc:ping`, `tasks:rpc:create`, `tasks:rpc:pending`, `tasks:rpc:clean`, and `tasks:rpc:update`
|
|
84
97
|
|
|
85
98
|
This fallback is session-sticky: `pi-loop` decides once at startup whether `pi-tasks` or native tasks own task management for that session.
|
|
86
99
|
|
|
100
|
+
### Task and backlog events
|
|
101
|
+
|
|
102
|
+
`pi-loop` emits native task lifecycle events that other extensions can consume directly:
|
|
103
|
+
|
|
104
|
+
- `tasks:created`
|
|
105
|
+
- `tasks:started`
|
|
106
|
+
- `tasks:completed`
|
|
107
|
+
- `tasks:reopened`
|
|
108
|
+
- `tasks:updated`
|
|
109
|
+
- `tasks:deleted`
|
|
110
|
+
|
|
111
|
+
Payloads carry `previousStatus`. Transition events (`tasks:started` /
|
|
112
|
+
`tasks:completed` / `tasks:reopened`) report the status before the transition;
|
|
113
|
+
`tasks:updated` (a details edit) reports the status current at edit time — so a
|
|
114
|
+
combined status+details update never fabricates a second transition. (Changed
|
|
115
|
+
in 0.6.0: the tool path previously reused the pre-transition status.)
|
|
116
|
+
- `tasks:backlog_empty` — emitted when a task-backlog worker observes zero pending tasks and is about to auto-delete
|
|
117
|
+
- `loops:autodeleted` — emitted for each loop that `pi-loop` auto-deletes, including backlog workers removed because the task queue drained
|
|
118
|
+
|
|
119
|
+
### Cross-extension task RPC
|
|
120
|
+
|
|
121
|
+
Other extensions can create and manage native tasks without importing `pi-loop` internals. `pi-loop` answers `ping` over `pi.events` from extension init; the other verbs unlock once the pi-tasks detection probe settles (a few seconds at most, immediate when no provider replies race it). The whole server stands down (silent no-op) once an external `pi-tasks` is detected:
|
|
122
|
+
|
|
123
|
+
| Channel | Request | Reply |
|
|
124
|
+
|---|---|---|
|
|
125
|
+
| `tasks:rpc:ping` | `{}` | `{ version, provider }` |
|
|
126
|
+
| `tasks:rpc:pending` | `{}` | `{ pending }` |
|
|
127
|
+
| `tasks:rpc:create` | `{ subject, description, metadata? }` | `{ id, task }` |
|
|
128
|
+
| `tasks:rpc:clean` | `{}` | `{ pruned }` |
|
|
129
|
+
| `tasks:rpc:update` | `{ id, status?, subject?, description? }` | `{ task }` |
|
|
130
|
+
|
|
131
|
+
Every request/reply pair follows the same contract: emit `{ requestId, ...params }` on the channel, receive an envelope on `<channel>:reply:<requestId>` — `{ success: true, data }` or `{ success: false, error }`.
|
|
132
|
+
|
|
133
|
+
`@trevonistrevon/pi-loop/api` exports typed channel constants and a client helper so consumers don't hand-roll the envelope:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { TASKS_RPC, rpcCall } from "@trevonistrevon/pi-loop/api";
|
|
137
|
+
|
|
138
|
+
const { id, task } = await rpcCall(pi.events, TASKS_RPC.create, {
|
|
139
|
+
subject: "Fix deploy polling",
|
|
140
|
+
description: "Switch deploy check to event-driven loop",
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`rpcCall` rejects on failure or timeout instead of returning a sentinel; wrap it in `try/catch` if you want fallback behavior. Import only from `@trevonistrevon/pi-loop/api` — the package's `exports` map blocks deep `src/` imports.
|
|
145
|
+
|
|
87
146
|
## Status line
|
|
88
147
|
|
|
89
148
|
`pi-loop` keeps a compact persistent status line in the TUI.
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API surface for sibling extensions (imported as
|
|
3
|
+
* `@trevonistrevon/pi-loop/api`). Everything else under src/ is internal —
|
|
4
|
+
* the package `exports` map deliberately blocks deep imports.
|
|
5
|
+
*/
|
|
6
|
+
export { type CleanReply, type CreateTaskParams, type CreateTaskReply, type PendingReply, type PingReply, replyChannel, type SpawnParams, type SpawnReply, SUBAGENTS_RPC, TASK_EVENTS, TASKS_RPC, type TaskEntryWire, type TaskStatusWire, type UpdateTaskParams, type UpdateTaskReply, } from "./rpc/channels.js";
|
|
7
|
+
export { type HandleRpcOptions, handleRpc, PROTOCOL_VERSION, RpcError, type RpcEventBus, type RpcReply, rpcCall, rpcProbe, } from "./rpc/cross-extension-rpc.js";
|
|
8
|
+
export { NATIVE_TASKS_PROVIDER } from "./runtime/native-task-rpc.js";
|
|
9
|
+
export { resolveLoopStorePath, resolveTaskStorePath } from "./runtime/scope.js";
|
|
10
|
+
export { TaskStore } from "./task-store.js";
|
|
11
|
+
export type { TaskEntry, TaskStatus, TaskStoreData } from "./task-types.js";
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API surface for sibling extensions (imported as
|
|
3
|
+
* `@trevonistrevon/pi-loop/api`). Everything else under src/ is internal —
|
|
4
|
+
* the package `exports` map deliberately blocks deep imports.
|
|
5
|
+
*/
|
|
6
|
+
export { replyChannel, SUBAGENTS_RPC, TASK_EVENTS, TASKS_RPC, } from "./rpc/channels.js";
|
|
7
|
+
export { handleRpc, PROTOCOL_VERSION, RpcError, rpcCall, rpcProbe, } from "./rpc/cross-extension-rpc.js";
|
|
8
|
+
export { NATIVE_TASKS_PROVIDER } from "./runtime/native-task-rpc.js";
|
|
9
|
+
export { resolveLoopStorePath, resolveTaskStorePath } from "./runtime/scope.js";
|
|
10
|
+
export { TaskStore } from "./task-store.js";
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { LoopEntry, Trigger } from "../types.js";
|
|
2
|
+
import type { DynamicLoopState, LoopEntry, Trigger } from "../types.js";
|
|
3
3
|
interface LoopStoreLike {
|
|
4
4
|
list(): LoopEntry[];
|
|
5
5
|
get(id: string): LoopEntry | undefined;
|
|
6
|
-
create(trigger: Trigger, prompt: string, options
|
|
6
|
+
create(trigger: Trigger, prompt: string, options: {
|
|
7
|
+
recurring: boolean;
|
|
8
|
+
autoTask?: boolean;
|
|
9
|
+
taskBacklog?: boolean;
|
|
10
|
+
readOnly?: boolean;
|
|
11
|
+
maxFires?: number;
|
|
12
|
+
dynamic?: Partial<DynamicLoopState>;
|
|
13
|
+
}): LoopEntry;
|
|
7
14
|
pause(id: string): LoopEntry | undefined;
|
|
8
15
|
resume(id: string): LoopEntry | undefined;
|
|
9
16
|
delete(id: string): boolean;
|
|
@@ -1,30 +1,69 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatTrigger } from "../loop-format.js";
|
|
2
|
+
import { isValidCronExpression, parseInterval } from "../loop-parse.js";
|
|
3
|
+
function parseLoopCommandRoute(input) {
|
|
4
|
+
const trimmed = input.trim();
|
|
5
|
+
if (!trimmed)
|
|
6
|
+
return { type: "menu" };
|
|
7
|
+
const eventMatch = trimmed.match(/^(?:event|when)\s+(\S+)\s+(.+)$/i);
|
|
8
|
+
if (eventMatch?.[1] && eventMatch[2]) {
|
|
9
|
+
return { type: "event", source: eventMatch[1], prompt: eventMatch[2].trim() };
|
|
10
|
+
}
|
|
11
|
+
const parts = trimmed.split(/\s+/);
|
|
12
|
+
if (parts.length > 5) {
|
|
13
|
+
const interval = parts.slice(0, 5).join(" ");
|
|
14
|
+
const cronShaped = parts.slice(0, 5).every((part) => /^[\d*/,-]+$/.test(part));
|
|
15
|
+
if (cronShaped) {
|
|
16
|
+
if (!isValidCronExpression(interval))
|
|
17
|
+
return { type: "invalid-cron", interval };
|
|
18
|
+
return { type: "cron", interval, prompt: parts.slice(5).join(" "), notifyEvery: false };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const intervalMatch = trimmed.match(/^(\d+\s*[smhdS]\b)/i);
|
|
22
|
+
if (intervalMatch) {
|
|
23
|
+
const interval = intervalMatch[1] ?? intervalMatch[0];
|
|
24
|
+
const prompt = trimmed.slice(intervalMatch[0].length).trim();
|
|
25
|
+
if (!prompt)
|
|
26
|
+
return { type: "missing-interval-prompt" };
|
|
27
|
+
return { type: "cron", interval, prompt, notifyEvery: true };
|
|
28
|
+
}
|
|
29
|
+
return { type: "dynamic", goal: trimmed };
|
|
30
|
+
}
|
|
2
31
|
export function registerLoopCommand(options) {
|
|
3
32
|
const { pi, getStore, getTriggerSystem, updateWidget } = options;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (!p)
|
|
7
|
-
return;
|
|
8
|
-
const interval = await ui.input("Interval (e.g., 5m, 2h, 1d)");
|
|
9
|
-
if (!interval)
|
|
10
|
-
return;
|
|
33
|
+
function createCronLoop(ui, interval, prompt, notifyEvery) {
|
|
34
|
+
let entry;
|
|
11
35
|
try {
|
|
12
36
|
const parsed = parseInterval(interval);
|
|
13
37
|
const trigger = { type: "cron", schedule: parsed.cron };
|
|
14
|
-
|
|
38
|
+
entry = getStore().create(trigger, prompt, { recurring: true });
|
|
15
39
|
getTriggerSystem().add(entry);
|
|
16
40
|
updateWidget();
|
|
17
|
-
|
|
41
|
+
const cadence = notifyEvery ? `every ${parsed.description}` : parsed.description;
|
|
42
|
+
ui.notify(`Loop #${entry.id} created: ${cadence} — ${prompt.slice(0, 50)}`, "info");
|
|
18
43
|
}
|
|
19
44
|
catch (err) {
|
|
45
|
+
if (entry) {
|
|
46
|
+
getTriggerSystem().remove(entry.id);
|
|
47
|
+
getStore().delete(entry.id);
|
|
48
|
+
updateWidget();
|
|
49
|
+
}
|
|
20
50
|
ui.notify(err.message, "error");
|
|
21
51
|
}
|
|
22
52
|
}
|
|
23
|
-
async function
|
|
53
|
+
async function scheduleLoop(ui, prompt) {
|
|
54
|
+
const p = prompt || await ui.input("Prompt (what should the agent check?)");
|
|
55
|
+
if (!p)
|
|
56
|
+
return;
|
|
57
|
+
const interval = await ui.input("Interval (e.g., 5m, 2h, 1d)");
|
|
58
|
+
if (!interval)
|
|
59
|
+
return;
|
|
60
|
+
createCronLoop(ui, interval, p, true);
|
|
61
|
+
}
|
|
62
|
+
async function eventLoop(ui, prompt, sourceOverride) {
|
|
24
63
|
const p = prompt || await ui.input("Prompt");
|
|
25
64
|
if (!p)
|
|
26
65
|
return;
|
|
27
|
-
const source = await ui.input("Pi event source (e.g., tool_execution_start, before_agent_start)");
|
|
66
|
+
const source = sourceOverride || await ui.input("Pi event source (e.g., tool_execution_start, before_agent_start)");
|
|
28
67
|
if (!source)
|
|
29
68
|
return;
|
|
30
69
|
const trigger = { type: "event", source };
|
|
@@ -33,6 +72,17 @@ export function registerLoopCommand(options) {
|
|
|
33
72
|
updateWidget();
|
|
34
73
|
ui.notify(`Event loop #${entry.id} created: fires on "${source}"`, "info");
|
|
35
74
|
}
|
|
75
|
+
function dynamicLoop(ui, goal) {
|
|
76
|
+
const trigger = { type: "dynamic" };
|
|
77
|
+
const entry = getStore().create(trigger, goal, {
|
|
78
|
+
recurring: true,
|
|
79
|
+
maxFires: 20,
|
|
80
|
+
dynamic: { goal, iteration: 0 },
|
|
81
|
+
});
|
|
82
|
+
getTriggerSystem().add(entry);
|
|
83
|
+
updateWidget();
|
|
84
|
+
ui.notify(`Dynamic loop #${entry.id} created — ${goal.slice(0, 50)}`, "info");
|
|
85
|
+
}
|
|
36
86
|
async function viewLoops(ui) {
|
|
37
87
|
const loops = getStore().list();
|
|
38
88
|
if (loops.length === 0) {
|
|
@@ -41,19 +91,14 @@ export function registerLoopCommand(options) {
|
|
|
41
91
|
}
|
|
42
92
|
const choices = loops.map((l) => {
|
|
43
93
|
const icon = l.status === "active" ? "*" : l.status === "paused" ? "-" : "x";
|
|
44
|
-
|
|
45
|
-
? `cron: ${l.trigger.schedule}`
|
|
46
|
-
: l.trigger.type === "event"
|
|
47
|
-
? `event: ${l.trigger.source}`
|
|
48
|
-
: `hybrid: ${l.trigger.cron}`;
|
|
49
|
-
return `${icon} #${l.id} [${l.status}] ${l.prompt.slice(0, 50)} (${triggerDesc})`;
|
|
94
|
+
return `${icon} #${l.id} [${l.status}] ${l.prompt.slice(0, 50)} (${formatTrigger(l.trigger, "command")})`;
|
|
50
95
|
});
|
|
51
96
|
choices.push("< Back");
|
|
52
97
|
const selected = await ui.select("Loops", choices);
|
|
53
98
|
if (!selected || selected === "< Back")
|
|
54
99
|
return;
|
|
55
100
|
const match = selected.match(/#(\d+)/);
|
|
56
|
-
if (match) {
|
|
101
|
+
if (match?.[1]) {
|
|
57
102
|
const entry = getStore().get(match[1]);
|
|
58
103
|
if (entry) {
|
|
59
104
|
const actions = ["x Delete"];
|
|
@@ -76,8 +121,10 @@ export function registerLoopCommand(options) {
|
|
|
76
121
|
ui.notify(`Loop #${entry.id} paused`, "info");
|
|
77
122
|
}
|
|
78
123
|
else if (action === "* Resume") {
|
|
79
|
-
getStore().resume(entry.id);
|
|
80
|
-
|
|
124
|
+
const resumed = getStore().resume(entry.id);
|
|
125
|
+
if (!resumed)
|
|
126
|
+
return viewLoops(ui);
|
|
127
|
+
getTriggerSystem().add(resumed);
|
|
81
128
|
updateWidget();
|
|
82
129
|
ui.notify(`Loop #${entry.id} resumed`, "info");
|
|
83
130
|
}
|
|
@@ -91,11 +138,11 @@ export function registerLoopCommand(options) {
|
|
|
91
138
|
ui.notify(`${active}/${loops.length} active loops (max 25)`, "info");
|
|
92
139
|
}
|
|
93
140
|
pi.registerCommand("loop", {
|
|
94
|
-
description: "Create a
|
|
141
|
+
description: "Create a loop. Use /loop [interval] [prompt] for scheduled loops, /loop event <source> <prompt> for event loops, or /loop <goal> for a dynamic goal loop.",
|
|
95
142
|
handler: async (args, ctx) => {
|
|
96
|
-
const trimmed = args.trim();
|
|
97
143
|
const ui = ctx.ui;
|
|
98
|
-
|
|
144
|
+
const route = parseLoopCommandRoute(args);
|
|
145
|
+
if (route.type === "menu") {
|
|
99
146
|
const choice = await ui.select("Loop", [
|
|
100
147
|
"Create scheduled loop",
|
|
101
148
|
"Create event-triggered loop",
|
|
@@ -112,36 +159,19 @@ export function registerLoopCommand(options) {
|
|
|
112
159
|
return viewLoops(ui);
|
|
113
160
|
return settings(ui);
|
|
114
161
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
try {
|
|
124
|
-
const parsed = parseInterval(interval);
|
|
125
|
-
const trigger = { type: "cron", schedule: parsed.cron };
|
|
126
|
-
const entry = getStore().create(trigger, prompt, { recurring: true });
|
|
127
|
-
getTriggerSystem().add(entry);
|
|
128
|
-
updateWidget();
|
|
129
|
-
ui.notify(`Loop #${entry.id} created: every ${parsed.description} — ${prompt.slice(0, 50)}`, "info");
|
|
130
|
-
}
|
|
131
|
-
catch (err) {
|
|
132
|
-
ui.notify(err.message, "error");
|
|
133
|
-
}
|
|
162
|
+
if (route.type === "event")
|
|
163
|
+
return eventLoop(ui, route.prompt, route.source);
|
|
164
|
+
if (route.type === "cron")
|
|
165
|
+
return createCronLoop(ui, route.interval, route.prompt, route.notifyEvery);
|
|
166
|
+
if (route.type === "invalid-cron") {
|
|
167
|
+
ui.notify(`Invalid cron expression: ${route.interval}`, "error");
|
|
134
168
|
return;
|
|
135
169
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
`Event-triggered: "${trimmed.slice(0, 50)}"`,
|
|
139
|
-
]);
|
|
140
|
-
if (!choice)
|
|
170
|
+
if (route.type === "missing-interval-prompt") {
|
|
171
|
+
ui.notify("Provide a prompt after the interval, e.g., /loop 5m check the deploy", "warning");
|
|
141
172
|
return;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return scheduleLoop(ui, trimmed);
|
|
173
|
+
}
|
|
174
|
+
return dynamicLoop(ui, route.goal);
|
|
145
175
|
},
|
|
146
176
|
});
|
|
147
177
|
}
|
|
@@ -47,10 +47,10 @@ export function registerTasksCommand(options) {
|
|
|
47
47
|
await createNativeTaskInteractively(ui);
|
|
48
48
|
return viewNativeTasks(ui);
|
|
49
49
|
}
|
|
50
|
-
const
|
|
51
|
-
if (!
|
|
50
|
+
const taskId = selected.match(/#(\d+)/)?.[1];
|
|
51
|
+
if (!taskId)
|
|
52
52
|
return viewNativeTasks(ui);
|
|
53
|
-
const task = taskStore.get(
|
|
53
|
+
const task = taskStore.get(taskId);
|
|
54
54
|
if (!task)
|
|
55
55
|
return viewNativeTasks(ui);
|
|
56
56
|
const actions = ["x Delete"];
|
package/dist/coordinator.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type ReducerSource = "tool" | "command" | "scheduler" | "eventbus" | "monitor" | "session" | "coordinator" | "system";
|
|
2
|
-
export type ReducerEntityType = "task" | "loop" | "monitor" | "notification"
|
|
2
|
+
export type ReducerEntityType = "task" | "loop" | "monitor" | "notification";
|
|
3
3
|
export interface ReducerEvent<TType extends string = string, TPayload = unknown> {
|
|
4
4
|
type: TType;
|
|
5
5
|
at: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
* Tools:
|
|
5
5
|
* LoopCreate — Create a scheduled or event-triggered re-wake loop
|
|
6
6
|
* LoopList — List all active loops with status and next-fire times
|
|
7
|
+
* LoopUpdate — Continue, pause, or complete a dynamic goal loop
|
|
7
8
|
* LoopDelete — Delete or pause a loop by ID
|
|
8
9
|
* MonitorCreate — Start a background command that streams output via pi events
|
|
9
10
|
* MonitorList — List running monitors
|
|
10
11
|
* MonitorStop — Stop a running monitor
|
|
11
12
|
*
|
|
12
13
|
* Commands:
|
|
13
|
-
* /loop — Schedule or manage re-wake loops
|
|
14
|
+
* /loop — Schedule or manage re-wake loops, including /loop <goal>
|
|
14
15
|
* /tasks — View or manage native fallback tasks when pi-tasks is absent
|
|
15
16
|
*/
|
|
16
17
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|