@bybrawe/opencode-loop 0.5.33 → 0.5.35

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.
@@ -0,0 +1,75 @@
1
+ import assert from "node:assert/strict"
2
+ import { normalizeLoopScheduleArgs } from "../src/source/core/schedule-syntax.js"
3
+
4
+ {
5
+ const result = normalizeLoopScheduleArgs("continue the project")
6
+ assert.equal(result.ok, true)
7
+ assert.equal(result.args, "continue the project")
8
+ assert.equal(result.defaults.intervalMs, 0)
9
+ assert.equal(result.defaults.immediate, true)
10
+ assert.equal(result.scheduleMode, "idle")
11
+ assert.equal(result.scheduleSyntax, "idle-shorthand")
12
+ }
13
+
14
+ {
15
+ const result = normalizeLoopScheduleArgs("--safe --ask-never continue the project")
16
+ assert.equal(result.ok, true)
17
+ assert.equal(result.defaults.intervalMs, 0)
18
+ assert.equal(result.args, "--safe --ask-never continue the project")
19
+ }
20
+
21
+ {
22
+ const result = normalizeLoopScheduleArgs("idle continue forever")
23
+ assert.equal(result.args, "continue forever")
24
+ assert.equal(result.defaults.intervalMs, 0)
25
+ assert.equal(result.defaults.immediate, true)
26
+ assert.equal(result.scheduleMode, "idle")
27
+ }
28
+
29
+ {
30
+ const result = normalizeLoopScheduleArgs("every 5m continue forever")
31
+ assert.equal(result.args, "continue forever")
32
+ assert.equal(result.defaults.intervalMs, 300_000)
33
+ assert.equal(result.defaults.immediate, false)
34
+ assert.equal(result.scheduleMode, "interval")
35
+ assert.equal(result.scheduleSyntax, "every")
36
+ }
37
+
38
+ {
39
+ const result = normalizeLoopScheduleArgs("after 5m continue once")
40
+ assert.equal(result.args, "continue once")
41
+ assert.equal(result.defaults.intervalMs, 300_000)
42
+ assert.equal(result.defaults.immediate, false)
43
+ assert.equal(result.defaults.maxRuns, 1)
44
+ assert.equal(result.scheduleMode, "once")
45
+ }
46
+
47
+ {
48
+ const result = normalizeLoopScheduleArgs("in 30s ping once")
49
+ assert.equal(result.args, "ping once")
50
+ assert.equal(result.defaults.intervalMs, 30_000)
51
+ assert.equal(result.defaults.maxRuns, 1)
52
+ assert.equal(result.scheduleSyntax, "after")
53
+ }
54
+
55
+ {
56
+ const result = normalizeLoopScheduleArgs("5m continue legacy")
57
+ assert.equal(result.args, "5m continue legacy")
58
+ assert.equal(result.scheduleMode, "interval")
59
+ assert.equal(result.scheduleSyntax, "legacy")
60
+ }
61
+
62
+ {
63
+ const result = normalizeLoopScheduleArgs("--allow-goal-overlap continue")
64
+ assert.equal(result.args, "continue")
65
+ assert.equal(result.allowGoalOverlap, true)
66
+ assert.equal(result.scheduleMode, "idle")
67
+ }
68
+
69
+ {
70
+ const result = normalizeLoopScheduleArgs("every nope continue")
71
+ assert.equal(result.ok, false)
72
+ assert.match(result.error, /Invalid every schedule/)
73
+ }
74
+
75
+ console.log("schedule syntax tests passed")
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict"
2
+ import { clearSessionActivity } from "../src/source/runtime/session-activity.js"
3
+ import { createSessionStatusRuntime } from "../src/source/runtime/session-status.js"
4
+
5
+ const activeRuns = new Map()
6
+ const logs = []
7
+ let clock = 100_000
8
+ let completion = "completed"
9
+
10
+ const runtime = createSessionStatusRuntime({
11
+ activeRuns,
12
+ now: () => clock,
13
+ sessionStatusCacheMs: 0,
14
+ appendLoopLog: async (...args) => logs.push(args),
15
+ activeRunCompletionFromMessages: async () => completion,
16
+ })
17
+
18
+ const busyClient = {
19
+ session: {
20
+ status: async () => ({ data: { session: { type: "busy" } } }),
21
+ },
22
+ }
23
+
24
+ try {
25
+ runtime.markSessionStatus("session", "busy", clock - 2_000)
26
+ assert.equal(await runtime.sessionStatusType(busyClient, "session", "/repo"), "idle")
27
+ assert.equal(logs.at(-1)[1], "status-message-idle-recovery")
28
+ assert.equal(logs.at(-1)[2].staleStatus, "busy")
29
+
30
+ runtime.clearSessionStatus("session")
31
+ completion = "incomplete"
32
+ clock += 5_000
33
+ assert.equal(await runtime.sessionStatusType(busyClient, "session", "/repo"), "busy", "an unfinished tail must never be force-recovered")
34
+
35
+ runtime.clearSessionStatus("session")
36
+ completion = "unknown"
37
+ clock += 5_000
38
+ assert.equal(await runtime.sessionStatusType(busyClient, "session", "/repo"), "busy", "unknown tail completion must remain conservative")
39
+
40
+ console.log("session status idle recovery tests passed")
41
+ } finally {
42
+ activeRuns.clear()
43
+ clearSessionActivity("session")
44
+ }
@@ -158,17 +158,31 @@ try {
158
158
  }
