@bojackduy/opencode-loopd 1.5.3 → 1.6.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/CHANGELOG.md +51 -8
- package/README.md +23 -0
- package/commands/goal.md +15 -10
- package/dist/server.js +1349 -462
- package/dist/tui.js +121 -71
- package/package.json +1 -1
- package/skills/loopd/SKILL.md +76 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.0 (2026-08-24) — Hardened Lifecycle
|
|
4
|
+
|
|
5
|
+
> Merge `feat/goal-evaluation` (43 files, +5516 / -1001 since `1.5.3` `5461e0d`). Hardens prompt correlation, maintenance recovery, workspace serialization, and evaluator verification.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Prompt correlation & generation fencing** — each turn pre-generates `activePromptMessageID` (`msg-` UUID), correlates `message.updated`/`message.part.updated` by `runGeneration` + `idleCandidateGeneration`; stale `session.idle` can never release a newer lease (`src/application/loop-engine.ts`, `src/domain/runtime.ts:85`, `src/server/worker-session.ts:62`).
|
|
10
|
+
- **Transcript anchoring** — finalization requires latest user prompt == engine prompt **and** assistant response `completedAt` (`src/application/loop-engine.ts:438` `inspectPromptTurn`); prevents phantom completions.
|
|
11
|
+
- **Workspace serialization** — single active `workspaceWrite:true` writer enforced on `start`/`resume`/`retry` with rollback; safe default `workspaceWrite=true`; `goal-policy.ts:37` shared by tool + control bus (`src/application/goal-service.ts:79`, `src/application/control-worker.ts:158`).
|
|
12
|
+
- **Contract validation** — `defaultAgent`/`defaultChecks` plugin options (`src/server/plugin.ts:152` `parsePluginDefaults`), mandatory `checks` for writers, `checkCwd` defaults to project root for writers / `artifactDir` for readers (`src/application/goal-policy.ts:16`, `src/domain/goal.ts:97` `maxEvaluatorRejections`).
|
|
13
|
+
- **Verification tracking** — new domain `src/domain/verification.ts:4` `VerificationAttempt` (id, sequence, runGeneration, cwd, checks with `stdout`/`stderr`), bounded `recentVerificationAttempts` (10), exposed in `get_goal` runtime.
|
|
14
|
+
- **Evaluator rejection event** — `goal.completion_rejected` (`src/domain/events.ts:52`) with `attemptID`, `rejectionCount`, `failureSummary`; `freeRetryPending` prevents `budgetTurnCount` charge for <3 rejections, `blocked` after 3 (`src/server/goal-tools.ts:298`).
|
|
15
|
+
- **Recovery tool** — `nudge_goal` force re-prompts stuck `active` worker: clears `activeRunID`/`idleCandidate`/`activePromptMessageID`/lease (`phase→idle`) and calls `continueTurn({force:true})` (`src/application/goal-service.ts:696`, `src/server/owner-tools.ts:334`).
|
|
16
|
+
- **Tool-call guard** — `activeToolCallIDs: string[]` tracked via `tool.execute.before/after` (`src/server/plugin.ts:67`), blocks idle finalization while tools run (`src/domain/runtime.ts:244` `hasActiveToolCalls`).
|
|
17
|
+
- **Maintenance (30s)** — auto-clears stale `phase=idle + activeRunID`, bounds `unknown` status polls (`unknownStatusCount` threshold 3 → one `notifyOwner` per episode via `workerUnreachableNotifiedAt`), recovers counters on success, `active`-only polling (`src/application/loop-engine.ts:694`).
|
|
18
|
+
- **Per-goal mutex** — `withGoalOperation(goalID)` serializes `continueTurn`/`pause`/`resume`/`retry`/`clear`/`nudge` and re-checks `leaseIsValid` inside `turn.acquire` transaction (`src/application/goal-service.ts:64`).
|
|
19
|
+
- **State migrations v3→v5** — `turnCount → budgetTurnCount + runCount + runGeneration`, `freeRetryPending`, verification fields, `workspaceWrite` default, `activePromptObservedAt`/`unknownStatusCount` (`src/infrastructure/state-repository.ts:178`).
|
|
20
|
+
- **Docs** — `skills/loopd/SKILL.md` expanded Contract & Evaluation Semantics, Hardened loop guarantees, Owner/Worker tool refs, Safety Patterns; `docs/planning/remaining-work.md`, `docs/reports/final-report.md`, `docs/bugs/bugs-new.md`, `docs/development/testing-context.md`, `commands/goal.md` updated.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **Loop engine idle** — two-stage debounce `CONFIRM_IDLE_DURATION_MS=2000` requires two `session.idle` 2s apart with no `lastActivityAt` in between; `session.status` handler removed in favor of `session.idle` + `message.*` activity (`src/application/loop-engine.ts:245`, `src/application/loop-engine.ts:183` test).
|
|
25
|
+
- **Budget split** — `turnCount` → `budgetTurnCount` (charged) vs `runCount` (monotonic) vs `runGeneration` (fence); `freeRetryPending` path does not bump budget (`src/domain/runtime.ts:39`, `src/application/goal-service.ts:361`).
|
|
26
|
+
- **Host adapter** — `sessionStatus` returns `unknown` (was `idle`) for missing status; `promptWorker` sends `messageID` and collapses duplicate `msg-` prefixes; `readMessages` returns `messageID`/`parentMessageID`/`completedAt` (`src/server/host-adapter.ts:88`, `src/server/host-adapter.ts:113`).
|
|
27
|
+
- **State repository locking** — `acquireLock` uses `fs.open wx` direct (was `tmp+rename` TOCTOU); `releaseLock` re-verifies `acquiredAt`/`pid` before `rm`; stale `LOCK_STALE_MS=10s`, retry 25ms backoff (`src/infrastructure/state-repository.ts:68`).
|
|
28
|
+
- **Goal tools descriptions** — all 5 tools rewritten to reflect host-acceptance semantics (rejection/free retry/blocked, `workspaceWrite` serialization, `checkCwd`) (`src/server/goal-tools.ts:35`, `src/server/owner-tools.ts:19`).
|
|
29
|
+
- **Review fixes (PR follow-up `392ecee`)** — `runCompletionChecks` now captures `stdout`+`stderr` (was stderr-only), `VerificationAttempt` stores both, `lastRejectionDetails` includes both; `releaseLock` race fixed; `turnCount` migration properly deletes key; `withGoalOperation` documented; `maxEvaluatorRejections` added to `GoalConfig` (`src/domain/goal.ts:97`).
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **Race: prompt to subagent session** (`5a13398`, `e41fb84`) — `promptWorker` `messageID` double-prefix (`msg-msg-`) collapsed; `FakeHost` same.
|
|
34
|
+
- **Race: withGoalOperation gate leak** — `gate` never-rejects chain + deletion guard documented.
|
|
35
|
+
- **Race: state lock** — `acquireLock` TOCTOU and `releaseLock` fresh-acquirer deletion fixed.
|
|
36
|
+
- **Turn accounting** — evaluator rejection no longer `turnCount--` hack; `budgetTurnCount` unchanged, `freeRetryPending` consumed once.
|
|
37
|
+
- **Stdout loss** — completion check `stdout` now preserved in rejection evidence (`src/server/goal-tools.ts:253`).
|
|
38
|
+
- **Blocked needed message** — `recordPromptFailure` now contextual: API unavailable only for `blockImmediately`, else `Fix error and retry_goal` (`src/application/goal-service.ts:92`).
|
|
39
|
+
- **Dashboard keybinding** — `o` for open child, `L` log toggle (`5921ec5`).
|
|
40
|
+
- **Docs refs** — kebab paths `bugs/bugs-new.md`, reports index (`12ad22e`, `78c63b3`).
|
|
41
|
+
|
|
42
|
+
### Tests & Build
|
|
43
|
+
|
|
44
|
+
- 159 tests pass (15 files, 386 expects); new suites: `goal-policy.test.ts:43`, `loop-engine` generation-fence/transcript-anchor/unknown-status/stale-run, `goal-service` acquire-race/concurrent/pause-fence/nudge/workspace-serialization, `state-store` v5 migration.
|
|
45
|
+
- `dist/server.js` 129KB / `dist/tui.js` 66KB rebuilt (`07ad6c0` `bun run build`).
|
|
46
|
+
|
|
47
|
+
## 1.0.1 (2026-08-21)
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- Removed redundant `ask_user` tool and `awaiting_user` status. Workers now use OpenCode's native `question` tool for clarification — questions appear in the TUI footer as blocker tabs, no goal status change needed.
|
|
52
|
+
- Removed `:answer` dashboard command (native question answers flow through OpenCode).
|
|
53
|
+
|
|
3
54
|
## 1.0.0 (2026-08-21)
|
|
4
55
|
|
|
5
56
|
### Features
|
|
@@ -21,11 +72,3 @@
|
|
|
21
72
|
- Parent visibility layer: owner tools read child transcript, progress, and state
|
|
22
73
|
- Bidirectional messaging: inbox system for user→child instructions
|
|
23
74
|
- Awaiting_user status: child can pause and ask questions
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
## 1.0.1 (2026-08-21)
|
|
27
|
-
|
|
28
|
-
### Changed
|
|
29
|
-
|
|
30
|
-
- Removed redundant `ask_user` tool and `awaiting_user` status. Workers now use OpenCode's native `question` tool for clarification — questions appear in the TUI footer as blocker tabs, no goal status change needed.
|
|
31
|
-
- Removed `:answer` dashboard command (native question answers flow through OpenCode).
|
package/README.md
CHANGED
|
@@ -102,6 +102,21 @@ Or for local checkout:
|
|
|
102
102
|
}
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
For safe code-editing defaults, configure the server plugin with an agent and
|
|
106
|
+
verification commands:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"plugin": [[
|
|
111
|
+
"@bojackduy/opencode-loopd",
|
|
112
|
+
{
|
|
113
|
+
"defaultAgent": "smart-agent",
|
|
114
|
+
"defaultChecks": ["bun test", "bun run typecheck"]
|
|
115
|
+
}
|
|
116
|
+
]]
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
105
120
|
`opencode-loopd` exposes:
|
|
106
121
|
- `opencode-loopd/server` → engine plugin
|
|
107
122
|
- `opencode-loopd/tui` → dashboard plugin
|
|
@@ -231,11 +246,19 @@ Goals accept:
|
|
|
231
246
|
| `timeoutMs` | `300000` | Per-turn lease (5 min) |
|
|
232
247
|
| `compactEvery` | — | Compact child every N turns |
|
|
233
248
|
| `checks` | `[]` | Shell commands that must pass for `complete_goal` |
|
|
249
|
+
| `checkCwd` | artifact directory | Directory where completion checks run; workspace writes default to project root |
|
|
250
|
+
| `workspaceWrite` | `true` | Marks shared-workspace mutation; only one active workspace writer is allowed. Set `false` explicitly for artifact-only/read-only work |
|
|
251
|
+
| `agent` | `defaultAgent` | Worker agent; goal creation fails if neither is configured nor supplied |
|
|
234
252
|
| `progressFile` | `<artifactDir>/progress.md` | Transaction state file |
|
|
235
253
|
| `artifactDir` | `goals/<id>/` | Auto — override only if objective names another dir |
|
|
236
254
|
|
|
237
255
|
Artifacts, logs, and state live under `.opencode/loopd/` (project-local). Locks live in `/tmp/loopd-locks/<project-hash>/` to avoid snapshot noise.
|
|
238
256
|
|
|
257
|
+
Workspace-writing goals must have deterministic checks and are serialized to
|
|
258
|
+
prevent concurrent agents or `git stash` operations from overwriting each
|
|
259
|
+
other. Artifact-only research goals may run concurrently by setting
|
|
260
|
+
`workspaceWrite: false` explicitly.
|
|
261
|
+
|
|
239
262
|
## Dashboard tips
|
|
240
263
|
|
|
241
264
|
- Press `:` then type bare text → sent as `send_goal_input` to the selected goal.
|
package/commands/goal.md
CHANGED
|
@@ -4,17 +4,22 @@ description: Create a new background loop goal. Asks clarifying questions, then
|
|
|
4
4
|
|
|
5
5
|
Create a new background loop goal using the `loopd_create_goal` tool.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
A goal is a **contract**: `objective` (semantic requirements) + `checks` (deterministic host acceptance) + `agent`/`workspaceWrite`/`checkCwd`/`limits`. The host is the acceptance authority; the worker proposes completion and the host rejects it if `checks` fail (free retry <3, `blocked` after 3).
|
|
8
|
+
|
|
9
|
+
First, gather what you need to craft a good contract:
|
|
10
|
+
- If the user gave a vague objective, ask 1–3 short clarifying questions (what to accomplish, where, and how they'll verify it — i.e., what `checks` should be).
|
|
9
11
|
- If the user was specific, skip straight to creating it.
|
|
10
12
|
|
|
11
|
-
When you have enough to write a concrete
|
|
13
|
+
When you have enough to write a concrete contract:
|
|
12
14
|
1. Call `loopd_create_goal` with:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
- `name` — a short slug (e.g. "pdf-notes")
|
|
16
|
+
- `objective` — a precise, self-contained statement including verification criteria
|
|
17
|
+
- `agent` — required unless the plugin has `defaultAgent` configured; determines which model runs the worker
|
|
18
|
+
- `checks` — shell commands that must pass before `complete_goal` is accepted; **mandatory when `workspaceWrite:true`** (the default) — or configure `defaultChecks` in `opencode.jsonc`
|
|
19
|
+
- `workspaceWrite` — default `true` (safe — may touch the shared repo; only one active writer allowed — second `start`/`resume` fails with `already active`); set `false` explicitly for artifact-only/read-only work to allow concurrency
|
|
20
|
+
- `checkCwd` — optional directory where `checks` run; writers default to project root, artifact-only jobs default to their `artifactDir`
|
|
21
|
+
- `progressFile` — optional path to a markdown progress file (defaults to `<artifactDir>/progress.md`)
|
|
22
|
+
- limits — optional `maxTurns` (default 50), `maxNoProgress`, `maxFailures`, `compactEvery`, `timeoutMs`
|
|
23
|
+
2. After it returns (`ok:true` with `goalID`/`artifactDir`/`defaultsApplied`, or `ok:false` with `errorCode: "missing_agent"|"missing_checks"|"already active"`), tell the user the goal is running in the background and they can monitor it with `/loop` (or <leader>d). On `missing_*`, explain the contract violation and ask for the missing piece; on `already active`, tell them to `pause`/`clear` the current writer or use `workspaceWrite:false`.
|
|
19
24
|
|
|
20
|
-
Important: the worker session runs autonomously — do not try to do the goal's work in this chat. This chat only creates the goal.
|
|
25
|
+
Important: the worker session runs autonomously — do not try to do the goal's work in this chat. This chat only creates the goal. Completion is host-judged: if `checks` fail, the worker will see `HOST VERDICT: COMPLETION REJECTED` with exact `stderr` and must fix the behavior (not just rewrite evidence) before retrying.
|