@prevalentware/opencode-loop-plugin 0.1.0
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/LICENSE +21 -0
- package/README.md +195 -0
- package/dist/server.js +924 -0
- package/package.json +76 -0
- package/src/tui.tsx +337 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Prevalentware
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# OpenCode Loop Plugin
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@prevalentware/opencode-loop-plugin)
|
|
4
|
+
[](https://github.com/prevalentWare/opencode-loop-plugin)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
OpenCode Loop Plugin adds Claude Code-style `/loop` recurring prompts to OpenCode. It gives AI coding agents a `/loop` slash command backed by a persistent scheduler that re-injects an instruction into the session on an interval — or at agent-chosen delays — but only while the session is idle. Use it to babysit CI, watch a deploy, poll PR reviews, triage new issues, or keep any external state under watch without driving every check yourself.
|
|
8
|
+
|
|
9
|
+
`/loop` is the complement to goal mode ([`@prevalentware/opencode-goal-plugin`](https://github.com/prevalentWare/opencode-goal-plugin)): a goal defines when a task is *done*; a loop defines when to *wake the agent up again* to look at something that changes over time.
|
|
10
|
+
|
|
11
|
+
The OpenCode Loop Plugin adds:
|
|
12
|
+
|
|
13
|
+
- `/loop <interval> <instruction>` and `/loop <instruction>` (dynamic pacing) as an OpenCode command for TUI, desktop, and web.
|
|
14
|
+
- A server-side scheduler with per-loop timers that injects a synthetic iteration prompt only when the session is idle, with busy backoff.
|
|
15
|
+
- Dynamic loops where the agent itself picks the delay before each next iteration via `schedule_next_run`, mirroring Claude Code's self-paced `/loop`.
|
|
16
|
+
- Agent tools: `create_loop`, `list_loops`, `stop_loop`, `pause_loop`, `resume_loop`, `run_loop`, `schedule_next_run`, and `clear_loops`.
|
|
17
|
+
- Persistent loop state that survives OpenCode restarts, with atomic writes and owner-only file permissions.
|
|
18
|
+
- A TUI sidebar with live countdowns and a `Loops` command-palette entry to run, pause, resume, or stop loops.
|
|
19
|
+
- Plan-mode safety: iterations are deferred while the session's last prompt came from a restricted agent (default: `plan`).
|
|
20
|
+
- Compaction context so active loops are preserved when OpenCode summarizes a long session.
|
|
21
|
+
- Safety rails: minimum interval, per-session loop limit, optional max runs, and automatic expiry after 7 days.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
Install locally for the current OpenCode project:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
opencode plugin @prevalentware/opencode-loop-plugin
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Install globally:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
opencode plugin -g @prevalentware/opencode-loop-plugin
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
OpenCode detects both package entrypoints and writes the plugin into the server and TUI config targets.
|
|
38
|
+
|
|
39
|
+
## Manual Config
|
|
40
|
+
|
|
41
|
+
If you configure it manually, add the package to both config files.
|
|
42
|
+
|
|
43
|
+
`opencode.json`:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"plugin": ["@prevalentware/opencode-loop-plugin"]
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`tui.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"plugin": ["@prevalentware/opencode-loop-plugin"]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
Create a fixed-interval loop:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
/loop 10m review the current PR. If there are new comments, address them. If CI fails, diagnose the logs and fix it. If everything is green, report and stop this loop.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The interval can lead the instruction (`/loop 10m ...`) or trail it as an `every` clause (`/loop check the deploy every 20m`). Supported units are `s`, `m`, `h`, and `d`; the default minimum is 30 seconds.
|
|
68
|
+
|
|
69
|
+
Create a dynamic loop — the agent picks the delay between iterations based on what it observes, one iteration at a time:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
/loop watch the staging deploy and run smoke checks when it finishes
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Manage loops:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
/loop list
|
|
79
|
+
/loop stop loop_7k3p9
|
|
80
|
+
/loop pause loop_7k3p9
|
|
81
|
+
/loop resume loop_7k3p9
|
|
82
|
+
/loop run loop_7k3p9
|
|
83
|
+
/loop clear
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
After creating a loop, the agent immediately performs the first iteration in the same turn — it does not wait for the first scheduled run. On each scheduled iteration the scheduler injects a synthetic prompt telling the agent to perform exactly one iteration, never to sleep or poll inside the turn, and to call `stop_loop` once the loop's purpose is achieved (or `pause_loop` if it is blocked on the user).
|
|
87
|
+
|
|
88
|
+
### Dynamic loops
|
|
89
|
+
|
|
90
|
+
A dynamic loop mirrors Claude Code's self-paced `/loop`: at the end of each iteration the agent calls `schedule_next_run` with a delay in seconds and a one-sentence reason ("watching CI run"), or calls `stop_loop` to end the loop. If an iteration ends without doing either, the loop ends — exactly like omitting `ScheduleWakeup` in Claude Code.
|
|
91
|
+
|
|
92
|
+
### How iterations are scheduled
|
|
93
|
+
|
|
94
|
+
- Iterations only run while the session is idle. If a loop comes due while the session is busy, it is deferred with a short backoff and retried when the session goes idle.
|
|
95
|
+
- If several loops in one session are due at once, one iteration is injected and the rest wait for the next idle.
|
|
96
|
+
- Failed injections are recorded in the loop's `lastError` and retried after a backoff; they never crash OpenCode.
|
|
97
|
+
- Loops are stopped automatically when their session is deleted, when `max_runs` is reached, or after 7 days (configurable).
|
|
98
|
+
|
|
99
|
+
## Options
|
|
100
|
+
|
|
101
|
+
Server options can be configured in `opencode.json`:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"plugin": [
|
|
106
|
+
[
|
|
107
|
+
"@prevalentware/opencode-loop-plugin",
|
|
108
|
+
{
|
|
109
|
+
"min_interval_seconds": 30,
|
|
110
|
+
"max_loops_per_session": 5,
|
|
111
|
+
"busy_backoff_seconds": 60,
|
|
112
|
+
"failure_backoff_seconds": 60,
|
|
113
|
+
"max_loop_age_days": 7,
|
|
114
|
+
"dynamic_max_delay_seconds": 86400,
|
|
115
|
+
"restricted_agents": ["plan"],
|
|
116
|
+
"register_command": true,
|
|
117
|
+
"command_name": "loop"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Defaults:
|
|
125
|
+
|
|
126
|
+
- `min_interval_seconds`: `30`; the smallest accepted interval and the lower clamp for dynamic delays.
|
|
127
|
+
- `max_loops_per_session`: `5` open (active or paused) loops per session.
|
|
128
|
+
- `busy_backoff_seconds`: `60`; retry delay when an iteration comes due while the session is busy.
|
|
129
|
+
- `failure_backoff_seconds`: `60`; retry delay when injecting the iteration prompt fails.
|
|
130
|
+
- `max_loop_age_days`: `7`; loops stop automatically after this age. Set `0` to disable expiry.
|
|
131
|
+
- `dynamic_max_delay_seconds`: `86400`; upper clamp for `schedule_next_run` delays.
|
|
132
|
+
- `restricted_agents`: `["plan"]`; iterations are deferred while the session's last prompt came from one of these agents.
|
|
133
|
+
- `register_command`: `true`
|
|
134
|
+
- `command_name`: `"loop"`
|
|
135
|
+
|
|
136
|
+
## State
|
|
137
|
+
|
|
138
|
+
Loop state is stored at:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
$XDG_DATA_HOME/opencode-loop-plugin/loops.json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
If `XDG_DATA_HOME` is not set, the default is:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
~/.local/share/opencode-loop-plugin/loops.json
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Set `OPENCODE_LOOP_STATE_PATH` to use a custom file.
|
|
151
|
+
|
|
152
|
+
The state file is written atomically with owner-only permissions when the host filesystem supports it. Active interval loops are rehydrated and rescheduled when OpenCode restarts. Dynamic loops that were waiting on the agent to schedule their next run cannot recover on their own after a restart and are stopped with an explanatory reason.
|
|
153
|
+
|
|
154
|
+
## Credits
|
|
155
|
+
|
|
156
|
+
This plugin follows the semantics of Claude Code's `/loop` skill (interval parsing, immediate first iteration, dynamic self-pacing with an explicit schedule-or-stop contract, and 7-day auto-expiry) implemented on top of OpenCode plugin hooks. The package structure, persistence approach, and idle-continuation mechanics follow [`@prevalentware/opencode-goal-plugin`](https://github.com/prevalentWare/opencode-goal-plugin).
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bun install
|
|
162
|
+
bun test
|
|
163
|
+
bun run lint
|
|
164
|
+
bun run typecheck
|
|
165
|
+
bun run build
|
|
166
|
+
npm pack --dry-run
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Publishing
|
|
170
|
+
|
|
171
|
+
This package is set up for npm Trusted Publishing from GitHub Actions. On every push to `main`, CI runs typecheck, lint, and unit tests in parallel. If they all pass, the publish job computes the next patch version from the latest version on npm, builds the package, and runs `npm publish`.
|
|
172
|
+
|
|
173
|
+
Before the first automated publish, configure the package on npm:
|
|
174
|
+
|
|
175
|
+
1. Open the package settings on npmjs.com.
|
|
176
|
+
2. Add a Trusted Publisher for GitHub Actions.
|
|
177
|
+
3. Use repository `prevalentWare/opencode-loop-plugin`.
|
|
178
|
+
4. Use workflow file `publish.yml`.
|
|
179
|
+
|
|
180
|
+
The repository must be public for npm provenance to be generated automatically.
|
|
181
|
+
|
|
182
|
+
## Notes
|
|
183
|
+
|
|
184
|
+
OpenCode plugin modules are target-specific. This package exports separate modules for server hooks/tools and TUI UI:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"exports": {
|
|
189
|
+
"./server": "./dist/server.js",
|
|
190
|
+
"./tui": "./src/tui.tsx"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Claude Code's `/loop` has deeper runtime integration (cron scheduling, cache-aware wake-ups, event monitors). This plugin implements the same workflow with OpenCode plugin hooks: timers on the server plugin, idle detection through `session.status` / `session.idle` events, and prompt injection through `session.promptAsync`. The TUI sidebar reads loop state from the plugin's tool outputs in the session, so it works without a private channel between the TUI and the server.
|