@bybrawe/opencode-loop 0.5.18 → 0.5.19

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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.19
4
+
5
+ - Made the asynchronous goal prompt smoke test wait for the observable SDK call instead of assuming a single event-loop tick is always sufficient under load.
6
+
3
7
  ## 0.5.18
4
8
 
5
9
  - Fixed package-based installs loading stale OpenCode cache entries after npm had installed a newer release. The installer now pins an existing package config entry to its exact installed version while preserving JSONC comments.
package/README.md CHANGED
@@ -16,7 +16,7 @@ v0.5.11 includes a referenced heartbeat scheduler. This is important in OpenCode
16
16
 
17
17
  ## Current status
18
18
 
19
- **v0.5.18 hardens Goal Mode, package updates, and the background daemon.** Package installs are pinned to the installed version so OpenCode cannot keep loading an older cached release, scheduler-created goal messages no longer self-interrupt on delayed updates, finite daemon failures return nonzero, model/agent selection is supported, and Windows scheduled tasks use a short launcher that stays below the `/TR` limit.
19
+ **v0.5.19 hardens Goal Mode, package updates, and the background daemon.** Package installs are pinned to the installed version so OpenCode cannot keep loading an older cached release, scheduler-created goal messages no longer self-interrupt on delayed updates, finite daemon failures return nonzero, model/agent selection is supported, Windows scheduled tasks use a short launcher that stays below the `/TR` limit, and asynchronous release verification is reliable under load.
20
20
 
21
21
  The known update-related symptoms from older builds are fixed:
22
22
 
@@ -32,7 +32,7 @@ The known update-related symptoms from older builds are fixed:
32
32
 
33
33
  The TUI loop is still intentionally session-bound: it runs while OpenCode is open and the current session emits status/idle events. For long-running background work after closing the terminal or OpenCode, use `opencode-loopd`.
34
34
 
35
- ## v0.5.18 quick behavior guide
35
+ ## v0.5.19 quick behavior guide
36
36
 
37
37
  OpenCode Loop has two triggers now:
38
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bybrawe/opencode-loop",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "description": "Claude Code/Codex style /loop and experimental goal mode for OpenCode: heartbeat scheduler, idle-safe loops, scheduled commands, compact scheduling, verification, checkpoints, and persistent coding goals.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -12,6 +12,15 @@ const prompts = []
12
12
  const liveStatuses = new Map([[sessionID, "idle"]])
13
13
  let hooks
14
14
 
15
+ async function waitFor(predicate, timeoutMs = 2_000) {
16
+ const deadline = Date.now() + timeoutMs
17
+ while (!predicate()) {
18
+ if (Date.now() >= deadline) return false
19
+ await new Promise((resolve) => setTimeout(resolve, 10))
20
+ }
21
+ return true
22
+ }
23
+
15
24
  function legacyBody(name) {
16
25
  return async (args) => {
17
26
  attempts[name]++
@@ -86,8 +95,11 @@ try {
86
95
  assert.equal(output.parts.length, 0, "a locally handled slash command must not start a placeholder model turn")
87
96
 
88
97
  await hooks["command.execute.before"]({ command: "loop-now", sessionID, arguments: "goal" }, { parts: [] })
89
- await new Promise((resolve) => setTimeout(resolve, 0))
90
- assert.ok(prompts.some((prompt) => prompt.includes(path.resolve(directory))), "goal prompt must include the working directory")
98
+ assert.equal(
99
+ await waitFor(() => prompts.some((prompt) => prompt.includes(path.resolve(directory)))),
100
+ true,
101
+ "goal prompt must include the working directory",
102
+ )
91
103
  assert.ok(prompts.some((prompt) => prompt.includes("never turn a relative path into a root path")))
92
104
 
93
105
  const stateFile = path.join(directory, ".opencode", "opencode-loop", `${sessionID}.json`)
@@ -122,8 +134,11 @@ try {
122
134
 
123
135
  await hooks["tool.execute.after"]({ tool: "bash", sessionID, callID: "call_background", args: {} }, { title: "done", output: "", metadata: {} })
124
136
  await hooks.event({ event: { type: "session.idle", properties: { sessionID } } })
125
- await new Promise((resolve) => setTimeout(resolve, 1_400))
126
- assert.equal(prompts.length, promptCountBeforeBackgroundTool + 1, "the due loop must resume after the active tool finishes and the session becomes idle")
137
+ assert.equal(
138
+ await waitFor(() => prompts.length === promptCountBeforeBackgroundTool + 1, 5_000),
139
+ true,
140
+ "the due loop must resume after the active tool finishes and the session becomes idle",
141
+ )
127
142
 
128
143
  await hooks["command.execute.before"]({ command: "loop-clear", sessionID, arguments: "" }, { parts: [] })
129
144
 
@@ -143,8 +158,11 @@ try {
143
158
  liveStatuses.set(childSessionID, "idle")
144
159
  await hooks.event({ event: { type: "session.status", properties: { sessionID: childSessionID, status: { type: "idle" } } } })
145
160
  await hooks.event({ event: { type: "session.idle", properties: { sessionID } } })
146
- await new Promise((resolve) => setTimeout(resolve, 1_400))
147
- assert.equal(prompts.length, promptCountBeforeBackgroundChild + 1, "the parent loop must resume after its background child becomes idle")
161
+ assert.equal(
162
+ await waitFor(() => prompts.length === promptCountBeforeBackgroundChild + 1, 5_000),
163
+ true,
164
+ "the parent loop must resume after its background child becomes idle",
165
+ )
148
166
 
149
167
  await hooks["command.execute.before"]({ command: "loop-clear", sessionID, arguments: "" }, { parts: [] })
150
168
  assert.equal(attempts.log, 1)