159
159
 
160
160
  {
161
- const sessionID = "stale-recovery"
161
+ const sessionID = "retry-remains-owned"
162
162
  track(sessionID)
163
163
  runtime.clearSessionStatus(sessionID)
164
164
  activeRuns.set(sessionID, { jobId: "job-2", job: { id: "job-2" }, startedAt: 1_000 })
165
165
  completion = "unknown"
166
+ const beforeLogs = logs.length
166
167
  const client = { session: { status: async () => ({ data: { [sessionID]: { type: "retry" } } }) } }
167
- assert.equal(await runtime.sessionStatusType(client, sessionID, "/repo"), "idle")
168
- assert.deepEqual(logs.at(-1), ["/repo", "status-stale-recovery", { sessionID, job: "job-2", startedAt: 1_000 }])
168
+ assert.equal(await runtime.sessionStatusType(client, sessionID, "/repo"), "retry")
169
+ assert.equal(logs.length, beforeLogs, "unknown retry must not use stale-busy recovery")
169
170
  activeRuns.delete(sessionID)
170
171
  }
171
172
 
173
+ {
174
+ const sessionID = "retry-message-complete"
175
+ track(sessionID)
176
+ runtime.clearSessionStatus(sessionID)
177
+ activeRuns.set(sessionID, { jobId: "job-retry-complete", job: { id: "job-retry-complete", name: "retry-complete" }, startedAt: 1_000 })
178
+ completion = "completed"
179
+ const client = { session: { status: async () => ({ data: { [sessionID]: { type: "retry" } } }) } }
180
+ assert.equal(await runtime.sessionStatusType(client, sessionID, "/repo"), "idle", "a genuinely completed current assistant turn may release stale retry")
181
+ assert.deepEqual(logs.at(-1), ["/repo", "status-message-complete-recovery", { sessionID, job: "retry-complete", startedAt: 1_000 }])
182
+ activeRuns.delete(sessionID)
183
+ completion = "unknown"
184
+ }
185
+
172
186
  {
173
187
  const sessionID = "fallback-fresh"
174
188
  track(sessionID)
@@ -178,7 +192,7 @@ try {
178
192
  assert.equal(await runtime.sessionStatusType(client, sessionID, "/repo"), "busy")
179
193
  activeRuns.set(sessionID, { jobId: "stale", job: {}, startedAt: clock - 60_000 })
180
194
  runtime.clearSessionStatus(sessionID)
181
- assert.equal(await runtime.sessionStatusType(client, sessionID, "/repo"), "idle")
195
+ assert.equal(await runtime.sessionStatusType(client, sessionID, "/repo"), "busy", "status transport failure must fail closed even for an old active run")
182
196
  activeRuns.delete(sessionID)
183
197
  }
184
198